diff --git a/.github/classifier.yml b/.github/classifier.yml index a7bcb40c96f..c1c067e5c53 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -15,7 +15,7 @@ css-less-scss: [ aeschli ], debug-console: [], debug: { - assignees: [ isidorn ], + assignees: [ weinand ], assignLabel: false }, diff-editor: [], diff --git a/OSSREADME.json b/OSSREADME.json index 8f167afc904..ddfe2a1fa05 100644 --- a/OSSREADME.json +++ b/OSSREADME.json @@ -2,7 +2,7 @@ [ { "name": "chromium", - "version": "58.0.3029.110", + "version": "61.0.3163.100", "repositoryURL": "http://www.chromium.org/Home", "licenseDetail": [ "BSD License", @@ -38,20 +38,20 @@ }, { "name": "libchromiumcontent", - "version": "58.0.3029.110", + "version": "61.0.3163.100", "license": "MIT", "repositoryURL": "https://github.com/electron/libchromiumcontent", "isProd": true }, { "name": "nodejs", - "version": "7.9.0", + "version": "8.9.3", "repositoryURL": "https://github.com/nodejs/node", "isProd": true }, { "name": "electron", - "version": "1.7.3", + "version": "2.0.5", "license": "MIT", "repositoryURL": "https://github.com/electron/electron", "isProd": true diff --git a/README.md b/README.md index 4c4f434141d..b2ba4ca1dcb 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # Visual Studio Code - Open Source [![Build Status](https://vscode.visualstudio.com/_apis/public/build/definitions/a4cdce18-a05c-4bb8-9476-5d07e63bfd76/1/badge?branch=master)](https://aka.ms/vscode-builds) -[![Coverage Status](https://img.shields.io/coveralls/Microsoft/vscode/master.svg)](https://coveralls.io/github/Microsoft/vscode?branch=master) [![Gitter](https://img.shields.io/badge/chat-on%20gitter-blue.svg)](https://gitter.im/Microsoft/vscode) [VS Code](https://code.visualstudio.com) is a new type of tool that combines the simplicity of diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index b2fe9e4c585..80e9fd6e808 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -21,48 +21,51 @@ This project incorporates components from the projects listed below. The origina 14. davidrios/pug-tmbundle (https://github.com/davidrios/pug-tmbundle) 15. definitelytyped (https://github.com/DefinitelyTyped/DefinitelyTyped) 16. demyte/language-cshtml (https://github.com/demyte/language-cshtml) -17. dotnet/csharp-tmLanguage version 0.1.0 (https://github.com/dotnet/csharp-tmLanguage) -18. expand-abbreviation version 0.5.8 (https://github.com/emmetio/expand-abbreviation) -19. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) -20. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) -21. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) -22. Ikuyadeu/vscode-R (https://github.com/Ikuyadeu/vscode-R) -23. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) -24. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) -25. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) -26. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) -27. language-docker (https://github.com/moby/moby) -28. language-go version 0.39.0 (https://github.com/atom/language-go) -29. language-less (https://github.com/atom/language-less) -30. language-php (https://github.com/atom/language-php) -31. language-rust version 0.4.9 (https://github.com/zargony/atom-language-rust) -32. MagicStack/MagicPython (https://github.com/MagicStack/MagicPython) -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 (https://github.com/Microsoft/vscode-mssql) -37. mmims/language-batchfile (https://github.com/mmims/language-batchfile) -38. octicons-code version 3.1.0 (https://octicons.github.com) -39. octicons-font version 3.1.0 (https://octicons.github.com) -40. PowerShell/EditorSyntax (https://github.com/powershell/editorsyntax) -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. vscode-logfile-highlighter version 1.2.0 (https://github.com/emilast/vscode-logfile-highlighter) -58. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +17. Document Object Model () +18. dotnet/csharp-tmLanguage version 0.1.0 (https://github.com/dotnet/csharp-tmLanguage) +19. expand-abbreviation version 0.5.8 (https://github.com/emmetio/expand-abbreviation) +20. fadeevab/make.tmbundle (https://github.com/fadeevab/make.tmbundle) +21. freebroccolo/atom-language-swift (https://github.com/freebroccolo/atom-language-swift) +22. HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) +23. Ikuyadeu/vscode-R (https://github.com/Ikuyadeu/vscode-R) +24. Ionic documentation version 1.2.4 (https://github.com/ionic-team/ionic-site) +25. ionide/ionide-fsgrammar (https://github.com/ionide/ionide-fsgrammar) +26. js-beautify version 1.6.8 (https://github.com/beautify-web/js-beautify) +27. Jxck/assert version 1.0.0 (https://github.com/Jxck/assert) +28. language-docker (https://github.com/moby/moby) +29. language-go version 0.39.0 (https://github.com/atom/language-go) +30. language-less (https://github.com/atom/language-less) +31. language-php (https://github.com/atom/language-php) +32. language-rust version 0.4.9 (https://github.com/zargony/atom-language-rust) +33. MagicStack/MagicPython (https://github.com/MagicStack/MagicPython) +34. mdn-data version 1.1.12 (https://github.com/mdn/data) +35. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) +36. Microsoft/vscode-JSON.tmLanguage (https://github.com/Microsoft/vscode-JSON.tmLanguage) +37. Microsoft/vscode-mssql (https://github.com/Microsoft/vscode-mssql) +38. mmims/language-batchfile (https://github.com/mmims/language-batchfile) +39. octicons-code version 3.1.0 (https://octicons.github.com) +40. octicons-font version 3.1.0 (https://octicons.github.com) +41. PowerShell/EditorSyntax (https://github.com/powershell/editorsyntax) +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. Unicode () +59. vscode-logfile-highlighter version 1.2.0 (https://github.com/emilast/vscode-logfile-highlighter) +60. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) +61. Web Background Synchronization (https://github.com/WICG/BackgroundSync) %% atom/language-c NOTICES AND INFORMATION BEGIN HERE @@ -600,6 +603,27 @@ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF demyte/language-cshtml NOTICES AND INFORMATION +%% Document Object Model NOTICES AND INFORMATION BEGIN HERE +========================================= +W3C License +This work is being provided by the copyright holders under the following license. +By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions. +Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following +on ALL copies of the work or portions thereof, including modifications: +* The full text of this NOTICE in a location viewable to users of the redistributed or derivative work. +* Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included. +* Notice of any changes or modifications, through a copyright statement on the new code or document such as "This software or document includes material copied from or derived +from Document Object Model. Copyright © 2015 W3C® (MIT, ERCIM, Keio, Beihang)." +Disclaimers +THIS WORK IS PROVIDED "AS IS + AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR +FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. +COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. +The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. +Title to copyright in this work will at all times remain with copyright holders. +========================================= +END OF Document Object Model NOTICES AND INFORMATION + %% dotnet/csharp-tmLanguage NOTICES AND INFORMATION BEGIN HERE ========================================= MIT License @@ -831,7 +855,7 @@ END OF ionide/ionide-fsgrammar NOTICES AND INFORMATION ========================================= The MIT License (MIT) -Copyright (c) 2007-2017 Einar Lielmanis, Liam Newman, and contributors. +Copyright (c) 2007-2018 Einar Lielmanis, Liam Newman, and contributors. 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: @@ -2225,6 +2249,66 @@ THE SOFTWARE. ========================================= END OF TypeScript-TmLanguage NOTICES AND INFORMATION +%% Unicode NOTICES AND INFORMATION BEGIN HERE +========================================= +Unicode Data Files include all data files under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +Unicode Data Files do not include PDF online code charts under the +directory http://www.unicode.org/Public/. + +Software includes any source code published in the Unicode Standard +or under the directories +http://www.unicode.org/Public/, http://www.unicode.org/reports/, +http://www.unicode.org/cldr/data/, http://source.icu-project.org/repos/icu/, and +http://www.unicode.org/utility/trac/browser/. + +NOTICE TO USER: Carefully read the following legal agreement. +BY DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING UNICODE INC.'S +DATA FILES ("DATA FILES"), AND/OR SOFTWARE ("SOFTWARE"), +YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE +TERMS AND CONDITIONS OF THIS AGREEMENT. +IF YOU DO NOT AGREE, DO NOT DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE +THE DATA FILES OR SOFTWARE. + +COPYRIGHT AND PERMISSION NOTICE + +Copyright (c) 1991-2017 Unicode, Inc. All rights reserved. +Distributed under the Terms of Use in http://www.unicode.org/copyright.html. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Unicode data files and any associated documentation +(the "Data Files") or Unicode software and any associated documentation +(the "Software") to deal in the Data Files or Software +without restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, and/or sell copies of +the Data Files or Software, and to permit persons to whom the Data Files +or Software are furnished to do so, provided that either +(a) this copyright and permission notice appear with all copies +of the Data Files or Software, or +(b) this copyright and permission notice appear in associated +Documentation. + +THE DATA FILES AND SOFTWARE ARE 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 OF THIRD PARTY RIGHTS. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS +NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL +DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, +DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER +TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THE DATA FILES OR SOFTWARE. + +Except as contained in this notice, the name of a copyright holder +shall not be used in advertising or otherwise to promote the sale, +use or other dealings in these Data Files or Software without prior +written authorization of the copyright holder. +========================================= +END OF Unicode NOTICES AND INFORMATION + %% vscode-logfile-highlighter NOTICES AND INFORMATION BEGIN HERE ========================================= The MIT License (MIT) @@ -2274,4 +2358,210 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= -END OF vscode-swift NOTICES AND INFORMATION \ No newline at end of file +END OF vscode-swift NOTICES AND INFORMATION + +%% Web Background Synchronization NOTICES AND INFORMATION BEGIN HERE +========================================= +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: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) 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 + + (d) 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 + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +========================================= +END OF Web Background Synchronization NOTICES AND INFORMATION \ No newline at end of file diff --git a/build/builtInExtensions.json b/build/builtInExtensions.json index a017fbcce91..e927280e32f 100644 --- a/build/builtInExtensions.json +++ b/build/builtInExtensions.json @@ -1,12 +1,12 @@ [ { "name": "ms-vscode.node-debug", - "version": "1.26.6", + "version": "1.27.1", "repo": "https://github.com/Microsoft/vscode-node-debug" }, { "name": "ms-vscode.node-debug2", - "version": "1.26.7", + "version": "1.26.8", "repo": "https://github.com/Microsoft/vscode-node-debug2" } ] diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index 218d4cdf8f5..e39100bc3bc 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -44,7 +44,7 @@ declare namespace monaco { } -#include(vs/base/common/winjs.base.d.ts): TValueCallback, ProgressCallback, Promise +#include(vs/base/common/winjs.base.d.ts): TValueCallback, Promise #include(vs/base/common/cancellation): CancellationTokenSource, CancellationToken #include(vs/base/common/uri): URI, UriComponents #include(vs/editor/common/standalone/standaloneBase): KeyCode, KeyMod diff --git a/build/tfs/linux/product-build-linux.yml b/build/tfs/linux/product-build-linux.yml index f6e067ebfe3..9505d71ff34 100644 --- a/build/tfs/linux/product-build-linux.yml +++ b/build/tfs/linux/product-build-linux.yml @@ -103,10 +103,10 @@ steps: # Write config files needed by API, use eval to force environment variable expansion pushd build/tfs/linux # Submit to apt repo - if [ "$DEB_ARCH" = "amd64" ]; then - echo "{ \"server\": \"azure-apt-cat.cloudapp.net\", \"protocol\": \"https\", \"port\": \"443\", \"repositoryId\": \"58a4adf642421134a1a48d1a\", \"username\": \"vscode\", \"password\": \"$(LINUX_REPO_PASSWORD)\" }" > apt-config.json - ./repoapi_client.sh -config apt-config.json -addfile $DEB_PATH - fi + # if [ "$DEB_ARCH" = "amd64" ]; then + # echo "{ \"server\": \"azure-apt-cat.cloudapp.net\", \"protocol\": \"https\", \"port\": \"443\", \"repositoryId\": \"58a4adf642421134a1a48d1a\", \"username\": \"vscode\", \"password\": \"$(LINUX_REPO_PASSWORD)\" }" > apt-config.json + # ./repoapi_client.sh -config apt-config.json -addfile $DEB_PATH + # fi # Submit to yum repo (disabled as it's manual until signing is automated) # eval echo '{ \"server\": \"azure-apt-cat.cloudapp.net\", \"protocol\": \"https\", \"port\": \"443\", \"repositoryId\": \"58a4ae3542421134a1a48d1b\", \"username\": \"vscode\", \"password\": \"$(LINUX_REPO_PASSWORD)\" }' > yum-config.json diff --git a/build/win32/code.iss b/build/win32/code.iss index cad2e27d65e..4be0ea854b5 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -975,7 +975,7 @@ begin RegKey := 'SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\' + copy('{#IncompatibleTargetAppId}', 2, 38) + '_is1'; if RegKeyExists({#IncompatibleArchRootKey}, RegKey) then begin - if MsgBox('{#NameShort} is already installed on this system for all users. Are you sure you want to install it for this user?', mbConfirmation, MB_YESNO) = IDNO then begin + if MsgBox('{#NameShort} is already installed on this system for all users. We recommend first uninstalling that version before installing this one. Are you sure you want to continue the installation?', mbConfirmation, MB_YESNO) = IDNO then begin Result := false; end; end; @@ -1139,4 +1139,4 @@ end; #ifdef Debug #expr SaveToFile(AddBackslash(SourcePath) + "code-processed.iss") -#endif \ No newline at end of file +#endif diff --git a/extensions/css-language-features/.vscode/launch.json b/extensions/css-language-features/.vscode/launch.json index 9aad19d5b4e..d6393141c5d 100644 --- a/extensions/css-language-features/.vscode/launch.json +++ b/extensions/css-language-features/.vscode/launch.json @@ -54,6 +54,26 @@ ], "smartStep": true, "restart": true + }, + { + "name": "Server Unit Tests", + "type": "node", + "request": "launch", + "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", + "stopOnEntry": false, + "args": [ + "--timeout", + "999999", + "--colors" + ], + "cwd": "${workspaceRoot}", + "runtimeExecutable": null, + "runtimeArgs": [], + "env": {}, + "sourceMaps": true, + "outFiles": [ + "${workspaceRoot}/server/out/**" + ] } ] } \ No newline at end of file diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 4c1b1a151c9..41b83b02164 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -19,6 +19,7 @@ "scripts": { "compile": "gulp compile-extension:css-language-features-client compile-extension:css-language-features-server", "watch": "gulp watch-extension:css-language-features-client watch-extension:css-language-features-server", + "test": "mocha", "postinstall": "cd server && yarn install", "install-client-next": "yarn add vscode-languageclient@next" }, @@ -109,7 +110,7 @@ "error" ], "default": "ignore", - "description": "%css.lint.boxModel.desc%" + "markdownDescription": "%css.lint.boxModel.desc%" }, "css.lint.universalSelector": { "type": "string", @@ -120,7 +121,7 @@ "error" ], "default": "ignore", - "description": "%css.lint.universalSelector.desc%" + "markdownDescription": "%css.lint.universalSelector.desc%" }, "css.lint.zeroUnits": { "type": "string", @@ -208,7 +209,7 @@ "error" ], "default": "warning", - "description": "%css.lint.propertyIgnoredDueToDisplay.desc%" + "markdownDescription": "%css.lint.propertyIgnoredDueToDisplay.desc%" }, "css.lint.important": { "type": "string", @@ -230,7 +231,7 @@ "error" ], "default": "ignore", - "description": "%css.lint.float.desc%" + "markdownDescription": "%css.lint.float.desc%" }, "css.lint.idSelector": { "type": "string", @@ -349,7 +350,7 @@ "error" ], "default": "ignore", - "description": "%scss.lint.boxModel.desc%" + "markdownDescription": "%scss.lint.boxModel.desc%" }, "scss.lint.universalSelector": { "type": "string", @@ -360,7 +361,7 @@ "error" ], "default": "ignore", - "description": "%scss.lint.universalSelector.desc%" + "markdownDescription": "%scss.lint.universalSelector.desc%" }, "scss.lint.zeroUnits": { "type": "string", @@ -382,7 +383,7 @@ "error" ], "default": "warning", - "description": "%scss.lint.fontFaceProperties.desc%" + "markdownDescription": "%scss.lint.fontFaceProperties.desc%" }, "scss.lint.hexColorLength": { "type": "string", @@ -448,7 +449,7 @@ "error" ], "default": "warning", - "description": "%scss.lint.propertyIgnoredDueToDisplay.desc%" + "markdownDescription": "%scss.lint.propertyIgnoredDueToDisplay.desc%" }, "scss.lint.important": { "type": "string", @@ -459,7 +460,7 @@ "error" ], "default": "ignore", - "description": "%scss.lint.important.desc%" + "markdownDescription": "%scss.lint.important.desc%" }, "scss.lint.float": { "type": "string", @@ -470,7 +471,7 @@ "error" ], "default": "ignore", - "description": "%scss.lint.float.desc%" + "markdownDescription": "%scss.lint.float.desc%" }, "scss.lint.idSelector": { "type": "string", @@ -568,7 +569,7 @@ "error" ], "default": "ignore", - "description": "%less.lint.boxModel.desc%" + "markdownDescription": "%less.lint.boxModel.desc%" }, "less.lint.universalSelector": { "type": "string", @@ -579,7 +580,7 @@ "error" ], "default": "ignore", - "description": "%less.lint.universalSelector.desc%" + "markdownDescription": "%less.lint.universalSelector.desc%" }, "less.lint.zeroUnits": { "type": "string", @@ -601,7 +602,7 @@ "error" ], "default": "warning", - "description": "%less.lint.fontFaceProperties.desc%" + "markdownDescription": "%less.lint.fontFaceProperties.desc%" }, "less.lint.hexColorLength": { "type": "string", @@ -667,7 +668,7 @@ "error" ], "default": "warning", - "description": "%less.lint.propertyIgnoredDueToDisplay.desc%" + "markdownDescription": "%less.lint.propertyIgnoredDueToDisplay.desc%" }, "less.lint.important": { "type": "string", @@ -689,7 +690,7 @@ "error" ], "default": "ignore", - "description": "%less.lint.float.desc%" + "markdownDescription": "%less.lint.float.desc%" }, "less.lint.idSelector": { "type": "string", diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index 6686d0e4f21..f62c6fb669c 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -2,73 +2,73 @@ "displayName": "CSS Language Features", "description": "Provides rich language support for CSS, LESS and SCSS files.", "css.title": "CSS", - "css.lint.argumentsInColorFunction.desc": "Invalid number of parameters", - "css.lint.boxModel.desc": "Do not use width or height when using padding or border", - "css.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties", - "css.lint.duplicateProperties.desc": "Do not use duplicate style definitions", - "css.lint.emptyRules.desc": "Do not use empty rulesets", - "css.lint.float.desc": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", - "css.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties", - "css.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers", + "css.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", + "css.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", + "css.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties.", + "css.lint.duplicateProperties.desc": "Do not use duplicate style definitions.", + "css.lint.emptyRules.desc": "Do not use empty rulesets.", + "css.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", + "css.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", + "css.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", "css.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", "css.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", - "css.lint.important.desc": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", + "css.lint.important.desc": "Avoid using `!important`. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", "css.lint.importStatement.desc": "Import statements do not load in parallel.", - "css.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect.", - "css.lint.universalSelector.desc": "The universal selector (*) is known to be slow.", + "css.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with `display: inline`, the `width`, `height`, `margin-top`, `margin-bottom`, and `float` properties have no effect.", + "css.lint.universalSelector.desc": "The universal selector (`*`) is known to be slow.", "css.lint.unknownAtRules.desc": "Unknown at-rule.", "css.lint.unknownProperties.desc": "Unknown property.", "css.lint.unknownVendorSpecificProperties.desc": "Unknown vendor specific property.", - "css.lint.vendorPrefix.desc": "When using a vendor-specific prefix also include the standard property.", + "css.lint.vendorPrefix.desc": "When using a vendor-specific prefix, also include the standard property.", "css.lint.zeroUnits.desc": "No unit for zero needed.", "css.trace.server.desc": "Traces the communication between VS Code and the CSS language server.", "css.validate.title": "Controls CSS validation and problem severities.", - "css.validate.desc": "Enables or disables all validations", + "css.validate.desc": "Enables or disables all validations.", "less.title": "LESS", - "less.lint.argumentsInColorFunction.desc": "Invalid number of parameters", - "less.lint.boxModel.desc": "Do not use width or height when using padding or border", - "less.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties", - "less.lint.duplicateProperties.desc": "Do not use duplicate style definitions", - "less.lint.emptyRules.desc": "Do not use empty rulesets", - "less.lint.float.desc": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", - "less.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties", - "less.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers", + "less.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", + "less.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", + "less.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties.", + "less.lint.duplicateProperties.desc": "Do not use duplicate style definitions.", + "less.lint.emptyRules.desc": "Do not use empty rulesets.", + "less.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", + "less.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", + "less.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", "less.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", - "less.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older", + "less.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "less.lint.important.desc": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", - "less.lint.importStatement.desc": "Import statements do not load in parallel", - "less.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect", - "less.lint.universalSelector.desc": "The universal selector (*) is known to be slow", + "less.lint.importStatement.desc": "Import statements do not load in parallel.", + "less.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with `display: inline`, the `width`, `height`, `margin-top`, `margin-bottom`, and `float` properties have no effect.", + "less.lint.universalSelector.desc": "The universal selector (`*`) is known to be slow.", "less.lint.unknownProperties.desc": "Unknown property.", "less.lint.unknownVendorSpecificProperties.desc": "Unknown vendor specific property.", - "less.lint.vendorPrefix.desc": "When using a vendor-specific prefix also include the standard property", - "less.lint.zeroUnits.desc": "No unit for zero needed", + "less.lint.vendorPrefix.desc": "When using a vendor-specific prefix, also include the standard property.", + "less.lint.zeroUnits.desc": "No unit for zero needed.", "less.validate.title": "Controls LESS validation and problem severities.", - "less.validate.desc": "Enables or disables all validations", + "less.validate.desc": "Enables or disables all validations.", "scss.title": "SCSS (Sass)", - "scss.lint.argumentsInColorFunction.desc": "Invalid number of parameters", - "scss.lint.boxModel.desc": "Do not use width or height when using padding or border", - "scss.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties", - "scss.lint.duplicateProperties.desc": "Do not use duplicate style definitions", - "scss.lint.emptyRules.desc": "Do not use empty rulesets", - "scss.lint.float.desc": "Avoid using 'float'. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", - "scss.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties", - "scss.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers", + "scss.lint.argumentsInColorFunction.desc": "Invalid number of parameters.", + "scss.lint.boxModel.desc": "Do not use `width` or `height` when using `padding` or `border`.", + "scss.lint.compatibleVendorPrefixes.desc": "When using a vendor-specific prefix make sure to also include all other vendor-specific properties.", + "scss.lint.duplicateProperties.desc": "Do not use duplicate style definitions.", + "scss.lint.emptyRules.desc": "Do not use empty rulesets.", + "scss.lint.float.desc": "Avoid using `float`. Floats lead to fragile CSS that is easy to break if one aspect of the layout changes.", + "scss.lint.fontFaceProperties.desc": "`@font-face` rule must define `src` and `font-family` properties.", + "scss.lint.hexColorLength.desc": "Hex colors must consist of three or six hex numbers.", "scss.lint.idSelector.desc": "Selectors should not contain IDs because these rules are too tightly coupled with the HTML.", - "scss.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older", + "scss.lint.ieHack.desc": "IE hacks are only necessary when supporting IE7 and older.", "scss.lint.important.desc": "Avoid using !important. It is an indication that the specificity of the entire CSS has gotten out of control and needs to be refactored.", - "scss.lint.importStatement.desc": "Import statements do not load in parallel", - "scss.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with 'display: inline', the width, height, margin-top, margin-bottom, and float properties have no effect", - "scss.lint.universalSelector.desc": "The universal selector (*) is known to be slow", + "scss.lint.importStatement.desc": "Import statements do not load in parallel.", + "scss.lint.propertyIgnoredDueToDisplay.desc": "Property is ignored due to the display. E.g. with `display: inline`, the `width`, `height`, `margin-top`, `margin-bottom`, and `float` properties have no effect.", + "scss.lint.universalSelector.desc": "The universal selector (`*`) is known to be slow.", "scss.lint.unknownProperties.desc": "Unknown property.", "scss.lint.unknownVendorSpecificProperties.desc": "Unknown vendor specific property.", - "scss.lint.vendorPrefix.desc": "When using a vendor-specific prefix also include the standard property", - "scss.lint.zeroUnits.desc": "No unit for zero needed", + "scss.lint.vendorPrefix.desc": "When using a vendor-specific prefix, also include the standard property.", + "scss.lint.zeroUnits.desc": "No unit for zero needed.", "scss.validate.title": "Controls SCSS validation and problem severities.", - "scss.validate.desc": "Enables or disables all validations", - "less.colorDecorators.enable.desc": "Enables or disables color decorators", - "scss.colorDecorators.enable.desc": "Enables or disables color decorators", - "css.colorDecorators.enable.desc": "Enables or disables color decorators", + "scss.validate.desc": "Enables or disables all validations.", + "less.colorDecorators.enable.desc": "Enables or disables color decorators.", + "scss.colorDecorators.enable.desc": "Enables or disables color decorators.", + "css.colorDecorators.enable.desc": "Enables or disables color decorators.", "css.colorDecorators.enable.deprecationMessage": "The setting `css.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "scss.colorDecorators.enable.deprecationMessage": "The setting `scss.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "less.colorDecorators.enable.deprecationMessage": "The setting `less.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`." diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index 756396ae8fa..7b98111d447 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -8,7 +8,7 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.9", + "vscode-css-languageservice": "^3.0.10-next.2", "vscode-languageserver": "^4.4.0" }, "devDependencies": { diff --git a/extensions/css-language-features/server/src/pathCompletion.ts b/extensions/css-language-features/server/src/pathCompletion.ts index b072c4136f6..4ea06882be2 100644 --- a/extensions/css-language-features/server/src/pathCompletion.ts +++ b/extensions/css-language-features/server/src/pathCompletion.ts @@ -12,7 +12,7 @@ import { TextDocument, CompletionList, CompletionItemKind, CompletionItem, TextE import { WorkspaceFolder } from 'vscode-languageserver'; import { ICompletionParticipant } from 'vscode-css-languageservice'; -import { startsWith } from './utils/strings'; +import { startsWith, endsWith } from './utils/strings'; export function getPathCompletionParticipant( document: TextDocument, @@ -21,32 +21,73 @@ export function getPathCompletionParticipant( ): ICompletionParticipant { return { onCssURILiteralValue: ({ position, range, uriValue }) => { - const isValueQuoted = startsWith(uriValue, `'`) || startsWith(uriValue, `"`); const fullValue = stripQuotes(uriValue); - const valueBeforeCursor = isValueQuoted - ? fullValue.slice(0, position.character - (range.start.character + 1)) - : fullValue.slice(0, position.character - range.start.character); - - if (fullValue === '.' || fullValue === '..') { - result.isIncomplete = true; + if (!shouldDoPathCompletion(uriValue, workspaceFolders)) { + if (fullValue === '.' || fullValue === '..') { + result.isIncomplete = true; + } return; } - if (!workspaceFolders || workspaceFolders.length === 0) { + let suggestions = providePathSuggestions(uriValue, position, range, document, workspaceFolders); + result.items = [...suggestions, ...result.items]; + }, + onCssImportPath: ({ position, range, pathValue }) => { + const fullValue = stripQuotes(pathValue); + if (!shouldDoPathCompletion(pathValue, workspaceFolders)) { + if (fullValue === '.' || fullValue === '..') { + result.isIncomplete = true; + } return; } - const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); - const paths = providePaths(valueBeforeCursor, URI.parse(document.uri).fsPath, workspaceRoot); - const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range; - const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange); - const suggestions = paths.map(p => pathToSuggestion(p, replaceRange)); + let suggestions = providePathSuggestions(pathValue, position, range, document, workspaceFolders); + + if (document.languageId === 'scss') { + suggestions.forEach(s => { + if (startsWith(s.label, '_') && endsWith(s.label, '.scss')) { + if (s.textEdit) { + s.textEdit.newText = s.label.slice(1, -5); + } else { + s.label = s.label.slice(1, -5); + } + } + }); + } result.items = [...suggestions, ...result.items]; } - }; } +function providePathSuggestions(pathValue: string, position: Position, range: Range, document: TextDocument, workspaceFolders: WorkspaceFolder[]) { + const fullValue = stripQuotes(pathValue); + const isValueQuoted = startsWith(pathValue, `'`) || startsWith(pathValue, `"`); + const valueBeforeCursor = isValueQuoted + ? fullValue.slice(0, position.character - (range.start.character + 1)) + : fullValue.slice(0, position.character - range.start.character); + const workspaceRoot = resolveWorkspaceRoot(document, workspaceFolders); + + const paths = providePaths(valueBeforeCursor, URI.parse(document.uri).fsPath, workspaceRoot); + const fullValueRange = isValueQuoted ? shiftRange(range, 1, -1) : range; + const replaceRange = pathToReplaceRange(valueBeforeCursor, fullValue, fullValueRange); + + const suggestions = paths.map(p => pathToSuggestion(p, replaceRange)); + return suggestions; +} + +function shouldDoPathCompletion(pathValue: string, workspaceFolders: WorkspaceFolder[]): boolean { + const fullValue = stripQuotes(pathValue); + if (fullValue === '.' || fullValue === '..') { + return false; + } + + if (!workspaceFolders || workspaceFolders.length === 0) { + return false; + } + + return true; +} + function stripQuotes(fullValue: string) { if (startsWith(fullValue, `'`) || startsWith(fullValue, `"`)) { return fullValue.slice(1, -1); diff --git a/extensions/css-language-features/server/src/test/completion.test.ts b/extensions/css-language-features/server/src/test/completion.test.ts index 62094de6b5b..2a68b5797cf 100644 --- a/extensions/css-language-features/server/src/test/completion.test.ts +++ b/extensions/css-language-features/server/src/test/completion.test.ts @@ -33,11 +33,11 @@ suite('Completions', () => { } }; - function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: WorkspaceFolder[]): void { + function assertCompletions(value: string, expected: { count?: number, items?: ItemDescription[] }, testUri: string, workspaceFolders?: WorkspaceFolder[], lang: string = 'css'): void { const offset = value.indexOf('|'); value = value.substr(0, offset) + value.substr(offset + 1); - const document = TextDocument.create(testUri, 'css', 0, value); + const document = TextDocument.create(testUri, lang, 0, value); const position = document.positionAt(offset); if (!workspaceFolders) { @@ -61,7 +61,7 @@ suite('Completions', () => { } } - test('CSS Path completion', function () { + test('CSS url() Path completion', function () { let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString(); let folders = [{ name: 'x', uri: Uri.file(path.resolve(__dirname, '../../test')).toString() }]; @@ -121,7 +121,7 @@ suite('Completions', () => { }, testUri, folders); }); - test('CSS Path Completion - Unquoted url', function () { + test('CSS url() Path Completion - Unquoted url', function () { let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString(); let folders = [{ name: 'x', uri: Uri.file(path.resolve(__dirname, '../../test')).toString() }]; @@ -149,4 +149,50 @@ suite('Completions', () => { ] }, testUri, folders); }); + + test('CSS @import Path completion', function () { + let testUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString(); + let folders = [{ name: 'x', uri: Uri.file(path.resolve(__dirname, '../../test')).toString() }]; + + assertCompletions(`@import './|'`, { + items: [ + { label: 'about.css', resultText: `@import './about.css'` }, + { label: 'about.html', resultText: `@import './about.html'` }, + ] + }, testUri, folders); + + assertCompletions(`@import '../|'`, { + items: [ + { label: 'about/', resultText: `@import '../about/'` }, + { label: 'scss/', resultText: `@import '../scss/'` }, + { label: 'index.html', resultText: `@import '../index.html'` }, + { label: 'src/', resultText: `@import '../src/'` } + ] + }, testUri, folders); + }); + + /** + * For SCSS, `@import 'foo';` can be used for importing partial file `_foo.scss` + */ + test('SCSS @import Path completion', function () { + let testCSSUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/about/about.css')).toString(); + let folders = [{ name: 'x', uri: Uri.file(path.resolve(__dirname, '../../test')).toString() }]; + + /** + * We are in a CSS file, so no special treatment for SCSS partial files + */ + assertCompletions(`@import '../scss/|'`, { + items: [ + { label: 'main.scss', resultText: `@import '../scss/main.scss'` }, + { label: '_foo.scss', resultText: `@import '../scss/_foo.scss'` } + ] + }, testCSSUri, folders); + + let testSCSSUri = Uri.file(path.resolve(__dirname, '../../test/pathCompletionFixtures/scss/main.scss')).toString(); + assertCompletions(`@import './|'`, { + items: [ + { label: '_foo.scss', resultText: `@import './foo'` } + ] + }, testSCSSUri, folders, 'scss'); + }); }); \ No newline at end of file diff --git a/extensions/css-language-features/server/src/utils/strings.ts b/extensions/css-language-features/server/src/utils/strings.ts index f7ad0845cc8..114fb4f0808 100644 --- a/extensions/css-language-features/server/src/utils/strings.ts +++ b/extensions/css-language-features/server/src/utils/strings.ts @@ -17,3 +17,17 @@ export function startsWith(haystack: string, needle: string): boolean { return true; } + +/** + * Determines if haystack ends with needle. + */ +export function endsWith(haystack: string, needle: string): boolean { + let diff = haystack.length - needle.length; + if (diff > 0) { + return haystack.lastIndexOf(needle) === diff; + } else if (diff === 0) { + return haystack === needle; + } else { + return false; + } +} diff --git a/extensions/css-language-features/server/test/pathCompletionFixtures/scss/_foo.scss b/extensions/css-language-features/server/test/pathCompletionFixtures/scss/_foo.scss new file mode 100644 index 00000000000..adae63e647c --- /dev/null +++ b/extensions/css-language-features/server/test/pathCompletionFixtures/scss/_foo.scss @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/extensions/css-language-features/server/test/pathCompletionFixtures/scss/main.scss b/extensions/css-language-features/server/test/pathCompletionFixtures/scss/main.scss new file mode 100644 index 00000000000..adae63e647c --- /dev/null +++ b/extensions/css-language-features/server/test/pathCompletionFixtures/scss/main.scss @@ -0,0 +1,4 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ \ No newline at end of file diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index c2025f27675..6878038d8f8 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -194,9 +194,9 @@ supports-color@5.4.0: dependencies: has-flag "^3.0.0" -vscode-css-languageservice@^3.0.9: - version "3.0.9" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.9.tgz#770471350120c5bcf6918632a125638fc0ece3be" +vscode-css-languageservice@^3.0.10-next.2: + version "3.0.10-next.2" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.10-next.2.tgz#b703af89be433507836178efd7f88bb0669fc4e8" dependencies: vscode-languageserver-types "^3.10.0" vscode-nls "^3.2.4" diff --git a/extensions/css-language-features/test/mocha.opts b/extensions/css-language-features/test/mocha.opts new file mode 100644 index 00000000000..20fcfb6eef6 --- /dev/null +++ b/extensions/css-language-features/test/mocha.opts @@ -0,0 +1,3 @@ +--ui tdd +--useColors true +server/out/test/**.test.js \ No newline at end of file diff --git a/extensions/css/syntaxes/css.tmLanguage.json b/extensions/css/syntaxes/css.tmLanguage.json index f426e662f58..07977e3835a 100644 --- a/extensions/css/syntaxes/css.tmLanguage.json +++ b/extensions/css/syntaxes/css.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/octref/language-css/commit/f8a70d3f2540a7bf12a7cbc13c1d7bb76cc8cdd0", + "version": "https://github.com/octref/language-css/commit/aadd130de82cf2351b459041109b49f586142f11", "name": "CSS", "scopeName": "source.css", "patterns": [ @@ -508,7 +508,7 @@ ] }, { - "begin": "(?i)((@)viewport)(?=[\\s'\"{;]|/\\*|$)", + "begin": "(?i)((@)(-ms-|-o-)?viewport)(?=[\\s'\"{;]|/\\*|$)", "beginCaptures": { "1": { "name": "keyword.control.at-rule.viewport.css" diff --git a/extensions/debug-auto-launch/src/autoAttach.ts b/extensions/debug-auto-launch/src/autoAttach.ts deleted file mode 100644 index a6bb925ef4f..00000000000 --- a/extensions/debug-auto-launch/src/autoAttach.ts +++ /dev/null @@ -1,24 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as vscode from 'vscode'; -import * as nls from 'vscode-nls'; -import { basename } from 'path'; -import { pollProcesses, attachToProcess } from './nodeProcessTree'; - -const localize = nls.loadMessageBundle(); - -export function startAutoAttach(rootPid: number): vscode.Disposable { - - return pollProcesses(rootPid, true, (pid, cmdPath, args) => { - const cmdName = basename(cmdPath, '.exe'); - if (cmdName === 'node') { - const name = localize('process.with.pid.label', "Process {0}", pid); - attachToProcess(undefined, name, pid, args); - } - }); -} diff --git a/extensions/debug-auto-launch/src/nodeProcessTree.ts b/extensions/debug-auto-launch/src/nodeProcessTree.ts index 16bc55476e1..1a62971bc30 100644 --- a/extensions/debug-auto-launch/src/nodeProcessTree.ts +++ b/extensions/debug-auto-launch/src/nodeProcessTree.ts @@ -95,10 +95,10 @@ export function attachToProcess(folder: vscode.WorkspaceFolder | undefined, name function findChildProcesses(rootPid: number, inTerminal: boolean, cb: (pid: number, cmd: string, args: string) => void): Promise { - function walker(node: ProcessTreeNode, terminal: boolean, renderer: number) { + function walker(node: ProcessTreeNode, terminal: boolean, terminalPids: number[]) { - if (node.args.indexOf('--type=terminal') >= 0 && (renderer === 0 || node.ppid === renderer)) { - terminal = true; + if (terminalPids.indexOf(node.pid) >= 0) { + terminal = true; // found the terminal shell } let { protocol } = analyseArguments(node.args); @@ -107,32 +107,17 @@ function findChildProcesses(rootPid: number, inTerminal: boolean, cb: (pid: numb } for (const child of node.children || []) { - walker(child, terminal, renderer); + walker(child, terminal, terminalPids); } } - function finder(node: ProcessTreeNode, pid: number): ProcessTreeNode | undefined { - if (node.pid === pid) { - return node; - } - for (const child of node.children || []) { - const p = finder(child, pid); - if (p) { - return p; - } - } - return undefined; - } - return getProcessTree(rootPid).then(tree => { if (tree) { - - // find the pid of the renderer process - const extensionHost = finder(tree, process.pid); - let rendererPid = extensionHost ? extensionHost.ppid : 0; - - for (const child of tree.children || []) { - walker(child, !inTerminal, rendererPid); + const terminals = vscode.window.terminals; + if (terminals.length > 0) { + Promise.all(terminals.map(terminal => terminal.processId)).then(terminalPids => { + walker(tree, !inTerminal, terminalPids); + }); } } }); diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 406b25c97d2..d9be62f74d1 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -39,17 +39,17 @@ "inMarkupAndStylesheetFilesOnly" ], "default": "always", - "description": "%emmetShowExpandedAbbreviation%" + "markdownDescription": "%emmetShowExpandedAbbreviation%" }, "emmet.showAbbreviationSuggestions": { "type": "boolean", "default": true, - "description": "%emmetShowAbbreviationSuggestions%" + "markdownDescription": "%emmetShowAbbreviationSuggestions%" }, "emmet.includeLanguages": { "type": "object", "default": {}, - "description": "%emmetIncludeLanguages%" + "markdownDescription": "%emmetIncludeLanguages%" }, "emmet.variables": { "type": "object", @@ -183,22 +183,22 @@ "css.webkitProperties": { "type": "string", "default": null, - "description": "%emmetPreferencesCssWebkitProperties%" + "markdownDescription": "%emmetPreferencesCssWebkitProperties%" }, "css.mozProperties": { "type": "string", "default": null, - "description": "%emmetPreferencesCssMozProperties%" + "markdownDescription": "%emmetPreferencesCssMozProperties%" }, "css.oProperties": { "type": "string", "default": null, - "description": "%emmetPreferencesCssOProperties%" + "markdownDescription": "%emmetPreferencesCssOProperties%" }, "css.msProperties": { "type": "string", "default": null, - "description": "%emmetPreferencesCssMsProperties%" + "markdownDescription": "%emmetPreferencesCssMsProperties%" }, "css.fuzzySearchMinScore": { "type": "number", @@ -210,12 +210,12 @@ "emmet.showSuggestionsAsSnippets": { "type": "boolean", "default": false, - "description": "%emmetShowSuggestionsAsSnippets%" + "markdownDescription": "%emmetShowSuggestionsAsSnippets%" }, "emmet.optimizeStylesheetParsing": { "type": "boolean", "default": true, - "description": "%emmetOptimizeStylesheetParsing%" + "markdownDescription": "%emmetOptimizeStylesheetParsing%" } } }, @@ -447,8 +447,6 @@ "@emmetio/html-matcher": "^0.3.3", "@emmetio/math-expression": "^0.1.1", "image-size": "^0.5.2", - "vscode-emmet-helper": "^1.2.10", - "vscode-languageserver-types": "^3.5.0", - "vscode-nls": "3.2.4" + "vscode-emmet-helper": "^1.2.11" } } diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 5ec3f401a70..26444af31e5 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -498,6 +498,12 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen i--; continue; } + // Fix for https://github.com/Microsoft/vscode/issues/55411 + // A space is not a valid character right after < in a tag name. + if (/\s/.test(char) && textToBackTrack[i] === startAngle) { + i--; + continue; + } if (char !== startAngle && char !== endAngle) { continue; } diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 14286654d05..b5fb87d8b07 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -466,6 +466,16 @@ suite('Tests for jsx, xml and xsl', () => { }); }); + test('Expand abbreviation with condition containing less than sign for jsx', () => { + return withRandomFileEditor('if (foo < 10) { span.bar', 'javascriptreact', (editor, doc) => { + editor.selection = new Selection(0, 27, 0, 27); + return expandEmmetAbbreviation({ language: 'javascriptreact' }).then(() => { + assert.equal(editor.document.getText(), 'if (foo < 10) { '); + return Promise.resolve(); + }); + }); + }); + test('No expanding text inside open tag in completion list (jsx)', () => { return testNoCompletion('jsx', htmlContents, new Selection(2, 4, 2, 4)); }); diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 040417ab779..e910402265e 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -2115,26 +2115,18 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-emmet-helper@^1.2.10: - version "1.2.10" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.10.tgz#c4e08c721fa379f57e53c6bdf2843d4b3c830d16" +vscode-emmet-helper@^1.2.11: + version "1.2.11" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.2.11.tgz#4de78223666bf917eb6dc4b225b6c40f6901950c" dependencies: "@emmetio/extract-abbreviation" "0.1.6" jsonc-parser "^1.0.0" vscode-languageserver-types "^3.6.0-next.1" -vscode-languageserver-types@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374" - vscode-languageserver-types@^3.6.0-next.1: version "3.6.0-next.1" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.6.0-next.1.tgz#98e488d3f87b666b4ee1a3d89f0023e246d358f3" -vscode-nls@3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" - vscode@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b" diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 04b0521c591..2724038bdf7 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -263,6 +263,9 @@ export class ExtensionLinter { } private async loadPackageJson(folder: Uri) { + if (folder.scheme === 'git') { // #36236 + return undefined; + } const file = folder.with({ path: path.posix.join(folder.path, 'package.json') }); try { const document = await workspace.openTextDocument(file); diff --git a/extensions/git/package.json b/extensions/git/package.json index 96b81482585..0eba9ba5c94 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -885,7 +885,7 @@ "string", "null" ], - "description": "%config.path%", + "markdownDescription": "%config.path%", "default": null, "scope": "application" }, @@ -900,6 +900,12 @@ "subFolders", "openEditors" ], + "enumDescriptions": [ + "%config.autoRepositoryDetection.true%", + "%config.autoRepositoryDetection.false%", + "%config.autoRepositoryDetection.subFolders%", + "%config.autoRepositoryDetection.openEditors%" + ], "description": "%config.autoRepositoryDetection%", "default": true }, @@ -912,7 +918,9 @@ "type": "boolean", "description": "%config.autofetch%", "default": false, - "tags": ["usesOnlineServices"] + "tags": [ + "usesOnlineServices" + ] }, "git.confirmSync": { "type": "boolean", @@ -948,7 +956,7 @@ "%config.checkoutType.tags%", "%config.checkoutType.remote%" ], - "description": "%config.checkoutType%", + "markdownDescription": "%config.checkoutType%", "default": "all" }, "git.ignoreLegacyWarning": { @@ -1054,7 +1062,7 @@ "id": "gitDecoration.modifiedResourceForeground", "description": "%colors.modified%", "defaults": { - "light": "#a76e12", + "light": "#895503", "dark": "#E2C08D", "highContrast": "#E2C08D" } @@ -1189,7 +1197,7 @@ "file-type": "^7.2.0", "iconv-lite": "0.4.19", "jschardet": "^1.6.0", - "vscode-extension-telemetry": "0.0.17", + "vscode-extension-telemetry": "0.0.18", "vscode-nls": "^3.2.4", "which": "^1.3.0" }, diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 38050ced547..bc4c812d75b 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -51,11 +51,15 @@ "command.stashPop": "Pop Stash...", "command.stashPopLatest": "Pop Latest Stash", "config.enabled": "Whether git is enabled.", - "config.path": "Path to the git executable.", + "config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows).", "config.autoRepositoryDetection": "Configures when repositories should be automatically detected.", - "config.autorefresh": "Whether auto refreshing is enabled", - "config.autofetch": "Whether auto fetching is enabled", - "config.enableLongCommitWarning": "Whether long commit messages should be warned about", + "config.autoRepositoryDetection.true": "Scan for both subfolders of the current opened folder and parent folders of open files.", + "config.autoRepositoryDetection.false": "Disable automatic repository scanning.", + "config.autoRepositoryDetection.subFolders": "Scan for subfolders of the currently opened folder.", + "config.autoRepositoryDetection.openEditors": "Scan for parent folders of open files.", + "config.autorefresh": "Whether auto refreshing is enabled.", + "config.autofetch": "Whether auto fetching is enabled.", + "config.enableLongCommitWarning": "Whether long commit messages should be warned about.", "config.confirmSync": "Confirm before synchronizing git repositories.", "config.countBadge": "Controls the git badge counter.", "config.countBadge.all": "Count all changes.", @@ -67,8 +71,8 @@ "config.checkoutType.tags": "Show only tags.", "config.checkoutType.remote": "Show only remote branches.", "config.ignoreLegacyWarning": "Ignores the legacy Git warning.", - "config.ignoreMissingGitWarning": "Ignores the warning when Git is missing", - "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository", + "config.ignoreMissingGitWarning": "Ignores the warning when Git is missing.", + "config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.", "config.defaultCloneDirectory": "The default location to clone a git repository.", "config.enableSmartCommit": "Commit all changes when there are no staged changes.", "config.enableCommitSigning": "Enables commit signing with GPG.", @@ -81,8 +85,8 @@ "config.detectSubmodules": "Controls whether to automatically detect git submodules.", "colors.added": "Color for added resources.", "config.detectSubmodulesLimit": "Controls the limit of git submodules detected.", - "config.alwaysSignOff": "Controls the signoff flag for all commits", - "config.ignoredRepositories": "List of git repositories to ignore", + "config.alwaysSignOff": "Controls the signoff flag for all commits.", + "config.ignoredRepositories": "List of git repositories to ignore.", "colors.modified": "Color for modified resources.", "colors.deleted": "Color for deleted resources.", "colors.untracked": "Color for untracked resources.", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 19c3cbdbe59..2e1c115c14f 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -1021,8 +1021,8 @@ export class CommandCenter { if (unsavedTextDocuments.length > 0) { const message = unsavedTextDocuments.length === 1 - ? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before comitting?", path.basename(unsavedTextDocuments[0].uri.fsPath)) - : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before comitting?", unsavedTextDocuments.length); + ? localize('unsaved files single', "The following file is unsaved: {0}.\n\nWould you like to save it before committing?", path.basename(unsavedTextDocuments[0].uri.fsPath)) + : localize('unsaved files', "There are {0} unsaved files.\n\nWould you like to save them before committing?", unsavedTextDocuments.length); const saveAndCommit = localize('save and commit', "Save All & Commit"); const commit = localize('commit', "Commit Anyway"); const pick = await window.showWarningMessage(message, { modal: true }, saveAndCommit, commit); diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index d8a4d64e743..497e26b21d6 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -257,9 +257,9 @@ supports-color@3.1.2: dependencies: has-flag "^1.0.0" -vscode-extension-telemetry@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.17.tgz#15123e7edb34e7b9724b6056f54a869bbb922cb7" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/html-language-features/package.json b/extensions/html-language-features/package.json index fbe7c1f157b..586b1684cda 100644 --- a/extensions/html-language-features/package.json +++ b/extensions/html-language-features/package.json @@ -125,13 +125,13 @@ "html.suggest.angular1": { "type": "boolean", "scope": "resource", - "default": true, + "default": false, "description": "%html.suggest.angular1.desc%" }, "html.suggest.ionic": { "type": "boolean", "scope": "resource", - "default": true, + "default": false, "description": "%html.suggest.ionic.desc%" }, "html.suggest.html5": { @@ -173,7 +173,7 @@ } }, "dependencies": { - "vscode-extension-telemetry": "0.0.17", + "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "^4.4.0", "vscode-nls": "^3.2.4" }, diff --git a/extensions/html-language-features/yarn.lock b/extensions/html-language-features/yarn.lock index 2ff934b3440..a33a4a08108 100644 --- a/extensions/html-language-features/yarn.lock +++ b/extensions/html-language-features/yarn.lock @@ -28,9 +28,9 @@ semver@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" -vscode-extension-telemetry@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.17.tgz#15123e7edb34e7b9724b6056f54a869bbb922cb7" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/json-language-features/package.json b/extensions/json-language-features/package.json index 610fa55776c..f814f5e747d 100644 --- a/extensions/json-language-features/package.json +++ b/extensions/json-language-features/package.json @@ -100,7 +100,7 @@ } }, "dependencies": { - "vscode-extension-telemetry": "0.0.17", + "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "^4.4.0", "vscode-nls": "^3.2.4" }, diff --git a/extensions/json-language-features/package.nls.json b/extensions/json-language-features/package.nls.json index f9d52e8ebcf..943de414e15 100644 --- a/extensions/json-language-features/package.nls.json +++ b/extensions/json-language-features/package.nls.json @@ -6,7 +6,7 @@ "json.schemas.fileMatch.desc": "An array of file patterns to match against when resolving JSON files to schemas.", "json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.", "json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.", - "json.format.enable.desc": "Enable/disable default JSON formatter (requires restart)", + "json.format.enable.desc": "Enable/disable default JSON formatter", "json.tracing.desc": "Traces the communication between VS Code and the JSON language server.", "json.colorDecorators.enable.desc": "Enables or disables color decorators", "json.colorDecorators.enable.deprecationMessage": "The setting `json.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`." diff --git a/extensions/json-language-features/yarn.lock b/extensions/json-language-features/yarn.lock index 2ff934b3440..a33a4a08108 100644 --- a/extensions/json-language-features/yarn.lock +++ b/extensions/json-language-features/yarn.lock @@ -28,9 +28,9 @@ semver@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" -vscode-extension-telemetry@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.17.tgz#15123e7edb34e7b9724b6056f54a869bbb922cb7" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/make/syntaxes/make.tmLanguage.json b/extensions/make/syntaxes/make.tmLanguage.json index 505336ac862..acfd8adea23 100644 --- a/extensions/make/syntaxes/make.tmLanguage.json +++ b/extensions/make/syntaxes/make.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/fadeevab/make.tmbundle/commit/43e1a67476dea3ddefbb4f0ee7901834b31b8bee", + "version": "https://github.com/fadeevab/make.tmbundle/commit/d94d403d6d31623763a4ff86b656886fa699ef60", "name": "Makefile", "scopeName": "source.makefile", "patterns": [ @@ -257,7 +257,7 @@ } ] }, - "interpolation": { + "shell-interpolation": { "begin": "(?=`)", "end": "(?!\\G)", "name": "meta.embedded.line.shell", @@ -288,18 +288,6 @@ } ] }, - "braces-interpolation": { - "begin": "\\(", - "end": "\\)", - "patterns": [ - { - "include": "#variables" - }, - { - "include": "#braces-interpolation" - } - ] - }, "recipe": { "begin": "^(?!\\t)([^:]*)(:)(?!\\=)", "beginCaptures": { @@ -404,6 +392,40 @@ { "include": "#comment" }, + { + "include": "#variables" + }, + { + "include": "#shell-interpolation" + } + ] + }, + "interpolation": { + "patterns": [ + { + "include": "#parentheses-interpolation" + }, + { + "include": "#braces-interpolation" + } + ] + }, + "parentheses-interpolation": { + "begin": "\\(", + "end": "\\)", + "patterns": [ + { + "include": "#variables" + }, + { + "include": "#interpolation" + } + ] + }, + "braces-interpolation": { + "begin": "{", + "end": "}", + "patterns": [ { "include": "#variables" }, @@ -415,11 +437,28 @@ "variables": { "patterns": [ { - "match": "\\$[^\\(\\)]", - "name": "variable.language.makefile" + "include": "#simple-variable" }, { - "begin": "(\\$|(?<=\\$))\\(", + "include": "#variable-parentheses" + }, + { + "include": "#variable-braces" + } + ] + }, + "simple-variable": { + "patterns": [ + { + "match": "\\$[^(){}]", + "name": "variable.language.makefile" + } + ] + }, + "variable-parentheses": { + "patterns": [ + { + "begin": "\\$\\(", "captures": { "0": { "name": "punctuation.definition.variable.makefile" @@ -432,64 +471,199 @@ "include": "#variables" }, { - "match": "(?<=\\()(MAKEFILES|VPATH|SHELL|MAKESHELL|MAKE|MAKELEVEL|MAKEFLAGS|MAKECMDGOALS|CURDIR|SUFFIXES|\\.LIBPATTERNS)(?=\\s*\\))", - "name": "variable.language.makefile" + "include": "#builtin-variable-parentheses" }, { - "begin": "(?<=\\()(subst|patsubst|strip|findstring|filter(-out)?|sort|word(list)?|firstword|lastword|dir|notdir|suffix|basename|addsuffix|addprefix|join|wildcard|realpath|abspath|info|error|warning|shell|foreach|if|or|and|call|eval|value|file|guile)\\s", - "beginCaptures": { - "1": { - "name": "support.function.$1.makefile" - } - }, - "end": "(?=\\)|((?= 40800) + +ok := ok +$(info Braces {} in parentheses ({}): ${ok}) +${info Parentheses () in braces {()}: $(ok)} + +ifeq ("${ok}", "skip") + $(ok))} + ${ok}}) +endif diff --git a/extensions/make/test/colorize-results/makefile.json b/extensions/make/test/colorize-results/makefile.json index 6fbce98489a..1e2cee1cb1c 100644 --- a/extensions/make/test/colorize-results/makefile.json +++ b/extensions/make/test/colorize-results/makefile.json @@ -692,6 +692,94 @@ "hc_black": "default: #FFFFFF" } }, + { + "c": "all", + "t": "source.makefile meta.scope.target.makefile entity.name.function.target.makefile", + "r": { + "dark_plus": "entity.name.function: #DCDCAA", + "light_plus": "entity.name.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "entity.name.function: #DCDCAA" + } + }, + { + "c": ":", + "t": "source.makefile meta.scope.target.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "\t", + "t": "source.makefile punctuation.whitespace.comment.leading.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "#", + "t": "source.makefile comment.line.number-sign.makefile punctuation.definition.comment.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " \"$$\" in a shell means to escape makefile's variable substitution.", + "t": "source.makefile comment.line.number-sign.makefile", + "r": { + "dark_plus": "comment: #6A9955", + "light_plus": "comment: #008000", + "dark_vs": "comment: #6A9955", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "\tsome_shell_var=", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "$$", + "t": "source.makefile variable.language.makefile", + "r": { + "dark_plus": "variable.language: #569CD6", + "light_plus": "variable.language: #0000FF", + "dark_vs": "variable.language: #569CD6", + "light_vs": "variable.language: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "(sed -nre 's/some regex with (group)/\\1/p')", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "define", "t": "source.makefile meta.scope.conditional.makefile keyword.control.define.makefile", @@ -1572,6 +1660,545 @@ "hc_black": "string: #CE9178" } }, + { + "c": "endif", + "t": "source.makefile meta.scope.conditional.makefile keyword.control.endif.makefile", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": "CXXVER_GE480", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ":=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "$(", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "shell", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile support.function.shell.makefile", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": " expr `", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "$(", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "CXX", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": " -dumpversion | sed -e 's/\\.\\([0-9][0-9]\\)/\\1/g' -e 's/\\.\\([0-9]\\)/0\\1/g' -e 's/^[0-9]\\{3,4\\}", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "$$", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile variable.language.makefile", + "r": { + "dark_plus": "variable.language: #569CD6", + "light_plus": "variable.language: #0000FF", + "dark_vs": "variable.language: #569CD6", + "light_vs": "variable.language: #0000FF", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "/&00/'` \\>= 40800", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": " ", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": ":=", + "t": "source.makefile punctuation.separator.key-value.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ok", + "t": "source.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "$(", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "info", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile support.function.info.makefile", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": " Braces {} in parentheses ({}): ", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "${", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "}", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "${", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "info", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile support.function.info.makefile", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "support.function: #DCDCAA" + } + }, + { + "c": " Parentheses () in braces {()}: ", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "$(", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.makefile string.interpolated.makefile meta.scope.function-call.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "}", + "t": "source.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ifeq", + "t": "source.makefile meta.scope.conditional.makefile keyword.control.ifeq.makefile", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0" + } + }, + { + "c": " (\"", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "${", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "}", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "\", \"skip\")", + "t": "source.makefile meta.scope.conditional.makefile meta.scope.condition.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.makefile meta.scope.conditional.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "$(", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": ")", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": ")}", + "t": "source.makefile meta.scope.conditional.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.makefile meta.scope.conditional.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "${", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "ok", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile variable.other.makefile", + "r": { + "dark_plus": "variable: #9CDCFE", + "light_plus": "variable: #001080", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "variable: #9CDCFE" + } + }, + { + "c": "}", + "t": "source.makefile meta.scope.conditional.makefile string.interpolated.makefile punctuation.definition.variable.makefile", + "r": { + "dark_plus": "string: #CE9178", + "light_plus": "string: #A31515", + "dark_vs": "string: #CE9178", + "light_vs": "string: #A31515", + "hc_black": "string: #CE9178" + } + }, + { + "c": "})", + "t": "source.makefile meta.scope.conditional.makefile", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, { "c": "endif", "t": "source.makefile meta.scope.conditional.makefile keyword.control.endif.makefile", diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 984967f637f..b49638d9707 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -295,7 +295,7 @@ "highlight.js": "9.12.0", "markdown-it": "^8.4.1", "markdown-it-named-headers": "0.0.4", - "vscode-extension-telemetry": "0.0.17", + "vscode-extension-telemetry": "0.0.18", "vscode-nls": "^3.2.4" }, "devDependencies": { diff --git a/extensions/markdown-language-features/src/features/foldingProvider.ts b/extensions/markdown-language-features/src/features/foldingProvider.ts index 236908a1f1f..5f0d6480ff3 100644 --- a/extensions/markdown-language-features/src/features/foldingProvider.ts +++ b/extensions/markdown-language-features/src/features/foldingProvider.ts @@ -7,6 +7,7 @@ import * as vscode from 'vscode'; import { MarkdownEngine } from '../markdownEngine'; import { TableOfContentsProvider } from '../tableOfContentsProvider'; +import { Token } from 'markdown-it'; const rangeLimit = 5000; @@ -16,15 +17,44 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi private readonly engine: MarkdownEngine ) { } + private async getRegions(document: vscode.TextDocument): Promise { + + const isStartRegion = (t: string) => /^\s*/.test(t); + const isEndRegion = (t: string) => /^\s*/.test(t); + + const isRegionMarker = (token: Token) => token.type === 'html_block' && + (isStartRegion(token.content) || isEndRegion(token.content)); + + + const tokens = await this.engine.parse(document.uri, document.getText()); + const regionMarkers = tokens.filter(isRegionMarker) + .map(token => ({ line: token.map[0], isStart: isStartRegion(token.content) })); + + const nestingStack: { line: number, isStart: boolean }[] = []; + return regionMarkers + .map(marker => { + if (marker.isStart) { + nestingStack.push(marker); + } else if (nestingStack.length && nestingStack[nestingStack.length - 1].isStart) { + return new vscode.FoldingRange(nestingStack.pop()!.line, marker.line, vscode.FoldingRangeKind.Region); + } else { + // noop: invalid nesting (i.e. [end, start] or [start, end, end]) + } + return null; + }) + .filter((region: vscode.FoldingRange | null): region is vscode.FoldingRange => !!region); + } + public async provideFoldingRanges( document: vscode.TextDocument, _: vscode.FoldingContext, _token: vscode.CancellationToken ): Promise { const tocProvider = new TableOfContentsProvider(this.engine, document); - let toc = await tocProvider.getToc(); - if (toc.length > rangeLimit) { - toc = toc.slice(0, rangeLimit); + let [regions, toc] = await Promise.all([this.getRegions(document), tocProvider.getToc()]); + + if (toc.length > rangeLimit - regions.length) { + toc = toc.slice(0, rangeLimit - regions.length); } const foldingRanges = toc.map((entry, startIndex) => { @@ -44,7 +74,6 @@ export default class MarkdownFoldingProvider implements vscode.FoldingRangeProvi typeof end === 'number' ? end : document.lineCount - 1); }); - - return foldingRanges; + return [...regions, ...foldingRanges]; } } \ No newline at end of file diff --git a/extensions/markdown-language-features/src/test/foldingProvider.test.ts b/extensions/markdown-language-features/src/test/foldingProvider.test.ts index 44c570d64d4..cd3b82d599e 100644 --- a/extensions/markdown-language-features/src/test/foldingProvider.test.ts +++ b/extensions/markdown-language-features/src/test/foldingProvider.test.ts @@ -78,6 +78,31 @@ y`); assert.strictEqual(firstFold.end, 2); }); + test('Should fold nested markers', async () => { + const folds = await getFoldsForDocument(`a + +b + +b.a + +b + +b.b + +b + +a`); + assert.strictEqual(folds.length, 3); + const [outer, first, second] = folds.sort((a, b) => a.start - b.start); + + assert.strictEqual(outer.start, 1); + assert.strictEqual(outer.end, 11); + assert.strictEqual(first.start, 3); + assert.strictEqual(first.end, 5); + assert.strictEqual(second.start, 7); + assert.strictEqual(second.end, 9); + }); + }); diff --git a/extensions/markdown-language-features/yarn.lock b/extensions/markdown-language-features/yarn.lock index c44b9715579..a2e69ddb6b8 100644 --- a/extensions/markdown-language-features/yarn.lock +++ b/extensions/markdown-language-features/yarn.lock @@ -5456,9 +5456,9 @@ vm-browserify@0.0.4: dependencies: indexof "0.0.1" -vscode-extension-telemetry@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.17.tgz#15123e7edb34e7b9724b6056f54a869bbb922cb7" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/npm/package.json b/extensions/npm/package.json index b0017b08b49..dc6a25a9dca 100644 --- a/extensions/npm/package.json +++ b/extensions/npm/package.json @@ -191,7 +191,7 @@ "type": "boolean", "default": false, "scope": "resource", - "description": "%config.npm.runSilent%" + "markdownDescription": "%config.npm.runSilent%" }, "npm.packageManager": { "scope": "resource", @@ -226,7 +226,7 @@ "open", "run" ], - "description": "%config.npm.scriptExplorerAction%", + "markdownDescription": "%config.npm.scriptExplorerAction%", "scope": "window", "default": "open" }, diff --git a/extensions/npm/package.nls.json b/extensions/npm/package.nls.json index 7bdfed8e973..4a33bcc0bc3 100644 --- a/extensions/npm/package.nls.json +++ b/extensions/npm/package.nls.json @@ -6,7 +6,7 @@ "config.npm.packageManager": "The package manager used to run scripts.", "config.npm.exclude": "Configure glob patterns for folders that should be excluded from automatic script detection.", "config.npm.enableScriptExplorer": "Enable an explorer view for npm scripts.", - "config.npm.scriptExplorerAction": "The default click action used in the scripts explorer: 'open' or 'run', the default is 'open'.", + "config.npm.scriptExplorerAction": "The default click action used in the scripts explorer: `open` or `run`, the default is `open`.", "config.npm.fetchOnlinePackageInfo": "Fetch data from https://registry.npmjs/org and https://registry.bower.io to provide auto-completion and information on hover features on npm dependencies.", "npm.parseError": "Npm task detection: failed to parse the file {0}", "taskdef.script": "The npm script to customize.", diff --git a/extensions/package.json b/extensions/package.json index 7483338ab01..8e143a8485e 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -3,7 +3,7 @@ "version": "0.0.1", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "3.0.1-insiders.20180726" + "typescript": "3.0.1" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/php-language-features/package.nls.json b/extensions/php-language-features/package.nls.json index c991103e41d..3920760d1d2 100644 --- a/extensions/php-language-features/package.nls.json +++ b/extensions/php-language-features/package.nls.json @@ -1,5 +1,5 @@ { - "configuration.suggest.basic": "Configures if the built-in PHP language suggestions are enabled. The support suggests PHP globals and variables.", + "configuration.suggest.basic": "Controls whether the built-in PHP language suggestions are enabled. The support suggests PHP globals and variables.", "configuration.validate.enable": "Enable/disable built-in PHP validation.", "configuration.validate.executablePath": "Points to the PHP executable.", "configuration.validate.run": "Whether the linter is run on save or on type.", diff --git a/extensions/search-rg/package.json b/extensions/search-rg/package.json index d798d7b2842..c4621670df8 100644 --- a/extensions/search-rg/package.json +++ b/extensions/search-rg/package.json @@ -13,7 +13,7 @@ }, "categories": [], "dependencies": { - "vscode-extension-telemetry": "0.0.15", + "vscode-extension-telemetry": "0.0.18", "vscode-nls": "^3.2.4", "vscode-ripgrep": "^1.0.1" }, diff --git a/extensions/search-rg/src/utils.ts b/extensions/search-rg/src/utils.ts index fd083fe075f..d06e4f1ab8a 100644 --- a/extensions/search-rg/src/utils.ts +++ b/extensions/search-rg/src/utils.ts @@ -20,8 +20,6 @@ export function anchorGlob(glob: string): string { } export function joinPath(resource: vscode.Uri, pathFragment: string): vscode.Uri { - const joinedPath = path.join(resource.path || '/', pathFragment); - return resource.with({ - path: joinedPath - }); + const joinedPath = path.join(resource.fsPath || '/', pathFragment); + return vscode.Uri.file(joinedPath); } diff --git a/extensions/search-rg/yarn.lock b/extensions/search-rg/yarn.lock index 99b491f01ff..1be6ca92db5 100644 --- a/extensions/search-rg/yarn.lock +++ b/extensions/search-rg/yarn.lock @@ -1539,9 +1539,9 @@ vinyl@^2.0.1, vinyl@^2.0.2: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-extension-telemetry@0.0.15: - version "0.0.15" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index e8915e04b78..81a8591a875 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -529,6 +529,7 @@ "inputValidation.errorBorder": "#f1897f", "errorForeground": "#f1897f", "badge.background": "#705697AA", - "progressBar.background": "#705697" + "progressBar.background": "#705697", + "walkThrough.embeddedEditorBackground": "#00000014" } } diff --git a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json index 37f3e385d7b..1f0cd3202e7 100644 --- a/extensions/theme-solarized-light/themes/solarized-light-color-theme.json +++ b/extensions/theme-solarized-light/themes/solarized-light-color-theme.json @@ -481,6 +481,9 @@ "terminal.ansiBrightBlue": "#839496", "terminal.ansiBrightMagenta": "#6c71c4", "terminal.ansiBrightCyan": "#93a1a1", - "terminal.ansiBrightWhite": "#eee8d5" + "terminal.ansiBrightWhite": "#eee8d5", + + // Interactive Playground + "walkThrough.embeddedEditorBackground": "#00000014" } } \ No newline at end of file diff --git a/extensions/typescript-language-features/OSSREADME.json b/extensions/typescript-language-features/OSSREADME.json index d36b840718e..692c0592a16 100644 --- a/extensions/typescript-language-features/OSSREADME.json +++ b/extensions/typescript-language-features/OSSREADME.json @@ -7,19 +7,10 @@ "description": "The files syntaxes/TypeScript.tmLanguage.json and syntaxes/TypeScriptReact.tmLanguage.json were derived from TypeScript.tmLanguage and TypeScriptReact.tmLanguage in https://github.com/Microsoft/TypeScript-TmLanguage." }, { - "name": "DefinitelyTyped", - "version": "0.0.2", + "name": "definitelytyped", "license": "MIT", "repositoryURL": "https://github.com/DefinitelyTyped/DefinitelyTyped", - "description": "Typings files that are downloaded by TypeScript. These typings power IntelliSense for JavaScript and TypeScript.", - "licenseDetail": [ - "MIT License", - "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.", - ] + "description": "Typings files that are downloaded by TypeScript. These typings power IntelliSense for JavaScript and TypeScript." }, { "name": "Unicode", @@ -80,7 +71,7 @@ "Except as contained in this notice, the name of a copyright holder", "shall not be used in advertising or otherwise to promote the sale,", "use or other dealings in these Data Files or Software without prior", - "written authorization of the copyright holder.", + "written authorization of the copyright holder." ] }, { @@ -91,74 +82,25 @@ "W3C License", "This work is being provided by the copyright holders under the following license.", "By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, and will comply with the following terms and conditions.", - "Permission to copy, modify, and distribute this work, with or without modification,�for any purpose and without fee or royalty is hereby granted, provided that you include the following ", + "Permission to copy, modify, and distribute this work, with or without modification, for any purpose and without fee or royalty is hereby granted, provided that you include the following ", "on ALL copies of the work or portions thereof, including modifications:", "* The full text of this NOTICE in a location viewable to users of the redistributed or derivative work.", "* Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none exist, the W3C Software and Document Short Notice should be included.", "* Notice of any changes or modifications, through a copyright statement on the new code or document such as \"This software or document includes material copied from or derived ", - "from [title and URI of the W3C document]. Copyright � [YEAR] W3C� (MIT, ERCIM, Keio, Beihang).\" ", + "from Document Object Model. Copyright © 2015 W3C® (MIT, ERCIM, Keio, Beihang).\" ", "Disclaimers", "THIS WORK IS PROVIDED \"AS IS", " AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR ", "FITNESS FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS.", "COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT.", "The name and trademarks of copyright holders may NOT be used in advertising or publicity pertaining to the work without specific, written prior permission. ", - "Title to copyright in this work will at all times remain with copyright holders.", + "Title to copyright in this work will at all times remain with copyright holders." ] }, { "name": "Web Background Synchronization", - "license": "W3C Community Final Specification Agreement", - "description": "TypeScript includes files related to this specification", - "licenseDetail": [ - "W3C Community Final Specification Agreement ", - "To secure commitments from participants for the full text of a Community or Business Group Report, the group may call for voluntary commitments to the following terms; a \"summary\" is ", - "available. See also the related \"W3C Community Contributor License Agreement\".", - "1. The Purpose of this Agreement.", - "This Agreement sets forth the terms under which I make certain copyright and patent rights available to you for your implementation of the Specification. ", - "Any other capitalized terms not specifically defined herein have the same meaning as those terms have in the \"W3C Patent Policy\", and if not defined there, in the \"W3C Process Document\".", - "2. Copyrights. ", - "2.1. Copyright Grant. I grant to you a perpetual (for the duration of the applicable copyright), worldwide, non-exclusive, no-charge, royalty-free, copyright license, without any obligation for accounting to me, to reproduce, prepare derivative works of, publicly display, publicly perform, sublicense, distribute, and implement the Specification to the full extent of my copyright interest in the Specification. ", - "2.2. Attribution. As a condition of the copyright grant, you must include an attribution to the Specification in any derivative work you make based on the Specification. That attribution must include, at minimum, the Specification name and version number.", - "3. Patents. ", - "3.1. Patent Licensing Commitment. I agree to license my Essential Claims under the W3C Community RF Licensing Requirements. This requirement includes Essential Claims that I own and any that I have the right to license without obligation of payment or other consideration to an unrelated third party. W3C Community RF Licensing Requirements obligations made concerning the Specification and described in this policy are binding on me for the life of the patents in question and encumber the patents containing Essential Claims, regardless of changes in participation status or W3C Membership. I also agree to license my Essential Claims under the W3C Community RF Licensing Requirements in derivative works of the Specification so long as all normative portions of the Specification are maintained and that this licensing commitment does not extend to any portion of the derivative work that was not included in the Specification.", - "3.2. Optional, Additional Patent Grant. In addition to the provisions of Section 3.1, I may also, at my option, make certain intellectual property rights infringed by implementations of the Specification, including Essential Claims, available by providing those terms via the W3C Web site.", - "4. No Other Rights. Except as specifically set forth in this Agreement, no other express or implied patent, trademark, copyright, or other property rights are granted under this Agreement, including by implication, waiver, or estoppel.", - "5. Antitrust Compliance. I acknowledge that I may compete with other participants, that I am under no obligation to implement the Specification, that each participant is free to develop competing technologies and standards, and that each party is free to license its patent rights to third parties, including for the purpose of enabling competing technologies and standards.", - "6. Non-Circumvention. I agree that I will not intentionally take or willfully assist any third party to take any action for the purpose of circumventing my obligations under this Agreement.", - "7. Transition to W3C Recommendation Track. The Specification developed by the Project may transition to the W3C Recommendation Track. The W3C Team is responsible for notifying me that a Corresponding Working Group has been chartered. I have no obligation to join the Corresponding Working Group. If the Specification developed by the Project transitions to the W3C Recommendation Track, the following terms apply: ", - "7.1. If I join the Corresponding Working Group. If I join the Corresponding Working Group, I will be subject to all W3C rules, obligations, licensing commitments, and policies that govern that Corresponding Working Group.", - "7.2. If I Do Not Join the Corresponding Working Group. ", - "7.2.1. Licensing Obligations to Resulting Specification. If I do not join the Corresponding Working Group, I agree to offer patent licenses according to the W3C Royalty-Free licensing requirements described in Section 5 of the W3C Patent Policy for the portions of the Specification included in the resulting Recommendation. This licensing commitment does not extend to any portion of an implementation of the Recommendation that was not included in the Specification. This licensing commitment may not be revoked but may be modified through the exclusion process defined in Section 4 of the W3C Patent Policy. I am not required to join the Corresponding Working Group to exclude patents from the W3C Royalty-Free licensing commitment, but must otherwise follow the normal exclusion procedures defined by the W3C Patent Policy. The W3C Team will notify me of any Call for Exclusion in the Corresponding Working Group as set forth in Section 4.5 of the W3C Patent Policy.", - "7.2.2. No Disclosure Obligation. If I do not join the Corresponding Working Group, I have no patent disclosure obligations outside of those set forth in Section 6 of the W3C Patent Policy.", - "8. Conflict of Interest. I will disclose significant relationships when those relationships might reasonably be perceived as creating a conflict of interest with my role. I will notify W3C of any change in my affiliation using W3C-provided mechanisms.", - "9. Representations, Warranties and Disclaimers. I represent and warrant that I am legally entitled to grant the rights and promises set forth in this Agreement. IN ALL OTHER RESPECTS THE SPECIFICATION IS PROVIDED �AS IS.� The entire risk as to implementing or otherwise using the Specification is assumed by the implementer and user. Except as stated herein, I expressly disclaim any warranties (express, implied, or otherwise), including implied warranties of merchantability, non-infringement, fitness for a particular purpose, or title, related to the Specification. IN NO EVENT WILL ANY PARTY BE LIABLE TO ANY OTHER PARTY FOR LOST PROFITS OR ANY FORM OF INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES OF ANY CHARACTER FROM ANY CAUSES OF ACTION OF ANY KIND WITH RESPECT TO THIS AGREEMENT, WHETHER BASED ON BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), OR OTHERWISE, AND WHETHER OR NOT THE OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. All of my obligations under Section 3 regarding the transfer, successors in interest, or assignment of Granted Claims will be satisfied if I notify the transferee or assignee of any patent that I know contains Granted Claims of the obligations under Section 3. Nothing in this Agreement requires me to undertake a patent search.", - "10. Definitions. ", - "10.1. Agreement. �Agreement� means this W3C Community Final Specification Agreement.", - "10.2. Corresponding Working Group. �Corresponding Working Group� is a W3C Working Group that is chartered to develop a Recommendation, as defined in the W3C Process Document, that takes the Specification as an input.", - "10.3. Essential Claims. �Essential Claims� shall mean all claims in any patent or patent application in any jurisdiction in the world that would necessarily be infringed by implementation of the Specification. A claim is necessarily infringed hereunder only when it is not possible to avoid infringing it because there is no non-infringing alternative for implementing the normative portions of the Specification. Existence of a non-infringing alternative shall be judged based on the state of the art at the time of the publication of the Specification. The following are expressly excluded from and shall not be deemed to constitute Essential Claims: ", - "10.3.1. any claims other than as set forth above even if contained in the same patent as Essential Claims; and", - "10.3.2. claims which would be infringed only by: ", - "portions of an implementation that are not specified in the normative portions of the Specification, or", - "enabling technologies that may be necessary to make or use any product or portion thereof that complies with the Specification and are not themselves expressly set forth in the Specification (e.g., semiconductor manufacturing technology, compiler technology, object-oriented technology, basic operating system technology, and the like); or", - "the implementation of technology developed elsewhere and merely incorporated by reference in the body of the Specification.", - "10.3.3. design patents and design registrations.", - "For purposes of this definition, the normative portions of the Specification shall be deemed to include only architectural and interoperability requirements. Optional features in the RFC 2119 sense are considered normative unless they are specifically identified as informative. Implementation examples or any other material that merely illustrate the requirements of the Specification are informative, rather than normative.", - "10.4. I, Me, or My. �I,� �me,� or �my� refers to the signatory.", - "10.5 Project. �Project� means the W3C Community Group or Business Group for which I executed this Agreement.", - "10.6. Specification. �Specification� means the Specification identified by the Project as the target of this agreement in a call for Final Specification Commitments. W3C shall provide the authoritative mechanisms for the identification of this Specification.", - "10.7. W3C Community RF Licensing Requirements. �W3C Community RF Licensing Requirements� license shall mean a non-assignable, non-sublicensable license to make, have made, use, sell, have sold, offer to sell, import, and distribute and dispose of implementations of the Specification that: ", - "10.7.1. shall be available to all, worldwide, whether or not they are W3C Members;", - "10.7.2. shall extend to all Essential Claims owned or controlled by me;", - "10.7.3. may be limited to implementations of the Specification, and to what is required by the Specification;", - "10.7.4. may be conditioned on a grant of a reciprocal RF license (as defined in this policy) to all Essential Claims owned or controlled by the licensee. A reciprocal license may be required to be available to all, and a reciprocal license may itself be conditioned on a further reciprocal license from all.", - "10.7.5. may not be conditioned on payment of royalties, fees or other consideration;", - "10.7.6. may be suspended with respect to any licensee when licensor issued by licensee for infringement of claims essential to implement the Specification or any W3C Recommendation;", - "10.7.7. may not impose any further conditions or restrictions on the use of any technology, intellectual property rights, or other restrictions on behavior of the licensee, but may include reasonable, customary terms relating to operation or maintenance of the license relationship such as the following: choice of law and dispute resolution;", - "10.7.8. shall not be considered accepted by an implementer who manifests an intent not to accept the terms of the W3C Community RF Licensing Requirements license as offered by the licensor.", - "10.7.9. The RF license conforming to the requirements in this policy shall be made available by the licensor as long as the Specification is in effect. The term of such license shall be for the life of the patents in question.", - "I am encouraged to provide a contact from which licensing information can be obtained and other relevant licensing information. Any such information will be made publicly available. ", - "10.8. You or Your. �You,� �you,� or �your� means any person or entity who exercises copyright or patent rights granted under this Agreement, and any person that person or entity controls.", - ] + "license": "Apache2", + "repositoryURL": "https://github.com/WICG/BackgroundSync", + "description": "TypeScript includes files related to this specification" } ] \ No newline at end of file diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index cd5dc87bf4c..cf13bdf97cd 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -18,7 +18,7 @@ "dependencies": { "jsonc-parser": "^2.0.1", "semver": "4.3.6", - "vscode-extension-telemetry": "0.0.17", + "vscode-extension-telemetry": "0.0.18", "vscode-nls": "^3.2.4" }, "devDependencies": { @@ -367,7 +367,7 @@ "javascript.implicitProjectConfig.experimentalDecorators": { "type": "boolean", "default": false, - "description": "%javascript.implicitProjectConfig.experimentalDecorators%", + "markdownDescription": "%javascript.implicitProjectConfig.experimentalDecorators%", "scope": "window" }, "javascript.nameSuggestions": { @@ -425,7 +425,7 @@ null ], "default": null, - "description": "%typescript.locale%", + "markdownDescription": "%typescript.locale%", "scope": "window" }, "javascript.suggestionActions.enabled": { @@ -448,7 +448,7 @@ "double" ], "default": "auto", - "description": "%typescript.preferences.quoteStyle%", + "markdownDescription": "%typescript.preferences.quoteStyle%", "scope": "resource" }, "typescript.preferences.quoteStyle": { @@ -459,7 +459,7 @@ "double" ], "default": "auto", - "description": "%typescript.preferences.quoteStyle%", + "markdownDescription": "%typescript.preferences.quoteStyle%", "scope": "resource" }, "javascript.preferences.importModuleSpecifier": { @@ -469,7 +469,7 @@ "relative", "non-relative" ], - "enumDescriptions": [ + "markdownEnumDescriptions": [ "%typescript.preferences.importModuleSpecifier.auto%", "%typescript.preferences.importModuleSpecifier.relative%", "%typescript.preferences.importModuleSpecifier.nonRelative%" @@ -485,6 +485,11 @@ "relative", "non-relative" ], + "enumDescriptions": [ + "%typescript.preferences.importModuleSpecifier.auto%", + "%typescript.preferences.importModuleSpecifier.relative%", + "%typescript.preferences.importModuleSpecifier.nonRelative%" + ], "default": "auto", "description": "%typescript.preferences.importModuleSpecifier%", "scope": "resource" diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 918366a21ca..15b80915353 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -32,12 +32,12 @@ "goToProjectConfig.title": "Go to Project Configuration", "javascript.referencesCodeLens.enabled": "Enable/disable references CodeLens in JavaScript files.", "typescript.referencesCodeLens.enabled": "Enable/disable references CodeLens in TypeScript files.", - "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens.", - "typescript.openTsServerLog.title": "Open TS Server log", - "typescript.restartTsServer": "Restart TS server", - "typescript.selectTypeScriptVersion.title": "Select TypeScript Version", - "typescript.reportStyleChecksAsWarnings": "Report style checks as warnings", - "jsDocCompletion.enabled": "Enable/disable auto JSDoc comments", + "typescript.implementationsCodeLens.enabled": "Enable/disable implementations CodeLens. This CodeLens shows the implementers of an interface.", + "typescript.openTsServerLog.title": "Open TS Server log.", + "typescript.restartTsServer": "Restart TS server.", + "typescript.selectTypeScriptVersion.title": "Select TypeScript Version.", + "typescript.reportStyleChecksAsWarnings": "Report style checks as warnings.", + "jsDocCompletion.enabled": "Enable/disable auto JSDoc comments.", "javascript.implicitProjectConfig.checkJs": "Enable/disable semantic checking of JavaScript files. Existing jsconfig.json or tsconfig.json files override this setting. Requires using TypeScript 2.3.1 or newer in the workspace.", "typescript.npm": "Specifies the path to the NPM executable used for Automatic Type Acquisition. Requires using TypeScript 2.3.4 or newer in the workspace.", "typescript.check.npmIsInstalled": "Check if NPM is installed for Automatic Type Acquisition.", @@ -50,13 +50,13 @@ "typescript.problemMatchers.tsc.label": "TypeScript problems", "typescript.problemMatchers.tscWatch.label": "TypeScript problems (watch mode)", "typescript.quickSuggestionsForPaths": "Enable/disable quick suggestions when typing out an import path.", - "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.", + "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.", "typescript.autoImportSuggestions.enabled": "Enable/disable auto import suggestions. Requires using TypeScript 2.6.1 or newer in the workspace.", "taskDefinition.tsconfig.description": "The tsconfig file that defines the TS build.", "javascript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for JavaScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace.", "typescript.suggestionActions.enabled": "Enable/disable suggestion diagnostics for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace.", - "typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes: 'single' quotes, 'double' quotes, or 'auto' infer quote type from existing imports. Requires using TypeScript 2.9 or newer in the workspace.", + "typescript.preferences.quoteStyle": "Preferred quote style to use for quick fixes: `single` quotes, `double` quotes, or `auto` infer quote type from existing imports. Requires using TypeScript 2.9 or newer in the workspace.", "typescript.preferences.importModuleSpecifier": "Preferred path style for auto imports.", "typescript.preferences.importModuleSpecifier.auto": "Infer the shortest path type.", "typescript.preferences.importModuleSpecifier.relative": "Relative to the file location.", @@ -66,4 +66,4 @@ "typescript.updateImportsOnFileMove.enabled.always": "Always update paths automatically.", "typescript.updateImportsOnFileMove.enabled.never": "Never rename paths and don't prompt.", "typescript.autoClosingTags": "Enable/disable automatic closing of JSX tags. Requires using TypeScript 3.0 or newer in the workspace." -} \ No newline at end of file +} diff --git a/extensions/typescript-language-features/src/features/tagClosing.ts b/extensions/typescript-language-features/src/features/tagClosing.ts index 838779848da..ab9843ae7f2 100644 --- a/extensions/typescript-language-features/src/features/tagClosing.ts +++ b/extensions/typescript-language-features/src/features/tagClosing.ts @@ -73,8 +73,10 @@ class TagClosing extends Disposable { return; } - const secondToLastCharacter = lastChange.text[lastChange.text.length - 2]; - if (secondToLastCharacter === '>') { + const priorCharacter = lastChange.range.start.character > 0 + ? document.getText(new vscode.Range(lastChange.range.start.translate({ characterDelta: -1 }), lastChange.range.start)) + : ''; + if (priorCharacter === '>') { return; } diff --git a/extensions/typescript-language-features/src/features/tsconfig.ts b/extensions/typescript-language-features/src/features/tsconfig.ts index 048ac79ab2c..a7e89ea1332 100644 --- a/extensions/typescript-language-features/src/features/tsconfig.ts +++ b/extensions/typescript-language-features/src/features/tsconfig.ts @@ -33,7 +33,16 @@ class TsconfigLinkProvider implements vscode.DocumentLinkProvider { } private getExendsLink(document: vscode.TextDocument, root: jsonc.Node): vscode.DocumentLink | undefined { - return this.pathNodeToLink(document, jsonc.findNodeAtLocation(root, ['extends'])); + const extendsNode = jsonc.findNodeAtLocation(root, ['extends']); + if (!this.isPathValue(extendsNode)) { + return undefined; + } + + return new vscode.DocumentLink( + this.getRange(document, extendsNode), + basename(extendsNode.value).match('.json$') + ? this.getFileTarget(document, extendsNode) + : vscode.Uri.file(join(dirname(document.uri.fsPath), extendsNode!.value + '.json'))); } private getFilesLinks(document: vscode.TextDocument, root: jsonc.Node) { diff --git a/extensions/typescript-language-features/yarn.lock b/extensions/typescript-language-features/yarn.lock index 3f1d2b4ed86..b0a99345853 100644 --- a/extensions/typescript-language-features/yarn.lock +++ b/extensions/typescript-language-features/yarn.lock @@ -1546,9 +1546,9 @@ vinyl@^2.0.1, vinyl@^2.0.2: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-extension-telemetry@0.0.17: - version "0.0.17" - resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.17.tgz#15123e7edb34e7b9724b6056f54a869bbb922cb7" +vscode-extension-telemetry@0.0.18: + version "0.0.18" + resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327" dependencies: applicationinsights "1.0.1" diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts index a350f5334e2..41305d076b2 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/workspace.test.ts @@ -520,6 +520,16 @@ suite('workspace-namespace', () => { assert.equal(vscode.workspace.asRelativePath(results[0].uri), '10linefile.ts'); }); + test('findTextInFiles, cancellation', async () => { + const results: vscode.TextSearchResult[] = []; + const cancellation = new vscode.CancellationTokenSource(); + cancellation.cancel(); + + await vscode.workspace.findTextInFiles({ pattern: 'foo' }, result => { + results.push(result); + }, cancellation.token); + }); + test('applyEdit', () => { return vscode.workspace.openTextDocument(vscode.Uri.parse('untitled:' + join(vscode.workspace.rootPath || '', './new2.txt'))).then(doc => { diff --git a/extensions/yarn.lock b/extensions/yarn.lock index a9d7e2b8a70..67d803d4e5e 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -2,6 +2,6 @@ # yarn lockfile v1 -typescript@3.0.1-insiders.20180726: - version "3.0.1-insiders.20180726" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1-insiders.20180726.tgz#3f921f23c8768b6fb665ee8a6895b5fca14b0c5f" +typescript@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.1.tgz#43738f29585d3a87575520a4b93ab6026ef11fdb" diff --git a/package.json b/package.json index 84dcc5c59b2..11eeb0c2ecd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.26.0", - "distro": "4b5c6aa6ea6f222d62d08ca5653049b200be93a5", + "version": "1.27.0", + "distro": "73cb7173636931afba8d741be343ceafb11a177e", "author": { "name": "Microsoft Corporation" }, @@ -46,11 +46,12 @@ "sudo-prompt": "8.2.0", "v8-inspect-profiler": "^0.0.8", "vscode-chokidar": "1.6.2", - "vscode-debugprotocol": "1.30.0", + "vscode-debugprotocol": "1.31.0", "vscode-nsfw": "1.0.17", "vscode-ripgrep": "^1.0.1", "vscode-textmate": "^4.0.1", - "vscode-xterm": "3.6.0-beta11", + "vscode-xterm": "3.7.0-beta3", + "winreg": "^1.2.4", "yauzl": "^2.9.1" }, "devDependencies": { @@ -60,7 +61,6 @@ "@types/mocha": "2.2.39", "@types/sinon": "1.16.34", "asar": "^0.14.0", - "azure-storage": "^0.3.1", "chromium-pickle-js": "^0.2.0", "clean-css": "3.4.6", "coveralls": "^2.11.11", @@ -102,7 +102,7 @@ "istanbul": "^0.3.17", "jsdom-no-contextify": "^3.1.0", "lazy.js": "^0.4.2", - "mime": "1.2.11", + "mime": "^1.4.1", "minimatch": "^2.0.10", "mkdirp": "^0.5.0", "mocha": "^2.2.5", diff --git a/resources/linux/debian/control.template b/resources/linux/debian/control.template index d84aba31105..92dbac93f45 100644 --- a/resources/linux/debian/control.template +++ b/resources/linux/debian/control.template @@ -1,7 +1,7 @@ Package: @@NAME@@ Version: @@VERSION@@ Section: devel -Depends: libnotify4, libnss3 (>= 3.26), gnupg, apt, libxkbfile1, libgconf-2-4, libsecret-1-0, libgtk-3-0 (>= 3.10.0) +Depends: libnotify4, libnss3 (>= 2:3.26), gnupg, apt, libxkbfile1, libgconf-2-4, libsecret-1-0, libgtk-3-0 (>= 3.10.0) Priority: optional Architecture: @@ARCHITECTURE@@ Maintainer: Microsoft Corporation diff --git a/src/typings/vscode-xterm.d.ts b/src/typings/vscode-xterm.d.ts index b3a6c367c60..5f1e27de082 100644 --- a/src/typings/vscode-xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -692,7 +692,7 @@ declare module 'vscode-xterm' { translateBufferLineToString(lineIndex: number, trimRight: boolean): string; }; - send(text: string): void; + handler(text: string): void; /** * Emit an event on the terminal. diff --git a/src/typings/winreg.d.ts b/src/typings/winreg.d.ts new file mode 100644 index 00000000000..70047d8b50f --- /dev/null +++ b/src/typings/winreg.d.ts @@ -0,0 +1,338 @@ +// Type definitions for Winreg v1.2.0 +// Project: http://fresc81.github.io/node-winreg/ +// Definitions by: RX14 , BobBuehler +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +declare var Winreg: WinregStatic; + +interface WinregStatic { + /** + * Creates a registry object, which provides access to a single registry key. + * Note: This class is returned by a call to ```require('winreg')```. + * + * @public + * @class + * + * @param {@link Options} options - the options + * + * @example + * var Registry = require('winreg') + * , autoStartCurrentUser = new Registry({ + * hive: Registry.HKCU, + * key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Run' + * }); + */ + new (options: Winreg.Options): Winreg.Registry; + + /** + * Registry hive key HKEY_LOCAL_MACHINE. + * Note: For writing to this hive your program has to run with admin privileges. + */ + HKLM: string; + + /** + * Registry hive key HKEY_CURRENT_USER. + */ + HKCU: string; + + /** + * Registry hive key HKEY_CLASSES_ROOT. + * Note: For writing to this hive your program has to run with admin privileges. + */ + HKCR: string; + + /** + * Registry hive key HKEY_USERS. + * Note: For writing to this hive your program has to run with admin privileges. + */ + HKU: string; + + /** + * Registry hive key HKEY_CURRENT_CONFIG. + * Note: For writing to this hive your program has to run with admin privileges. + */ + HKCC: string; + + /** + * Collection of available registry hive keys. + */ + HIVES: Array; + + /** + * Registry value type STRING. + * + * Values of this type contain a string. + */ + REG_SZ: string; + + /** + * Registry value type MULTILINE_STRING. + * + * Values of this type contain a multiline string. + */ + REG_MULTI_SZ: string; + + /** + * Registry value type EXPANDABLE_STRING. + * + * Values of this type contain an expandable string. + */ + REG_EXPAND_SZ: string; + + /** + * Registry value type DOUBLE_WORD. + * + * Values of this type contain a double word (32 bit integer). + */ + REG_DWORD: string; + + /** + * Registry value type QUAD_WORD. + * + * Values of this type contain a quad word (64 bit integer). + */ + REG_QWORD: string; + + /** + * Registry value type BINARY. + * + * Values of this type contain a binary value. + */ + REG_BINARY: string; + + /** + * Registry value type UNKNOWN. + * + * Values of this type contain a value of an unknown type. + */ + REG_NONE: string; + + /** + * Collection of available registry value types. + */ + REG_TYPES: Array; + + /** + * The name of the default value. May be used instead of the empty string literal for better readability. + */ + DEFAULT_VALUE: string; +} + +declare namespace Winreg { + export interface Options { + /** + * Optional hostname, must start with '\\' sequence. + */ + host?: string; + + /** + * Optional hive ID, default is HKLM. + */ + hive?: string; + + /** + * Optional key, default is the root key. + */ + key?: string; + + /** + * Optional registry hive architecture ('x86' or 'x64'; only valid on Windows 64 Bit Operating Systems). + */ + arch?: string; + } + + /** + * A registry object, which provides access to a single registry key. + */ + export interface Registry { + /** + * The hostname. + * @readonly + */ + host: string; + + /** + * The hive id. + * @readonly + */ + hive: string; + + /** + * The registry key name. + * @readonly + */ + key: string; + + /** + * The full path to the registry key. + * @readonly + */ + path: string; + + /** + * The registry hive architecture ('x86' or 'x64'). + * @readonly + */ + arch: string; + + /** + * Creates a new {@link Registry} instance that points to the parent registry key. + * @readonly + */ + parent: Registry; + + /** + * Retrieve all values from this registry key. + * @param {valuesCallback} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @param {array=} cb.items - an array of {@link RegistryItem} objects + * @returns {Registry} this registry key object + */ + values(cb: (err: Error, result: Array) => void): Registry; + + /** + * Retrieve all subkeys from this registry key. + * @param {function (err, items)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @param {array=} cb.items - an array of {@link Registry} objects + * @returns {Registry} this registry key object + */ + keys(cb: (err: Error, result: Array) => void): Registry; + + /** + * Gets a named value from this registry key. + * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value + * @param {function (err, item)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @param {RegistryItem=} cb.item - the retrieved registry item + * @returns {Registry} this registry key object + */ + get(name: string, cb: (err: Error, result: Winreg.RegistryItem) => void): Registry; + + /** + * Sets a named value in this registry key, overwriting an already existing value. + * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value + * @param {string} type - the value type + * @param {string} value - the value + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + set(name: string, type: string, value: string, cb: (err: Error) => void): Registry; + + /** + * Remove a named value from this registry key. If name is empty, sets the default value of this key. + * Note: This key must be already existing. + * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + remove(name: string, cb: (err: Error) => void): Registry; + + /** + * Remove all subkeys and values (including the default value) from this registry key. + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + clear(cb: (err: Error) => void): Registry; + + /** + * Alias for the clear method to keep it backward compatible. + * @method + * @deprecated Use {@link Registry#clear} or {@link Registry#destroy} in favour of this method. + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + erase(cb: (err: Error) => void): Registry; + + /** + * Delete this key and all subkeys from the registry. + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + destroy(cb: (err: Error) => void): Registry; + + /** + * Create this registry key. Note that this is a no-op if the key already exists. + * @param {function (err)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @returns {Registry} this registry key object + */ + create(cb: (err: Error) => void): Registry; + + /** + * Checks if this key already exists. + * @param {function (err, exists)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @param {boolean=} cb.exists - true if a registry key with this name already exists + * @returns {Registry} this registry key object + */ + keyExists(cb: (err: Error, exists: boolean) => void): Registry; + + /** + * Checks if a value with the given name already exists within this key. + * @param {string} name - the value name, use {@link Registry.DEFAULT_VALUE} or an empty string for the default value + * @param {function (err, exists)} cb - callback function + * @param {error=} cb.err - error object or null if successful + * @param {boolean=} cb.exists - true if a value with the given name was found in this key + * @returns {Registry} this registry key object + */ + valueExists(name: string, cb: (err: Error, exists: boolean) => void): Registry; + } + + /** + * A single registry value record. + * Objects of this type are created internally and returned by methods of {@link Registry} objects. + */ + export interface RegistryItem { + /** + * The hostname. + * @readonly + */ + host: string; + + /** + * The hive id. + * @readonly + */ + hive: string; + + /** + * The registry key. + * @readonly + */ + key: string; + + /** + * The value name. + * @readonly + */ + name: string; + + /** + * The value type. + * @readonly + */ + type: string; + + /** + * The value. + * @readonly + */ + value: string; + + /** + * The hive architecture. + * @readonly + */ + arch: string; + } +} + +declare module "winreg" { + export = Winreg; +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/aria/aria.ts b/src/vs/base/browser/ui/aria/aria.ts index 9d59cecfdce..61c7dfd348d 100644 --- a/src/vs/base/browser/ui/aria/aria.ts +++ b/src/vs/base/browser/ui/aria/aria.ts @@ -50,13 +50,27 @@ export function status(msg: string): void { } } +let repeatedTimes = 0; +let prevText: string | undefined = undefined; function insertMessage(target: HTMLElement, msg: string): void { if (!ariaContainer) { // console.warn('ARIA support needs a container. Call setARIAContainer() first.'); return; } - if (target.textContent === msg) { - msg = nls.localize('repeated', "{0} (occurred again)", msg); + + if (prevText === msg) { + repeatedTimes++; + } + else { + prevText = msg; + repeatedTimes = 0; + } + + + switch (repeatedTimes) { + case 0: break; + case 1: msg = nls.localize('repeated', "{0} (occurred again)", msg); break; + default: msg = nls.localize('repeatedNtimes', "{0} (occurred {1} times)", msg, repeatedTimes); break; } dom.clearNode(target); diff --git a/src/vs/base/browser/ui/contextview/contextview.ts b/src/vs/base/browser/ui/contextview/contextview.ts index 8a14b87c36c..8da408322a4 100644 --- a/src/vs/base/browser/ui/contextview/contextview.ts +++ b/src/vs/base/browser/ui/contextview/contextview.ts @@ -54,51 +54,46 @@ export interface ISize { export interface IView extends IPosition, ISize { } -function layout(view: ISize, around: IView, viewport: IView, anchorPosition: AnchorPosition, anchorAlignment: AnchorAlignment): IPosition { +export enum LayoutAnchorPosition { + Before, + After +} - let chooseBiased = (a: number, aIsGood: boolean, b: number, bIsGood: boolean) => { - if (aIsGood) { - return a; +export interface ILayoutAnchor { + offset: number; + size: number; + position: LayoutAnchorPosition; +} + +/** + * Lays out a one dimensional view next to an anchor in a viewport. + * + * @returns The view offset within the viewport. + */ +export function layout(viewportSize: number, viewSize: number, anchor: ILayoutAnchor): number { + const anchorEnd = anchor.offset + anchor.size; + + if (anchor.position === LayoutAnchorPosition.Before) { + if (viewSize <= viewportSize - anchorEnd) { + return anchorEnd; // happy case, lay it out after the anchor } - if (bIsGood) { - return b; + + if (viewSize <= anchor.offset) { + return anchor.offset - viewSize; // ok case, lay it out before the anchor } - return a; - }; - let chooseOne = (a: number, aIsGood: boolean, b: number, bIsGood: boolean, aIsPreferred: boolean) => { - if (aIsPreferred) { - return chooseBiased(a, aIsGood, b, bIsGood); - } else { - return chooseBiased(b, bIsGood, a, aIsGood); + return Math.max(viewportSize - viewSize, 0); // sad case, lay it over the anchor + } else { + if (viewSize <= anchor.offset) { + return anchor.offset - viewSize; // happy case, lay it out before the anchor } - }; - let top = (() => { - // Compute both options (putting the segment above and below) - let posAbove = around.top - view.height; - let posBelow = around.top + around.height; + if (viewSize <= viewportSize - anchorEnd) { + return anchorEnd; // ok case, lay it out after the anchor + } - // Check for both options if they are good - let aboveIsGood = (posAbove >= viewport.top && posAbove + view.height <= viewport.top + viewport.height); - let belowIsGood = (posBelow >= viewport.top && posBelow + view.height <= viewport.top + viewport.height); - - return chooseOne(posAbove, aboveIsGood, posBelow, belowIsGood, anchorPosition === AnchorPosition.ABOVE); - })(); - - let left = (() => { - // Compute both options (aligning left and right) - let posLeft = around.left; - let posRight = around.left + around.width - view.width; - - // Check for both options if they are good - let leftIsGood = (posLeft >= viewport.left && posLeft + view.width <= viewport.left + viewport.width); - let rightIsGood = (posRight >= viewport.left && posRight + view.width <= viewport.left + viewport.width); - - return chooseOne(posLeft, leftIsGood, posRight, rightIsGood, anchorAlignment === AnchorAlignment.LEFT); - })(); - - return { top: top, left: left }; + return 0; // sad case, lay it over the anchor + } } export class ContextView { @@ -205,30 +200,28 @@ export class ContextView { }; } - let viewport = { - top: DOM.StandardWindow.scrollY, - left: DOM.StandardWindow.scrollX, - height: window.innerHeight, - width: window.innerWidth - }; + const viewSize = this.$view.getTotalSize(); + const anchorPosition = this.delegate.anchorPosition || AnchorPosition.BELOW; + const anchorAlignment = this.delegate.anchorAlignment || AnchorAlignment.LEFT; - // Get the view's size - let viewSize = this.$view.getTotalSize(); - let view = { width: viewSize.width, height: viewSize.height }; + const verticalAnchor: ILayoutAnchor = { offset: around.top, size: around.height, position: anchorPosition === AnchorPosition.BELOW ? LayoutAnchorPosition.Before : LayoutAnchorPosition.After }; - let anchorPosition = this.delegate.anchorPosition || AnchorPosition.BELOW; - let anchorAlignment = this.delegate.anchorAlignment || AnchorAlignment.LEFT; + let horizontalAnchor: ILayoutAnchor; - let result = layout(view, around, viewport, anchorPosition, anchorAlignment); + if (anchorAlignment === AnchorAlignment.LEFT) { + horizontalAnchor = { offset: around.left, size: 0, position: LayoutAnchorPosition.Before }; + } else { + horizontalAnchor = { offset: around.left + around.width, size: 0, position: LayoutAnchorPosition.After }; + } - let containerPosition = DOM.getDomNodePagePosition(this.$container.getHTMLElement()); - result.top -= containerPosition.top; - result.left -= containerPosition.left; + const containerPosition = DOM.getDomNodePagePosition(this.$container.getHTMLElement()); + const top = layout(window.innerHeight, viewSize.height, verticalAnchor) - containerPosition.top; + const left = layout(window.innerWidth, viewSize.width, horizontalAnchor) - containerPosition.left; this.$view.removeClass('top', 'bottom', 'left', 'right'); this.$view.addClass(anchorPosition === AnchorPosition.BELOW ? 'bottom' : 'top'); this.$view.addClass(anchorAlignment === AnchorAlignment.LEFT ? 'left' : 'right'); - this.$view.style({ top: result.top + 'px', left: result.left + 'px', width: 'initial' }); + this.$view.style({ top: `${top}px`, left: `${left}px`, width: 'initial' }); } public hide(data?: any): void { diff --git a/src/vs/base/browser/ui/iconLabel/iconLabel.ts b/src/vs/base/browser/ui/iconLabel/iconLabel.ts index cb972251f77..74be56db003 100644 --- a/src/vs/base/browser/ui/iconLabel/iconLabel.ts +++ b/src/vs/base/browser/ui/iconLabel/iconLabel.ts @@ -9,9 +9,6 @@ import 'vs/css!./iconlabel'; import * as dom from 'vs/base/browser/dom'; import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel'; import { IMatch } from 'vs/base/common/filters'; -import uri from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; -import { IWorkspaceFolderProvider, getPathLabel, IUserHomeProvider, getBaseLabel } from 'vs/base/common/labels'; import { IDisposable, combinedDisposable, Disposable } from 'vs/base/common/lifecycle'; export interface IIconLabelCreationOptions { @@ -167,17 +164,3 @@ export class IconLabel extends Disposable { } } -export class FileLabel extends IconLabel { - - constructor(container: HTMLElement, file: uri, provider: IWorkspaceFolderProvider, userHome?: IUserHomeProvider) { - super(container); - - this.setFile(file, provider, userHome); - } - - setFile(file: uri, provider: IWorkspaceFolderProvider, userHome: IUserHomeProvider): void { - const parent = paths.dirname(file.fsPath); - - this.setValue(getBaseLabel(file), parent && parent !== '.' ? getPathLabel(parent, userHome, provider) : '', { title: file.fsPath }); - } -} diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index ef82b63ddad..3a79019d4f1 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -241,6 +241,8 @@ class MenuActionItem extends BaseActionItem { class SubmenuActionItem extends MenuActionItem { private mysubmenu: Menu; private submenuContainer: Builder; + private mouseOver: boolean; + private showScheduler: RunOnceScheduler; private hideScheduler: RunOnceScheduler; constructor( @@ -251,6 +253,13 @@ class SubmenuActionItem extends MenuActionItem { ) { super(action, action, { label: true, isMenu: true }); + this.showScheduler = new RunOnceScheduler(() => { + if (this.mouseOver) { + this.cleanupExistingSubmenu(false); + this.createSubmenu(false); + } + }, 250); + this.hideScheduler = new RunOnceScheduler(() => { if ((!isAncestor(document.activeElement, this.builder.getHTMLElement()) && this.parentData.submenu === this.mysubmenu)) { this.parentData.parent.focus(false); @@ -283,8 +292,15 @@ class SubmenuActionItem extends MenuActionItem { }); $(this.builder).on(EventType.MOUSE_OVER, (e) => { - this.cleanupExistingSubmenu(false); - this.createSubmenu(false); + if (!this.mouseOver) { + this.mouseOver = true; + + this.showScheduler.schedule(); + } + }); + + $(this.builder).on(EventType.MOUSE_LEAVE, (e) => { + this.mouseOver = false; }); $(this.builder).on(EventType.FOCUS_OUT, (e) => { diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 2efed5c5dcf..d78f3f89e15 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -58,6 +58,9 @@ class SelectListRenderer implements IRendererdata.root), 'option-disabled'); diff --git a/src/vs/base/common/amd.ts b/src/vs/base/common/amd.ts new file mode 100644 index 00000000000..45e45a7bbe2 --- /dev/null +++ b/src/vs/base/common/amd.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import URI from 'vs/base/common/uri'; + +export function getPathFromAmdModule(requirefn: typeof require, relativePath: string): string { + return URI.parse(requirefn.toUrl(relativePath)).fsPath; +} diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 4c06af4e2f5..9b4a65a1bcb 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -6,7 +6,7 @@ 'use strict'; import * as errors from 'vs/base/common/errors'; -import { TPromise, ValueCallback, ErrorCallback, ProgressCallback } from 'vs/base/common/winjs.base'; +import { TPromise, ValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; @@ -68,7 +68,7 @@ export function createCancelablePromise(callback: (token: CancellationToken) export function asWinJsPromise(callback: (token: CancellationToken) => T | TPromise | Thenable): TPromise { let source = new CancellationTokenSource(); - return new TPromise((resolve, reject, progress) => { + return new TPromise((resolve, reject) => { let item = callback(source.token); if (item instanceof TPromise) { item.then(result => { @@ -77,7 +77,7 @@ export function asWinJsPromise(callback: (token: CancellationToken) => T | TP }, err => { source.dispose(); reject(err); - }, progress); + }); } else if (isThenable(item)) { item.then(result => { source.dispose(); @@ -194,15 +194,15 @@ export class Throttler { return result; }; - this.queuedPromise = new TPromise((c, e, p) => { - this.activePromise.then(onComplete, onComplete, p).done(c); + this.queuedPromise = new TPromise(c => { + this.activePromise.then(onComplete, onComplete).done(c); }, () => { this.activePromise.cancel(); }); } - return new TPromise((c, e, p) => { - this.queuedPromise.then(c, e, p); + return new TPromise((c, e) => { + this.queuedPromise.then(c, e); }, () => { // no-op }); @@ -210,14 +210,14 @@ export class Throttler { this.activePromise = promiseFactory(); - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { this.activePromise.done((result: any) => { this.activePromise = null; c(result); }, (err: any) => { this.activePromise = null; e(err); - }, p); + }); }, () => { this.activePromise.cancel(); }); @@ -378,20 +378,18 @@ export class ShallowCancelThenPromise extends TPromise { constructor(outer: TPromise) { let completeCallback: ValueCallback, - errorCallback: ErrorCallback, - progressCallback: ProgressCallback; + errorCallback: ErrorCallback; - super((c, e, p) => { + super((c, e) => { completeCallback = c; errorCallback = e; - progressCallback = p; }, () => { // cancel this promise but not the // outer promise errorCallback(errors.canceled()); }); - outer.then(completeCallback, errorCallback, progressCallback); + outer.then(completeCallback, errorCallback); } } @@ -425,7 +423,7 @@ export function always(thenable: TPromise, f: Function): TPromise; export function always(promise: Thenable, f: Function): Thenable; export function always(winjsPromiseOrThenable: Thenable | TPromise, f: Function): TPromise | Thenable { if (isWinJSPromise(winjsPromiseOrThenable)) { - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { winjsPromiseOrThenable.done((result) => { try { f(result); @@ -440,8 +438,6 @@ export function always(winjsPromiseOrThenable: Thenable | TPromise, f: errors.onUnexpectedError(e1); } e(err); - }, (progress) => { - p(progress); }); }, () => { winjsPromiseOrThenable.cancel(); @@ -534,7 +530,6 @@ interface ILimitedTaskFactory { factory: ITask; c: ValueCallback; e: ErrorCallback; - p: ProgressCallback; } /** @@ -563,14 +558,9 @@ export class Limiter { } queue(promiseFactory: ITask): TPromise; - queue(promiseFactory: ITask>): TPromise { - return new TPromise((c, e, p) => { - this.outstandingPromises.push({ - factory: promiseFactory, - c: c, - e: e, - p: p - }); + queue(factory: ITask>): TPromise { + return new TPromise((c, e) => { + this.outstandingPromises.push({ factory, c, e }); this.consume(); }); @@ -582,7 +572,7 @@ export class Limiter { this.runningPromises++; const promise = iLimitedTask.factory(); - promise.done(iLimitedTask.c, iLimitedTask.e, iLimitedTask.p); + promise.done(iLimitedTask.c, iLimitedTask.e); promise.done(() => this.consumed(), () => this.consumed()); } } diff --git a/src/vs/base/common/keybindingLabels.ts b/src/vs/base/common/keybindingLabels.ts index 29916e201d4..6cb492d858c 100644 --- a/src/vs/base/common/keybindingLabels.ts +++ b/src/vs/base/common/keybindingLabels.ts @@ -59,6 +59,13 @@ export const UILabelProvider = new ModifierLabelProvider( altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"), metaKey: nls.localize({ key: 'windowsKey', comment: ['This is the short form for the Windows key on the keyboard'] }, "Windows"), separator: '+', + }, + { + ctrlKey: nls.localize({ key: 'ctrlKey', comment: ['This is the short form for the Control key on the keyboard'] }, "Ctrl"), + shiftKey: nls.localize({ key: 'shiftKey', comment: ['This is the short form for the Shift key on the keyboard'] }, "Shift"), + altKey: nls.localize({ key: 'altKey', comment: ['This is the short form for the Alt key on the keyboard'] }, "Alt"), + metaKey: nls.localize({ key: 'superKey', comment: ['This is the short form for the Super key on the keyboard'] }, "Super"), + separator: '+', } ); @@ -79,6 +86,13 @@ export const AriaLabelProvider = new ModifierLabelProvider( altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"), metaKey: nls.localize({ key: 'windowsKey.long', comment: ['This is the long form for the Windows key on the keyboard'] }, "Windows"), separator: '+', + }, + { + ctrlKey: nls.localize({ key: 'ctrlKey.long', comment: ['This is the long form for the Control key on the keyboard'] }, "Control"), + shiftKey: nls.localize({ key: 'shiftKey.long', comment: ['This is the long form for the Shift key on the keyboard'] }, "Shift"), + altKey: nls.localize({ key: 'altKey.long', comment: ['This is the long form for the Alt key on the keyboard'] }, "Alt"), + metaKey: nls.localize({ key: 'superKey.long', comment: ['This is the long form for the Super key on the keyboard'] }, "Super"), + separator: '+', } ); diff --git a/src/vs/base/common/paths.ts b/src/vs/base/common/paths.ts index a837ce51685..e5ed22fb7b9 100644 --- a/src/vs/base/common/paths.ts +++ b/src/vs/base/common/paths.ts @@ -19,9 +19,12 @@ export const sep = '/'; export const nativeSep = isWindows ? '\\' : '/'; /** + * @param path the path to get the dirname from + * @param separator the separator to use * @returns the directory name of a path. + * */ -export function dirname(path: string): string { +export function dirname(path: string, separator = nativeSep): string { const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\'); if (idx === 0) { return '.'; @@ -32,7 +35,7 @@ export function dirname(path: string): string { } else { let res = path.substring(0, ~idx); if (isWindows && res[res.length - 1] === ':') { - res += nativeSep; // make sure drive letters end with backslash + res += separator; // make sure drive letters end with backslash } return res; } @@ -328,7 +331,7 @@ export function isEqual(pathA: string, pathB: string, ignoreCase?: boolean): boo return equalsIgnoreCase(pathA, pathB); } -export function isEqualOrParent(path: string, candidate: string, ignoreCase?: boolean): boolean { +export function isEqualOrParent(path: string, candidate: string, ignoreCase?: boolean, separator = nativeSep): boolean { if (path === candidate) { return true; } @@ -352,15 +355,15 @@ export function isEqualOrParent(path: string, candidate: string, ignoreCase?: bo } let sepOffset = candidate.length; - if (candidate.charAt(candidate.length - 1) === nativeSep) { + if (candidate.charAt(candidate.length - 1) === separator) { sepOffset--; // adjust the expected sep offset in case our candidate already ends in separator character } - return path.charAt(sepOffset) === nativeSep; + return path.charAt(sepOffset) === separator; } - if (candidate.charAt(candidate.length - 1) !== nativeSep) { - candidate += nativeSep; + if (candidate.charAt(candidate.length - 1) !== separator) { + candidate += separator; } return path.indexOf(candidate) === 0; diff --git a/src/vs/base/common/resources.ts b/src/vs/base/common/resources.ts index 141b17598b2..95d05f973df 100644 --- a/src/vs/base/common/resources.ts +++ b/src/vs/base/common/resources.ts @@ -5,38 +5,38 @@ 'use strict'; import * as paths from 'vs/base/common/paths'; -import uri from 'vs/base/common/uri'; +import URI from 'vs/base/common/uri'; import { equalsIgnoreCase } from 'vs/base/common/strings'; import { Schemas } from 'vs/base/common/network'; -import { isLinux } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; +import { CharCode } from 'vs/base/common/charCode'; -export function getComparisonKey(resource: uri): string { +export function getComparisonKey(resource: URI): string { return hasToIgnoreCase(resource) ? resource.toString().toLowerCase() : resource.toString(); } -export function hasToIgnoreCase(resource: uri): boolean { +export function hasToIgnoreCase(resource: URI): boolean { // A file scheme resource is in the same platform as code, so ignore case for non linux platforms // Resource can be from another platform. Lowering the case as an hack. Should come from File system provider - return resource.scheme === Schemas.file ? !isLinux : true; + return resource && resource.scheme === Schemas.file ? !isLinux : true; } -export function basenameOrAuthority(resource: uri): string { - return paths.basename(resource.path) || resource.authority; +export function basenameOrAuthority(resource: URI): string { + return basename(resource) || resource.authority; } -export function isEqualOrParent(resource: uri, candidate: uri, ignoreCase?: boolean): boolean { +export function isEqualOrParent(resource: URI, candidate: URI, ignoreCase?: boolean): boolean { if (resource.scheme === candidate.scheme && resource.authority === candidate.authority) { - if (resource.scheme === 'file') { + if (resource.scheme === Schemas.file) { return paths.isEqualOrParent(resource.fsPath, candidate.fsPath, ignoreCase); } - - return paths.isEqualOrParent(resource.path, candidate.path, ignoreCase); + return paths.isEqualOrParent(resource.path, candidate.path, ignoreCase, '/'); } return false; } -export function isEqual(first: uri, second: uri, ignoreCase?: boolean): boolean { +export function isEqual(first: URI, second: URI, ignoreCase?: boolean): boolean { const identityEquals = (first === second); if (identityEquals) { return true; @@ -53,25 +53,71 @@ export function isEqual(first: uri, second: uri, ignoreCase?: boolean): boolean return first.toString() === second.toString(); } -export function dirname(resource: uri): uri { - const dirname = paths.dirname(resource.path); - if (resource.authority && dirname && !paths.isAbsolute(dirname)) { - return null; // If a URI contains an authority component, then the path component must either be empty or begin with a slash ("/") character - } +export function basename(resource: URI): string { + return paths.basename(resource.path); +} +/** + * Return a URI representing the directory of a URI path. + * + * @param resource The input URI. + * @returns The URI representing the directory of the input URI. + */ +export function dirname(resource: URI): URI { + let dirname = paths.dirname(resource.path, '/'); + if (resource.authority && dirname.length && dirname.charCodeAt(0) !== CharCode.Slash) { + return null; // If a URI contains an authority component, then the path component must either be empty or begin with a CharCode.Slash ("/") character + } return resource.with({ path: dirname }); } -export function joinPath(resource: uri, pathFragment: string): uri { - const joinedPath = paths.join(resource.path || '/', pathFragment); +/** + * Join a URI path with a path fragment and normalizes the resulting path. + * + * @param resource The input URI. + * @param pathFragment The path fragment to add to the URI path. + * @returns The resulting URI. + */ +export function joinPath(resource: URI, pathFragment: string): URI { + let joinedPath: string; + if (resource.scheme === Schemas.file) { + joinedPath = URI.file(paths.join(resource.fsPath, pathFragment)).path; + } else { + joinedPath = paths.join(resource.path, pathFragment); + } return resource.with({ path: joinedPath }); } -export function distinctParents(items: T[], resourceAccessor: (item: T) => uri): T[] { +/** + * Normalizes the path part of a URI: Resolves `.` and `..` elements with directory names. + * + * @param resource The URI to normalize the path. + * @returns The URI with the normalized path. + */ +export function normalizePath(resource: URI): URI { + let normalizedPath: string; + if (resource.scheme === Schemas.file) { + normalizedPath = URI.file(paths.normalize(resource.fsPath)).path; + } else { + normalizedPath = paths.normalize(resource.path); + } + return resource.with({ + path: normalizedPath + }); +} + +/** + * Returns true if the URI path is absolute. + */ +export function isAbsolutePath(resource: URI): boolean { + return paths.isAbsolute(resource.path); +} + +export function distinctParents(items: T[], resourceAccessor: (item: T) => URI): T[] { const distinctParents: T[] = []; for (let i = 0; i < items.length; i++) { const candidateResource = resourceAccessor(items[i]); @@ -90,3 +136,11 @@ export function distinctParents(items: T[], resourceAccessor: (item: T) => ur return distinctParents; } + +export function isMalformedFileUri(candidate: URI): URI | undefined { + if (!candidate.scheme || isWindows && candidate.scheme.match(/^[a-zA-Z]$/)) { + return URI.file((candidate.scheme ? candidate.scheme + ':' : '') + candidate.path); + } + return void 0; +} + diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index 412efc17388..7ead50439d3 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -12,6 +12,12 @@ const _singleSlashStart = /^\//; const _doubleSlashStart = /^\/\//; function _validateUri(ret: URI): void { + + // // scheme, must be set + // if (!ret.scheme) { + // throw new Error('[UriError]: Scheme is missing.'); + // } + // scheme, https://tools.ietf.org/html/rfc3986#section-3.1 // ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) if (ret.scheme && !_schemePattern.test(ret.scheme)) { diff --git a/src/vs/base/common/winjs.base.d.ts b/src/vs/base/common/winjs.base.d.ts index f5b941a8873..c568d4d7b05 100644 --- a/src/vs/base/common/winjs.base.d.ts +++ b/src/vs/base/common/winjs.base.d.ts @@ -5,25 +5,21 @@ /// Interfaces for WinJS export type ErrorCallback = (error: any) => void; -export type ProgressCallback = (progress: TProgress) => void; -export declare class Promise { +export class Promise { constructor( executor: ( resolve: (value: T | PromiseLike) => void, - reject: (reason: any) => void, - progress: (progress: TProgress) => void) => void, + reject: (reason: any) => void) => void, oncancel?: () => void); public then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | null, - onprogress?: (progress: TProgress) => void): Promise; + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null): Promise; public done( onfulfilled?: (value: T) => void, - onrejected?: (reason: any) => void, - onprogress?: (progress: TProgress) => void): void; + onrejected?: (reason: any) => void): void; public cancel(): void; @@ -56,9 +52,7 @@ export type TValueCallback = (value: T | PromiseLike) => void; export { Promise as TPromise, - Promise as PPromise, - TValueCallback as ValueCallback, - ProgressCallback as TProgressCallback + TValueCallback as ValueCallback }; export interface IPromiseErrorDetail { diff --git a/src/vs/base/node/paths.ts b/src/vs/base/node/paths.ts index dfdc28def8a..66930cdaf4b 100644 --- a/src/vs/base/node/paths.ts +++ b/src/vs/base/node/paths.ts @@ -3,14 +3,14 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import uri from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; interface IPaths { getAppDataPath(platform: string): string; getDefaultUserDataPath(platform: string): string; } -const pathsPath = uri.parse(require.toUrl('paths')).fsPath; +const pathsPath = getPathFromAmdModule(require, 'paths'); const paths = require.__$__nodeRequire(pathsPath); export const getAppDataPath = paths.getAppDataPath; -export const getDefaultUserDataPath = paths.getDefaultUserDataPath; \ No newline at end of file +export const getDefaultUserDataPath = paths.getDefaultUserDataPath; diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index ebe0dff4cc4..c08d79aaf2d 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -11,12 +11,12 @@ import * as nls from 'vs/nls'; import { TPromise, TValueCallback, ErrorCallback } from 'vs/base/common/winjs.base'; import * as Types from 'vs/base/common/types'; import { IStringDictionary } from 'vs/base/common/collections'; -import URI from 'vs/base/common/uri'; import * as Objects from 'vs/base/common/objects'; import * as TPath from 'vs/base/common/paths'; import * as Platform from 'vs/base/common/platform'; import { LineDecoder } from 'vs/base/node/decoder'; import { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode, Executable } from 'vs/base/common/processes'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export { CommandOptions, ForkOptions, SuccessData, Source, TerminateResponse, TerminateResponseCode }; export type TProgressCallback = (progress: T) => void; @@ -54,7 +54,7 @@ export function terminateProcess(process: cp.ChildProcess, cwd?: string): Termin } } else if (Platform.isLinux || Platform.isMacintosh) { try { - let cmd = URI.parse(require.toUrl('vs/base/node/terminateProcess.sh')).fsPath; + let cmd = getPathFromAmdModule(require, 'vs/base/node/terminateProcess.sh'); let result = cp.spawnSync(cmd, [process.pid.toString()]); if (result.error) { return { success: false, error: result.error }; diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index a7d2d36d0ca..fc8c25a1153 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -6,7 +6,8 @@ 'use strict'; import { exec } from 'child_process'; -import URI from 'vs/base/common/uri'; + +import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface ProcessItem { name: string; @@ -207,7 +208,7 @@ export function listProcesses(rootPid: number): Promise { // The cpu usage value reported on Linux is the average over the process lifetime, // recalculate the usage over a one second interval // JSON.stringify is needed to escape spaces, https://github.com/nodejs/node/issues/6803 - let cmd = JSON.stringify(URI.parse(require.toUrl('vs/base/node/cpuUsage.sh')).fsPath); + let cmd = JSON.stringify(getPathFromAmdModule(require, 'vs/base/node/cpuUsage.sh')); cmd += ' ' + pids.join(' '); exec(cmd, {}, (err, stdout, stderr) => { diff --git a/src/vs/base/node/stdFork.ts b/src/vs/base/node/stdFork.ts index c7c36559465..17782445328 100644 --- a/src/vs/base/node/stdFork.ts +++ b/src/vs/base/node/stdFork.ts @@ -9,7 +9,7 @@ import * as path from 'path'; import * as os from 'os'; import * as net from 'net'; import * as cp from 'child_process'; -import uri from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IForkOpts { cwd?: string; @@ -117,7 +117,7 @@ export function fork(modulePath: string, args: string[], options: IForkOpts, cal }; // Create the process - let bootstrapperPath = (uri.parse(require.toUrl('./stdForkStart.js')).fsPath); + let bootstrapperPath = (getPathFromAmdModule(require, './stdForkStart.js')); childProcess = cp.fork(bootstrapperPath, [modulePath].concat(args), { silent: true, cwd: options.cwd, diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index 67376daaf3d..8543a0ba8bf 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -14,7 +14,6 @@ enum MessageType { RequestPromiseCancel, ResponseInitialize, ResponsePromiseSuccess, - ResponsePromiseProgress, ResponsePromiseError, ResponsePromiseErrorObj, @@ -24,7 +23,11 @@ enum MessageType { } function isResponse(messageType: MessageType): boolean { - return messageType >= MessageType.ResponseInitialize; + return messageType === MessageType.ResponseInitialize + || messageType === MessageType.ResponsePromiseSuccess + || messageType === MessageType.ResponsePromiseError + || messageType === MessageType.ResponsePromiseErrorObj + || messageType === MessageType.ResponseEventFire; } interface IRawMessage { @@ -172,8 +175,6 @@ export class ChannelServer implements IChannelServer, IDisposable { } delete this.activeRequests[request.id]; - }, data => { - this.protocol.send({ id, data, type: MessageType.ResponsePromiseProgress }); }); this.activeRequests[request.id] = toDisposable(() => requestPromise.cancel()); @@ -283,7 +284,7 @@ export class ChannelClient implements IChannelClient, IDisposable { private doRequest(request: IRequest): Promise { const id = request.raw.id; - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { this.handlers[id] = response => { switch (response.type) { case MessageType.ResponsePromiseSuccess: @@ -303,10 +304,6 @@ export class ChannelClient implements IChannelClient, IDisposable { delete this.handlers[id]; e(response.data); break; - - case MessageType.ResponsePromiseProgress: - p(response.data); - break; } }; @@ -318,12 +315,12 @@ export class ChannelClient implements IChannelClient, IDisposable { private bufferRequest(request: IRequest): Promise { let flushedRequest: Promise = null; - return new TPromise((c, e, p) => { + return new TPromise((c, e) => { this.bufferedRequests.push(request); request.flush = () => { request.flush = null; - flushedRequest = this.doRequest(request).then(c, e, p); + flushedRequest = this.doRequest(request).then(c, e); }; }, () => { request.flush = null; diff --git a/src/vs/base/parts/ipc/node/ipc.cp.ts b/src/vs/base/parts/ipc/node/ipc.cp.ts index 3d863aa9e0b..1221c2c7dfa 100644 --- a/src/vs/base/parts/ipc/node/ipc.cp.ts +++ b/src/vs/base/parts/ipc/node/ipc.cp.ts @@ -107,9 +107,8 @@ export class Client implements IChannelClient, IDisposable { const channel = this.channels[channelName] || (this.channels[channelName] = this.client.getChannel(channelName)); const request: TPromise = channel.call(name, arg); - // Progress doesn't propagate across 'then', we need to create a promise wrapper - const result = new TPromise((c, e, p) => { - request.then(c, e, p).done(() => { + const result = new TPromise((c, e) => { + request.then(c, e).done(() => { if (!this.activeRequests) { return; } diff --git a/src/vs/base/parts/ipc/test/node/ipc.perf.ts b/src/vs/base/parts/ipc/test/node/ipc.perf.ts deleted file mode 100644 index 2e1fcbcbd83..00000000000 --- a/src/vs/base/parts/ipc/test/node/ipc.perf.ts +++ /dev/null @@ -1,114 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as assert from 'assert'; -import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import uri from 'vs/base/common/uri'; -import { always } from 'vs/base/common/async'; -import { ITestChannel, TestServiceClient, ITestService } from './testService'; - -function createClient(): Client { - return new Client(uri.parse(require.toUrl('bootstrap')).fsPath, { - serverName: 'TestServer', - env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true } - }); -} - -// Rename to ipc.perf.test.ts and run with ./scripts/test.sh --grep IPC.performance --timeout 60000 -suite('IPC performance', () => { - - test('increasing batch size', () => { - const client = createClient(); - const channel = client.getChannel('test'); - const service = new TestServiceClient(channel); - - const runs = [ - { batches: 250000, size: 1 }, - { batches: 2500, size: 100 }, - { batches: 500, size: 500 }, - { batches: 250, size: 1000 }, - { batches: 50, size: 5000 }, - { batches: 25, size: 10000 }, - // { batches: 10, size: 25000 }, - // { batches: 5, size: 50000 }, - // { batches: 1, size: 250000 }, - ]; - const dataSizes = [ - 100, - 250, - ]; - let i = 0, j = 0; - const result = measure(service, 10, 10, 250) // warm-up - .then(() => { - return (function nextRun() { - if (i >= runs.length) { - if (++j >= dataSizes.length) { - return; - } - i = 0; - } - const run = runs[i++]; - return measure(service, run.batches, run.size, dataSizes[j]) - .then(() => { - return nextRun(); - }); - })(); - }); - - return always(result, () => client.dispose()); - }); - - test('increasing raw data size', () => { - const client = createClient(); - const channel = client.getChannel('test'); - const service = new TestServiceClient(channel); - - const runs = [ - { batches: 250000, dataSize: 100 }, - { batches: 25000, dataSize: 1000 }, - { batches: 2500, dataSize: 10000 }, - { batches: 1250, dataSize: 20000 }, - { batches: 500, dataSize: 50000 }, - { batches: 250, dataSize: 100000 }, - { batches: 125, dataSize: 200000 }, - { batches: 50, dataSize: 500000 }, - { batches: 25, dataSize: 1000000 }, - ]; - let i = 0; - const result = measure(service, 10, 10, 250) // warm-up - .then(() => { - return (function nextRun() { - if (i >= runs.length) { - return; - } - const run = runs[i++]; - return measure(service, run.batches, 1, run.dataSize) - .then(() => { - return nextRun(); - }); - })(); - }); - - return always(result, () => client.dispose()); - }); - - function measure(service: ITestService, batches: number, size: number, dataSize: number) { - const start = Date.now(); - let hits = 0; - let count = 0; - return service.batchPerf(batches, size, dataSize) - .then(() => { - console.log(`Batches: ${batches}, size: ${size}, dataSize: ${dataSize}, n: ${batches * size * dataSize}, duration: ${Date.now() - start}`); - assert.strictEqual(hits, batches); - assert.strictEqual(count, batches * size); - }, err => assert.fail(err), - batch => { - hits++; - count += batch.length; - }); - } -}); \ No newline at end of file diff --git a/src/vs/base/parts/ipc/test/node/ipc.test.ts b/src/vs/base/parts/ipc/test/node/ipc.test.ts index 3091fce1d7f..01deb13302b 100644 --- a/src/vs/base/parts/ipc/test/node/ipc.test.ts +++ b/src/vs/base/parts/ipc/test/node/ipc.test.ts @@ -8,13 +8,13 @@ import * as assert from 'assert'; import { TPromise } from 'vs/base/common/winjs.base'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import uri from 'vs/base/common/uri'; import { always } from 'vs/base/common/async'; import { isPromiseCanceledError } from 'vs/base/common/errors'; import { ITestChannel, TestServiceClient } from './testService'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function createClient(): Client { - return new Client(uri.parse(require.toUrl('bootstrap')).fsPath, { + return new Client(getPathFromAmdModule(require, 'bootstrap'), { serverName: 'TestServer', env: { AMD_ENTRYPOINT: 'vs/base/parts/ipc/test/node/testApp', verbose: true } }); @@ -101,4 +101,4 @@ suite('IPC', () => { return always(result, () => client.dispose()); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/parts/ipc/test/node/testService.ts b/src/vs/base/parts/ipc/test/node/testService.ts index a53ff5ed37b..f90d54708f6 100644 --- a/src/vs/base/parts/ipc/test/node/testService.ts +++ b/src/vs/base/parts/ipc/test/node/testService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise, PPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event, Emitter } from 'vs/base/common/event'; @@ -17,7 +17,6 @@ export interface ITestService { marco(): TPromise; pong(ping: string): TPromise<{ incoming: string, outgoing: string }>; cancelMe(): TPromise; - batchPerf(batches: number, size: number, dataSize: number): PPromise; } export class TestService implements ITestService { @@ -25,8 +24,6 @@ export class TestService implements ITestService { private _onMarco = new Emitter(); onMarco: Event = this._onMarco.event; - private _data = 'abcdefghijklmnopqrstuvwxyz'; - marco(): TPromise { this._onMarco.fire({ answer: 'polo' }); return TPromise.as('polo'); @@ -39,32 +36,6 @@ export class TestService implements ITestService { cancelMe(): TPromise { return TPromise.timeout(100).then(() => true); } - - batchPerf(batches: number, size: number, dataSize: number): PPromise { - while (this._data.length < dataSize) { - this._data += this._data; - } - const self = this; - return new PPromise((complete, error, progress) => { - let j = 0; - function send() { - if (j >= batches) { - complete(null); - return; - } - j++; - const batch = []; - for (let i = 0; i < size; i++) { - batch.push({ - prop: `${i}${self._data}`.substr(0, dataSize) - }); - } - progress(batch); - process.nextTick(send); - } - process.nextTick(send); - }); - } } export interface ITestChannel extends IChannel { @@ -74,7 +45,6 @@ export interface ITestChannel extends IChannel { call(command: 'marco'): TPromise; call(command: 'pong', ping: string): TPromise; call(command: 'cancelMe'): TPromise; - call(command: 'batchPerf', args: { batches: number; size: number; dataSize: number; }): PPromise; call(command: string, ...args: any[]): TPromise; } @@ -95,7 +65,6 @@ export class TestChannel implements ITestChannel { case 'pong': return this.testService.pong(args[0]); case 'cancelMe': return this.testService.cancelMe(); case 'marco': return this.testService.marco(); - case 'batchPerf': return this.testService.batchPerf(args[0].batches, args[0].size, args[0].dataSize); default: return TPromise.wrapError(new Error('command not found')); } } @@ -118,8 +87,4 @@ export class TestServiceClient implements ITestService { cancelMe(): TPromise { return this.channel.call('cancelMe'); } - - batchPerf(batches: number, size: number, dataSize: number): PPromise { - return this.channel.call('batchPerf', { batches, size, dataSize }); - } } \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/treeImpl.ts b/src/vs/base/parts/tree/browser/treeImpl.ts index 10b350737ec..dc008d1eede 100644 --- a/src/vs/base/parts/tree/browser/treeImpl.ts +++ b/src/vs/base/parts/tree/browser/treeImpl.ts @@ -211,7 +211,10 @@ export class Tree implements _.ITree { public getFirstVisibleElement(): any { return this.view.getFirstVisibleElement(); + } + public getLastVisibleElement(): any { + return this.view.getLastVisibleElement(); } public getScrollPosition(): number { diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index 5d56011feac..bb125f1c905 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -658,6 +658,11 @@ export class TreeView extends HeightMap { return item && item.model.getElement(); } + public getLastVisibleElement(): any { + const item = this.itemAtIndex(this.indexAt(this.lastRenderTop + this.lastRenderHeight)); + return item && item.model.getElement(); + } + private render(scrollTop: number, viewHeight: number, scrollLeft: number, viewWidth: number, scrollWidth: number): void { var i: number; var stop: number; diff --git a/src/vs/base/test/browser/ui/contextview/contextview.test.ts b/src/vs/base/test/browser/ui/contextview/contextview.test.ts new file mode 100644 index 00000000000..09214cfc06d --- /dev/null +++ b/src/vs/base/test/browser/ui/contextview/contextview.test.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview'; + +suite('Contextview', function () { + + test('layout', function () { + assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.Before }), 0); + assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.Before }), 50); + assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.Before }), 180); + + assert.equal(layout(200, 20, { offset: 0, size: 0, position: LayoutAnchorPosition.After }), 0); + assert.equal(layout(200, 20, { offset: 50, size: 0, position: LayoutAnchorPosition.After }), 30); + assert.equal(layout(200, 20, { offset: 200, size: 0, position: LayoutAnchorPosition.After }), 180); + + assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.Before }), 50); + assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.Before }), 100); + assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.Before }), 130); + + assert.equal(layout(200, 20, { offset: 0, size: 50, position: LayoutAnchorPosition.After }), 50); + assert.equal(layout(200, 20, { offset: 50, size: 50, position: LayoutAnchorPosition.After }), 30); + assert.equal(layout(200, 20, { offset: 150, size: 50, position: LayoutAnchorPosition.After }), 130); + }); +}); diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 24ef5347f56..822d4063908 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -278,30 +278,6 @@ suite('Async', () => { return TPromise.join(promises); }); - test('Throttler - progress should work', function () { - let order = 0; - let factory = () => new TPromise((c, e, p) => { - TPromise.timeout(0).done(() => { - p(order++); - c(true); - }); - }); - - let throttler = new async.Throttler(); - let promises: TPromise[] = []; - let progresses: any[][] = [[], [], []]; - - promises.push(throttler.queue(factory).then(null, null, (p) => progresses[0].push(p))); - promises.push(throttler.queue(factory).then(null, null, (p) => progresses[1].push(p))); - promises.push(throttler.queue(factory).then(null, null, (p) => progresses[2].push(p))); - - return TPromise.join(promises).then(() => { - assert.deepEqual(progresses[0], [0]); - assert.deepEqual(progresses[1], [0]); - assert.deepEqual(progresses[2], [0]); - }); - }); - test('Delayer', function () { let count = 0; let factory = () => { @@ -453,54 +429,6 @@ suite('Async', () => { return p; }); - test('Delayer - progress should work', function () { - let order = 0; - let factory = () => new TPromise((c, e, p) => { - TPromise.timeout(0).done(() => { - p(order++); - c(true); - }); - }); - - let delayer = new async.Delayer(0); - let promises: TPromise[] = []; - let progresses: any[][] = [[], [], []]; - - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[0].push(p))); - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[1].push(p))); - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[2].push(p))); - - return TPromise.join(promises).then(() => { - assert.deepEqual(progresses[0], [0]); - assert.deepEqual(progresses[1], [0]); - assert.deepEqual(progresses[2], [0]); - }); - }); - - test('ThrottledDelayer - progress should work', function () { - let order = 0; - let factory = () => new TPromise((c, e, p) => { - TPromise.timeout(0).done(() => { - p(order++); - c(true); - }); - }); - - let delayer = new async.ThrottledDelayer(0); - let promises: TPromise[] = []; - let progresses: any[][] = [[], [], []]; - - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[0].push(p))); - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[1].push(p))); - promises.push(delayer.trigger(factory).then(null, null, (p) => progresses[2].push(p))); - - return TPromise.join(promises).then(() => { - assert.deepEqual(progresses[0], [0]); - assert.deepEqual(progresses[1], [0]); - assert.deepEqual(progresses[2], [0]); - }); - }); - test('Sequence', function () { let factoryFactory = (n: number) => () => { return TPromise.as(n); diff --git a/src/vs/base/test/common/network.test.ts b/src/vs/base/test/common/network.test.ts deleted file mode 100644 index 7aba51f153b..00000000000 --- a/src/vs/base/test/common/network.test.ts +++ /dev/null @@ -1,92 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import URI from 'vs/base/common/uri'; - -function assertUrl(raw: string, scheme: string, domain: string, port: string, path: string, queryString: string, fragmentId: string): void { - // check for equivalent behaviour - const uri = URI.parse(raw); - assert.equal(uri.scheme, scheme); - assert.equal(uri.authority, port ? domain + ':' + port : domain); - assert.equal(uri.path, path); - assert.equal(uri.query, queryString); - assert.equal(uri.fragment, fragmentId); -} - -suite('Network', () => { - test('urls', () => { - assertUrl('http://www.test.com:8000/this/that/theother.html?query=foo#hash', - 'http', 'www.test.com', '8000', '/this/that/theother.html', 'query=foo', 'hash' - ); - - assertUrl('http://www.test.com:8000/this/that/theother.html?query=foo', - 'http', 'www.test.com', '8000', '/this/that/theother.html', 'query=foo', '' - ); - - assertUrl('http://www.test.com:8000/this/that/theother.html#hash', - 'http', 'www.test.com', '8000', '/this/that/theother.html', '', 'hash' - ); - - assertUrl('http://www.test.com:8000/#hash', - 'http', 'www.test.com', '8000', '/', '', 'hash' - ); - - assertUrl('http://www.test.com:8000#hash', - 'http', 'www.test.com', '8000', '/', '', 'hash' - ); - - assertUrl('http://www.test.com/#hash', - 'http', 'www.test.com', '', '/', '', 'hash' - ); - - assertUrl('http://www.test.com#hash', - 'http', 'www.test.com', '', '/', '', 'hash' - ); - - assertUrl('http://www.test.com:8000/this/that/theother.html', - 'http', 'www.test.com', '8000', '/this/that/theother.html', '', '' - ); - - assertUrl('http://www.test.com:8000/', - 'http', 'www.test.com', '8000', '/', '', '' - ); - - assertUrl('http://www.test.com:8000', - 'http', 'www.test.com', '8000', '/', '', '' - ); - - assertUrl('http://www.test.com/', - 'http', 'www.test.com', '', '/', '', '' - ); - - assertUrl('//www.test.com/', - '', 'www.test.com', '', '/', '', '' - ); - - assertUrl('//www.test.com:8000/this/that/theother.html?query=foo#hash', - '', 'www.test.com', '8000', '/this/that/theother.html', 'query=foo', 'hash' - ); - - assertUrl('//www.test.com/this/that/theother.html?query=foo#hash', - '', 'www.test.com', '', '/this/that/theother.html', 'query=foo', 'hash' - ); - - assertUrl('https://www.test.com:8000/this/that/theother.html?query=foo#hash', - 'https', 'www.test.com', '8000', '/this/that/theother.html', 'query=foo', 'hash' - ); - - assertUrl('f12://www.test.com:8000/this/that/theother.html?query=foo#hash', - 'f12', 'www.test.com', '8000', '/this/that/theother.html', 'query=foo', 'hash' - ); - - assertUrl('inmemory://model/0', - 'inmemory', 'model', '', '/0', '', '' - ); - - assertUrl('file:///c/far/boo/file.cs', 'file', '', '', '/c/far/boo/file.cs', '', ''); - }); -}); diff --git a/src/vs/base/test/common/resources.test.ts b/src/vs/base/test/common/resources.test.ts index 6a613730db3..e35a9bb438d 100644 --- a/src/vs/base/test/common/resources.test.ts +++ b/src/vs/base/test/common/resources.test.ts @@ -5,9 +5,9 @@ 'use strict'; import * as assert from 'assert'; -import { normalize } from 'vs/base/common/paths'; -import { dirname, distinctParents, joinPath } from 'vs/base/common/resources'; +import { dirname, basename, distinctParents, joinPath, isEqual, isEqualOrParent, hasToIgnoreCase, normalizePath, isAbsolutePath, isMalformedFileUri } from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; +import { isWindows } from 'vs/base/common/platform'; suite('Resources', () => { @@ -43,29 +43,185 @@ suite('Resources', () => { }); test('dirname', () => { - const f = URI.file('/some/file/test.txt'); - const d = dirname(f); - assert.equal(d.fsPath, normalize('/some/file', true)); + if (isWindows) { + assert.equal(dirname(URI.file('c:\\some\\file\\test.txt')).toString(), 'file:///c%3A/some/file'); + assert.equal(dirname(URI.file('c:\\some\\file')).toString(), 'file:///c%3A/some'); + assert.equal(dirname(URI.file('c:\\some\\file\\')).toString(), 'file:///c%3A/some'); + assert.equal(dirname(URI.file('c:\\some')).toString(), 'file:///c%3A/'); + } else { + assert.equal(dirname(URI.file('/some/file/test.txt')).toString(), 'file:///some/file'); + assert.equal(dirname(URI.file('/some/file/')).toString(), 'file:///some'); + assert.equal(dirname(URI.file('/some/file')).toString(), 'file:///some'); + } + assert.equal(dirname(URI.parse('foo://a/some/file/test.txt')).toString(), 'foo://a/some/file'); + assert.equal(dirname(URI.parse('foo://a/some/file/')).toString(), 'foo://a/some'); + assert.equal(dirname(URI.parse('foo://a/some/file')).toString(), 'foo://a/some'); + assert.equal(dirname(URI.parse('foo://a/some')).toString(), 'foo://a/'); // does not explode (https://github.com/Microsoft/vscode/issues/41987) dirname(URI.from({ scheme: 'file', authority: '/users/someone/portal.h' })); }); + test('basename', () => { + if (isWindows) { + assert.equal(basename(URI.file('c:\\some\\file\\test.txt')), 'test.txt'); + assert.equal(basename(URI.file('c:\\some\\file')), 'file'); + assert.equal(basename(URI.file('c:\\some\\file\\')), 'file'); + } else { + assert.equal(basename(URI.file('/some/file/test.txt')), 'test.txt'); + assert.equal(basename(URI.file('/some/file/')), 'file'); + assert.equal(basename(URI.file('/some/file')), 'file'); + assert.equal(basename(URI.file('/some')), 'some'); + } + assert.equal(basename(URI.parse('foo://a/some/file/test.txt')), 'test.txt'); + assert.equal(basename(URI.parse('foo://a/some/file/')), 'file'); + assert.equal(basename(URI.parse('foo://a/some/file')), 'file'); + assert.equal(basename(URI.parse('foo://a/some')), 'some'); + assert.equal(basename(URI.parse('foo://a/')), ''); + assert.equal(basename(URI.parse('foo://a')), ''); + }); + test('joinPath', () => { - assert.equal( - joinPath(URI.file('/foo/bar'), '/file.js').toString(), - 'file:///foo/bar/file.js'); - - assert.equal( - joinPath(URI.file('/foo/bar/'), '/file.js').toString(), - 'file:///foo/bar/file.js'); - - assert.equal( - joinPath(URI.file('/'), '/file.js').toString(), - 'file:///file.js'); + if (isWindows) { + assert.equal(joinPath(URI.file('c:\\foo\\bar'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.equal(joinPath(URI.file('c:\\foo\\bar\\'), 'file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.equal(joinPath(URI.file('c:\\foo\\bar\\'), '/file.js').toString(), 'file:///c%3A/foo/bar/file.js'); + assert.equal(joinPath(URI.file('c:\\'), '/file.js').toString(), 'file:///c%3A/file.js'); + assert.equal(joinPath(URI.file('c:\\'), 'bar/file.js').toString(), 'file:///c%3A/bar/file.js'); + assert.equal(joinPath(URI.file('c:\\foo'), './file.js').toString(), 'file:///c%3A/foo/file.js'); + assert.equal(joinPath(URI.file('c:\\foo'), '/./file.js').toString(), 'file:///c%3A/foo/file.js'); + assert.equal(joinPath(URI.file('c:\\foo'), '../file.js').toString(), 'file:///c%3A/file.js'); + assert.equal(joinPath(URI.file('c:\\foo\\.'), '../file.js').toString(), 'file:///c%3A/file.js'); + } else { + assert.equal(joinPath(URI.file('/foo/bar'), '/file.js').toString(), 'file:///foo/bar/file.js'); + assert.equal(joinPath(URI.file('/foo/bar'), 'file.js').toString(), 'file:///foo/bar/file.js'); + assert.equal(joinPath(URI.file('/foo/bar/'), '/file.js').toString(), 'file:///foo/bar/file.js'); + assert.equal(joinPath(URI.file('/'), '/file.js').toString(), 'file:///file.js'); + assert.equal(joinPath(URI.file('/foo/bar'), './file.js').toString(), 'file:///foo/bar/file.js'); + assert.equal(joinPath(URI.file('/foo/bar'), '/./file.js').toString(), 'file:///foo/bar/file.js'); + assert.equal(joinPath(URI.file('/foo/bar'), '../file.js').toString(), 'file:///foo/file.js'); + } + assert.equal(joinPath(URI.parse('foo://a/foo/bar'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.equal(joinPath(URI.parse('foo://a/foo/bar'), 'file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '/file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.equal(joinPath(URI.parse('foo://a/'), '/file.js').toString(), 'foo://a/file.js'); + assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), './file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '/./file.js').toString(), 'foo://a/foo/bar/file.js'); + assert.equal(joinPath(URI.parse('foo://a/foo/bar/'), '../file.js').toString(), 'foo://a/foo/file.js'); assert.equal( joinPath(URI.from({ scheme: 'myScheme', authority: 'authority', path: '/path', query: 'query', fragment: 'fragment' }), '/file.js').toString(), 'myScheme://authority/path/file.js?query#fragment'); }); + + test('normalizePath', () => { + if (isWindows) { + assert.equal(normalizePath(URI.file('c:\\foo\\.\\bar')).toString(), 'file:///c%3A/foo/bar'); + assert.equal(normalizePath(URI.file('c:\\foo\\.')).toString(), 'file:///c%3A/foo'); + assert.equal(normalizePath(URI.file('c:\\foo\\.\\')).toString(), 'file:///c%3A/foo/'); + assert.equal(normalizePath(URI.file('c:\\foo\\..')).toString(), 'file:///c%3A/'); + assert.equal(normalizePath(URI.file('c:\\foo\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.equal(normalizePath(URI.file('c:\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.equal(normalizePath(URI.file('c:\\foo\\foo\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.equal(normalizePath(URI.file('c:\\foo\\foo\\.\\..\\..\\bar')).toString(), 'file:///c%3A/bar'); + assert.equal(normalizePath(URI.file('c:\\foo\\foo\\.\\..\\some\\..\\bar')).toString(), 'file:///c%3A/foo/bar'); + } else { + assert.equal(normalizePath(URI.file('/foo/./bar')).toString(), 'file:///foo/bar'); + assert.equal(normalizePath(URI.file('/foo/.')).toString(), 'file:///foo'); + assert.equal(normalizePath(URI.file('/foo/./')).toString(), 'file:///foo/'); + assert.equal(normalizePath(URI.file('/foo/..')).toString(), 'file:///'); + assert.equal(normalizePath(URI.file('/foo/../bar')).toString(), 'file:///bar'); + assert.equal(normalizePath(URI.file('/foo/../../bar')).toString(), 'file:///bar'); + assert.equal(normalizePath(URI.file('/foo/foo/../../bar')).toString(), 'file:///bar'); + assert.equal(normalizePath(URI.file('/foo/foo/./../../bar')).toString(), 'file:///bar'); + assert.equal(normalizePath(URI.file('/foo/foo/./../some/../bar')).toString(), 'file:///foo/bar'); + } + assert.equal(normalizePath(URI.parse('foo://a/foo/./bar')).toString(), 'foo://a/foo/bar'); + assert.equal(normalizePath(URI.parse('foo://a/foo/.')).toString(), 'foo://a/foo'); + assert.equal(normalizePath(URI.parse('foo://a/foo/./')).toString(), 'foo://a/foo/'); + assert.equal(normalizePath(URI.parse('foo://a/foo/..')).toString(), 'foo://a/'); + assert.equal(normalizePath(URI.parse('foo://a/foo/../bar')).toString(), 'foo://a/bar'); + assert.equal(normalizePath(URI.parse('foo://a/foo/../../bar')).toString(), 'foo://a/bar'); + assert.equal(normalizePath(URI.parse('foo://a/foo/foo/../../bar')).toString(), 'foo://a/bar'); + assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../../bar')).toString(), 'foo://a/bar'); + assert.equal(normalizePath(URI.parse('foo://a/foo/foo/./../some/../bar')).toString(), 'foo://a/foo/bar'); + }); + + test('isAbsolute', () => { + if (isWindows) { + assert.equal(isAbsolutePath(URI.file('c:\\foo\\')), true); + assert.equal(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute + } else { + assert.equal(isAbsolutePath(URI.file('/foo/bar')), true); + assert.equal(isAbsolutePath(URI.file('bar')), true); // URI normalizes all file URIs to be absolute + } + assert.equal(isAbsolutePath(URI.parse('foo:foo')), false); + assert.equal(isAbsolutePath(URI.parse('foo://a/foo/.')), true); + }); + + test('isEqual', () => { + let fileURI = URI.file('/foo/bar'); + let fileURI2 = URI.file('/foo/Bar'); + assert.equal(isEqual(fileURI, fileURI, true), true); + assert.equal(isEqual(fileURI, fileURI, false), true); + assert.equal(isEqual(fileURI, fileURI, hasToIgnoreCase(fileURI)), true); + assert.equal(isEqual(fileURI, fileURI2, true), true); + assert.equal(isEqual(fileURI, fileURI2, false), false); + + let fileURI3 = URI.parse('foo://server:453/foo/bar'); + let fileURI4 = URI.parse('foo://server:453/foo/Bar'); + assert.equal(isEqual(fileURI3, fileURI3, true), true); + assert.equal(isEqual(fileURI3, fileURI3, false), true); + assert.equal(isEqual(fileURI3, fileURI3, hasToIgnoreCase(fileURI3)), true); + assert.equal(isEqual(fileURI3, fileURI4, true), true); + assert.equal(isEqual(fileURI3, fileURI4, false), false); + + + assert.equal(isEqual(fileURI, fileURI3, true), false); + }); + + test('isEqualOrParent', () => { + let fileURI = isWindows ? URI.file('c:\\foo\\bar') : URI.file('/foo/bar'); + let fileURI2 = isWindows ? URI.file('c:\\foo') : URI.file('/foo'); + let fileURI2b = isWindows ? URI.file('C:\\Foo\\') : URI.file('/Foo/'); + assert.equal(isEqualOrParent(fileURI, fileURI, true), true, '1'); + assert.equal(isEqualOrParent(fileURI, fileURI, false), true, '2'); + assert.equal(isEqualOrParent(fileURI, fileURI2, true), true, '3'); + assert.equal(isEqualOrParent(fileURI, fileURI2, false), true, '4'); + assert.equal(isEqualOrParent(fileURI, fileURI2b, true), true, '5'); + assert.equal(isEqualOrParent(fileURI, fileURI2b, false), false, '6'); + + assert.equal(isEqualOrParent(fileURI2, fileURI, false), false, '7'); + assert.equal(isEqualOrParent(fileURI2b, fileURI2, true), true, '8'); + + let fileURI3 = URI.parse('foo://server:453/foo/bar/goo'); + let fileURI4 = URI.parse('foo://server:453/foo/'); + let fileURI5 = URI.parse('foo://server:453/foo'); + assert.equal(isEqualOrParent(fileURI3, fileURI3, true), true, '11'); + assert.equal(isEqualOrParent(fileURI3, fileURI3, false), true, '12'); + assert.equal(isEqualOrParent(fileURI3, fileURI4, true), true, '13'); + assert.equal(isEqualOrParent(fileURI3, fileURI4, false), true, '14'); + assert.equal(isEqualOrParent(fileURI3, fileURI, true), false, '15'); + assert.equal(isEqualOrParent(fileURI5, fileURI5, true), true, '16'); + }); + + function assertMalformedFileUri(path: string, expected: string) { + const newURI = isMalformedFileUri(URI.parse(path)); + assert.equal(newURI && newURI.toString(), expected); + } + + test('isMalformedFileUri', () => { + if (isWindows) { + assertMalformedFileUri('c:/foo/bar', 'file:///c%3A/foo/bar'); + assertMalformedFileUri('c:\\foo\\bar', 'file:///c%3A/foo/bar'); + assertMalformedFileUri('\\\\localhost\\c$\\devel\\test', 'file://localhost/c%24/devel/test'); + } + assertMalformedFileUri('/foo/bar', 'file:///foo/bar'); + + assertMalformedFileUri('file:///foo/bar', void 0); + assertMalformedFileUri('file:///c%3A/foo/bar', void 0); + assertMalformedFileUri('file://localhost/c$/devel/test', void 0); + assertMalformedFileUri('foo://dadie/foo/bar', void 0); + assertMalformedFileUri('foo:///dadie/foo/bar', void 0); + }); }); \ No newline at end of file diff --git a/src/vs/base/test/common/uri.test.ts b/src/vs/base/test/common/uri.test.ts index 618a56a1493..e74f82046d0 100644 --- a/src/vs/base/test/common/uri.test.ts +++ b/src/vs/base/test/common/uri.test.ts @@ -65,8 +65,6 @@ suite('URI', () => { assert.equal(URI.from({ scheme: 'http', authority: 'www.MSFT.com', path: '/my/path' }).toString(), 'http://www.msft.com/my/path'); assert.equal(URI.from({ scheme: 'http', authority: '', path: 'my/path' }).toString(), 'http:/my/path'); assert.equal(URI.from({ scheme: 'http', authority: '', path: '/my/path' }).toString(), 'http:/my/path'); - assert.equal(URI.from({ scheme: '', authority: '', path: 'my/path' }).toString(), 'my/path'); - assert.equal(URI.from({ scheme: '', authority: '', path: '/my/path' }).toString(), '/my/path'); //http://a-test-site.com/#test=true assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(), 'http://a-test-site.com/?test%3Dtrue'); assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(), 'http://a-test-site.com/#test%3Dtrue'); @@ -75,7 +73,7 @@ suite('URI', () => { test('http#toString, encode=FALSE', () => { assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: 'test=true' }).toString(true), 'http://a-test-site.com/?test=true'); assert.equal(URI.from({ scheme: 'http', authority: 'a-test-site.com', path: '/', query: '', fragment: 'test=true' }).toString(true), 'http://a-test-site.com/#test=true'); - assert.equal(URI.from({}).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(true), 'http:/api/files/test.me?t=1234'); + assert.equal(URI.from({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(true), 'http:/api/files/test.me?t=1234'); var value = URI.parse('file://shares/pröjects/c%23/#l12'); assert.equal(value.authority, 'shares'); @@ -107,12 +105,12 @@ suite('URI', () => { test('with, changes', () => { assert.equal(URI.parse('before:some/file/path').with({ scheme: 'after' }).toString(), 'after:some/file/path'); - assert.equal(URI.from({}).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({}).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({}).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({}).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({}).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234'); - assert.equal(URI.from({}).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', path: '/api/files/test.me', query: 't=1234' }).toString(), 'http:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'http', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'http:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'https', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'https:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTP', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTP:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'HTTPS', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'HTTPS:/api/files/test.me?t%3D1234'); + assert.equal(URI.from({ scheme: 's' }).with({ scheme: 'boo', authority: '', path: '/api/files/test.me', query: 't=1234', fragment: '' }).toString(), 'boo:/api/files/test.me?t%3D1234'); }); test('with, remove components #8465', () => { @@ -186,34 +184,13 @@ suite('URI', () => { assert.equal(value.query, ''); assert.equal(value.fragment, ''); - value = URI.parse('api/files/test'); - assert.equal(value.scheme, ''); + value = URI.parse('foo:api/files/test'); + assert.equal(value.scheme, 'foo'); assert.equal(value.authority, ''); assert.equal(value.path, 'api/files/test'); assert.equal(value.query, ''); assert.equal(value.fragment, ''); - value = URI.parse('api'); - assert.equal(value.scheme, ''); - assert.equal(value.authority, ''); - assert.equal(value.path, 'api'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); - - value = URI.parse('/api/files/test'); - assert.equal(value.scheme, ''); - assert.equal(value.authority, ''); - assert.equal(value.path, '/api/files/test'); - assert.equal(value.query, ''); - assert.equal(value.fragment, ''); - - value = URI.parse('?test'); - assert.equal(value.scheme, ''); - assert.equal(value.authority, ''); - assert.equal(value.path, ''); - assert.equal(value.query, 'test'); - assert.equal(value.fragment, ''); - value = URI.parse('file:?q'); assert.equal(value.scheme, 'file'); assert.equal(value.authority, ''); @@ -221,13 +198,6 @@ suite('URI', () => { assert.equal(value.query, 'q'); assert.equal(value.fragment, ''); - value = URI.parse('#test'); - assert.equal(value.scheme, ''); - assert.equal(value.authority, ''); - assert.equal(value.path, ''); - assert.equal(value.query, ''); - assert.equal(value.fragment, 'test'); - value = URI.parse('file:#d'); assert.equal(value.scheme, 'file'); assert.equal(value.authority, ''); diff --git a/src/vs/base/test/node/encoding/encoding.test.ts b/src/vs/base/test/node/encoding/encoding.test.ts index 252ee281e37..99d05d437a0 100644 --- a/src/vs/base/test/node/encoding/encoding.test.ts +++ b/src/vs/base/test/node/encoding/encoding.test.ts @@ -10,10 +10,11 @@ import * as fs from 'fs'; import * as encoding from 'vs/base/node/encoding'; import { readExactlyByFile } from 'vs/base/node/stream'; import { Readable } from 'stream'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('Encoding', () => { test('detectBOM UTF-8', () => { - const file = require.toUrl('./fixtures/some_utf8.css'); + const file = getPathFromAmdModule(require, './fixtures/some_utf8.css'); return encoding.detectEncodingByBOM(file).then((encoding: string) => { assert.equal(encoding, 'utf8'); @@ -21,7 +22,7 @@ suite('Encoding', () => { }); test('detectBOM UTF-16 LE', () => { - const file = require.toUrl('./fixtures/some_utf16le.css'); + const file = getPathFromAmdModule(require, './fixtures/some_utf16le.css'); return encoding.detectEncodingByBOM(file).then((encoding: string) => { assert.equal(encoding, 'utf16le'); @@ -29,7 +30,7 @@ suite('Encoding', () => { }); test('detectBOM UTF-16 BE', () => { - const file = require.toUrl('./fixtures/some_utf16be.css'); + const file = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); return encoding.detectEncodingByBOM(file).then((encoding: string) => { assert.equal(encoding, 'utf16be'); @@ -37,7 +38,7 @@ suite('Encoding', () => { }); test('detectBOM ANSI', function () { - const file = require.toUrl('./fixtures/some_ansi.css'); + const file = getPathFromAmdModule(require, './fixtures/some_ansi.css'); return encoding.detectEncodingByBOM(file).then((encoding: string) => { assert.equal(encoding, null); @@ -45,7 +46,7 @@ suite('Encoding', () => { }); test('detectBOM ANSI', function () { - const file = require.toUrl('./fixtures/empty.txt'); + const file = getPathFromAmdModule(require, './fixtures/empty.txt'); return encoding.detectEncodingByBOM(file).then((encoding: string) => { assert.equal(encoding, null); @@ -68,7 +69,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (JSON saved as PNG)', function () { - const file = require.toUrl('./fixtures/some.json.png'); + const file = getPathFromAmdModule(require, './fixtures/some.json.png'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); @@ -77,7 +78,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (PNG saved as TXT)', function () { - const file = require.toUrl('./fixtures/some.png.txt'); + const file = getPathFromAmdModule(require, './fixtures/some.png.txt'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.seemsBinary, true); @@ -85,7 +86,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (XML saved as PNG)', function () { - const file = require.toUrl('./fixtures/some.xml.png'); + const file = getPathFromAmdModule(require, './fixtures/some.xml.png'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.seemsBinary, false); @@ -93,7 +94,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (QWOFF saved as TXT)', function () { - const file = require.toUrl('./fixtures/some.qwoff.txt'); + const file = getPathFromAmdModule(require, './fixtures/some.qwoff.txt'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.seemsBinary, true); @@ -101,7 +102,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (CSS saved as QWOFF)', function () { - const file = require.toUrl('./fixtures/some.css.qwoff'); + const file = getPathFromAmdModule(require, './fixtures/some.css.qwoff'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.seemsBinary, false); @@ -109,7 +110,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (PDF)', function () { - const file = require.toUrl('./fixtures/some.pdf'); + const file = getPathFromAmdModule(require, './fixtures/some.pdf'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.seemsBinary, true); @@ -117,7 +118,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (guess UTF-16 LE from content without BOM)', function () { - const file = require.toUrl('./fixtures/utf16_le_nobom.txt'); + const file = getPathFromAmdModule(require, './fixtures/utf16_le_nobom.txt'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.encoding, encoding.UTF16le); @@ -126,7 +127,7 @@ suite('Encoding', () => { }); test('detectEncodingFromBuffer (guess UTF-16 BE from content without BOM)', function () { - const file = require.toUrl('./fixtures/utf16_be_nobom.txt'); + const file = getPathFromAmdModule(require, './fixtures/utf16_be_nobom.txt'); return readExactlyByFile(file, 512).then(buffer => { const mimes = encoding.detectEncodingFromBuffer(buffer); assert.equal(mimes.encoding, encoding.UTF16be); @@ -135,7 +136,7 @@ suite('Encoding', () => { }); test('autoGuessEncoding (ShiftJIS)', function () { - const file = require.toUrl('./fixtures/some.shiftjis.txt'); + const file = getPathFromAmdModule(require, './fixtures/some.shiftjis.txt'); return readExactlyByFile(file, 512 * 8).then(buffer => { return encoding.detectEncodingFromBuffer(buffer, true).then(mimes => { assert.equal(mimes.encoding, 'shiftjis'); @@ -144,7 +145,7 @@ suite('Encoding', () => { }); test('autoGuessEncoding (CP1252)', function () { - const file = require.toUrl('./fixtures/some.cp1252.txt'); + const file = getPathFromAmdModule(require, './fixtures/some.cp1252.txt'); return readExactlyByFile(file, 512 * 8).then(buffer => { return encoding.detectEncodingFromBuffer(buffer, true).then(mimes => { assert.equal(mimes.encoding, 'windows1252'); @@ -238,7 +239,7 @@ suite('Encoding', () => { test('toDecodeStream - encoding, utf16be', async function () { - let path = require.toUrl('./fixtures/some_utf16be.css'); + let path = getPathFromAmdModule(require, './fixtures/some_utf16be.css'); let source = fs.createReadStream(path); let { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 64 }); @@ -254,7 +255,7 @@ suite('Encoding', () => { test('toDecodeStream - empty file', async function () { - let path = require.toUrl('./fixtures/empty.txt'); + let path = getPathFromAmdModule(require, './fixtures/empty.txt'); let source = fs.createReadStream(path); let { detected, stream } = await encoding.toDecodeStream(source, {}); diff --git a/src/vs/base/test/node/extfs/extfs.test.ts b/src/vs/base/test/node/extfs/extfs.test.ts index d5a363f2b5c..d1740085fd0 100644 --- a/src/vs/base/test/node/extfs/extfs.test.ts +++ b/src/vs/base/test/node/extfs/extfs.test.ts @@ -14,6 +14,7 @@ import { canNormalize } from 'vs/base/common/normalization'; import { isLinux, isWindows } from 'vs/base/common/platform'; import * as uuid from 'vs/base/common/uuid'; import * as extfs from 'vs/base/node/extfs'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; @@ -169,7 +170,7 @@ suite('Extfs', () => { test('copy, move and delete', function (done) { const id = uuid.generateUuid(); const id2 = uuid.generateUuid(); - const sourceDir = require.toUrl('./fixtures'); + const sourceDir = getPathFromAmdModule(require, './fixtures'); const parentDir = path.join(os.tmpdir(), 'vsctests', 'extfs'); const targetDir = path.join(parentDir, id); const targetDir2 = path.join(parentDir, id2); @@ -320,7 +321,7 @@ suite('Extfs', () => { test('writeFileAndFlush (file stream)', function (done) { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const sourceFile = require.toUrl('./fixtures/index.html'); + const sourceFile = getPathFromAmdModule(require, './fixtures/index.html'); const newDir = path.join(parentDir, 'extfs', id); const testFile = path.join(newDir, 'flushed.txt'); @@ -453,7 +454,7 @@ suite('Extfs', () => { test('writeFileAndFlush (file stream, error handling)', function (done) { const id = uuid.generateUuid(); const parentDir = path.join(os.tmpdir(), 'vsctests', id); - const sourceFile = require.toUrl('./fixtures/index.html'); + const sourceFile = getPathFromAmdModule(require, './fixtures/index.html'); const newDir = path.join(parentDir, 'extfs', id); const testFile = path.join(newDir, 'flushed.txt'); diff --git a/src/vs/base/test/node/processes/processes.test.ts b/src/vs/base/test/node/processes/processes.test.ts index 332bdcdbff7..ae4f062bd1c 100644 --- a/src/vs/base/test/node/processes/processes.test.ts +++ b/src/vs/base/test/node/processes/processes.test.ts @@ -9,8 +9,8 @@ import * as assert from 'assert'; import * as cp from 'child_process'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import URI from 'vs/base/common/uri'; import * as processes from 'vs/base/node/processes'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function fork(id: string): cp.ChildProcess { const opts: any = { @@ -21,7 +21,7 @@ function fork(id: string): cp.ChildProcess { }) }; - return cp.fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=processTests'], opts); + return cp.fork(getPathFromAmdModule(require, 'bootstrap'), ['--type=processTests'], opts); } suite('Processes', () => { diff --git a/src/vs/base/test/node/stream/stream.test.ts b/src/vs/base/test/node/stream/stream.test.ts index c2c5f1b2f9a..d52ed4c43b1 100644 --- a/src/vs/base/test/node/stream/stream.test.ts +++ b/src/vs/base/test/node/stream/stream.test.ts @@ -8,10 +8,11 @@ import * as assert from 'assert'; import * as stream from 'vs/base/node/stream'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('Stream', () => { test('readExactlyByFile - ANSI', function () { - const file = require.toUrl('./fixtures/file.css'); + const file = getPathFromAmdModule(require, './fixtures/file.css'); return stream.readExactlyByFile(file, 10).then(({ buffer, bytesRead }) => { assert.equal(bytesRead, 10); @@ -20,7 +21,7 @@ suite('Stream', () => { }); test('readExactlyByFile - empty', function () { - const file = require.toUrl('./fixtures/empty.txt'); + const file = getPathFromAmdModule(require, './fixtures/empty.txt'); return stream.readExactlyByFile(file, 10).then(({ bytesRead }) => { assert.equal(bytesRead, 0); @@ -28,7 +29,7 @@ suite('Stream', () => { }); test('readToMatchingString - ANSI', function () { - const file = require.toUrl('./fixtures/file.css'); + const file = getPathFromAmdModule(require, './fixtures/file.css'); return stream.readToMatchingString(file, '\n', 10, 100).then((result: string) => { // \r may be present on Windows @@ -37,10 +38,10 @@ suite('Stream', () => { }); test('readToMatchingString - empty', function () { - const file = require.toUrl('./fixtures/empty.txt'); + const file = getPathFromAmdModule(require, './fixtures/empty.txt'); return stream.readToMatchingString(file, '\n', 10, 100).then((result: string) => { assert.equal(result, null); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/base/test/node/uri.test.perf.ts b/src/vs/base/test/node/uri.test.perf.ts index 3689d197b4d..492a676322a 100644 --- a/src/vs/base/test/node/uri.test.perf.ts +++ b/src/vs/base/test/node/uri.test.perf.ts @@ -7,13 +7,14 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import { readFileSync } from 'fs'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('URI - perf', function () { let manyFileUris: URI[]; setup(function () { manyFileUris = []; - let data = readFileSync(URI.parse(require.toUrl('./uri.test.data.txt')).fsPath).toString(); + let data = readFileSync(getPathFromAmdModule(require, './uri.test.data.txt')).toString(); let lines = data.split('\n'); for (let line of lines) { manyFileUris.push(URI.file(line)); diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index f53894f5ebb..21adac26149 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -8,13 +8,13 @@ import * as assert from 'assert'; import * as path from 'path'; import * as os from 'os'; -import URI from 'vs/base/common/uri'; import { extract } from 'vs/base/node/zip'; import { generateUuid } from 'vs/base/common/uuid'; import { rimraf, exists } from 'vs/base/node/pfs'; import { NullLogService } from 'vs/platform/log/common/log'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; -const fixtures = URI.parse(require.toUrl('./fixtures')).fsPath; +const fixtures = getPathFromAmdModule(require, './fixtures'); suite('Zip', () => { @@ -27,4 +27,4 @@ suite('Zip', () => { .then(exists => assert(exists)) .then(() => rimraf(target)); }); -}); \ 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 a642157cdca..2e25cb33248 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -413,6 +413,16 @@ export class IssueReporter extends Disposable { if (cmdOrCtrlKey && e.keyCode === 189) { this.applyZoom(webFrame.getZoomLevel() - 1); } + + // With latest electron upgrade, cmd+a is no longer propagating correctly for inputs in this window on mac + // Manually perform the selection + if (platform.isMacintosh) { + if (cmdOrCtrlKey && e.keyCode === 65 && e.target) { + if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { + (e.target).select(); + } + } + } }; } diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 54b6be346e7..e6c6f9fb91d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -64,7 +64,7 @@ import { IMenubarService } from 'vs/platform/menubar/common/menubar'; import { MenubarService } from 'vs/platform/menubar/electron-main/menubarService'; import { MenubarChannel } from 'vs/platform/menubar/common/menubarIpc'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; -import { asArray } from 'vs/code/node/args'; +import { CodeMenu } from 'vs/code/electron-main/menus'; export class CodeApplication { @@ -520,6 +520,14 @@ export class CodeApplication { } } + // TODO@sbatten: Remove when switching back to dynamic menu + // Install Menu + const instantiationService = accessor.get(IInstantiationService); + const configurationService = accessor.get(IConfigurationService); + if (platform.isMacintosh || configurationService.getValue('window.titleBarStyle') !== 'custom') { + instantiationService.createInstance(CodeMenu); + } + // Jump List this.historyMainService.updateWindowsJumpList(); this.historyMainService.onRecentlyOpenedChange(() => this.historyMainService.updateWindowsJumpList()); diff --git a/src/vs/code/electron-main/keyboard.ts b/src/vs/code/electron-main/keyboard.ts index 9400d80b5a1..ecd7e284db6 100644 --- a/src/vs/code/electron-main/keyboard.ts +++ b/src/vs/code/electron-main/keyboard.ts @@ -7,7 +7,14 @@ import * as nativeKeymap from 'native-keymap'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { IStateService } from 'vs/platform/state/common/state'; +import { Event, Emitter, once } from 'vs/base/common/event'; +import { ConfigWatcher } from 'vs/base/node/config'; +import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ipcMain as ipc } from 'electron'; +import { IWindowsMainService } from 'vs/platform/windows/electron-main/windows'; +import { ILogService } from 'vs/platform/log/common/log'; export class KeyboardLayoutMonitor { @@ -31,4 +38,109 @@ export class KeyboardLayoutMonitor { } return this._emitter.event(callback); } +} + +export interface IKeybinding { + id: string; + label: string; + isNative: boolean; +} + +export class KeybindingsResolver { + + private static readonly lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings'; + + private commandIds: Set; + private keybindings: { [commandId: string]: IKeybinding }; + private keybindingsWatcher: ConfigWatcher; + + private _onKeybindingsChanged = new Emitter(); + onKeybindingsChanged: Event = this._onKeybindingsChanged.event; + + constructor( + @IStateService private stateService: IStateService, + @IEnvironmentService environmentService: IEnvironmentService, + @IWindowsMainService private windowsMainService: IWindowsMainService, + @ILogService private logService: ILogService + ) { + this.commandIds = new Set(); + this.keybindings = this.stateService.getItem<{ [id: string]: string; }>(KeybindingsResolver.lastKnownKeybindingsMapStorageKey) || Object.create(null); + this.keybindingsWatcher = new ConfigWatcher(environmentService.appKeybindingsPath, { changeBufferDelay: 100, onError: error => this.logService.error(error) }); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Listen to resolved keybindings from window + ipc.on('vscode:keybindingsResolved', (event, rawKeybindings: string) => { + let keybindings: IKeybinding[] = []; + try { + keybindings = JSON.parse(rawKeybindings); + } catch (error) { + // Should not happen + } + + // Fill hash map of resolved keybindings and check for changes + let keybindingsChanged = false; + let keybindingsCount = 0; + const resolvedKeybindings: { [commandId: string]: IKeybinding } = Object.create(null); + keybindings.forEach(keybinding => { + keybindingsCount++; + + resolvedKeybindings[keybinding.id] = keybinding; + + if (!this.keybindings[keybinding.id] || keybinding.label !== this.keybindings[keybinding.id].label) { + keybindingsChanged = true; + } + }); + + // A keybinding might have been unassigned, so we have to account for that too + if (Object.keys(this.keybindings).length !== keybindingsCount) { + keybindingsChanged = true; + } + + if (keybindingsChanged) { + this.keybindings = resolvedKeybindings; + this.stateService.setItem(KeybindingsResolver.lastKnownKeybindingsMapStorageKey, this.keybindings); // keep to restore instantly after restart + + this._onKeybindingsChanged.fire(); + } + }); + + // Resolve keybindings when any first window is loaded + const onceOnWindowReady = once(this.windowsMainService.onWindowReady); + onceOnWindowReady(win => this.resolveKeybindings(win)); + + // Resolve keybindings again when keybindings.json changes + this.keybindingsWatcher.onDidUpdateConfiguration(() => this.resolveKeybindings()); + + // Resolve keybindings when window reloads because an installed extension could have an impact + this.windowsMainService.onWindowReload(() => this.resolveKeybindings()); + } + + private resolveKeybindings(win = this.windowsMainService.getLastActiveWindow()): void { + if (this.commandIds.size && win) { + const commandIds: string[] = []; + this.commandIds.forEach(id => commandIds.push(id)); + win.sendWhenReady('vscode:resolveKeybindings', JSON.stringify(commandIds)); + } + } + + public getKeybinding(commandId: string): IKeybinding { + if (!commandId) { + return void 0; + } + + if (!this.commandIds.has(commandId)) { + this.commandIds.add(commandId); + } + + return this.keybindings[commandId]; + } + + public dispose(): void { + this._onKeybindingsChanged.dispose(); + this.keybindingsWatcher.dispose(); + } } \ No newline at end of file diff --git a/src/vs/code/electron-main/menubar.ts b/src/vs/code/electron-main/menubar.ts index fcb357c0f9e..7f2a3082e0c 100644 --- a/src/vs/code/electron-main/menubar.ts +++ b/src/vs/code/electron-main/menubar.ts @@ -31,12 +31,13 @@ export class Menubar { private static readonly MAX_MENU_RECENT_ENTRIES = 10; private isQuitting: boolean; private appMenuInstalled: boolean; + private closedLastWindow: boolean; private menuUpdater: RunOnceScheduler; private nativeTabMenuItems: Electron.MenuItem[]; - private menubarMenus: IMenubarData = {}; + private menubarMenus: IMenubarData; private keybindings: { [commandId: string]: IMenubarKeybinding }; @@ -54,6 +55,8 @@ export class Menubar { this.keybindings = Object.create(null); + this.closedLastWindow = false; + this.install(); this.registerListeners(); @@ -112,8 +115,14 @@ export class Menubar { return enableNativeTabs; } - updateMenu(menus: IMenubarData, windowId: number) { + updateMenu(menus: IMenubarData, windowId: number, additionalKeybindings?: Array) { this.menubarMenus = menus; + if (additionalKeybindings) { + additionalKeybindings.forEach(keybinding => { + this.keybindings[keybinding.id] = keybinding; + }); + } + this.scheduleUpdateMenu(); } @@ -143,9 +152,9 @@ export class Menubar { return; } - // Update menu if window count goes from N > 0 or 0 > N to update menu item enablement if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) { + this.closedLastWindow = e.newCount === 0; this.scheduleUpdateMenu(); } @@ -272,7 +281,7 @@ export class Menubar { // Mac: Window let macWindowMenuItem: Electron.MenuItem; - if (isMacintosh) { + if (this.shouldDrawMenu('Window')) { const windowMenu = new Menu(); macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' }); this.setMacWindowMenu(windowMenu); @@ -310,7 +319,11 @@ export class Menubar { menubar.append(helpMenuItem); } - Menu.setApplicationMenu(menubar); + if (menubar.items && menubar.items.length > 0) { + Menu.setApplicationMenu(menubar); + } else { + Menu.setApplicationMenu(null); + } } private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void { @@ -370,14 +383,22 @@ export class Menubar { switch (menuId) { case 'File': case 'Help': - return true; + if (isMacintosh) { + return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || (!!this.menubarMenus && !!this.menubarMenus[menuId]); + } + + case 'Window': + if (isMacintosh) { + return (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow) || !!this.menubarMenus; + } + default: - return this.windowsMainService.getWindowCount() > 0 && !!this.menubarMenus[menuId]; + return this.windowsMainService.getWindowCount() > 0 && (!!this.menubarMenus && !!this.menubarMenus[menuId]); } } private shouldFallback(menuId: string): boolean { - return this.shouldDrawMenu(menuId) && (this.windowsMainService.getWindowCount() === 0); + return this.shouldDrawMenu(menuId) && (this.windowsMainService.getWindowCount() === 0 && this.closedLastWindow && isMacintosh); } private setFallbackMenuById(menu: Electron.Menu, menuId: string): void { @@ -462,16 +483,12 @@ export class Menubar { }); } - const openProcessExplorer = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")), click: () => this.runActionInRenderer('workbench.action.openProcessExplorer') }); - if (twitterItem) { menu.append(twitterItem); } if (featureRequestsItem) { menu.append(featureRequestsItem); } if (reportIssuesItem) { menu.append(reportIssuesItem); } - if (twitterItem || featureRequestsItem || reportIssuesItem) { menu.append(__separator__()); } + if ((twitterItem || featureRequestsItem || reportIssuesItem) && (licenseItem || privacyStatementItem)) { menu.append(__separator__()); } if (licenseItem) { menu.append(licenseItem); } if (privacyStatementItem) { menu.append(privacyStatementItem); } - if (licenseItem || privacyStatementItem) { menu.append(__separator__()); } - menu.append(openProcessExplorer); break; } @@ -489,6 +506,8 @@ export class Menubar { } else if (isMenubarMenuItemAction(item)) { if (item.id === 'workbench.action.openRecent') { this.insertRecentMenuItems(menu); + } else if (item.id === 'workbench.action.showAboutDialog') { + this.insertCheckForUpdatesItems(menu); } // Store the keybinding @@ -505,11 +524,19 @@ export class Menubar { } private setMenuById(menu: Electron.Menu, menuId: string): void { - if (this.menubarMenus[menuId]) { + if (this.menubarMenus && this.menubarMenus[menuId]) { this.setMenu(menu, this.menubarMenus[menuId].items); } } + private insertCheckForUpdatesItems(menu: Electron.Menu) { + const updateItems = this.getUpdateMenuItems(); + if (updateItems.length) { + updateItems.forEach(i => menu.append(i)); + menu.append(__separator__()); + } + } + private insertRecentMenuItems(menu: Electron.Menu) { const { workspaces, files } = this.historyMainService.getRecentlyOpened(); @@ -580,6 +607,7 @@ export class Menubar { if (this.currentEnableNativeTabs) { const hasMultipleWindows = this.windowsMainService.getWindowCount() > 1; + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mNewTab', "New Tab"), 'workbench.action.newWindowTab')); this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowPreviousTab', "Show Previous Tab"), 'workbench.action.showPreviousWindowTab', hasMultipleWindows)); this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowNextTab', "Show Next Tab"), 'workbench.action.showNextWindowTab', hasMultipleWindows)); this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows)); diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts new file mode 100644 index 00000000000..f2cd26b67f1 --- /dev/null +++ b/src/vs/code/electron-main/menus.ts @@ -0,0 +1,1320 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as nls from 'vs/nls'; +import { isMacintosh, isLinux, isWindows, language } from 'vs/base/common/platform'; +import * as arrays from 'vs/base/common/arrays'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { app, shell, Menu, MenuItem, BrowserWindow } from 'electron'; +import { OpenContext, IRunActionInWindowRequest, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { AutoSaveConfiguration } from 'vs/platform/files/common/files'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IUpdateService, StateType } from 'vs/platform/update/common/update'; +import product from 'vs/platform/node/product'; +import { RunOnceScheduler } from 'vs/base/common/async'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { mnemonicMenuLabel as baseMnemonicLabel, unmnemonicLabel } from 'vs/base/common/labels'; +import { KeybindingsResolver } from 'vs/code/electron-main/keyboard'; +import { IWindowsMainService, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; +import { IHistoryMainService } from 'vs/platform/history/common/history'; +import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; +import URI from 'vs/base/common/uri'; +import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; + +interface IMenuItemClickHandler { + inDevTools: (contents: Electron.WebContents) => void; + inNoWindow: () => void; +} + +const telemetryFrom = 'menu'; + +export class CodeMenu { + + private static readonly MAX_MENU_RECENT_ENTRIES = 10; + + private keys = [ + 'files.autoSave', + 'editor.multiCursorModifier', + 'workbench.sideBar.location', + 'workbench.statusBar.visible', + 'workbench.activityBar.visible', + 'window.enableMenuBarMnemonics', + 'window.nativeTabs' + ]; + + private isQuitting: boolean; + private appMenuInstalled: boolean; + + private menuUpdater: RunOnceScheduler; + + private keybindingsResolver: KeybindingsResolver; + + private closeFolder: Electron.MenuItem; + private closeWorkspace: Electron.MenuItem; + + private nativeTabMenuItems: Electron.MenuItem[]; + + constructor( + @IUpdateService private updateService: IUpdateService, + @IInstantiationService instantiationService: IInstantiationService, + @IConfigurationService private configurationService: IConfigurationService, + @IWindowsMainService private windowsMainService: IWindowsMainService, + @IWindowsService private windowsService: IWindowsService, + @IEnvironmentService private environmentService: IEnvironmentService, + @ITelemetryService private telemetryService: ITelemetryService, + @IHistoryMainService private historyMainService: IHistoryMainService, + @IUriDisplayService private uriDisplayService: IUriDisplayService + ) { + this.nativeTabMenuItems = []; + + this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); + this.keybindingsResolver = instantiationService.createInstance(KeybindingsResolver); + + this.install(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Keep flag when app quits + app.on('will-quit', () => { + this.isQuitting = true; + }); + + // Listen to some events from window service to update menu + this.historyMainService.onRecentlyOpenedChange(() => this.updateMenu()); + this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e)); + this.windowsMainService.onActiveWindowChanged(() => this.updateWorkspaceMenuItems()); + this.windowsMainService.onWindowReady(() => this.updateWorkspaceMenuItems()); + this.windowsMainService.onWindowClose(() => this.updateWorkspaceMenuItems()); + + // Update when auto save config changes + this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)); + + // Listen to update service + this.updateService.onStateChange(() => this.updateMenu()); + + // Listen to keybindings change + this.keybindingsResolver.onKeybindingsChanged(() => this.updateMenu()); + } + + private onConfigurationUpdated(event: IConfigurationChangeEvent): void { + if (this.keys.some(key => event.affectsConfiguration(key))) { + this.updateMenu(); + } + } + + private get currentAutoSaveSetting(): string { + return this.configurationService.getValue('files.autoSave'); + } + + private get currentMultiCursorModifierSetting(): string { + return this.configurationService.getValue('editor.multiCursorModifier'); + } + + private get currentSidebarLocation(): string { + return this.configurationService.getValue('workbench.sideBar.location') || 'left'; + } + + private get currentStatusbarVisible(): boolean { + let statusbarVisible = this.configurationService.getValue('workbench.statusBar.visible'); + if (typeof statusbarVisible !== 'boolean') { + statusbarVisible = true; + } + return statusbarVisible; + } + + private get currentActivityBarVisible(): boolean { + let activityBarVisible = this.configurationService.getValue('workbench.activityBar.visible'); + if (typeof activityBarVisible !== 'boolean') { + activityBarVisible = true; + } + return activityBarVisible; + } + + private get currentEnableMenuBarMnemonics(): boolean { + let enableMenuBarMnemonics = this.configurationService.getValue('window.enableMenuBarMnemonics'); + if (typeof enableMenuBarMnemonics !== 'boolean') { + enableMenuBarMnemonics = true; + } + return enableMenuBarMnemonics; + } + + private get currentEnableNativeTabs(): boolean { + let enableNativeTabs = this.configurationService.getValue('window.nativeTabs'); + if (typeof enableNativeTabs !== 'boolean') { + enableNativeTabs = false; + } + return enableNativeTabs; + } + + private updateMenu(): void { + this.menuUpdater.schedule(); // buffer multiple attempts to update the menu + } + + private doUpdateMenu(): void { + + // Due to limitations in Electron, it is not possible to update menu items dynamically. The suggested + // workaround from Electron is to set the application menu again. + // See also https://github.com/electron/electron/issues/846 + // + // Run delayed to prevent updating menu while it is open + if (!this.isQuitting) { + setTimeout(() => { + if (!this.isQuitting) { + this.install(); + } + }, 10 /* delay this because there is an issue with updating a menu when it is open */); + } + } + + private onWindowsCountChanged(e: IWindowsCountChangedEvent): void { + if (!isMacintosh) { + return; + } + + // Update menu if window count goes from N > 0 or 0 > N to update menu item enablement + if ((e.oldCount === 0 && e.newCount > 0) || (e.oldCount > 0 && e.newCount === 0)) { + this.updateMenu(); + } + + // Update specific items that are dependent on window count + else if (this.currentEnableNativeTabs) { + this.nativeTabMenuItems.forEach(item => { + if (item) { + item.enabled = e.newCount > 1; + } + }); + } + } + + private updateWorkspaceMenuItems(): void { + const window = this.windowsMainService.getLastActiveWindow(); + const isInWorkspaceContext = window && !!window.openedWorkspace; + const isInFolderContext = window && !!window.openedFolderUri; + + this.closeWorkspace.visible = isInWorkspaceContext; + this.closeFolder.visible = !isInWorkspaceContext; + this.closeFolder.enabled = isInFolderContext || isLinux /* https://github.com/Microsoft/vscode/issues/36431 */; + } + + private install(): void { + + // Menus + const menubar = new Menu(); + + // Mac: Application + let macApplicationMenuItem: Electron.MenuItem; + if (isMacintosh) { + const applicationMenu = new Menu(); + macApplicationMenuItem = new MenuItem({ label: product.nameShort, submenu: applicationMenu }); + this.setMacApplicationMenu(applicationMenu); + } + + // File + const fileMenu = new Menu(); + const fileMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu }); + this.setFileMenu(fileMenu); + + // Edit + const editMenu = new Menu(); + const editMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu }); + this.setEditMenu(editMenu); + + // Selection + const selectionMenu = new Menu(); + const selectionMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mSelection', comment: ['&& denotes a mnemonic'] }, "&&Selection")), submenu: selectionMenu }); + this.setSelectionMenu(selectionMenu); + + // View + const viewMenu = new Menu(); + const viewMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu }); + this.setViewMenu(viewMenu); + + // Goto + const gotoMenu = new Menu(); + const gotoMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu }); + this.setGotoMenu(gotoMenu); + + // Terminal + const terminalMenu = new Menu(); + const terminalMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "Ter&&minal")), submenu: terminalMenu }); + this.setTerminalMenu(terminalMenu); + + // Debug + const debugMenu = new Menu(); + const debugMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")), submenu: debugMenu }); + this.setDebugMenu(debugMenu); + + // Mac: Window + let macWindowMenuItem: Electron.MenuItem; + if (isMacintosh) { + const windowMenu = new Menu(); + macWindowMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' }); + this.setMacWindowMenu(windowMenu); + } + + // Help + const helpMenu = new Menu(); + const helpMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' }); + this.setHelpMenu(helpMenu); + + // Tasks + const taskMenu = new Menu(); + const taskMenuItem = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'mTask', comment: ['&& denotes a mnemonic'] }, "&&Tasks")), submenu: taskMenu }); + this.setTaskMenu(taskMenu); + + // Menu Structure + if (macApplicationMenuItem) { + menubar.append(macApplicationMenuItem); + } + + menubar.append(fileMenuItem); + menubar.append(editMenuItem); + menubar.append(selectionMenuItem); + menubar.append(viewMenuItem); + menubar.append(gotoMenuItem); + menubar.append(terminalMenuItem); + menubar.append(debugMenuItem); + menubar.append(taskMenuItem); + + if (macWindowMenuItem) { + menubar.append(macWindowMenuItem); + } + + menubar.append(helpMenuItem); + + Menu.setApplicationMenu(menubar); + + // Dock Menu + if (isMacintosh && !this.appMenuInstalled) { + this.appMenuInstalled = true; + + const dockMenu = new Menu(); + dockMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.DOCK) })); + + app.dock.setMenu(dockMenu); + } + } + + private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void { + const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", product.nameLong), role: 'about' }); + const checkForUpdates = this.getUpdateMenuItems(); + const preferences = this.getPreferencesMenu(); + const servicesMenu = new Menu(); + const services = new MenuItem({ label: nls.localize('mServices', "Services"), role: 'services', submenu: servicesMenu }); + const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", product.nameLong), role: 'hide', accelerator: 'Command+H' }); + const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' }); + const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); + const quit = new MenuItem(this.likeAction('workbench.action.quit', { + label: nls.localize('miQuit', "Quit {0}", product.nameLong), click: () => { + if (this.windowsMainService.getWindowCount() === 0 || !!BrowserWindow.getFocusedWindow()) { + this.windowsMainService.quit(); // fix for https://github.com/Microsoft/vscode/issues/39191 + } + } + })); + + const actions = [about]; + actions.push(...checkForUpdates); + actions.push(...[ + __separator__(), + preferences, + __separator__(), + services, + __separator__(), + hide, + hideOthers, + showAll, + __separator__(), + quit + ]); + + actions.forEach(i => macApplicationMenu.append(i)); + } + + private setFileMenu(fileMenu: Electron.Menu): void { + const hasNoWindows = (this.windowsMainService.getWindowCount() === 0); + + let newFile: Electron.MenuItem; + if (hasNoWindows) { + newFile = new MenuItem(this.likeAction('workbench.action.files.newUntitledFile', { label: this.mnemonicLabel(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) })); + } else { + newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile'); + } + + let open: Electron.MenuItem; + if (hasNoWindows) { + open = new MenuItem(this.likeAction('workbench.action.files.openFileFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), click: (menuItem, win, event) => this.windowsMainService.pickFileFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) })); + } else { + open = this.createMenuItem(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open..."), ['workbench.action.files.openFileFolder', 'workbench.action.files.openFileFolderInNewWindow']); + } + + let openWorkspace: Electron.MenuItem; + if (hasNoWindows) { + openWorkspace = new MenuItem(this.likeAction('workbench.action.openWorkspace', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace...")), click: (menuItem, win, event) => this.windowsMainService.pickWorkspaceAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) })); + } else { + openWorkspace = this.createMenuItem(nls.localize({ key: 'miOpenWorkspace', comment: ['&& denotes a mnemonic'] }, "Open Wor&&kspace..."), ['workbench.action.openWorkspace', 'workbench.action.openWorkspaceInNewWindow']); + } + + let openFolder: Electron.MenuItem; + if (hasNoWindows) { + openFolder = new MenuItem(this.likeAction('workbench.action.files.openFolder', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), click: (menuItem, win, event) => this.windowsMainService.pickFolderAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) })); + } else { + openFolder = this.createMenuItem(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder..."), ['workbench.action.files.openFolder', 'workbench.action.files.openFolderInNewWindow']); + } + + let openFile: Electron.MenuItem; + if (hasNoWindows) { + openFile = new MenuItem(this.likeAction('workbench.action.files.openFile', { label: this.mnemonicLabel(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File...")), click: (menuItem, win, event) => this.windowsMainService.pickFileAndOpen({ forceNewWindow: this.isOptionClick(event), telemetryExtraData: { from: telemetryFrom } }) })); + } else { + openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), ['workbench.action.files.openFile', 'workbench.action.files.openFileInNewWindow']); + } + + const openRecentMenu = new Menu(); + this.setOpenRecentMenu(openRecentMenu); + const openRecent = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 }); + + const saveWorkspaceAs = this.createMenuItem(nls.localize('miSaveWorkspaceAs', "Save Workspace As..."), 'workbench.action.saveWorkspaceAs'); + const addFolder = this.createMenuItem(nls.localize({ key: 'miAddFolderToWorkspace', comment: ['&& denotes a mnemonic'] }, "A&&dd Folder to Workspace..."), 'workbench.action.addRootFolder'); + + const saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save'); + const saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs'); + const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll'); + + const autoSaveEnabled = [AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE].some(s => this.currentAutoSaveSetting === s); + + const autoSave = this.createMenuItem(this.mnemonicLabel(nls.localize('miAutoSave', "Auto Save")), 'workbench.action.toggleAutoSave', this.windowsMainService.getWindowCount() > 0, autoSaveEnabled); + + const preferences = this.getPreferencesMenu(); + + const newWindow = new MenuItem(this.likeAction('workbench.action.newWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "New &&Window")), click: () => this.windowsMainService.openNewWindow(OpenContext.MENU) })); + const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Re&&vert File"), 'workbench.action.files.revert'); + const closeWindow = new MenuItem(this.likeAction('workbench.action.closeWindow', { label: this.mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Clos&&e Window")), click: () => this.windowsMainService.getLastActiveWindow().win.close(), enabled: this.windowsMainService.getWindowCount() > 0 })); + + this.closeWorkspace = this.createMenuItem(nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace"), 'workbench.action.closeFolder'); + this.closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder'); + + const closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "&&Close Editor"), 'workbench.action.closeActiveEditor'); + + const exit = new MenuItem(this.likeAction('workbench.action.quit', { label: this.mnemonicLabel(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit")), click: () => this.windowsMainService.quit() })); + + this.updateWorkspaceMenuItems(); + + arrays.coalesce([ + newFile, + newWindow, + __separator__(), + isMacintosh ? open : null, + !isMacintosh ? openFile : null, + !isMacintosh ? openFolder : null, + openWorkspace, + openRecent, + __separator__(), + addFolder, + saveWorkspaceAs, + __separator__(), + saveFile, + saveFileAs, + saveAllFiles, + __separator__(), + autoSave, + __separator__(), + !isMacintosh ? preferences : null, + !isMacintosh ? __separator__() : null, + revertFile, + closeEditor, + this.closeWorkspace, + this.closeFolder, + closeWindow, + !isMacintosh ? __separator__() : null, + !isMacintosh ? exit : null + ]).forEach(item => fileMenu.append(item)); + } + + private getPreferencesMenu(): Electron.MenuItem { + const settings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings"), 'workbench.action.openSettings2'); + const kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings'); + const keymapExtensions = this.createMenuItem(nls.localize({ key: 'miOpenKeymapExtensions', comment: ['&& denotes a mnemonic'] }, "&&Keymap Extensions"), 'workbench.extensions.action.showRecommendedKeymapExtensions'); + const snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets'); + const colorThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme'); + const iconThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme"), 'workbench.action.selectIconTheme'); + + const preferencesMenu = new Menu(); + preferencesMenu.append(settings); + preferencesMenu.append(__separator__()); + preferencesMenu.append(kebindingSettings); + preferencesMenu.append(keymapExtensions); + preferencesMenu.append(__separator__()); + preferencesMenu.append(snippetsSettings); + preferencesMenu.append(__separator__()); + preferencesMenu.append(colorThemeSelection); + preferencesMenu.append(iconThemeSelection); + + return new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miPreferences', comment: ['&& denotes a mnemonic'] }, "&&Preferences")), submenu: preferencesMenu }); + } + + private setOpenRecentMenu(openRecentMenu: Electron.Menu): void { + openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor')); + + const { workspaces, files } = this.historyMainService.getRecentlyOpened(); + + // Workspaces + if (workspaces.length > 0) { + openRecentMenu.append(__separator__()); + + for (let i = 0; i < CodeMenu.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { + openRecentMenu.append(this.createOpenRecentMenuItem(workspaces[i], 'openRecentWorkspace', false)); + } + } + + // Files + if (files.length > 0) { + openRecentMenu.append(__separator__()); + + for (let i = 0; i < CodeMenu.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { + openRecentMenu.append(this.createOpenRecentMenuItem(files[i], 'openRecentFile', true)); + } + } + + if (workspaces.length || files.length) { + openRecentMenu.append(__separator__()); + openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miMore', comment: ['&& denotes a mnemonic'] }, "&&More..."), 'workbench.action.openRecent')); + openRecentMenu.append(__separator__()); + openRecentMenu.append(new MenuItem(this.likeAction('workbench.action.clearRecentFiles', { label: this.mnemonicLabel(nls.localize({ key: 'miClearRecentOpen', comment: ['&& denotes a mnemonic'] }, "&&Clear Recently Opened")), click: () => this.historyMainService.clearRecentlyOpened() }))); + } + } + + private createOpenRecentMenuItem(workspace: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | string, commandId: string, isFile: boolean): Electron.MenuItem { + let label: string; + let uri: URI; + if (isSingleFolderWorkspaceIdentifier(workspace)) { + label = unmnemonicLabel(getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true })); + uri = workspace; + } else if (isWorkspaceIdentifier(workspace)) { + label = getWorkspaceLabel(workspace, this.environmentService, this.uriDisplayService, { verbose: true }); + uri = URI.file(workspace.configPath); + } else { + uri = URI.file(workspace); + label = unmnemonicLabel(this.uriDisplayService.getLabel(uri)); + } + + return new MenuItem(this.likeAction(commandId, { + label, + click: (menuItem, win, event) => { + const openInNewWindow = this.isOptionClick(event); + const success = this.windowsMainService.open({ + context: OpenContext.MENU, + cli: this.environmentService.args, + urisToOpen: [uri], + forceNewWindow: openInNewWindow, + forceOpenWorkspaceAsFile: isFile + }).length > 0; + + if (!success) { + this.historyMainService.removeFromRecentlyOpened([workspace]); + } + } + }, false)); + } + + private isOptionClick(event: Electron.Event): boolean { + return event && ((!isMacintosh && (event.ctrlKey || event.shiftKey)) || (isMacintosh && (event.metaKey || event.altKey))); + } + + private createRoleMenuItem(label: string, commandId: string, role: Electron.MenuItemRole): Electron.MenuItem { + const options: Electron.MenuItemConstructorOptions = { + label: this.mnemonicLabel(label), + role, + enabled: true + }; + + return new MenuItem(this.withKeybinding(commandId, options)); + } + + private setEditMenu(winLinuxEditMenu: Electron.Menu): void { + let undo: Electron.MenuItem; + let redo: Electron.MenuItem; + let cut: Electron.MenuItem; + let copy: Electron.MenuItem; + let paste: Electron.MenuItem; + + if (isMacintosh) { + undo = this.createContextAwareMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo', { + inDevTools: devTools => devTools.undo(), + inNoWindow: () => Menu.sendActionToFirstResponder('undo:') + }); + redo = this.createContextAwareMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo', { + inDevTools: devTools => devTools.redo(), + inNoWindow: () => Menu.sendActionToFirstResponder('redo:') + }); + cut = this.createRoleMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), 'editor.action.clipboardCutAction', 'cut'); + copy = this.createRoleMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), 'editor.action.clipboardCopyAction', 'copy'); + paste = this.createRoleMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction', 'paste'); + } else { + undo = this.createMenuItem(nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"), 'undo'); + redo = this.createMenuItem(nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"), 'redo'); + cut = this.createMenuItem(nls.localize({ key: 'miCut', comment: ['&& denotes a mnemonic'] }, "Cu&&t"), 'editor.action.clipboardCutAction'); + copy = this.createMenuItem(nls.localize({ key: 'miCopy', comment: ['&& denotes a mnemonic'] }, "&&Copy"), 'editor.action.clipboardCopyAction'); + paste = this.createMenuItem(nls.localize({ key: 'miPaste', comment: ['&& denotes a mnemonic'] }, "&&Paste"), 'editor.action.clipboardPasteAction'); + } + + const find = this.createMenuItem(nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), 'actions.find'); + const replace = this.createMenuItem(nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), 'editor.action.startFindReplaceAction'); + const findInFiles = this.createMenuItem(nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), 'workbench.action.findInFiles'); + const replaceInFiles = this.createMenuItem(nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files"), 'workbench.action.replaceInFiles'); + + const emmetExpandAbbreviation = this.createMenuItem(nls.localize({ key: 'miEmmetExpandAbbreviation', comment: ['&& denotes a mnemonic'] }, "Emmet: E&&xpand Abbreviation"), 'editor.emmet.action.expandAbbreviation'); + const showEmmetCommands = this.createMenuItem(nls.localize({ key: 'miShowEmmetCommands', comment: ['&& denotes a mnemonic'] }, "E&&mmet..."), 'workbench.action.showEmmetCommands'); + const toggleLineComment = this.createMenuItem(nls.localize({ key: 'miToggleLineComment', comment: ['&& denotes a mnemonic'] }, "&&Toggle Line Comment"), 'editor.action.commentLine'); + const toggleBlockComment = this.createMenuItem(nls.localize({ key: 'miToggleBlockComment', comment: ['&& denotes a mnemonic'] }, "Toggle &&Block Comment"), 'editor.action.blockComment'); + + [ + undo, + redo, + __separator__(), + cut, + copy, + paste, + __separator__(), + find, + replace, + __separator__(), + findInFiles, + replaceInFiles, + __separator__(), + toggleLineComment, + toggleBlockComment, + emmetExpandAbbreviation, + showEmmetCommands + ].forEach(item => winLinuxEditMenu.append(item)); + } + + private setSelectionMenu(winLinuxEditMenu: Electron.Menu): void { + let multiCursorModifierLabel: string; + if (this.currentMultiCursorModifierSetting === 'ctrlCmd') { + multiCursorModifierLabel = nls.localize('miMultiCursorAlt', "Switch to Alt+Click for Multi-Cursor"); // The default has been overwritten + } else { + multiCursorModifierLabel = ( + isMacintosh + ? nls.localize('miMultiCursorCmd', "Switch to Cmd+Click for Multi-Cursor") + : nls.localize('miMultiCursorCtrl', "Switch to Ctrl+Click for Multi-Cursor") + ); + } + + const multicursorModifier = this.createMenuItem(multiCursorModifierLabel, 'workbench.action.toggleMultiCursorModifier'); + const insertCursorAbove = this.createMenuItem(nls.localize({ key: 'miInsertCursorAbove', comment: ['&& denotes a mnemonic'] }, "&&Add Cursor Above"), 'editor.action.insertCursorAbove'); + const insertCursorBelow = this.createMenuItem(nls.localize({ key: 'miInsertCursorBelow', comment: ['&& denotes a mnemonic'] }, "A&&dd Cursor Below"), 'editor.action.insertCursorBelow'); + const insertCursorAtEndOfEachLineSelected = this.createMenuItem(nls.localize({ key: 'miInsertCursorAtEndOfEachLineSelected', comment: ['&& denotes a mnemonic'] }, "Add C&&ursors to Line Ends"), 'editor.action.insertCursorAtEndOfEachLineSelected'); + const addSelectionToNextFindMatch = this.createMenuItem(nls.localize({ key: 'miAddSelectionToNextFindMatch', comment: ['&& denotes a mnemonic'] }, "Add &&Next Occurrence"), 'editor.action.addSelectionToNextFindMatch'); + const addSelectionToPreviousFindMatch = this.createMenuItem(nls.localize({ key: 'miAddSelectionToPreviousFindMatch', comment: ['&& denotes a mnemonic'] }, "Add P&&revious Occurrence"), 'editor.action.addSelectionToPreviousFindMatch'); + const selectHighlights = this.createMenuItem(nls.localize({ key: 'miSelectHighlights', comment: ['&& denotes a mnemonic'] }, "Select All &&Occurrences"), 'editor.action.selectHighlights'); + + const copyLinesUp = this.createMenuItem(nls.localize({ key: 'miCopyLinesUp', comment: ['&& denotes a mnemonic'] }, "&&Copy Line Up"), 'editor.action.copyLinesUpAction'); + const copyLinesDown = this.createMenuItem(nls.localize({ key: 'miCopyLinesDown', comment: ['&& denotes a mnemonic'] }, "Co&&py Line Down"), 'editor.action.copyLinesDownAction'); + const moveLinesUp = this.createMenuItem(nls.localize({ key: 'miMoveLinesUp', comment: ['&& denotes a mnemonic'] }, "Mo&&ve Line Up"), 'editor.action.moveLinesUpAction'); + const moveLinesDown = this.createMenuItem(nls.localize({ key: 'miMoveLinesDown', comment: ['&& denotes a mnemonic'] }, "Move &&Line Down"), 'editor.action.moveLinesDownAction'); + + let selectAll: Electron.MenuItem; + if (isMacintosh) { + selectAll = this.createContextAwareMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll', { + inDevTools: devTools => devTools.selectAll(), + inNoWindow: () => Menu.sendActionToFirstResponder('selectAll:') + }); + } else { + selectAll = this.createMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll'); + } + const smartSelectGrow = this.createMenuItem(nls.localize({ key: 'miSmartSelectGrow', comment: ['&& denotes a mnemonic'] }, "&&Expand Selection"), 'editor.action.smartSelect.grow'); + const smartSelectshrink = this.createMenuItem(nls.localize({ key: 'miSmartSelectShrink', comment: ['&& denotes a mnemonic'] }, "&&Shrink Selection"), 'editor.action.smartSelect.shrink'); + + [ + selectAll, + smartSelectGrow, + smartSelectshrink, + __separator__(), + copyLinesUp, + copyLinesDown, + moveLinesUp, + moveLinesDown, + __separator__(), + multicursorModifier, + insertCursorAbove, + insertCursorBelow, + insertCursorAtEndOfEachLineSelected, + addSelectionToNextFindMatch, + addSelectionToPreviousFindMatch, + selectHighlights, + ].forEach(item => winLinuxEditMenu.append(item)); + } + + private setViewMenu(viewMenu: Electron.Menu): void { + const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands'); + const openView = this.createMenuItem(nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View..."), 'workbench.action.openView'); + + // Views + const explorer = this.createMenuItem(nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), 'workbench.view.explorer'); + const search = this.createMenuItem(nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), 'workbench.view.search'); + const scm = this.createMenuItem(nls.localize({ key: 'miViewSCM', comment: ['&& denotes a mnemonic'] }, "S&&CM"), 'workbench.view.scm'); + const debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug'); + const extensions = this.createMenuItem(nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), 'workbench.view.extensions'); + + // Panels + const output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput'); + const debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl'); + const terminal = this.createMenuItem(nls.localize({ key: 'miToggleTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal"), 'workbench.action.terminal.toggleTerminal'); + const problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems'); + + // Appearance + + const appearanceMenu = new Menu(); + + const fullscreen = new MenuItem(this.withKeybinding('workbench.action.toggleFullScreen', { label: this.mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), click: () => this.windowsMainService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsMainService.getWindowCount() > 0 })); + const toggleZenMode = this.createMenuItem(nls.localize('miToggleZenMode', "Toggle Zen Mode"), 'workbench.action.toggleZenMode'); + const toggleCenteredLayout = this.createMenuItem(nls.localize('miToggleCenteredLayout', "Toggle Centered Layout"), 'workbench.action.toggleCenteredLayout'); + const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar'); + + const toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility'); + + let moveSideBarLabel: string; + if (this.currentSidebarLocation !== 'right') { + moveSideBarLabel = nls.localize({ key: 'miMoveSidebarRight', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Right"); + } else { + moveSideBarLabel = nls.localize({ key: 'miMoveSidebarLeft', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar Left"); + } + + const moveSidebar = this.createMenuItem(moveSideBarLabel, 'workbench.action.toggleSidebarPosition'); + const togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel'); + + let statusBarLabel: string; + if (this.currentStatusbarVisible) { + statusBarLabel = nls.localize({ key: 'miHideStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Hide Status Bar"); + } else { + statusBarLabel = nls.localize({ key: 'miShowStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Show Status Bar"); + } + const toggleStatusbar = this.createMenuItem(statusBarLabel, 'workbench.action.toggleStatusbarVisibility'); + + let activityBarLabel: string; + if (this.currentActivityBarVisible) { + activityBarLabel = nls.localize({ key: 'miHideActivityBar', comment: ['&& denotes a mnemonic'] }, "Hide &&Activity Bar"); + } else { + activityBarLabel = nls.localize({ key: 'miShowActivityBar', comment: ['&& denotes a mnemonic'] }, "Show &&Activity Bar"); + } + const toggleActivtyBar = this.createMenuItem(activityBarLabel, 'workbench.action.toggleActivityBarVisibility'); + + const zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn'); + const zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut'); + const resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset'); + + arrays.coalesce([ + fullscreen, + toggleZenMode, + toggleCenteredLayout, + isWindows || isLinux ? toggleMenuBar : void 0, + __separator__(), + moveSidebar, + toggleSidebar, + togglePanel, + toggleStatusbar, + toggleActivtyBar, + __separator__(), + zoomIn, + zoomOut, + resetZoom + ]).forEach(item => appearanceMenu.append(item)); + + const appearance = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAppearance', comment: ['&& denotes a mnemonic'] }, "&&Appearance")), submenu: appearanceMenu }); + + // Editor Layout + + const editorLayoutMenu = new Menu(); + + const splitEditorUp = this.createMenuItem(nls.localize({ key: 'miSplitEditorUp', comment: ['&& denotes a mnemonic'] }, "Split &&Up"), 'workbench.action.splitEditorUp'); + const splitEditorDown = this.createMenuItem(nls.localize({ key: 'miSplitEditorDown', comment: ['&& denotes a mnemonic'] }, "Split &&Down"), 'workbench.action.splitEditorDown'); + const splitEditorLeft = this.createMenuItem(nls.localize({ key: 'miSplitEditorLeft', comment: ['&& denotes a mnemonic'] }, "Split &&Left"), 'workbench.action.splitEditorLeft'); + const splitEditorRight = this.createMenuItem(nls.localize({ key: 'miSplitEditorRight', comment: ['&& denotes a mnemonic'] }, "Split &&Right"), 'workbench.action.splitEditorRight'); + + const singleColumnEditorLayout = this.createMenuItem(nls.localize({ key: 'miSingleColumnEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Single"), 'workbench.action.editorLayoutSingle'); + const twoColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Two Columns"), 'workbench.action.editorLayoutTwoColumns'); + const threeColumnsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeColumnsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&hree Columns"), 'workbench.action.editorLayoutThreeColumns'); + const twoRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "T&&wo Rows"), 'workbench.action.editorLayoutTwoRows'); + const threeRowsEditorLayout = this.createMenuItem(nls.localize({ key: 'miThreeRowsEditorLayout', comment: ['&& denotes a mnemonic'] }, "Three &&Rows"), 'workbench.action.editorLayoutThreeRows'); + const twoByTwoGridEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoByTwoGridEditorLayout', comment: ['&& denotes a mnemonic'] }, "&&Grid (2x2)"), 'workbench.action.editorLayoutTwoByTwoGrid'); + const twoRowsRightEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoRowsRightEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two R&&ows Right"), 'workbench.action.editorLayoutTwoRowsRight'); + const twoColumnsBottomEditorLayout = this.createMenuItem(nls.localize({ key: 'miTwoColumnsBottomEditorLayout', comment: ['&& denotes a mnemonic'] }, "Two &&Columns Bottom"), 'workbench.action.editorLayoutTwoColumnsBottom'); + + const toggleEditorLayout = this.createMenuItem(nls.localize({ key: 'miToggleEditorLayout', comment: ['&& denotes a mnemonic'] }, "Flip &&Layout"), 'workbench.action.toggleEditorGroupLayout'); + + [ + splitEditorUp, + splitEditorDown, + splitEditorLeft, + splitEditorRight, + __separator__(), + singleColumnEditorLayout, + twoColumnsEditorLayout, + threeColumnsEditorLayout, + twoRowsEditorLayout, + threeRowsEditorLayout, + twoByTwoGridEditorLayout, + twoRowsRightEditorLayout, + twoColumnsBottomEditorLayout, + __separator__(), + toggleEditorLayout + ].forEach(item => editorLayoutMenu.append(item)); + + const editorLayout = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miEditorLayout', comment: ['&& denotes a mnemonic'] }, "Editor &&Layout")), submenu: editorLayoutMenu }); + + const toggleWordWrap = this.createMenuItem(nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"), 'editor.action.toggleWordWrap'); + const toggleMinimap = this.createMenuItem(nls.localize({ key: 'miToggleMinimap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Minimap"), 'editor.action.toggleMinimap'); + const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace'); + const toggleRenderControlCharacters = this.createMenuItem(nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), 'editor.action.toggleRenderControlCharacter'); + const toggleBreadcrumbs = this.createMenuItem(nls.localize({ key: 'miToggleBreadcrumbs', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breadcrumbs"), 'breadcrumbs.toggle'); + + arrays.coalesce([ + commands, + openView, + __separator__(), + appearance, + editorLayout, + __separator__(), + explorer, + search, + scm, + debug, + extensions, + __separator__(), + output, + problems, + debugConsole, + terminal, + __separator__(), + toggleWordWrap, + toggleMinimap, + toggleRenderWhitespace, + toggleRenderControlCharacters, + toggleBreadcrumbs + ]).forEach(item => viewMenu.append(item)); + } + + private setGotoMenu(gotoMenu: Electron.Menu): void { + const back = this.createMenuItem(nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), 'workbench.action.navigateBack'); + const forward = this.createMenuItem(nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), 'workbench.action.navigateForward'); + + const switchEditorMenu = new Menu(); + + const nextEditor = this.createMenuItem(nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor"), 'workbench.action.nextEditor'); + const previousEditor = this.createMenuItem(nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor"), 'workbench.action.previousEditor'); + const nextEditorInGroup = this.createMenuItem(nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group"), 'workbench.action.openNextRecentlyUsedEditorInGroup'); + const previousEditorInGroup = this.createMenuItem(nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group"), 'workbench.action.openPreviousRecentlyUsedEditorInGroup'); + + [ + nextEditor, + previousEditor, + __separator__(), + nextEditorInGroup, + previousEditorInGroup + ].forEach(item => switchEditorMenu.append(item)); + + const switchEditor = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor")), submenu: switchEditorMenu, enabled: true }); + + const switchGroupMenu = new Menu(); + + const focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1"), 'workbench.action.focusFirstEditorGroup'); + const focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2"), 'workbench.action.focusSecondEditorGroup'); + const focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3"), 'workbench.action.focusThirdEditorGroup'); + const focusFourthGroup = this.createMenuItem(nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4"), 'workbench.action.focusFourthEditorGroup'); + const focusFifthGroup = this.createMenuItem(nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5"), 'workbench.action.focusFifthEditorGroup'); + const nextGroup = this.createMenuItem(nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), 'workbench.action.focusNextGroup'); + const previousGroup = this.createMenuItem(nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), 'workbench.action.focusPreviousGroup'); + + const focusLeftGroup = this.createMenuItem(nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left"), 'workbench.action.focusLeftGroup'); + const focusRightGroup = this.createMenuItem(nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right"), 'workbench.action.focusRightGroup'); + const focusAboveGroup = this.createMenuItem(nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above"), 'workbench.action.focusAboveGroup'); + const focusBelowGroup = this.createMenuItem(nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below"), 'workbench.action.focusBelowGroup'); + + [ + focusFirstGroup, + focusSecondGroup, + focusThirdGroup, + focusFourthGroup, + focusFifthGroup, + __separator__(), + nextGroup, + previousGroup, + __separator__(), + focusAboveGroup, + focusBelowGroup, + focusLeftGroup, + focusRightGroup + ].forEach(item => switchGroupMenu.append(item)); + + const switchGroup = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group")), submenu: switchGroupMenu, enabled: true }); + + const gotoFile = this.createMenuItem(nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File..."), 'workbench.action.quickOpen'); + const gotoSymbolInFile = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File..."), 'workbench.action.gotoSymbol'); + const gotoSymbolInWorkspace = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace..."), 'workbench.action.showAllSymbols'); + const gotoDefinition = this.createMenuItem(nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), 'editor.action.goToDeclaration'); + const gotoTypeDefinition = this.createMenuItem(nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition"), 'editor.action.goToTypeDefinition'); + const goToImplementation = this.createMenuItem(nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation"), 'editor.action.goToImplementation'); + const gotoLine = this.createMenuItem(nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line..."), 'workbench.action.gotoLine'); + + [ + back, + forward, + __separator__(), + switchEditor, + switchGroup, + __separator__(), + gotoFile, + gotoSymbolInFile, + gotoSymbolInWorkspace, + gotoDefinition, + gotoTypeDefinition, + goToImplementation, + gotoLine + ].forEach(item => gotoMenu.append(item)); + } + + private setTerminalMenu(terminalMenu: Electron.Menu): void { + const newTerminal = this.createMenuItem(nls.localize({ key: 'miNewTerminal', comment: ['&& denotes a mnemonic'] }, "&&New Terminal"), 'workbench.action.terminal.new'); + const splitTerminal = this.createMenuItem(nls.localize({ key: 'miSplitTerminal', comment: ['&& denotes a mnemonic'] }, "&&Split Terminal"), 'workbench.action.terminal.split'); + const killTerminal = this.createMenuItem(nls.localize({ key: 'miKillTerminal', comment: ['&& denotes a mnemonic'] }, "&&Kill Terminal"), 'workbench.action.terminal.kill'); + const clear = this.createMenuItem(nls.localize({ key: 'miClear', comment: ['&& denotes a mnemonic'] }, "&&Clear"), 'workbench.action.terminal.clear'); + const runActiveFile = this.createMenuItem(nls.localize({ key: 'miRunActiveFile', comment: ['&& denotes a mnemonic'] }, "Run &&Active File"), 'workbench.action.terminal.runActiveFile'); + const runSelectedText = this.createMenuItem(nls.localize({ key: 'miRunSelectedText', comment: ['&& denotes a mnemonic'] }, "Run &&Selected Text"), 'workbench.action.terminal.runSelectedText'); + const scrollToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miScrollToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Previous Command"), 'workbench.action.terminal.scrollToPreviousCommand'); + const scrollToNextCommand = this.createMenuItem(nls.localize({ key: 'miScrollToNextCommand', comment: ['&& denotes a mnemonic'] }, "Scroll To Next Command"), 'workbench.action.terminal.scrollToNextCommand'); + const selectToPreviousCommand = this.createMenuItem(nls.localize({ key: 'miSelectToPreviousCommand', comment: ['&& denotes a mnemonic'] }, "Select To Previous Command"), 'workbench.action.terminal.selectToPreviousCommand'); + const selectToNextCommand = this.createMenuItem(nls.localize({ key: 'miSelectToNextCommand', comment: ['&& denotes a mnemonic'] }, "Select To Next Command"), 'workbench.action.terminal.selectToNextCommand'); + + const menuItems: MenuItem[] = [ + newTerminal, + splitTerminal, + killTerminal, + __separator__(), + clear, + runActiveFile, + runSelectedText, + __separator__(), + scrollToPreviousCommand, + scrollToNextCommand, + selectToPreviousCommand, + selectToNextCommand + ]; + + menuItems.forEach(item => terminalMenu.append(item)); + } + + private setDebugMenu(debugMenu: Electron.Menu): void { + const start = this.createMenuItem(nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging"), 'workbench.action.debug.start'); + const startWithoutDebugging = this.createMenuItem(nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging"), 'workbench.action.debug.run'); + const stop = this.createMenuItem(nls.localize({ key: 'miStopDebugging', comment: ['&& denotes a mnemonic'] }, "&&Stop Debugging"), 'workbench.action.debug.stop'); + const restart = this.createMenuItem(nls.localize({ key: 'miRestart Debugging', comment: ['&& denotes a mnemonic'] }, "&&Restart Debugging"), 'workbench.action.debug.restart'); + + const openConfigurations = this.createMenuItem(nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations"), 'workbench.action.debug.configure'); + const addConfiguration = this.createMenuItem(nls.localize({ key: 'miAddConfiguration', comment: ['&& denotes a mnemonic'] }, "Add Configuration..."), 'debug.addConfiguration'); + + const stepOver = this.createMenuItem(nls.localize({ key: 'miStepOver', comment: ['&& denotes a mnemonic'] }, "Step &&Over"), 'workbench.action.debug.stepOver'); + const stepInto = this.createMenuItem(nls.localize({ key: 'miStepInto', comment: ['&& denotes a mnemonic'] }, "Step &&Into"), 'workbench.action.debug.stepInto'); + const stepOut = this.createMenuItem(nls.localize({ key: 'miStepOut', comment: ['&& denotes a mnemonic'] }, "Step O&&ut"), 'workbench.action.debug.stepOut'); + const continueAction = this.createMenuItem(nls.localize({ key: 'miContinue', comment: ['&& denotes a mnemonic'] }, "&&Continue"), 'workbench.action.debug.continue'); + + const toggleBreakpoint = this.createMenuItem(nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint"), 'editor.debug.action.toggleBreakpoint'); + const breakpointsMenu = new Menu(); + breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Conditional Breakpoint..."), 'editor.debug.action.conditionalBreakpoint')); + breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miInlineBreakpoint', comment: ['&& denotes a mnemonic'] }, "Inline Breakp&&oint"), 'editor.debug.action.toggleInlineBreakpoint')); + breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint..."), 'workbench.debug.viewlet.action.addFunctionBreakpointAction')); + breakpointsMenu.append(this.createMenuItem(nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "&&Logpoint..."), 'editor.debug.action.toggleLogPoint')); + const newBreakpoints = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miNewBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&New Breakpoint")), submenu: breakpointsMenu }); + const enableAllBreakpoints = this.createMenuItem(nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Enable All Breakpoints"), 'workbench.debug.viewlet.action.enableAllBreakpoints'); + const disableAllBreakpoints = this.createMenuItem(nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints"), 'workbench.debug.viewlet.action.disableAllBreakpoints'); + const removeAllBreakpoints = this.createMenuItem(nls.localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints"), 'workbench.debug.viewlet.action.removeAllBreakpoints'); + + const installAdditionalDebuggers = this.createMenuItem(nls.localize({ key: 'miInstallAdditionalDebuggers', comment: ['&& denotes a mnemonic'] }, "&&Install Additional Debuggers..."), 'debug.installAdditionalDebuggers'); + [ + start, + startWithoutDebugging, + stop, + restart, + __separator__(), + openConfigurations, + addConfiguration, + __separator__(), + stepOver, + stepInto, + stepOut, + continueAction, + __separator__(), + toggleBreakpoint, + newBreakpoints, + enableAllBreakpoints, + disableAllBreakpoints, + removeAllBreakpoints, + __separator__(), + installAdditionalDebuggers + ].forEach(item => debugMenu.append(item)); + } + + private setMacWindowMenu(macWindowMenu: Electron.Menu): void { + const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsMainService.getWindowCount() > 0 }); + const zoom = new MenuItem({ label: nls.localize('mZoom', "Zoom"), role: 'zoom', enabled: this.windowsMainService.getWindowCount() > 0 }); + const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsMainService.getWindowCount() > 0 }); + const switchWindow = this.createMenuItem(nls.localize({ key: 'miSwitchWindow', comment: ['&& denotes a mnemonic'] }, "Switch &&Window..."), 'workbench.action.switchWindow'); + + this.nativeTabMenuItems = []; + const nativeTabMenuItems: Electron.MenuItem[] = []; + if (this.currentEnableNativeTabs) { + const hasMultipleWindows = this.windowsMainService.getWindowCount() > 1; + + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mNewTab', "New Tab"), 'workbench.action.newWindowTab')); + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowPreviousTab', "Show Previous Tab"), 'workbench.action.showPreviousWindowTab', hasMultipleWindows)); + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mShowNextTab', "Show Next Tab"), 'workbench.action.showNextWindowTab', hasMultipleWindows)); + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMoveTabToNewWindow', "Move Tab to New Window"), 'workbench.action.moveWindowTabToNewWindow', hasMultipleWindows)); + this.nativeTabMenuItems.push(this.createMenuItem(nls.localize('mMergeAllWindows', "Merge All Windows"), 'workbench.action.mergeAllWindowTabs', hasMultipleWindows)); + + nativeTabMenuItems.push(__separator__(), ...this.nativeTabMenuItems); + } else { + this.nativeTabMenuItems = []; + } + + [ + minimize, + zoom, + switchWindow, + ...nativeTabMenuItems, + __separator__(), + bringAllToFront + ].forEach(item => macWindowMenu.append(item)); + } + + private toggleDevTools(): void { + const w = this.windowsMainService.getFocusedWindow(); + if (w && w.win) { + const contents = w.win.webContents; + if (isMacintosh && w.hasHiddenTitleBarStyle() && !w.win.isFullScreen() && !contents.isDevToolsOpened()) { + contents.openDevTools({ mode: 'undocked' }); // due to https://github.com/electron/electron/issues/3647 + } else { + contents.toggleDevTools(); + } + } + } + + private setHelpMenu(helpMenu: Electron.Menu): void { + const toggleDevToolsItem = new MenuItem(this.likeAction('workbench.action.toggleDevTools', { + label: this.mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")), + click: () => this.toggleDevTools(), + enabled: (this.windowsMainService.getWindowCount() > 0) + })); + + const showAccessibilityOptions = new MenuItem(this.likeAction('accessibilityOptions', { + label: this.mnemonicLabel(nls.localize({ key: 'miAccessibilityOptions', comment: ['&& denotes a mnemonic'] }, "Accessibility &&Options")), + accelerator: null, + click: () => { + this.openAccessibilityOptions(); + } + }, false)); + + const openProcessExplorer = new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer")), click: () => this.runActionInRenderer('workbench.action.openProcessExplorer') }); + + let reportIssuesItem: Electron.MenuItem = null; + if (product.reportIssueUrl) { + const label = nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue"); + + if (this.windowsMainService.getWindowCount() > 0) { + reportIssuesItem = this.createMenuItem(label, 'workbench.action.openIssueReporter'); + } else { + reportIssuesItem = new MenuItem({ label: this.mnemonicLabel(label), click: () => this.openUrl(product.reportIssueUrl, 'openReportIssues') }); + } + } + + const keyboardShortcutsUrl = isLinux ? product.keyboardShortcutsUrlLinux : isMacintosh ? product.keyboardShortcutsUrlMac : product.keyboardShortcutsUrlWin; + arrays.coalesce([ + new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome")), click: () => this.runActionInRenderer('workbench.action.showWelcomePage'), enabled: (this.windowsMainService.getWindowCount() > 0) }), + new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "&&Interactive Playground")), click: () => this.runActionInRenderer('workbench.action.showInteractivePlayground'), enabled: (this.windowsMainService.getWindowCount() > 0) }), + product.documentationUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation")), click: () => this.runActionInRenderer('workbench.action.openDocumentationUrl'), enabled: (this.windowsMainService.getWindowCount() > 0) }) : null, + product.releaseNotesUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes")), click: () => this.runActionInRenderer('update.showCurrentReleaseNotes'), enabled: (this.windowsMainService.getWindowCount() > 0) }) : null, + __separator__(), + keyboardShortcutsUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference")), click: () => this.runActionInRenderer('workbench.action.keybindingsReference'), enabled: (this.windowsMainService.getWindowCount() > 0) }) : null, + product.introductoryVideosUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos")), click: () => this.runActionInRenderer('workbench.action.openIntroductoryVideosUrl'), enabled: (this.windowsMainService.getWindowCount() > 0) }) : null, + product.tipsAndTricksUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "&&Tips and Tricks")), click: () => this.runActionInRenderer('workbench.action.openTipsAndTricksUrl'), enabled: (this.windowsMainService.getWindowCount() > 0) }) : null, + (product.introductoryVideosUrl || keyboardShortcutsUrl) ? __separator__() : null, + product.twitterUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter")), click: () => this.openUrl(product.twitterUrl, 'openTwitterUrl') }) : null, + product.requestFeatureUrl ? new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests")), click: () => this.openUrl(product.requestFeatureUrl, 'openUserVoiceUrl') }) : null, + reportIssuesItem, + (product.twitterUrl || product.requestFeatureUrl || product.reportIssueUrl) ? __separator__() : null, + product.licenseUrl ? new MenuItem({ + label: this.mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License")), click: () => { + if (language) { + const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + this.openUrl(`${product.licenseUrl}${queryArgChar}lang=${language}`, 'openLicenseUrl'); + } else { + this.openUrl(product.licenseUrl, 'openLicenseUrl'); + } + } + }) : null, + product.privacyStatementUrl ? new MenuItem({ + label: this.mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => { + if (language) { + const queryArgChar = product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + this.openUrl(`${product.privacyStatementUrl}${queryArgChar}lang=${language}`, 'openPrivacyStatement'); + } else { + this.openUrl(product.privacyStatementUrl, 'openPrivacyStatement'); + } + } + }) : null, + (product.licenseUrl || product.privacyStatementUrl) ? __separator__() : null, + toggleDevToolsItem, + openProcessExplorer, + isWindows && product.quality !== 'stable' ? showAccessibilityOptions : null, + ]).forEach(item => helpMenu.append(item)); + + if (!isMacintosh) { + const updateMenuItems = this.getUpdateMenuItems(); + if (updateMenuItems.length) { + helpMenu.append(__separator__()); + updateMenuItems.forEach(i => helpMenu.append(i)); + } + + helpMenu.append(__separator__()); + helpMenu.append(new MenuItem({ label: this.mnemonicLabel(nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About")), click: () => this.windowsService.openAboutDialog() })); + } + } + + private setTaskMenu(taskMenu: Electron.Menu): void { + const runTask = this.createMenuItem(nls.localize({ key: 'miRunTask', comment: ['&& denotes a mnemonic'] }, "&&Run Task..."), 'workbench.action.tasks.runTask'); + const buildTask = this.createMenuItem(nls.localize({ key: 'miBuildTask', comment: ['&& denotes a mnemonic'] }, "Run &&Build Task..."), 'workbench.action.tasks.build'); + const showTasks = this.createMenuItem(nls.localize({ key: 'miRunningTask', comment: ['&& denotes a mnemonic'] }, "Show Runnin&&g Tasks..."), 'workbench.action.tasks.showTasks'); + const restartTask = this.createMenuItem(nls.localize({ key: 'miRestartTask', comment: ['&& denotes a mnemonic'] }, "R&&estart Running Task..."), 'workbench.action.tasks.restartTask'); + const terminateTask = this.createMenuItem(nls.localize({ key: 'miTerminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task..."), 'workbench.action.tasks.terminate'); + const configureTask = this.createMenuItem(nls.localize({ key: 'miConfigureTask', comment: ['&& denotes a mnemonic'] }, "&&Configure Tasks..."), 'workbench.action.tasks.configureTaskRunner'); + const configureBuildTask = this.createMenuItem(nls.localize({ key: 'miConfigureBuildTask', comment: ['&& denotes a mnemonic'] }, "Configure De&&fault Build Task..."), 'workbench.action.tasks.configureDefaultBuildTask'); + + [ + //__separator__(), + runTask, + buildTask, + __separator__(), + terminateTask, + restartTask, + showTasks, + __separator__(), + configureTask, + configureBuildTask + ].forEach(item => taskMenu.append(item)); + } + + private openAccessibilityOptions(): void { + const win = new BrowserWindow({ + alwaysOnTop: true, + skipTaskbar: true, + resizable: false, + width: 450, + height: 300, + show: true, + title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options"), + webPreferences: { + disableBlinkFeatures: 'Auxclick' + } + }); + + win.setMenuBarVisibility(false); + + win.loadURL('chrome://accessibility'); + } + + private getUpdateMenuItems(): Electron.MenuItem[] { + const state = this.updateService.state; + + switch (state.type) { + case StateType.Uninitialized: + return []; + + case StateType.Idle: + return [new MenuItem({ + label: nls.localize('miCheckForUpdates', "Check for Updates..."), click: () => setTimeout(() => { + this.reportMenuActionTelemetry('CheckForUpdate'); + + const focusedWindow = this.windowsMainService.getFocusedWindow(); + const context = focusedWindow ? { windowId: focusedWindow.id } : null; + this.updateService.checkForUpdates(context); + }, 0) + })]; + + case StateType.CheckingForUpdates: + return [new MenuItem({ label: nls.localize('miCheckingForUpdates', "Checking For Updates..."), enabled: false })]; + + case StateType.AvailableForDownload: + return [new MenuItem({ + label: nls.localize('miDownloadUpdate', "Download Available Update"), click: () => { + this.updateService.downloadUpdate(); + } + })]; + + case StateType.Downloading: + return [new MenuItem({ label: nls.localize('miDownloadingUpdate', "Downloading Update..."), enabled: false })]; + + case StateType.Downloaded: + return [new MenuItem({ + label: nls.localize('miInstallUpdate', "Install Update..."), click: () => { + this.reportMenuActionTelemetry('InstallUpdate'); + this.updateService.applyUpdate(); + } + })]; + + case StateType.Updating: + return [new MenuItem({ label: nls.localize('miInstallingUpdate', "Installing Update..."), enabled: false })]; + + case StateType.Ready: + return [new MenuItem({ + label: nls.localize('miRestartToUpdate', "Restart to Update..."), click: () => { + this.reportMenuActionTelemetry('RestartToUpdate'); + this.updateService.quitAndInstall(); + } + })]; + } + } + + private createMenuItem(label: string, commandId: string | string[], enabled?: boolean, checked?: boolean): Electron.MenuItem; + private createMenuItem(label: string, click: () => void, enabled?: boolean, checked?: boolean): Electron.MenuItem; + private createMenuItem(arg1: string, arg2: any, arg3?: boolean, arg4?: boolean): Electron.MenuItem { + const label = this.mnemonicLabel(arg1); + const click: () => void = (typeof arg2 === 'function') ? arg2 : (menuItem: Electron.MenuItem, win: Electron.BrowserWindow, event: Electron.Event) => { + let commandId = arg2; + if (Array.isArray(arg2)) { + commandId = this.isOptionClick(event) ? arg2[1] : arg2[0]; // support alternative action if we got multiple action Ids and the option key was pressed while invoking + } + + this.runActionInRenderer(commandId); + }; + const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0; + const checked = typeof arg4 === 'boolean' ? arg4 : false; + + const options: Electron.MenuItemConstructorOptions = { + label, + click, + enabled + }; + + if (checked) { + options['type'] = 'checkbox'; + options['checked'] = checked; + } + + let commandId: string; + if (typeof arg2 === 'string') { + commandId = arg2; + } else if (Array.isArray(arg2)) { + commandId = arg2[0]; + } + + return new MenuItem(this.withKeybinding(commandId, options)); + } + + private createContextAwareMenuItem(label: string, commandId: string, clickHandler: IMenuItemClickHandler): Electron.MenuItem { + return new MenuItem(this.withKeybinding(commandId, { + label: this.mnemonicLabel(label), + enabled: this.windowsMainService.getWindowCount() > 0, + click: () => { + + // No Active Window + const activeWindow = this.windowsMainService.getFocusedWindow(); + if (!activeWindow) { + return clickHandler.inNoWindow(); + } + + // DevTools focused + if (activeWindow.win.webContents.isDevToolsFocused()) { + return clickHandler.inDevTools(activeWindow.win.webContents.devToolsWebContents); + } + + // Finally execute command in Window + this.runActionInRenderer(commandId); + } + })); + } + + private runActionInRenderer(id: string): void { + // We make sure to not run actions when the window has no focus, this helps + // for https://github.com/Microsoft/vscode/issues/25907 and specifically for + // https://github.com/Microsoft/vscode/issues/11928 + const activeWindow = this.windowsMainService.getFocusedWindow(); + if (activeWindow) { + this.windowsMainService.sendToFocused('vscode:runAction', { id, from: 'menu' } as IRunActionInWindowRequest); + } + } + + private withKeybinding(commandId: string, options: Electron.MenuItemConstructorOptions): Electron.MenuItemConstructorOptions { + const binding = this.keybindingsResolver.getKeybinding(commandId); + + // Apply binding if there is one + if (binding && binding.label) { + + // if the binding is native, we can just apply it + if (binding.isNative) { + options.accelerator = binding.label; + } + + // the keybinding is not native so we cannot show it as part of the accelerator of + // the menu item. we fallback to a different strategy so that we always display it + else { + const bindingIndex = options.label.indexOf('['); + if (bindingIndex >= 0) { + options.label = `${options.label.substr(0, bindingIndex)} [${binding.label}]`; + } else { + options.label = `${options.label} [${binding.label}]`; + } + } + } + + // Unset bindings if there is none + else { + options.accelerator = void 0; + } + + return options; + } + + private likeAction(commandId: string, options: Electron.MenuItemConstructorOptions, setAccelerator = !options.accelerator): Electron.MenuItemConstructorOptions { + if (setAccelerator) { + options = this.withKeybinding(commandId, options); + } + + const originalClick = options.click; + options.click = (item, window, event) => { + this.reportMenuActionTelemetry(commandId); + if (originalClick) { + originalClick(item, window, event); + } + }; + + return options; + } + + private openUrl(url: string, id: string): void { + shell.openExternal(url); + this.reportMenuActionTelemetry(id); + } + + private reportMenuActionTelemetry(id: string): void { + /* __GDPR__ + "workbenchActionExecuted" : { + "id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('workbenchActionExecuted', { id, from: telemetryFrom }); + } + + private mnemonicLabel(label: string): string { + return baseMnemonicLabel(label, !this.currentEnableMenuBarMnemonics); + } +} + +function __separator__(): Electron.MenuItem { + return new MenuItem({ type: 'separator' }); +} \ No newline at end of file diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index a3cd3bc01d4..d0151137c0b 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -500,6 +500,12 @@ export class CodeWindow implements ICodeWindow { }); } + addTabbedWindow(window: ICodeWindow): void { + if (isMacintosh) { + this._win.addTabbedWindow(window.win); + } + } + load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void { // If this is the first time the window is loaded, we associate the paths diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index dc463908bc9..fea7ce16394 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -82,6 +82,7 @@ interface IOpenBrowserWindowOptions { filesToWait?: IPathsToWaitFor; forceNewWindow?: boolean; + forceNewTabbedWindow?: boolean; windowToUse?: ICodeWindow; emptyWindowBackupFolder?: string; @@ -547,7 +548,7 @@ export class WindowsManager implements IWindowsMainService { // Special case: we started with --wait and we got back a folder to open. In this case // we actually prefer to not open the folder but operate purely on the file. if (typeof bestWindowOrFolder === 'string' && filesToWait) { - //TODO: #54483 Ben This should not happen + //TODO@Ben: #54483 This should not happen console.error(`This should not happen`, bestWindowOrFolder, WindowsManager.WINDOWS); bestWindowOrFolder = !openFilesInNewWindow ? this.getLastActiveWindow() : null; } @@ -581,7 +582,7 @@ export class WindowsManager implements IWindowsMainService { // We found a suitable folder to open: add it to foldersToOpen else if (typeof bestWindowOrFolder === 'string') { - //TODO: #54483 Ben This should not happen + //TODO@Ben: #54483 Ben This should not happen // foldersToOpen.push(bestWindowOrFolder); console.error(`This should not happen`, bestWindowOrFolder, WindowsManager.WINDOWS); } @@ -596,7 +597,8 @@ export class WindowsManager implements IWindowsMainService { filesToCreate, filesToDiff, filesToWait, - forceNewWindow: true + forceNewWindow: true, + forceNewTabbedWindow: openConfig.forceNewTabbedWindow })); // Reset these because we handled them @@ -701,6 +703,7 @@ export class WindowsManager implements IWindowsMainService { filesToDiff, filesToWait, forceNewWindow: true, + forceNewTabbedWindow: openConfig.forceNewTabbedWindow, emptyWindowBackupFolder })); @@ -721,7 +724,8 @@ export class WindowsManager implements IWindowsMainService { userEnv: openConfig.userEnv, cli: openConfig.cli, initialStartup: openConfig.initialStartup, - forceNewWindow: openFolderInNewWindow + forceNewWindow: openFolderInNewWindow, + forceNewTabbedWindow: openConfig.forceNewTabbedWindow })); openFolderInNewWindow = true; // any other window to open must open in new window then @@ -768,6 +772,7 @@ export class WindowsManager implements IWindowsMainService { filesToDiff, filesToWait, forceNewWindow, + forceNewTabbedWindow: openConfig.forceNewTabbedWindow, windowToUse }); @@ -1147,6 +1152,7 @@ export class WindowsManager implements IWindowsMainService { } private openInBrowserWindow(options: IOpenBrowserWindowOptions): ICodeWindow { + // Build IWindowConfiguration from config and options const configuration: IWindowConfiguration = mixin({}, options.cli); // inherit all properties from CLI configuration.appRoot = this.environmentService.appRoot; @@ -1171,7 +1177,7 @@ export class WindowsManager implements IWindowsMainService { } let window: ICodeWindow; - if (!options.forceNewWindow) { + if (!options.forceNewWindow && !options.forceNewTabbedWindow) { window = options.windowToUse || this.getLastActiveWindow(); if (window) { window.focus(); @@ -1198,12 +1204,21 @@ export class WindowsManager implements IWindowsMainService { state.mode = WindowMode.Normal; } + // Create the window window = this.instantiationService.createInstance(CodeWindow, { state, extensionDevelopmentPath: configuration.extensionDevelopmentPath, isExtensionTestHost: !!configuration.extensionTestsPath }); + // Add as window tab if configured (macOS only) + if (options.forceNewTabbedWindow) { + const activeWindow = this.getLastActiveWindow(); + if (activeWindow) { + activeWindow.addTabbedWindow(window); + } + } + // Add to our list of windows WindowsManager.WINDOWS.push(window); @@ -1494,6 +1509,10 @@ export class WindowsManager implements IWindowsMainService { return this.open({ context, cli: this.environmentService.args, forceNewWindow: true, forceEmpty: true }); } + openNewTabbedWindow(context: OpenContext): ICodeWindow[] { + return this.open({ context, cli: this.environmentService.args, forceNewTabbedWindow: true, forceEmpty: true }); + } + waitForWindowCloseOrLoad(windowId: number): TPromise { return new TPromise(c => { function handler(id: number) { diff --git a/src/vs/code/test/node/windowsFinder.test.ts b/src/vs/code/test/node/windowsFinder.test.ts index 8b774f68b7b..ed949d9a13a 100644 --- a/src/vs/code/test/node/windowsFinder.test.ts +++ b/src/vs/code/test/node/windowsFinder.test.ts @@ -11,8 +11,9 @@ import { OpenContext } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import URI from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; -const fixturesFolder = require.toUrl('./fixtures'); +const fixturesFolder = getPathFromAmdModule(require, './fixtures'); const testWorkspace: IWorkspaceIdentifier = { id: Date.now().toString(), diff --git a/src/vs/editor/browser/controller/textAreaState.ts b/src/vs/editor/browser/controller/textAreaState.ts index 09f54a8a013..fb3249c7903 100644 --- a/src/vs/editor/browser/controller/textAreaState.ts +++ b/src/vs/editor/browser/controller/textAreaState.ts @@ -121,7 +121,7 @@ export class TextAreaState { // See https://github.com/Microsoft/vscode/issues/42251 // where typing always happens at offset 0 in the textarea // when using a custom title area in OSX and moving the window - if (strings.endsWith(currentValue, previousValue)) { + if (!strings.startsWith(currentValue, previousValue) && strings.endsWith(currentValue, previousValue)) { // Looks like something was typed at offset 0 // ==> pretend we placed the cursor at offset 0 to begin with... previousSelectionStart = 0; diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index 931531d2719..d5797c01a92 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -6,11 +6,11 @@ import URI from 'vs/base/common/uri'; import * as dom from 'vs/base/browser/dom'; +import * as resources from 'vs/base/common/resources'; import { parse } from 'vs/base/common/marshalling'; import { Schemas } from 'vs/base/common/network'; import { TPromise } from 'vs/base/common/winjs.base'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; -import { normalize } from 'vs/base/common/paths'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -79,7 +79,7 @@ export class OpenerService implements IOpenerService { return TPromise.as(undefined); } else if (resource.scheme === Schemas.file) { - resource = resource.with({ path: normalize(resource.path) }); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) + resource = resources.normalizePath(resource); // workaround for non-normalized paths (https://github.com/Microsoft/vscode/issues/12954) } promise = this._editorService.openCodeEditor({ resource, options: { selection, } }, this._editorService.getFocusedCodeEditor(), options && options.openToSide); } diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 33a9a433690..07380bbb371 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -83,6 +83,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements ed this._rawOptions.minimap = objects.mixin({}, this._rawOptions.minimap || {}); this._rawOptions.find = objects.mixin({}, this._rawOptions.find || {}); this._rawOptions.hover = objects.mixin({}, this._rawOptions.hover || {}); + this._rawOptions.parameterHints = objects.mixin({}, this._rawOptions.parameterHints || {}); this._validatedOptions = editorOptions.EditorOptionsValidator.validate(this._rawOptions, EDITOR_DEFAULTS); this.editor = null; @@ -282,19 +283,19 @@ const editorConfiguration: IConfigurationNode = { 'type': 'number', 'default': EDITOR_MODEL_DEFAULTS.tabSize, 'minimum': 1, - 'description': nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on."), + 'markdownDescription': nls.localize('tabSize', "The number of spaces a tab is equal to. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on."), 'errorMessage': nls.localize('tabSize.errorMessage', "Expected 'number'. Note that the value \"auto\" has been replaced by the `editor.detectIndentation` setting.") }, 'editor.insertSpaces': { 'type': 'boolean', 'default': EDITOR_MODEL_DEFAULTS.insertSpaces, - 'description': nls.localize('insertSpaces', "Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on."), + 'markdownDescription': nls.localize('insertSpaces', "Insert spaces when pressing `Tab`. This setting is overridden based on the file contents when `#editor.detectIndentation#` is on."), 'errorMessage': nls.localize('insertSpaces.errorMessage', "Expected 'boolean'. Note that the value \"auto\" has been replaced by the `editor.detectIndentation` setting.") }, 'editor.detectIndentation': { 'type': 'boolean', 'default': EDITOR_MODEL_DEFAULTS.detectIndentation, - 'description': nls.localize('detectIndentation', "Controls whether `#editor.tabSize#` and `#editor.insertSpaces#` will be automatically detected when a file is opened based on the file contents.") + 'markdownDescription': nls.localize('detectIndentation', "Controls whether `#editor.tabSize#` and `#editor.insertSpaces#` will be automatically detected when a file is opened based on the file contents.") }, 'editor.roundedSelection': { 'type': 'boolean', @@ -377,7 +378,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.wordWrap': { 'type': 'string', 'enum': ['off', 'on', 'wordWrapColumn', 'bounded'], - 'enumDescriptions': [ + 'markdownEnumDescriptions': [ nls.localize('wordWrap.off', "Lines will never wrap."), nls.localize('wordWrap.on', "Lines will wrap at the viewport width."), nls.localize({ @@ -407,7 +408,7 @@ const editorConfiguration: IConfigurationNode = { 'type': 'integer', 'default': EDITOR_DEFAULTS.wordWrapColumn, 'minimum': 1, - 'description': nls.localize({ + 'markdownDescription': nls.localize({ key: 'wordWrapColumn', comment: [ '- `editor.wordWrap` refers to a different setting and should not be localized.', @@ -430,7 +431,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.mouseWheelScrollSensitivity': { 'type': 'number', 'default': EDITOR_DEFAULTS.viewInfo.scrollbar.mouseWheelScrollSensitivity, - 'description': nls.localize('mouseWheelScrollSensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.") + 'markdownDescription': nls.localize('mouseWheelScrollSensitivity', "A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events.") }, 'editor.multiCursorModifier': { 'type': 'string', @@ -440,7 +441,7 @@ const editorConfiguration: IConfigurationNode = { nls.localize('multiCursorModifier.alt', "Maps to `Alt` on Windows and Linux and to `Option` on macOS.") ], 'default': 'alt', - 'description': nls.localize({ + 'markdownDescription': nls.localize({ key: 'multiCursorModifier', comment: [ '- `ctrlCmd` refers to a value the setting can take and should not be localized.', @@ -488,10 +489,15 @@ const editorConfiguration: IConfigurationNode = { 'minimum': 0, 'description': nls.localize('quickSuggestionsDelay', "Controls the delay in milliseconds after which quick suggestions will show up.") }, - 'editor.parameterHints': { + 'editor.parameterHints.enabled': { 'type': 'boolean', - 'default': EDITOR_DEFAULTS.contribInfo.parameterHints, - 'description': nls.localize('parameterHints', "Enables a pop-up that shows parameter documentation and type information as you type.") + 'default': EDITOR_DEFAULTS.contribInfo.parameterHints.enabled, + 'description': nls.localize('parameterHints.enabled', "Enables a pop-up that shows parameter documentation and type information as you type.") + }, + 'editor.parameterHints.cycle': { + 'type': 'boolean', + 'default': EDITOR_DEFAULTS.contribInfo.parameterHints.cycle, + 'description': nls.localize('parameterHints.cycle', "Controls whether the parameter hints menu cycles or closes when reaching the end of the list.") }, 'editor.autoClosingBrackets': { 'type': 'boolean', @@ -527,12 +533,12 @@ const editorConfiguration: IConfigurationNode = { nls.localize('acceptSuggestionOnEnterSmart', "Only accept a suggestion with `Enter` when it makes a textual change."), '' ], - 'description': nls.localize('acceptSuggestionOnEnter', "Controls whether suggestions should be accepted on `Enter`, in addition to `Tab`. Helps to avoid ambiguity between inserting new lines or accepting suggestions.") + 'markdownDescription': nls.localize('acceptSuggestionOnEnter', "Controls whether suggestions should be accepted on `Enter`, in addition to `Tab`. Helps to avoid ambiguity between inserting new lines or accepting suggestions.") }, 'editor.acceptSuggestionOnCommitCharacter': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.acceptSuggestionOnCommitCharacter, - 'description': nls.localize('acceptSuggestionOnCommitCharacter', "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.") + 'markdownDescription': nls.localize('acceptSuggestionOnCommitCharacter', "Controls whether suggestions should be accepted on commit characters. For example, in JavaScript, the semi-colon (`;`) can be a commit character that accepts a suggestion and types that character.") }, 'editor.snippetSuggestions': { 'type': 'string', @@ -592,7 +598,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.selectionHighlight': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.contribInfo.selectionHighlight, - 'description': nls.localize('selectionHighlight', "Controls whether the editor should highlight similar matches to the selection") + 'description': nls.localize('selectionHighlight', "Controls whether the editor should highlight matches similar to the selection") }, 'editor.occurrencesHighlight': { 'type': 'boolean', @@ -618,7 +624,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.mouseWheelZoom': { 'type': 'boolean', 'default': EDITOR_DEFAULTS.viewInfo.mouseWheelZoom, - 'description': nls.localize('mouseWheelZoom', "Zoom the font of the editor when using mouse wheel and holding `Ctrl`.") + 'markdownDescription': nls.localize('mouseWheelZoom', "Zoom the font of the editor when using mouse wheel and holding `Ctrl`.") }, 'editor.cursorStyle': { 'type': 'string', @@ -629,7 +635,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.cursorWidth': { 'type': 'integer', 'default': EDITOR_DEFAULTS.viewInfo.cursorWidth, - 'description': nls.localize('cursorWidth', "Controls the width of the cursor when `#editor.cursorStyle#` is set to `line`.") + 'markdownDescription': nls.localize('cursorWidth', "Controls the width of the cursor when `#editor.cursorStyle#` is set to `line`.") }, 'editor.fontLigatures': { 'type': 'boolean', @@ -693,7 +699,7 @@ const editorConfiguration: IConfigurationNode = { 'type': 'string', 'enum': ['auto', 'indentation'], 'default': EDITOR_DEFAULTS.contribInfo.foldingStrategy, - 'description': nls.localize('foldingStrategy', "Controls the strategy for computing folding ranges. `auto` uses a language specific folding strategy, if available. `indentation` uses the indentation based folding strategy.") + 'markdownDescription': nls.localize('foldingStrategy', "Controls the strategy for computing folding ranges. `auto` uses a language specific folding strategy, if available. `indentation` uses the indentation based folding strategy.") }, 'editor.showFoldingControls': { 'type': 'string', @@ -724,7 +730,7 @@ const editorConfiguration: IConfigurationNode = { 'editor.stablePeek': { 'type': 'boolean', 'default': false, - 'description': nls.localize('stablePeek', "Keep peek editors open even when double clicking their content or when hitting `Escape`.") + 'markdownDescription': nls.localize('stablePeek', "Keep peek editors open even when double clicking their content or when hitting `Escape`.") }, 'editor.dragAndDrop': { 'type': 'boolean', diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 14365ee9925..3a510380338 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -158,6 +158,22 @@ export interface IEditorHoverOptions { sticky?: boolean; } +/** + * Configuration options for parameter hints + */ +export interface IEditorParameterHintOptions { + /** + * Enable parameter hints. + * Defaults to true. + */ + enabled?: boolean; + /** + * Enable cycling of parameter hints. + * Defaults to false. + */ + cycle?: boolean; +} + export interface ISuggestOptions { /** * Enable graceful matching. Defaults to true. @@ -451,9 +467,9 @@ export interface IEditorOptions { */ quickSuggestionsDelay?: number; /** - * Enables parameter hints + * Parameter hint options. */ - parameterHints?: boolean; + parameterHints?: IEditorParameterHintOptions; /** * Render icons in suggestions box. * Defaults to true. @@ -851,6 +867,11 @@ export interface InternalSuggestOptions { readonly snippetsPreventQuickSuggestions: boolean; } +export interface InternalParameterHintOptions { + readonly enabled: boolean; + readonly cycle: boolean; +} + export interface EditorWrappingInfo { readonly inDiffEditor: boolean; readonly isDominatedByLongLines: boolean; @@ -911,7 +932,7 @@ export interface EditorContribOptions { readonly contextmenu: boolean; readonly quickSuggestions: boolean | { other: boolean, comments: boolean, strings: boolean }; readonly quickSuggestionsDelay: number; - readonly parameterHints: boolean; + readonly parameterHints: InternalParameterHintOptions; readonly iconsInSuggestions: boolean; readonly formatOnType: boolean; readonly formatOnPaste: boolean; @@ -1237,6 +1258,16 @@ export class InternalEditorOptions { ); } + /** + * @internal + */ + private static _equalsParameterHintOptions(a: InternalParameterHintOptions, b: InternalParameterHintOptions): boolean { + return ( + a.enabled === b.enabled + && a.cycle === b.cycle + ); + } + /** * @internal */ @@ -1291,7 +1322,7 @@ export class InternalEditorOptions { && a.contextmenu === b.contextmenu && InternalEditorOptions._equalsQuickSuggestions(a.quickSuggestions, b.quickSuggestions) && a.quickSuggestionsDelay === b.quickSuggestionsDelay - && a.parameterHints === b.parameterHints + && this._equalsParameterHintOptions(a.parameterHints, b.parameterHints) && a.iconsInSuggestions === b.iconsInSuggestions && a.formatOnType === b.formatOnType && a.formatOnPaste === b.formatOnPaste @@ -1732,6 +1763,17 @@ export class EditorOptionsValidator { }; } + private static _sanitizeParameterHintOpts(opts: IEditorParameterHintOptions, defaults: InternalParameterHintOptions): InternalParameterHintOptions { + if (typeof opts !== 'object') { + return defaults; + } + + return { + enabled: _boolean(opts.enabled, defaults.enabled), + cycle: _boolean(opts.cycle, defaults.cycle) + }; + } + private static _santizeHoverOpts(_opts: boolean | IEditorHoverOptions, defaults: InternalEditorHoverOptions): InternalEditorHoverOptions { let opts: IEditorHoverOptions; if (typeof _opts === 'boolean') { @@ -1752,13 +1794,11 @@ export class EditorOptionsValidator { } private static _sanitizeSuggestOpts(opts: IEditorOptions, defaults: InternalSuggestOptions): InternalSuggestOptions { - if (!opts.suggest) { - return defaults; - } + const suggestOpts = opts.suggest || {}; return { - filterGraceful: _boolean(opts.suggest.filterGraceful, defaults.filterGraceful), + filterGraceful: _boolean(suggestOpts.filterGraceful, defaults.filterGraceful), snippets: _stringSet<'top' | 'bottom' | 'inline' | 'none'>(opts.snippetSuggestions, defaults.snippets, ['top', 'bottom', 'inline', 'none']), - snippetsPreventQuickSuggestions: _boolean(opts.suggest.snippetsPreventQuickSuggestions, defaults.filterGraceful), + snippetsPreventQuickSuggestions: _boolean(suggestOpts.snippetsPreventQuickSuggestions, defaults.filterGraceful), }; } @@ -1886,7 +1926,7 @@ export class EditorOptionsValidator { contextmenu: _boolean(opts.contextmenu, defaults.contextmenu), quickSuggestions: quickSuggestions, quickSuggestionsDelay: _clampedInt(opts.quickSuggestionsDelay, defaults.quickSuggestionsDelay, Constants.MIN_SAFE_SMALL_INTEGER, Constants.MAX_SAFE_SMALL_INTEGER), - parameterHints: _boolean(opts.parameterHints, defaults.parameterHints), + parameterHints: this._sanitizeParameterHintOpts(opts.parameterHints, defaults.parameterHints), iconsInSuggestions: _boolean(opts.iconsInSuggestions, defaults.iconsInSuggestions), formatOnType: _boolean(opts.formatOnType, defaults.formatOnType), formatOnPaste: _boolean(opts.formatOnPaste, defaults.formatOnPaste), @@ -2465,7 +2505,10 @@ export const EDITOR_DEFAULTS: IValidatedEditorOptions = { contextmenu: true, quickSuggestions: { other: true, comments: false, strings: false }, quickSuggestionsDelay: 10, - parameterHints: true, + parameterHints: { + enabled: true, + cycle: false + }, iconsInSuggestions: true, formatOnType: false, formatOnPaste: false, diff --git a/src/vs/editor/common/controller/cursorWordOperations.ts b/src/vs/editor/common/controller/cursorWordOperations.ts index b69d9799b53..da290068cf9 100644 --- a/src/vs/editor/common/controller/cursorWordOperations.ts +++ b/src/vs/editor/common/controller/cursorWordOperations.ts @@ -466,22 +466,37 @@ export class WordOperations { } export function _lastWordPartEnd(str: string, startIndex: number = str.length - 1): number { + let ignoreUpperCase = !strings.isLowerAsciiLetter(str.charCodeAt(startIndex + 1)); for (let i = startIndex; i >= 0; i--) { let chCode = str.charCodeAt(i); - if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || chCode === CharCode.Underline) { + if (chCode === CharCode.Space || chCode === CharCode.Tab || (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode)) || chCode === CharCode.Underline) { return i - 1; } + if (ignoreUpperCase && i < startIndex && strings.isLowerAsciiLetter(chCode)) { + return i; + } + ignoreUpperCase = ignoreUpperCase && strings.isUpperAsciiLetter(chCode); } return -1; } -export function _nextWordPartBegin(str: string, startIndex: number = str.length - 1): number { - const checkLowerCase = str.charCodeAt(startIndex - 1) === CharCode.Space; // does a lc char count as a part start? +export function _nextWordPartBegin(str: string, startIndex: number = 0): number { + let prevChCode = str.charCodeAt(startIndex - 1); + let chCode = str.charCodeAt(startIndex); + // handle the special case ' X' and ' x' which is different from the standard methods + if ((prevChCode === CharCode.Space || prevChCode === CharCode.Tab) && (strings.isLowerAsciiLetter(chCode) || strings.isUpperAsciiLetter(chCode))) { + return startIndex + 1; + } + let ignoreUpperCase = strings.isUpperAsciiLetter(chCode); for (let i = startIndex; i < str.length; ++i) { - let chCode = str.charCodeAt(i); - if (chCode === CharCode.Space || chCode === CharCode.Tab || strings.isUpperAsciiLetter(chCode) || (checkLowerCase && strings.isLowerAsciiLetter(chCode))) { + chCode = str.charCodeAt(i); + if (chCode === CharCode.Space || chCode === CharCode.Tab || (!ignoreUpperCase && strings.isUpperAsciiLetter(chCode))) { return i + 1; } + if (ignoreUpperCase && strings.isLowerAsciiLetter(chCode)) { + return i; // multiple UPPERCase : assume an upper case word and a CamelCase word - like DSLModel + } + ignoreUpperCase = ignoreUpperCase && strings.isUpperAsciiLetter(chCode); if (chCode === CharCode.Underline) { return i + 2; } diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts index 8f8c9de9ba5..28839e413af 100644 --- a/src/vs/editor/common/model/intervalTree.ts +++ b/src/vs/editor/common/model/intervalTree.ts @@ -12,14 +12,14 @@ import { IModelDecoration, TrackedRangeStickiness as ActualTrackedRangeStickines // The red-black tree is based on the "Introduction to Algorithms" by Cormen, Leiserson and Rivest. // -export const ClassName = { - EditorHintDecoration: 'squiggly-hint', - EditorInfoDecoration: 'squiggly-info', - EditorWarningDecoration: 'squiggly-warning', - EditorErrorDecoration: 'squiggly-error', - EditorUnnecessaryDecoration: 'squiggly-unnecessary', - EditorUnnecessaryInlineDecoration: 'squiggly-inline-unnecessary' -}; +export const enum ClassName { + EditorHintDecoration = 'squiggly-hint', + EditorInfoDecoration = 'squiggly-info', + EditorWarningDecoration = 'squiggly-warning', + EditorErrorDecoration = 'squiggly-error', + EditorUnnecessaryDecoration = 'squiggly-unnecessary', + EditorUnnecessaryInlineDecoration = 'squiggly-inline-unnecessary' +} /** * Describes the behavior of decorations when typing/editing near their edges. diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 02c236a53c2..0f643685413 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -848,12 +848,12 @@ export interface FoldingRangeProvider { export interface FoldingRange { /** - * The zero-based start line of the range to fold. The folded area starts after the line's last character. + * The one-based start line of the range to fold. The folded area starts after the line's last character. */ start: number; /** - * The zero-based end line of the range to fold. The folded area ends with the line's last character. + * The one-based end line of the range to fold. The folded area ends with the line's last character. */ end: number; diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 56a282da585..560ed428507 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -85,9 +85,12 @@ class ModelMarkerHandler { let ret = Range.lift(rawMarker); - if (rawMarker.severity === MarkerSeverity.Hint && Range.spansMultipleLines(ret)) { - // never render hints on multiple lines - ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn); + if (rawMarker.severity === MarkerSeverity.Hint) { + // * never render hints on multiple lines + // * make enough space for three dots + if (Range.spansMultipleLines(ret) || ret.endColumn - ret.startColumn < 2) { + ret = ret.setEndPosition(ret.startLineNumber, ret.startColumn + 2); + } } ret = model.validateRange(ret); diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index 5ed915fe01a..ed17ffb44dc 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -118,7 +118,13 @@ export class BracketMatchingController extends Disposable implements editorCommo this._updateBracketsSoon.schedule(); })); - this._register(editor.onDidChangeModel((e) => { this._decorations = []; this._updateBracketsSoon.schedule(); })); + this._register(editor.onDidChangeModelContent((e) => { + this._updateBracketsSoon.schedule(); + })); + this._register(editor.onDidChangeModel((e) => { + this._decorations = []; + this._updateBracketsSoon.schedule(); + })); this._register(editor.onDidChangeModelLanguageConfiguration((e) => { this._lastBracketsData = []; this._updateBracketsSoon.schedule(); diff --git a/src/vs/editor/contrib/format/format.ts b/src/vs/editor/contrib/format/format.ts index 60b2b6e72c5..c98fefbab91 100644 --- a/src/vs/editor/contrib/format/format.ts +++ b/src/vs/editor/contrib/format/format.ts @@ -12,8 +12,9 @@ import { ITextModel } from 'vs/editor/common/model'; import { registerDefaultLanguageCommand, registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { DocumentFormattingEditProviderRegistry, DocumentRangeFormattingEditProviderRegistry, OnTypeFormattingEditProviderRegistry, FormattingOptions, TextEdit } from 'vs/editor/common/modes'; import { IModelService } from 'vs/editor/common/services/modelService'; -import { asWinJsPromise, first } from 'vs/base/common/async'; +import { asWinJsPromise, first2 } from 'vs/base/common/async'; import { Position } from 'vs/editor/common/core/position'; +import { CancellationToken } from 'vs/base/common/cancellation'; export class NoProviderError extends Error { @@ -26,30 +27,30 @@ export class NoProviderError extends Error { } } -export function getDocumentRangeFormattingEdits(model: ITextModel, range: Range, options: FormattingOptions): TPromise { +export function getDocumentRangeFormattingEdits(model: ITextModel, range: Range, options: FormattingOptions, token: CancellationToken): Promise { const providers = DocumentRangeFormattingEditProviderRegistry.ordered(model); if (providers.length === 0) { - return TPromise.wrapError(new NoProviderError()); + return Promise.reject(new NoProviderError()); } - return first(providers.map(provider => () => { - return asWinJsPromise(token => provider.provideDocumentRangeFormattingEdits(model, range, options, token)) + return first2(providers.map(provider => () => { + return Promise.resolve(provider.provideDocumentRangeFormattingEdits(model, range, options, token)) .then(undefined, onUnexpectedExternalError); }), result => !isFalsyOrEmpty(result)); } -export function getDocumentFormattingEdits(model: ITextModel, options: FormattingOptions): TPromise { +export function getDocumentFormattingEdits(model: ITextModel, options: FormattingOptions, token: CancellationToken): Promise { const providers = DocumentFormattingEditProviderRegistry.ordered(model); // try range formatters when no document formatter is registered if (providers.length === 0) { - return getDocumentRangeFormattingEdits(model, model.getFullModelRange(), options); + return getDocumentRangeFormattingEdits(model, model.getFullModelRange(), options, token); } - return first(providers.map(provider => () => { - return asWinJsPromise(token => provider.provideDocumentFormattingEdits(model, options, token)) + return first2(providers.map(provider => () => { + return Promise.resolve(provider.provideDocumentFormattingEdits(model, options, token)) .then(undefined, onUnexpectedExternalError); }), result => !isFalsyOrEmpty(result)); } @@ -77,7 +78,7 @@ registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) if (!model) { throw illegalArgument('resource'); } - return getDocumentRangeFormattingEdits(model, Range.lift(range), options); + return getDocumentRangeFormattingEdits(model, Range.lift(range), options, CancellationToken.None); }); registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, args) { @@ -90,7 +91,7 @@ registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, ar throw illegalArgument('resource'); } - return getDocumentFormattingEdits(model, options); + return getDocumentFormattingEdits(model, options, CancellationToken.None); }); registerDefaultLanguageCommand('_executeFormatOnTypeProvider', function (model, position, args) { diff --git a/src/vs/editor/contrib/format/formatActions.ts b/src/vs/editor/contrib/format/formatActions.ts index 25dee8a028a..02a454eab11 100644 --- a/src/vs/editor/contrib/format/formatActions.ts +++ b/src/vs/editor/contrib/format/formatActions.ts @@ -27,6 +27,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { ISingleEditOperation } from 'vs/editor/common/model'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CancellationToken } from 'vs/base/common/cancellation'; function alertFormattingEdits(edits: ISingleEditOperation[]): void { @@ -239,7 +240,7 @@ class FormatOnPaste implements editorCommon.IEditorContribution { const { tabSize, insertSpaces } = model.getOptions(); const state = new EditorState(this.editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); - getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }).then(edits => { + getDocumentRangeFormattingEdits(model, range, { tabSize, insertSpaces }, CancellationToken.None).then(edits => { return this.workerService.computeMoreMinimalEdits(model.uri, edits); }).then(edits => { if (!state.validate(this.editor) || isFalsyOrEmpty(edits)) { @@ -267,7 +268,7 @@ export abstract class AbstractFormatAction extends EditorAction { const workerService = accessor.get(IEditorWorkerService); const notificationService = accessor.get(INotificationService); - const formattingPromise = this._getFormattingEdits(editor); + const formattingPromise = this._getFormattingEdits(editor, CancellationToken.None); if (!formattingPromise) { return TPromise.as(void 0); } @@ -276,7 +277,7 @@ export abstract class AbstractFormatAction extends EditorAction { const state = new EditorState(editor, CodeEditorStateFlag.Value | CodeEditorStateFlag.Position); // Receive formatted value from worker - return formattingPromise.then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits)).then(edits => { + return TPromise.wrap(formattingPromise).then(edits => workerService.computeMoreMinimalEdits(editor.getModel().uri, edits)).then(edits => { if (!state.validate(editor) || isFalsyOrEmpty(edits)) { return; } @@ -293,7 +294,8 @@ export abstract class AbstractFormatAction extends EditorAction { }); } - protected abstract _getFormattingEdits(editor: ICodeEditor): TPromise; + protected abstract _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise; + protected _notifyNoProviderError(notificationService: INotificationService, language: string): void { notificationService.info(nls.localize('no.provider', "There is no formatter for '{0}'-files installed.", language)); } @@ -322,10 +324,10 @@ export class FormatDocumentAction extends AbstractFormatAction { }); } - protected _getFormattingEdits(editor: ICodeEditor): TPromise { + protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise { const model = editor.getModel(); const { tabSize, insertSpaces } = model.getOptions(); - return getDocumentFormattingEdits(model, { tabSize, insertSpaces }); + return getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token); } protected _notifyNoProviderError(notificationService: INotificationService, language: string): void { @@ -354,10 +356,10 @@ export class FormatSelectionAction extends AbstractFormatAction { }); } - protected _getFormattingEdits(editor: ICodeEditor): TPromise { + protected _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise { const model = editor.getModel(); const { tabSize, insertSpaces } = model.getOptions(); - return getDocumentRangeFormattingEdits(model, editor.getSelection(), { tabSize, insertSpaces }); + return getDocumentRangeFormattingEdits(model, editor.getSelection(), { tabSize, insertSpaces }, token); } protected _notifyNoProviderError(notificationService: INotificationService, language: string): void { @@ -379,14 +381,14 @@ CommandsRegistry.registerCommand('editor.action.format', accessor => { constructor() { super({} as IActionOptions); } - _getFormattingEdits(editor: ICodeEditor): TPromise { + _getFormattingEdits(editor: ICodeEditor, token: CancellationToken): Promise { const model = editor.getModel(); const editorSelection = editor.getSelection(); const { tabSize, insertSpaces } = model.getOptions(); return editorSelection.isEmpty() - ? getDocumentFormattingEdits(model, { tabSize, insertSpaces }) - : getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces }); + ? getDocumentFormattingEdits(model, { tabSize, insertSpaces }, token) + : getDocumentRangeFormattingEdits(model, editorSelection, { tabSize, insertSpaces }, token); } }().run(accessor, editor); } diff --git a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts index 667e77dbf2d..6ca73451d74 100644 --- a/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts +++ b/src/vs/editor/contrib/goToDefinition/goToDefinitionCommands.ts @@ -26,6 +26,7 @@ import { ITextModel, IWordAtPosition } from 'vs/editor/common/model'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { createCancelablePromise } from 'vs/base/common/async'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export class DefinitionActionConfig { @@ -375,3 +376,31 @@ registerEditorAction(GoToImplementationAction); registerEditorAction(PeekImplementationAction); registerEditorAction(GoToTypeDefinitionAction); registerEditorAction(PeekTypeDefinitionAction); + +// Go to menu +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'editor.action.goToDeclaration', + title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'editor.action.goToTypeDefinition', + title: nls.localize({ key: 'miGotoTypeDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Type Definition") + }, + order: 5 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'editor.action.goToImplementation', + title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation") + }, + order: 6 +}); \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 26db8c908f0..071469c7a18 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -361,11 +361,11 @@ export class ModesContentHoverWidget extends ContentHoverWidget { newRange = range.setEndPosition(range.endLineNumber, range.startColumn + model.presentation.label.length); } - editorModel.pushEditOperations([], textEdits, () => []); + this._editor.executeEdits('colorpicker', textEdits); if (model.presentation.additionalTextEdits) { textEdits = [...model.presentation.additionalTextEdits]; - editorModel.pushEditOperations([], textEdits, () => []); + this._editor.executeEdits('colorpicker', textEdits); this.hide(); } this._editor.pushUndoStop(); diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index ab6c491ed74..7437bf9a56c 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -11,7 +11,6 @@ import { IEditorContribution, ICommand, ICursorStateComputerData, IEditOperation import { IIdentifiedSingleEditOperation, ITextModel } from 'vs/editor/common/model'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { registerEditorAction, ServicesAccessor, IActionOptions, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IModelService } from 'vs/editor/common/services/modelService'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; @@ -23,6 +22,7 @@ import { TextEdit, StandardTokenType } from 'vs/editor/common/modes'; import * as IndentUtil from './indentUtils'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IndentConsts } from 'vs/editor/common/modes/supports/indentRules'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export function shiftIndent(tabSize: number, indentation: string, count?: number): string { count = count || 1; @@ -217,7 +217,7 @@ export class ChangeIndentationSizeAction extends EditorAction { } public run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise { - const quickOpenService = accessor.get(IQuickOpenService); + const quickInputService = accessor.get(IQuickInputService); const modelService = accessor.get(IModelService); let model = editor.getModel(); @@ -237,7 +237,7 @@ export class ChangeIndentationSizeAction extends EditorAction { const autoFocusIndex = Math.min(model.getOptions().tabSize - 1, 7); return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).then(() => - quickOpenService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File"), autoFocus: { autoFocusIndex } }).then(pick => { + quickInputService.pick(picks, { placeHolder: nls.localize({ key: 'selectTabWidth', comment: ['Tab corresponds to the tab key'] }, "Select Tab Size for Current File"), activeItem: picks[autoFocusIndex] }).then(pick => { if (pick) { model.updateOptions({ tabSize: parseInt(pick.label, 10), diff --git a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts index 9a5cfbdb383..80c27ea30e5 100644 --- a/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/parameterHintsWidget.ts @@ -169,7 +169,7 @@ export class ParameterHintsModel extends Disposable { } private onEditorConfigurationChange(): void { - this.enabled = this.editor.getConfiguration().contribInfo.parameterHints; + this.enabled = this.editor.getConfiguration().contribInfo.parameterHints.enabled; if (!this.enabled) { this.cancel(); @@ -476,14 +476,20 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { next(): boolean { const length = this.hints.signatures.length; const last = (this.currentSignature % length) === (length - 1); + const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle; // If there is only one signature, or we're on last signature of list - if (length < 2 || last) { + if ((length < 2 || last) && !cycle) { this.cancel(); return false; } - this.currentSignature++; + if (last && cycle) { + this.currentSignature = 0; + } else { + this.currentSignature++; + } + this.render(); return true; } @@ -491,13 +497,20 @@ export class ParameterHintsWidget implements IContentWidget, IDisposable { previous(): boolean { const length = this.hints.signatures.length; const first = this.currentSignature === 0; + const cycle = this.editor.getConfiguration().contribInfo.parameterHints.cycle; - if (length < 2 || first) { + // If there is only one signature, or we're on first signature of list + if ((length < 2 || first) && !cycle) { this.cancel(); return false; } - this.currentSignature--; + if (first && cycle) { + this.currentSignature = length - 1; + } else { + this.currentSignature--; + } + this.render(); return true; } diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index efc09da762f..45fd356d9d9 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -20,10 +20,9 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; import { GestureEvent } from 'vs/base/browser/touch'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; -import { FileLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; import * as tree from 'vs/base/parts/tree/browser/tree'; -import { IInstantiationService, optional } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModel, ModelDecorationOptions } from 'vs/editor/common/model/textModel'; @@ -36,8 +35,6 @@ import { registerColor, activeContrastBorder, contrastBorder } from 'vs/platform import { registerThemingParticipant, ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import URI from 'vs/base/common/uri'; import { TrackedRangeStickiness, IModelDeltaDecoration } from 'vs/editor/common/model'; import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -45,6 +42,7 @@ import { Location } from 'vs/editor/common/modes'; import { ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; import { dirname, basenameOrAuthority } from 'vs/base/common/resources'; +import { getBaseLabel } from 'vs/base/common/labels'; class DecorationsManager implements IDisposable { @@ -296,20 +294,19 @@ class Controller extends WorkbenchTreeController { class FileReferencesTemplate { - readonly file: FileLabel; + readonly file: IconLabel; readonly badge: CountBadge; readonly dispose: () => void; constructor( container: HTMLElement, - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, - @optional(IEnvironmentService) private _environmentService: IEnvironmentService, + @IUriDisplayService private readonly _uriDisplay: IUriDisplayService, @IThemeService themeService: IThemeService, ) { const parent = document.createElement('div'); dom.addClass(parent, 'reference-file'); container.appendChild(parent); - this.file = new FileLabel(parent, URI.parse('no:file'), this._contextService, this._environmentService); + this.file = new IconLabel(parent); this.badge = new CountBadge($('.count').appendTo(parent).getHTMLElement()); const styler = attachBadgeStyler(this.badge, themeService); @@ -321,7 +318,8 @@ class FileReferencesTemplate { } set(element: FileReferences) { - this.file.setFile(element.uri, this._contextService, this._environmentService); + let parent = dirname(element.uri); + this.file.setValue(getBaseLabel(element.uri), parent ? this._uriDisplay.getLabel(parent, true) : undefined, { title: this._uriDisplay.getLabel(element.uri) }); const len = element.children.length; this.badge.setCount(len); if (element.failure) { @@ -369,9 +367,8 @@ class Renderer implements tree.IRenderer { }; constructor( - @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @IThemeService private readonly _themeService: IThemeService, - @optional(IEnvironmentService) private _environmentService: IEnvironmentService, + @IUriDisplayService private readonly _uriDisplay: IUriDisplayService, ) { // } @@ -391,7 +388,7 @@ class Renderer implements tree.IRenderer { renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement) { if (templateId === Renderer._ids.FileReferences) { - return new FileReferencesTemplate(container, this._contextService, this._environmentService, this._themeService); + return new FileReferencesTemplate(container, this._uriDisplay, this._themeService); } else if (templateId === Renderer._ids.OneReference) { return new OneReferenceTemplate(container); } diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index 4a427b85dfe..d3b11c702fb 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -123,6 +123,9 @@ export class SuggestModel implements IDisposable { this._updateTriggerCharacters(); this.cancel(); })); + this._toDispose.push(this._editor.onDidBlurEditorText(() => { + this.cancel(); + })); this._toDispose.push(this._editor.onDidChangeConfiguration(() => { this._updateTriggerCharacters(); this._updateQuickSuggest(); diff --git a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts index 2074dd9cfc6..ee993f138a0 100644 --- a/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts +++ b/src/vs/editor/contrib/wordPartOperations/test/wordPartOperations.test.ts @@ -39,7 +39,7 @@ suite('WordPartOperations', () => { test('move word part left basic', () => { withTestCodeEditor([ 'start line', - 'thisIsACamelCaseVar this_is_a_snake_case_var', + 'thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', 'end line' ], {}, (editor, _) => { editor.setPosition(new Position(3, 8)); @@ -47,6 +47,16 @@ suite('WordPartOperations', () => { [3, 5], [3, 4], [3, 1], + [2, 81], + [2, 78], + [2, 73], + [2, 70], + [2, 66], + [2, 65], + [2, 59], + [2, 54], + [2, 51], + [2, 47], [2, 46], [2, 42], [2, 37], @@ -82,7 +92,7 @@ suite('WordPartOperations', () => { test('move word part right basic', () => { withTestCodeEditor([ 'start line', - 'thisIsACamelCaseVar this_is_a_snake_case_var', + 'thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', 'end line' ], {}, (editor, _) => { editor.setPosition(new Position(1, 1)); @@ -105,6 +115,16 @@ suite('WordPartOperations', () => { [2, 38], [2, 43], [2, 46], + [2, 47], + [2, 52], + [2, 55], + [2, 60], + [2, 65], + [2, 66], + [2, 71], + [2, 73], + [2, 78], + [2, 81], [3, 1], [3, 4], [3, 5], @@ -117,93 +137,112 @@ suite('WordPartOperations', () => { const pos = editor.getPosition(); actualStops.push([pos.lineNumber, pos.column]); } - assert.deepEqual(actualStops, expectedStops); }); }); test('delete word part left basic', () => { withTestCodeEditor([ - ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var' + ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse' ], {}, (editor, _) => { const model = editor.getModel(); - editor.setPosition(new Position(1, 84)); + editor.setPosition(new Position(1, 1000)); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case', '001'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake', '002'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a', '003'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is', '004'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this', '005'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar ', '006'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar', '007'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCase', '008'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamel', '009'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsA', '010'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIs', '011'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ this', '012'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ ', '013'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */', '014'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 ', '015'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3', '015bis'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-', '016'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5', '017'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +', '018'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 ', '019'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3', '019bis'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= ', '020'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+=', '021'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a', '022'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text ', '023'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text', '024'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some ', '025'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some', '026'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just ', '027'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just', '028'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* ', '029'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /*', '030'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' ', '031'); - deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '', '032'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixed', '001'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_IS', '002'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this', '003'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE ', '004'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE', '005'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS', '006'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS', '007'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS', '008'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var ', '009'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '010'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case', '011'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake', '012'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a', '013'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is', '014'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this', '015'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar ', '016'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar', '017'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamelCase', '018'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsACamel', '019'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIsA', '020'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ thisIs', '021'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ this', '022'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */ ', '023'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 */', '024'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3 ', '025'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-3', '025bis'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5-', '026'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +5', '027'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 +', '028'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3 ', '029'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= 3', '029bis'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+= ', '030'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a+=', '031'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text a', '032'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text ', '033'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some text', '034'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some ', '035'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just some', '036'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just ', '037'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* Just', '038'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /* ', '039'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' /*', '040'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), ' ', '041'); + deleteWordPartLeft(editor); assert.equal(model.getLineContent(1), '', '042'); }); }); test('delete word part right basic', () => { withTestCodeEditor([ - ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var' + ' /* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse' ], {}, (editor, _) => { const model = editor.getModel(); editor.setPosition(new Position(1, 1)); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '/* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '001'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '002'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '003'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '004'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '005'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '006'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '007'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '008'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '009'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '010'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '011'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '012'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '013'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '-3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '014'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '3 */ thisIsACamelCaseVar this_is_a_snake_case_var', '015'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' */ thisIsACamelCaseVar this_is_a_snake_case_var', '016'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' thisIsACamelCaseVar this_is_a_snake_case_var', '017'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'thisIsACamelCaseVar this_is_a_snake_case_var', '018'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'IsACamelCaseVar this_is_a_snake_case_var', '019'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'ACamelCaseVar this_is_a_snake_case_var', '020'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CamelCaseVar this_is_a_snake_case_var', '021'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CaseVar this_is_a_snake_case_var', '022'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Var this_is_a_snake_case_var', '023'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' this_is_a_snake_case_var', '024'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'this_is_a_snake_case_var', '025'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'is_a_snake_case_var', '026'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a_snake_case_var', '027'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'snake_case_var', '028'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'case_var', '029'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'var', '030'); - deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '', '031'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '/* Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '001'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '002'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Just some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '003'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '004'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'some text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '005'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '006'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'text a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '007'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '008'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '009'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '+= 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '010'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' 3 +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '011'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' +5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '012'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '5-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '013'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '-3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '014'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '3 */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '015'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' */ thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '016'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '017'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'thisIsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '018'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'IsACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '019'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'ACamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '020'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CamelCaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '021'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CaseVar this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '022'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Var this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '023'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '024'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'this_is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '025'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'is_a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '026'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'a_snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '027'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'snake_case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '028'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'case_var THIS_IS_CAPS_SNAKE this_ISMixedUse', '029'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'var THIS_IS_CAPS_SNAKE this_ISMixedUse', '030'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' THIS_IS_CAPS_SNAKE this_ISMixedUse', '031'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'THIS_IS_CAPS_SNAKE this_ISMixedUse', '032'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'IS_CAPS_SNAKE this_ISMixedUse', '033'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'CAPS_SNAKE this_ISMixedUse', '034'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'SNAKE this_ISMixedUse', '035'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), ' this_ISMixedUse', '036'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'this_ISMixedUse', '037'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'ISMixedUse', '038'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'MixedUse', '039'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), 'Use', '040'); + deleteWordPartRight(editor); assert.equal(model.getLineContent(1), '', '041'); }); }); }); diff --git a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts index 229dbedfc18..fec93549d35 100644 --- a/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts +++ b/src/vs/editor/contrib/wordPartOperations/wordPartOperations.ts @@ -16,6 +16,7 @@ import { WordCharacterClassifier } from 'vs/editor/common/controller/wordCharact import { DeleteWordCommand, MoveWordCommand } from '../wordOperations/wordOperations'; import { Position } from 'vs/editor/common/core/position'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; export class DeleteWordPartLeft extends DeleteWordCommand { constructor() { @@ -26,7 +27,7 @@ export class DeleteWordPartLeft extends DeleteWordCommand { precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Backspace }, weight: KeybindingWeight.EditorContrib } @@ -51,7 +52,7 @@ export class DeleteWordPartRight extends DeleteWordCommand { precondition: EditorContextKeys.writable, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Delete, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Delete }, weight: KeybindingWeight.EditorContrib } @@ -79,33 +80,38 @@ export class CursorWordPartLeft extends WordPartLeftCommand { super({ inSelectionMode: false, wordNavigationType: WordNavigationType.WordStart, - id: 'cursorWordPartStartLeft', + id: 'cursorWordPartLeft', precondition: null, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.LeftArrow }, weight: KeybindingWeight.EditorContrib } }); } } +// Register previous id for compatibility purposes +CommandsRegistry.registerCommandAlias('cursorWordPartStartLeft', 'cursorWordPartLeft'); + export class CursorWordPartLeftSelect extends WordPartLeftCommand { constructor() { super({ inSelectionMode: true, wordNavigationType: WordNavigationType.WordStart, - id: 'cursorWordPartStartLeftSelect', + id: 'cursorWordPartLeftSelect', precondition: null, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.LeftArrow }, weight: KeybindingWeight.EditorContrib } }); } } +// Register previous id for compatibility purposes +CommandsRegistry.registerCommandAlias('cursorWordPartStartLeftSelect', 'cursorWordPartLeftSelect'); export class WordPartRightCommand extends MoveWordCommand { protected _move(wordSeparators: WordCharacterClassifier, model: ITextModel, position: Position, wordNavigationType: WordNavigationType): Position { @@ -121,7 +127,7 @@ export class CursorWordPartRight extends WordPartRightCommand { precondition: null, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.RightArrow }, weight: KeybindingWeight.EditorContrib } @@ -137,7 +143,7 @@ export class CursorWordPartRightSelect extends WordPartRightCommand { precondition: null, kbOpts: { kbExpr: EditorContextKeys.textInputFocus, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow, + primary: 0, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyMod.Shift | KeyCode.RightArrow }, weight: KeybindingWeight.EditorContrib } diff --git a/src/vs/editor/test/browser/controller/textAreaState.test.ts b/src/vs/editor/test/browser/controller/textAreaState.test.ts index 492ad210207..a954c51f42e 100644 --- a/src/vs/editor/test/browser/controller/textAreaState.test.ts +++ b/src/vs/editor/test/browser/controller/textAreaState.test.ts @@ -518,6 +518,20 @@ suite('TextAreaState', () => { ); }); + test('issue #49480: Double curly braces inserted', () => { + // Characters get doubled + testDeduceInput( + new TextAreaState( + 'aa', + 2, 2, + null, null + ), + 'aaa', + 3, 3, true, true, + 'a', 0 + ); + }); + suite('PagedScreenReaderStrategy', () => { function testPagedScreenReaderStrategy(lines: string[], selection: Selection, expected: TextAreaState): void { diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 438105b4d89..0642820ecfc 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -48,26 +48,21 @@ declare namespace monaco { export type TValueCallback = (value: T | PromiseLike) => void; - export type ProgressCallback = (progress: TProgress) => void; - - export class Promise { + export class Promise { constructor( executor: ( resolve: (value: T | PromiseLike) => void, - reject: (reason: any) => void, - progress: (progress: TProgress) => void) => void, + reject: (reason: any) => void) => void, oncancel?: () => void); public then( onfulfilled?: ((value: T) => TResult1 | PromiseLike) | null, - onrejected?: ((reason: any) => TResult2 | PromiseLike) | null, - onprogress?: (progress: TProgress) => void): Promise; + onrejected?: ((reason: any) => TResult2 | PromiseLike) | null): Promise; public done( onfulfilled?: (value: T) => void, - onrejected?: (reason: any) => void, - onprogress?: (progress: TProgress) => void): void; + onrejected?: (reason: any) => void): void; public cancel(): void; @@ -2508,6 +2503,22 @@ declare namespace monaco.editor { sticky?: boolean; } + /** + * Configuration options for parameter hints + */ + export interface IEditorParameterHintOptions { + /** + * Enable parameter hints. + * Defaults to true. + */ + enabled?: boolean; + /** + * Enable cycling of parameter hints. + * Defaults to false. + */ + cycle?: boolean; + } + export interface ISuggestOptions { /** * Enable graceful matching. Defaults to true. @@ -2793,9 +2804,9 @@ declare namespace monaco.editor { */ quickSuggestionsDelay?: number; /** - * Enables parameter hints + * Parameter hint options. */ - parameterHints?: boolean; + parameterHints?: IEditorParameterHintOptions; /** * Render icons in suggestions box. * Defaults to true. @@ -3130,6 +3141,11 @@ declare namespace monaco.editor { readonly snippetsPreventQuickSuggestions: boolean; } + export interface InternalParameterHintOptions { + readonly enabled: boolean; + readonly cycle: boolean; + } + export interface EditorWrappingInfo { readonly inDiffEditor: boolean; readonly isDominatedByLongLines: boolean; @@ -3194,7 +3210,7 @@ declare namespace monaco.editor { strings: boolean; }; readonly quickSuggestionsDelay: number; - readonly parameterHints: boolean; + readonly parameterHints: InternalParameterHintOptions; readonly iconsInSuggestions: boolean; readonly formatOnType: boolean; readonly formatOnPaste: boolean; @@ -5166,11 +5182,11 @@ declare namespace monaco.languages { export interface FoldingRange { /** - * The zero-based start line of the range to fold. The folded area starts after the line's last character. + * The one-based start line of the range to fold. The folded area starts after the line's last character. */ start: number; /** - * The zero-based end line of the range to fold. The folded area ends with the line's last character. + * The one-based end line of the range to fold. The folded area ends with the line's last character. */ end: number; /** diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 21c79a07b8c..f0ba370d22d 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -98,7 +98,6 @@ export class MenuId { static readonly MenubarDebugMenu = new MenuId(); static readonly MenubarNewBreakpointMenu = new MenuId(); static readonly MenubarTasksMenu = new MenuId(); - static readonly MenubarWindowMenu = new MenuId(); static readonly MenubarPreferencesMenu = new MenuId(); static readonly MenubarHelpMenu = new MenuId(); static readonly MenubarTerminalMenu = new MenuId(); diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index b5f23e0b9f3..b912a022dfc 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -46,6 +46,7 @@ export interface ICommandHandlerDescription { export interface ICommandRegistry { registerCommand(id: string, command: ICommandHandler): IDisposable; registerCommand(command: ICommand): IDisposable; + registerCommandAlias(oldId: string, newId: string): IDisposable; getCommand(id: string): ICommand; getCommands(): ICommandsMap; } @@ -99,6 +100,12 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR }); } + registerCommandAlias(oldId: string, newId: string): IDisposable { + return CommandsRegistry.registerCommand(oldId, (accessor, ...args) => { + accessor.get(ICommandService).executeCommand(newId, ...args); + }); + } + getCommand(id: string): ICommand { const list = this._commands.get(id); if (!list || list.isEmpty()) { diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index 4dbaaff6c95..6b4d876f49d 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -207,7 +207,7 @@ class WindowDriver implements IWindowDriver { return TPromise.wrapError(new Error('Xterm not found')); } - xterm._core.send(text); + xterm._core.handler(text); return TPromise.as(null); } diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index ec4a4d2e12c..52431b2e260 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -120,6 +120,7 @@ export interface IEnvironmentService { performance: boolean; // logging + log: string; logsPath: string; verbose: boolean; diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 9ef3481c43d..2224b6afd17 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -8,12 +8,12 @@ import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; import * as path from 'path'; -import URI from 'vs/base/common/uri'; import { memoize } from 'vs/base/common/decorators'; import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import { toLocalISOString } from 'vs/base/common/date'; import { isWindows, isLinux } from 'vs/base/common/platform'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; // Read this before there's any chance it is overwritten // Related to https://github.com/Microsoft/vscode/issues/30624 @@ -77,7 +77,7 @@ export class EnvironmentService implements IEnvironmentService { get args(): ParsedArgs { return this._args; } @memoize - get appRoot(): string { return path.dirname(URI.parse(require.toUrl('')).fsPath); } + get appRoot(): string { return path.dirname(getPathFromAmdModule(require, '')); } get execPath(): string { return this._execPath; } @@ -183,6 +183,7 @@ export class EnvironmentService implements IEnvironmentService { get isBuilt(): boolean { return !process.env['VSCODE_DEV']; } get verbose(): boolean { return this._args.verbose; } + get log(): string { return this._args.log; } get wait(): boolean { return this._args.wait; } get logExtensionHostCommunication(): boolean { return this._args.logExtensionHostCommunication; } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 2ff1e3bb3f2..fca1b8fcc1e 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -40,8 +40,9 @@ import { ExtensionsLifecycle } from 'vs/platform/extensionManagement/node/extens import { toErrorMessage } from 'vs/base/common/errorMessage'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isEngineValid } from 'vs/platform/extensions/node/extensionValidator'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; -const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); +const SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions')); const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index f2e7a4cc229..1bd96969e12 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -26,8 +26,12 @@ import { Schemas } from 'vs/base/common/network'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; interface ISerializedRecentlyOpened { - workspaces: (IWorkspaceIdentifier | string | UriComponents)[]; - files: (string | UriComponents)[]; + workspaces2: (IWorkspaceIdentifier | string)[]; // IWorkspaceIdentifier or URI.toString() + files: string[]; +} + +interface ILegacySerializedRecentlyOpened { + workspaces: (IWorkspaceIdentifier | string | UriComponents)[]; // legacy (UriComponents was also supported for a few insider builds) } export class HistoryMainService implements IHistoryMainService { @@ -255,16 +259,37 @@ export class HistoryMainService implements IHistoryMainService { } private getRecentlyOpenedFromStorage(): IRecentlyOpened { - const storedRecents: ISerializedRecentlyOpened = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey) || { workspaces: [], files: [] }; + const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); const result: IRecentlyOpened = { workspaces: [], files: [] }; - for (const workspace of storedRecents.workspaces) { - if (typeof workspace === 'string') { - // folder paths were strings <= 1.25 - result.workspaces.push(URI.file(workspace)); - } else if (isWorkspaceIdentifier(workspace)) { - result.workspaces.push(workspace); - } else { - result.workspaces.push(URI.revive(workspace)); + if (storedRecents) { + if (Array.isArray(storedRecents.workspaces2)) { + for (const workspace of storedRecents.workspaces2) { + if (isWorkspaceIdentifier(workspace)) { + result.workspaces.push(workspace); + } else if (typeof workspace === 'string') { + result.workspaces.push(URI.parse(workspace)); + } + } + } else if (Array.isArray(storedRecents.workspaces)) { + // TODO legacy support can be removed at some point (6 month?) + // format of 1.25 and before + for (const workspace of storedRecents.workspaces) { + if (typeof workspace === 'string') { + result.workspaces.push(URI.file(workspace)); + } else if (isWorkspaceIdentifier(workspace)) { + result.workspaces.push(workspace); + } else if (workspace && typeof workspace.path === 'string' && typeof workspace.scheme === 'string') { + // added by 1.26-insiders + result.workspaces.push(URI.revive(workspace)); + } + } + } + if (Array.isArray(storedRecents.files)) { + for (const file of storedRecents.files) { + if (typeof file === 'string') { + result.files.push(file); + } + } } } for (const file of storedRecents.files) { @@ -279,18 +304,18 @@ export class HistoryMainService implements IHistoryMainService { } private saveRecentlyOpened(recent: IRecentlyOpened): void { - const serialized: ISerializedRecentlyOpened = { workspaces: [], files: [] }; + const serialized: ISerializedRecentlyOpened = { workspaces2: [], files: [] }; for (const workspace of recent.workspaces) { if (isSingleFolderWorkspaceIdentifier(workspace)) { - serialized.workspaces.push(workspace.toJSON()); + serialized.workspaces2.push(workspace.toString()); } else { - serialized.workspaces.push(workspace); + serialized.workspaces2.push(workspace); } } for (const file of recent.files) { - serialized.workspaces.push(file.toJSON()); + serialized.files.push(file.toString()); } - this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, recent); + this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, serialized); } updateWindowsJumpList(): void { diff --git a/src/vs/platform/keybinding/common/abstractKeybindingService.ts b/src/vs/platform/keybinding/common/abstractKeybindingService.ts index 075cb92fe87..4c6c989f169 100644 --- a/src/vs/platform/keybinding/common/abstractKeybindingService.ts +++ b/src/vs/platform/keybinding/common/abstractKeybindingService.ts @@ -5,7 +5,7 @@ 'use strict'; import * as nls from 'vs/nls'; -import { ResolvedKeybinding, Keybinding } from 'vs/base/common/keyCodes'; +import { ResolvedKeybinding, Keybinding, KeyCode } from 'vs/base/common/keyCodes'; import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { KeybindingResolver, IResolveResult } from 'vs/platform/keybinding/common/keybindingResolver'; @@ -204,4 +204,18 @@ export abstract class AbstractKeybindingService extends Disposable implements IK return shouldPreventDefault; } + + mightProducePrintableCharacter(event: IKeyboardEvent): boolean { + if (event.ctrlKey || event.metaKey) { + // ignore ctrl/cmd-combination but not shift/alt-combinatios + return false; + } + // weak check for certain ranges. this is properly implemented in a subclass + // with access to the KeyboardMapperFactory. + if ((event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z) + || (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9)) { + return true; + } + return false; + } } diff --git a/src/vs/platform/keybinding/common/keybinding.ts b/src/vs/platform/keybinding/common/keybinding.ts index c491ec7a8d0..7110738c198 100644 --- a/src/vs/platform/keybinding/common/keybinding.ts +++ b/src/vs/platform/keybinding/common/keybinding.ts @@ -77,5 +77,11 @@ export interface IKeybindingService { getKeybindings(): ResolvedKeybindingItem[]; customKeybindingsCount(): number; + + /** + * Will the given key event produce a character that's rendered on screen, e.g. in a + * text box. *Note* that the results of this function can be incorrect. + */ + mightProducePrintableCharacter(event: IKeyboardEvent): boolean; } diff --git a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts index 19f285354cc..25dc83e6d4d 100644 --- a/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingLabels.test.ts @@ -55,24 +55,24 @@ suite('KeybindingLabels', () => { assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyCode.KEY_A, 'Ctrl+A'); assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyCode.KEY_A, 'Shift+A'); assertUSLabel(OperatingSystem.Linux, KeyMod.Alt | KeyCode.KEY_A, 'Alt+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.WinCtrl | KeyCode.KEY_A, 'Windows+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.WinCtrl | KeyCode.KEY_A, 'Super+A'); // two modifiers assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_A, 'Ctrl+Shift+A'); assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Alt+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Windows+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Super+A'); assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Shift+Alt+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Windows+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Alt+Windows+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Super+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Alt+Super+A'); // three modifiers assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_A, 'Ctrl+Shift+Alt+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Windows+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Alt+Windows+A'); - assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Alt+Windows+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Super+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Alt+Super+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Shift+Alt+Super+A'); // four modifiers - assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Windows+A'); + assertUSLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Ctrl+Shift+Alt+Super+A'); // chord assertUSLabel(OperatingSystem.Linux, KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_A, KeyMod.CtrlCmd | KeyCode.KEY_B), 'Ctrl+A Ctrl+B'); @@ -122,7 +122,7 @@ suite('KeybindingLabels', () => { } assertAriaLabel(OperatingSystem.Windows, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Windows+A'); - assertAriaLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Windows+A'); + assertAriaLabel(OperatingSystem.Linux, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Super+A'); assertAriaLabel(OperatingSystem.Macintosh, KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyMod.WinCtrl | KeyCode.KEY_A, 'Control+Shift+Alt+Command+A'); }); diff --git a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts index 8a44c7bd8db..a8b00dfb030 100644 --- a/src/vs/platform/keybinding/test/common/mockKeybindingService.ts +++ b/src/vs/platform/keybinding/test/common/mockKeybindingService.ts @@ -124,4 +124,8 @@ export class MockKeybindingService implements IKeybindingService { dispatchEvent(e: IKeyboardEvent, target: IContextKeyServiceTarget): boolean { return false; } + + mightProducePrintableCharacter(e: IKeyboardEvent): boolean { + return false; + } } diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index 19f6c28b08a..8df85fe9180 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -31,6 +31,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { onUnexpectedError, canceled } from 'vs/base/common/errors'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; export type ListWidget = List | PagedList | ITree; @@ -578,7 +579,8 @@ export class HighlightingTreeController extends WorkbenchTreeController { constructor( options: IControllerOptions, private readonly onType: () => any, - @IConfigurationService configurationService: IConfigurationService + @IConfigurationService configurationService: IConfigurationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, ) { super(options, configurationService); } @@ -591,16 +593,7 @@ export class HighlightingTreeController extends WorkbenchTreeController { if (this.upKeyBindingDispatcher.has(event.keyCode)) { return false; } - if (event.ctrlKey || event.metaKey) { - // ignore ctrl/cmd-combination but not shift/alt-combinatios - return false; - } - // crazy -> during keydown focus moves to the input box - // and because of that the keyup event is handled by the - // input field - if (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z) { - // todo@joh this is much weaker than using the KeyboardMapperFactory - // but due to layering-challanges that's not available here... + if (this._keybindingService.mightProducePrintableCharacter(event)) { this.onType(); return true; } @@ -748,20 +741,16 @@ configurationRegistry.registerConfiguration({ '- `ctrlCmd` refers to a value the setting can take and should not be localized.', '- `Control` and `Command` refer to the modifier keys Ctrl or Cmd on the keyboard and can be localized.' ] - }, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). `ctrlCmd` maps to `Control` on Windows and Linux and to `Command` on macOS. The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.") + }, "The modifier to be used to add an item in trees and lists to a multi-selection with the mouse (for example in the explorer, open editors and scm view). The 'Open to Side' mouse gestures - if supported - will adapt such that they do not conflict with the multiselect modifier.") }, [openModeSettingKey]: { 'type': 'string', 'enum': ['singleClick', 'doubleClick'], - 'enumDescriptions': [ - localize('openMode.singleClick', "Opens items on mouse single click."), - localize('openMode.doubleClick', "Open items on mouse double click.") - ], 'default': 'singleClick', 'description': localize({ key: 'openModeModifier', comment: ['`singleClick` and `doubleClick` refers to a value the setting can take and should not be localized.'] - }, "Controls how to open items in trees and lists using the mouse (if supported). Set to `singleClick` to open items with a single mouse click and `doubleClick` to only open via mouse double click. For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ") + }, "Controls how to open items in trees and lists using the mouse (if supported). For parents with children in trees, this setting will control if a single click expands the parent or a double click. Note that some trees and lists might choose to ignore this setting if it is not applicable. ") }, [horizontalScrollingKey]: { 'type': 'boolean', diff --git a/src/vs/platform/markers/test/common/markerService.test.ts b/src/vs/platform/markers/test/common/markerService.test.ts index 06495c239e4..25cb2e933a2 100644 --- a/src/vs/platform/markers/test/common/markerService.test.ts +++ b/src/vs/platform/markers/test/common/markerService.test.ts @@ -58,16 +58,16 @@ suite('Marker Service', () => { test('changeOne override', () => { let service = new markerService.MarkerService(); - service.changeOne('far', URI.parse('/path/only.cs'), [randomMarkerData()]); + service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); assert.equal(service.read().length, 1); assert.equal(service.read({ owner: 'far' }).length, 1); - service.changeOne('boo', URI.parse('/path/only.cs'), [randomMarkerData()]); + service.changeOne('boo', URI.parse('file:///path/only.cs'), [randomMarkerData()]); assert.equal(service.read().length, 2); assert.equal(service.read({ owner: 'far' }).length, 1); assert.equal(service.read({ owner: 'boo' }).length, 1); - service.changeOne('far', URI.parse('/path/only.cs'), [randomMarkerData(), randomMarkerData()]); + service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData(), randomMarkerData()]); assert.equal(service.read({ owner: 'far' }).length, 2); assert.equal(service.read({ owner: 'boo' }).length, 1); @@ -76,13 +76,13 @@ suite('Marker Service', () => { test('changeOne/All clears', () => { let service = new markerService.MarkerService(); - service.changeOne('far', URI.parse('/path/only.cs'), [randomMarkerData()]); - service.changeOne('boo', URI.parse('/path/only.cs'), [randomMarkerData()]); + service.changeOne('far', URI.parse('file:///path/only.cs'), [randomMarkerData()]); + service.changeOne('boo', URI.parse('file:///path/only.cs'), [randomMarkerData()]); assert.equal(service.read({ owner: 'far' }).length, 1); assert.equal(service.read({ owner: 'boo' }).length, 1); assert.equal(service.read().length, 2); - service.changeOne('far', URI.parse('/path/only.cs'), []); + service.changeOne('far', URI.parse('file:///path/only.cs'), []); assert.equal(service.read({ owner: 'far' }).length, 0); assert.equal(service.read({ owner: 'boo' }).length, 1); assert.equal(service.read().length, 1); diff --git a/src/vs/platform/menubar/common/menubar.ts b/src/vs/platform/menubar/common/menubar.ts index 817b099cc32..3e58c3e4c9b 100644 --- a/src/vs/platform/menubar/common/menubar.ts +++ b/src/vs/platform/menubar/common/menubar.ts @@ -13,7 +13,7 @@ export const IMenubarService = createDecorator('menubarService' export interface IMenubarService { _serviceBrand: any; - updateMenubar(windowId: number, menus: IMenubarData): TPromise; + updateMenubar(windowId: number, menus: IMenubarData, additionalKeybindings?: Array): TPromise; } export interface IMenubarData { diff --git a/src/vs/platform/menubar/common/menubarIpc.ts b/src/vs/platform/menubar/common/menubarIpc.ts index 13699176ef5..25a28a13845 100644 --- a/src/vs/platform/menubar/common/menubarIpc.ts +++ b/src/vs/platform/menubar/common/menubarIpc.ts @@ -6,7 +6,7 @@ 'use strict'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService, IMenubarData, IMenubarKeybinding } from 'vs/platform/menubar/common/menubar'; import { Event } from 'vs/base/common/event'; export interface IMenubarChannel extends IChannel { @@ -24,7 +24,7 @@ export class MenubarChannel implements IMenubarChannel { call(command: string, arg?: any): TPromise { switch (command) { - case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1]); + case 'updateMenubar': return this.service.updateMenubar(arg[0], arg[1], arg[2]); } return undefined; } @@ -36,7 +36,7 @@ export class MenubarChannelClient implements IMenubarService { constructor(private channel: IMenubarChannel) { } - updateMenubar(windowId: number, menus: IMenubarData): TPromise { - return this.channel.call('updateMenubar', [windowId, menus]); + updateMenubar(windowId: number, menus: IMenubarData, additionalKeybindings?: Array): TPromise { + return this.channel.call('updateMenubar', [windowId, menus, additionalKeybindings]); } } \ No newline at end of file diff --git a/src/vs/platform/menubar/electron-main/menubarService.ts b/src/vs/platform/menubar/electron-main/menubarService.ts index a24dd49126b..7d7997c6812 100644 --- a/src/vs/platform/menubar/electron-main/menubarService.ts +++ b/src/vs/platform/menubar/electron-main/menubarService.ts @@ -5,11 +5,12 @@ 'use strict'; -import { IMenubarService, IMenubarData } from 'vs/platform/menubar/common/menubar'; +import { IMenubarService, IMenubarData, IMenubarKeybinding } from 'vs/platform/menubar/common/menubar'; import { Menubar } from 'vs/code/electron-main/menubar'; import { ILogService } from 'vs/platform/log/common/log'; import { TPromise } from 'vs/base/common/winjs.base'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; export class MenubarService implements IMenubarService { _serviceBrand: any; @@ -21,14 +22,16 @@ export class MenubarService implements IMenubarService { @ILogService private logService: ILogService ) { // Install Menu - this._menubar = this.instantiationService.createInstance(Menubar); + if (isMacintosh && isWindows) { + this._menubar = this.instantiationService.createInstance(Menubar); + } } - updateMenubar(windowId: number, menus: IMenubarData): TPromise { + updateMenubar(windowId: number, menus: IMenubarData, additionalKeybindings?: Array): TPromise { this.logService.trace('menubarService#updateMenubar', windowId); if (this._menubar) { - this._menubar.updateMenu(menus, windowId); + this._menubar.updateMenu(menus, windowId, additionalKeybindings); } return TPromise.as(null); diff --git a/src/vs/platform/node/package.ts b/src/vs/platform/node/package.ts index fff85d911f2..93c32bc7117 100644 --- a/src/vs/platform/node/package.ts +++ b/src/vs/platform/node/package.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import uri from 'vs/base/common/uri'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IPackageConfiguration { name: string; version: string; } -const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath); +const rootPath = path.dirname(getPathFromAmdModule(require, '')); const packageJsonPath = path.join(rootPath, 'package.json'); -export default require.__$__nodeRequire(packageJsonPath) as IPackageConfiguration; \ No newline at end of file +export default require.__$__nodeRequire(packageJsonPath) as IPackageConfiguration; diff --git a/src/vs/platform/node/product.ts b/src/vs/platform/node/product.ts index b4ed6f55eab..dbb84fd473f 100644 --- a/src/vs/platform/node/product.ts +++ b/src/vs/platform/node/product.ts @@ -4,12 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import * as path from 'path'; -import uri from 'vs/base/common/uri'; +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; @@ -86,7 +90,7 @@ export interface ISurveyData { userProbability: number; } -const rootPath = path.dirname(uri.parse(require.toUrl('')).fsPath); +const rootPath = path.dirname(getPathFromAmdModule(require, '')); const productJsonPath = path.join(rootPath, 'product.json'); const product = require.__$__nodeRequire(productJsonPath) as IProductConfiguration; diff --git a/src/vs/platform/quickinput/common/quickInput.ts b/src/vs/platform/quickinput/common/quickInput.ts index 0ec12af97a1..8b88046261d 100644 --- a/src/vs/platform/quickinput/common/quickInput.ts +++ b/src/vs/platform/quickinput/common/quickInput.ts @@ -16,6 +16,8 @@ export interface IQuickPickItem { label: string; description?: string; detail?: string; + iconClasses?: string[]; + buttons?: IQuickInputButton[]; picked?: boolean; } @@ -50,7 +52,23 @@ export interface IPickOptions { */ canPickMany?: boolean; + /** + * enables quick navigate in the picker to open an element without typing + */ + quickNavigate?: IQuickNavigateConfiguration; + + /** + * a context key to set when this picker is active + */ + contextKey?: string; + + /** + * an optional property for the item to focus initially. + */ + activeItem?: TPromise | T; + onDidFocus?: (entry: T) => void; + onDidTriggerItemButton?: (context: IQuickPickItemButtonContext) => void; } export interface IInputOptions { @@ -98,6 +116,8 @@ export interface IQuickInput { enabled: boolean; + contextKey: string | undefined; + busy: boolean; ignoreFocusOut: boolean; @@ -125,6 +145,8 @@ export interface IQuickPick extends IQuickInput { readonly onDidTriggerButton: Event; + readonly onDidTriggerItemButton: Event>; + items: ReadonlyArray; canSelectMany: boolean; @@ -133,6 +155,8 @@ export interface IQuickPick extends IQuickInput { matchOnDetail: boolean; + quickNavigate: IQuickNavigateConfiguration | undefined; + activeItems: ReadonlyArray; readonly onDidChangeActive: Event; @@ -166,12 +190,24 @@ export interface IInputBox extends IQuickInput { } export interface IQuickInputButton { - iconPath: { dark: URI; light?: URI; }; - tooltip?: string | undefined; + iconPath?: { dark: URI; light?: URI; }; + iconClass?: string; + tooltip?: string; +} + +export interface IQuickPickItemButtonEvent { + button: IQuickInputButton; + item: T; +} + +export interface IQuickPickItemButtonContext extends IQuickPickItemButtonEvent { + removeItem(): void; } export const IQuickInputService = createDecorator('quickInputService'); +export type Omit = Pick>; + export interface IQuickInputService { _serviceBrand: any; @@ -179,7 +215,9 @@ export interface IQuickInputService { /** * Opens the quick input box for selecting items and returns a promise with the user selected item(s) if any. */ - pick>(picks: TPromise, options?: O, token?: CancellationToken): TPromise; + pick(picks: TPromise | T[], options?: IPickOptions & { canPickMany: true }, token?: CancellationToken): TPromise; + pick(picks: TPromise | T[], options?: IPickOptions & { canPickMany: false }, token?: CancellationToken): TPromise; + pick(picks: TPromise | T[], options?: Omit, 'canPickMany'>, token?: CancellationToken): TPromise; /** * Opens the quick input box for text input and returns a promise with the user typed value if any. diff --git a/src/vs/platform/telemetry/common/telemetryService.ts b/src/vs/platform/telemetry/common/telemetryService.ts index f30292529f7..72b7d856930 100644 --- a/src/vs/platform/telemetry/common/telemetryService.ts +++ b/src/vs/platform/telemetry/common/telemetryService.ts @@ -166,7 +166,7 @@ Registry.as(Extensions.Configuration).registerConfigurat 'properties': { 'telemetry.enableTelemetry': { 'type': 'boolean', - 'description': localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to Microsoft."), + 'description': localize('telemetry.enableTelemetry', "Enable usage data and errors to be sent to a Microsoft online service."), 'default': true, 'tags': ['usesOnlineServices'] } diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index a68ff28367b..83a4326ca5e 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -113,7 +113,8 @@ const configurationValueWhitelist = [ 'editor.multiCursorModifier', 'editor.quickSuggestions', 'editor.quickSuggestionsDelay', - 'editor.parameterHints', + 'editor.parameterHints.enabled', + 'editor.parameterHints.cycle', 'editor.autoClosingBrackets', 'editor.autoIndent', 'editor.formatOnType', diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index d2f8fd97639..8d984bd6f21 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -163,8 +163,8 @@ export const selectionBackground = registerColor('selection.background', { light // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hc: Color.black }, nls.localize('textSeparatorForeground', "Color for text separators.")); -export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hc: '#006AB1' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); -export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#007acc', dark: '#3794FF', hc: '#007acc' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); +export const textLinkForeground = registerColor('textLink.foreground', { light: '#006AB1', dark: '#3794FF', hc: '#3794FF' }, nls.localize('textLinkForeground', "Foreground color for links in text.")); +export const textLinkActiveForeground = registerColor('textLink.activeForeground', { light: '#006AB1', dark: '#3794FF', hc: '#3794FF' }, nls.localize('textLinkActiveForeground', "Foreground color for links in text when clicked on and on mouse hover.")); export const textPreformatForeground = registerColor('textPreformat.foreground', { light: '#A31515', dark: '#D7BA7D', hc: '#D7BA7D' }, nls.localize('textPreformatForeground', "Foreground color for preformatted text segments.")); export const textBlockQuoteBackground = registerColor('textBlockQuote.background', { light: '#7f7f7f1a', dark: '#7f7f7f1a', hc: null }, nls.localize('textBlockQuoteBackground', "Background color for block quotes in text.")); export const textBlockQuoteBorder = registerColor('textBlockQuote.border', { light: '#007acc80', dark: '#007acc80', hc: Color.white }, nls.localize('textBlockQuoteBorder', "Border color for block quotes in text.")); @@ -195,7 +195,7 @@ export const listFocusBackground = registerColor('list.focusBackground', { dark: export const listFocusForeground = registerColor('list.focusForeground', { dark: null, light: null, hc: null }, nls.localize('listFocusForeground', "List/Tree foreground color for the focused item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionBackground = registerColor('list.activeSelectionBackground', { dark: '#094771', light: '#2477CE', hc: null }, nls.localize('listActiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); export const listActiveSelectionForeground = registerColor('list.activeSelectionForeground', { dark: Color.white, light: Color.white, hc: null }, nls.localize('listActiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is active. An active list/tree has keyboard focus, an inactive does not.")); -export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#CCCEDB', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); +export const listInactiveSelectionBackground = registerColor('list.inactiveSelectionBackground', { dark: '#37373D', light: '#dddfea', hc: null }, nls.localize('listInactiveSelectionBackground', "List/Tree background color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveSelectionForeground = registerColor('list.inactiveSelectionForeground', { dark: null, light: null, hc: null }, nls.localize('listInactiveSelectionForeground', "List/Tree foreground color for the selected item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listInactiveFocusBackground = registerColor('list.inactiveFocusBackground', { dark: '#313135', light: '#d8dae6', hc: null }, nls.localize('listInactiveFocusBackground', "List/Tree background color for the focused item when the list/tree is inactive. An active list/tree has keyboard focus, an inactive does not.")); export const listHoverBackground = registerColor('list.hoverBackground', { dark: '#2A2D2E', light: '#F0F0F0', hc: null }, nls.localize('listHoverBackground', "List/Tree background when hovering over items using the mouse.")); @@ -223,10 +223,10 @@ export const scrollbarSliderActiveBackground = registerColor('scrollbarSlider.ac export const progressBarBackground = registerColor('progressBar.background', { dark: Color.fromHex('#0E70C0'), light: Color.fromHex('#0E70C0'), hc: contrastBorder }, nls.localize('progressBarBackground', "Background color of the progress bar that can show for long running operations.")); -export const breadcrumbsForeground = registerColor('breadcrumb.breadcrumbsForeground', { light: Color.fromHex('#6C6C6C').transparent(.7), dark: Color.fromHex('#CCCCCC').transparent(.7), hc: Color.white.transparent(.7) }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); -export const breadcrumbsFocusForeground = registerColor('breadcrumb.breadcrumbsFocusForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); -export const breadcrumbsActiveSelectionForeground = registerColor('breadcrumb.breadcrumbsActiveSelectionForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsSelectedForegound', "Color of selected breadcrumb items.")); -export const breadcrumbsPickerBackground = registerColor('breadcrumb.breadcrumbsPickerBackground', { light: '#ECECEC', dark: '#252526', hc: Color.black }, nls.localize('breadcrumbsSelectedBackground', "Background color of breadcrumb item picker.")); +export const breadcrumbsForeground = registerColor('breadcrumb.foreground', { light: Color.fromHex('#6C6C6C').transparent(.7), dark: Color.fromHex('#CCCCCC').transparent(.7), hc: Color.white.transparent(.7) }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); +export const breadcrumbsFocusForeground = registerColor('breadcrumb.focusForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsFocusForeground', "Color of focused breadcrumb items.")); +export const breadcrumbsActiveSelectionForeground = registerColor('breadcrumb.activeSelectionForeground', { light: '#6C6C6C', dark: '#CCCCCC', hc: Color.white }, nls.localize('breadcrumbsSelectedForegound', "Color of selected breadcrumb items.")); +export const breadcrumbsPickerBackground = registerColor('breadcrumbPicker.background', { light: '#ECECEC', dark: '#252526', hc: Color.black }, nls.localize('breadcrumbsSelectedBackground', "Background color of breadcrumb item picker.")); /** * Editor background color. diff --git a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts index 39858929cb5..e5658bcaf38 100644 --- a/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts +++ b/src/vs/platform/theme/test/electron-browser/colorRegistry.releaseTest.ts @@ -18,6 +18,7 @@ import { request, asText } from 'vs/base/node/request'; import * as pfs from 'vs/base/node/pfs'; import * as path from 'path'; import * as assert from 'assert'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; interface ColorInfo { @@ -103,7 +104,7 @@ function getDescription(color: ColorContribution) { } async function getColorsFromExtension(): Promise<{ [id: string]: string }> { - let extPath = require.toUrl('../../../../../../extensions'); + let extPath = getPathFromAmdModule(require, '../../../../../../extensions'); let extFolders = await pfs.readDirsInDir(extPath); let result: { [id: string]: string } = Object.create(null); for (let folder of extFolders) { @@ -127,4 +128,4 @@ async function getColorsFromExtension(): Promise<{ [id: string]: string }> { } return result; -} \ No newline at end of file +} diff --git a/src/vs/platform/update/node/update.config.contribution.ts b/src/vs/platform/update/node/update.config.contribution.ts index 7c4cc968e67..7ad60bdfc4f 100644 --- a/src/vs/platform/update/node/update.config.contribution.ts +++ b/src/vs/platform/update/node/update.config.contribution.ts @@ -21,20 +21,20 @@ configurationRegistry.registerConfiguration({ 'enum': ['none', 'default'], 'default': 'default', 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change."), + 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change. The updates are fetched from an online service."), 'tags': ['usesOnlineServices'] }, 'update.enableWindowsBackgroundUpdates': { 'type': 'boolean', 'default': true, 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates."), + 'description': nls.localize('enableWindowsBackgroundUpdates', "Enables Windows background updates. The updates are fetched from an online service."), 'tags': ['usesOnlineServices'] }, 'update.showReleaseNotes': { 'type': 'boolean', 'default': true, - 'description': nls.localize('showReleaseNotes', "Show Release Notes after an update."), + 'description': nls.localize('showReleaseNotes', "Show Release Notes after an update. The Release Notes are fetched from an online service."), 'tags': ['usesOnlineServices'] } } diff --git a/src/vs/platform/url/common/urlIpc.ts b/src/vs/platform/url/common/urlIpc.ts index 84fdc5c2e65..5cc4e919f95 100644 --- a/src/vs/platform/url/common/urlIpc.ts +++ b/src/vs/platform/url/common/urlIpc.ts @@ -39,7 +39,7 @@ export class URLServiceChannelClient implements IURLService { constructor(private channel: IChannel) { } - open(url: URI): TPromise { + open(url: URI): TPromise { return this.channel.call('open', url.toJSON()); } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 5a5dd0c6fdf..f00a214d5c5 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -143,6 +143,7 @@ export interface IWindowsService { relaunch(options: { addArgs?: string[], removeArgs?: string[] }): TPromise; // macOS Native Tabs + newWindowTab(): TPromise; showPreviousWindowTab(): TPromise; showNextWindowTab(): TPromise; moveWindowTabToNewWindow(): TPromise; @@ -173,7 +174,6 @@ export interface IWindowsService { // TODO: this is a bit backwards startCrashReporter(config: CrashReporterStartOptions): TPromise; - openAccessibilityOptions(): TPromise; openAboutDialog(): TPromise; } diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 30f4085be7d..c884a48217c 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -44,6 +44,7 @@ export interface IWindowsChannel extends IChannel { call(command: 'removeFromRecentlyOpened', arg: (IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier | URI)[]): TPromise; call(command: 'clearRecentlyOpened'): TPromise; call(command: 'getRecentlyOpened', arg: number): TPromise; + call(command: 'newWindowTab'): TPromise; call(command: 'showPreviousWindowTab'): TPromise; call(command: 'showNextWindowTab'): TPromise; call(command: 'moveWindowTabToNewWindow'): TPromise; @@ -73,7 +74,6 @@ export interface IWindowsChannel extends IChannel { call(command: 'getActiveWindowId'): TPromise; call(command: 'openExternal', arg: string): TPromise; call(command: 'startCrashReporter', arg: CrashReporterStartOptions): TPromise; - call(command: 'openAccessibilityOptions'): TPromise; call(command: 'openAboutDialog'): TPromise; } @@ -148,6 +148,7 @@ export class WindowsChannel implements IWindowsChannel { return this.service.removeFromRecentlyOpened(paths); } case 'clearRecentlyOpened': return this.service.clearRecentlyOpened(); + case 'newWindowTab': return this.service.newWindowTab(); case 'showPreviousWindowTab': return this.service.showPreviousWindowTab(); case 'showNextWindowTab': return this.service.showNextWindowTab(); case 'moveWindowTabToNewWindow': return this.service.moveWindowTabToNewWindow(); @@ -178,7 +179,6 @@ export class WindowsChannel implements IWindowsChannel { case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); - case 'openAccessibilityOptions': return this.service.openAccessibilityOptions(); case 'openAboutDialog': return this.service.openAboutDialog(); } return undefined; @@ -283,6 +283,10 @@ export class WindowsChannelClient implements IWindowsService { }); } + newWindowTab(): TPromise { + return this.channel.call('newWindowTab'); + } + showPreviousWindowTab(): TPromise { return this.channel.call('showPreviousWindowTab'); } @@ -399,10 +403,6 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('updateTouchBar', [windowId, items]); } - openAccessibilityOptions(): TPromise { - return this.channel.call('openAccessibilityOptions'); - } - openAboutDialog(): TPromise { return this.channel.call('openAboutDialog'); } diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index 1c38b451768..20b7711912c 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -48,6 +48,8 @@ export interface ICodeWindow { readyState: ReadyState; ready(): TPromise; + addTabbedWindow(window: ICodeWindow): void; + load(config: IWindowConfiguration, isReload?: boolean, disableExtensions?: boolean): void; reload(configuration?: IWindowConfiguration, cli?: ParsedArgs): void; @@ -110,6 +112,7 @@ export interface IWindowsMainService { getLastActiveWindow(): ICodeWindow; waitForWindowCloseOrLoad(windowId: number): TPromise; openNewWindow(context: OpenContext): ICodeWindow[]; + openNewTabbedWindow(context: OpenContext): ICodeWindow[]; sendToFocused(channel: string, ...args: any[]): void; sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void; getFocusedWindow(): ICodeWindow; @@ -127,6 +130,7 @@ export interface IOpenConfiguration { urisToOpen?: URI[]; preferNewWindow?: boolean; forceNewWindow?: boolean; + forceNewTabbedWindow?: boolean; forceReuseWindow?: boolean; forceEmpty?: boolean; diffMode?: boolean; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 58674c4a4c9..491a34db330 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -13,7 +13,7 @@ import URI from 'vs/base/common/uri'; import product from 'vs/platform/node/product'; import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IDevToolsOptions } from 'vs/platform/windows/common/windows'; import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; -import { shell, crashReporter, app, Menu, clipboard, BrowserWindow } from 'electron'; +import { shell, crashReporter, app, Menu, clipboard } from 'electron'; import { Event, fromNodeEventEmitter, mapEvent, filterEvent, anyEvent, latch } from 'vs/base/common/event'; import { IURLService, IURLHandler } from 'vs/platform/url/common/url'; import { ILifecycleService } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; @@ -258,6 +258,14 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(this.historyService.getRecentlyOpened()); } + newWindowTab(): TPromise { + this.logService.trace('windowsService#newWindowTab'); + + this.windowsMainService.openNewTabbedWindow(OpenContext.API); + + return TPromise.as(void 0); + } + showPreviousWindowTab(): TPromise { this.logService.trace('windowsService#showPreviousWindowTab'); Menu.sendActionToFirstResponder('selectPreviousTab:'); @@ -413,7 +421,9 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable openNewWindow(): TPromise { this.logService.trace('windowsService#openNewWindow'); + this.windowsMainService.openNewWindow(OpenContext.API); + return TPromise.as(null); } @@ -491,29 +501,6 @@ export class WindowsService implements IWindowsService, IURLHandler, IDisposable return TPromise.as(null); } - openAccessibilityOptions(): TPromise { - this.logService.trace('windowsService#openAccessibilityOptions'); - - const win = new BrowserWindow({ - alwaysOnTop: true, - skipTaskbar: true, - resizable: false, - width: 450, - height: 300, - show: true, - title: nls.localize('accessibilityOptionsWindowTitle', "Accessibility Options"), - webPreferences: { - disableBlinkFeatures: 'Auxclick' - } - }); - - win.setMenuBarVisibility(false); - - win.loadURL('chrome://accessibility'); - - return TPromise.as(null); - } - openAboutDialog(): TPromise { this.logService.trace('windowsService#openAboutDialog'); const lastActiveWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); diff --git a/src/vs/platform/workspace/common/workspace.ts b/src/vs/platform/workspace/common/workspace.ts index 4d2a8cc88d5..35d10686eb3 100644 --- a/src/vs/platform/workspace/common/workspace.ts +++ b/src/vs/platform/workspace/common/workspace.ts @@ -234,7 +234,7 @@ export class WorkspaceFolder implements IWorkspaceFolder { } toResource(relativePath: string): URI { - return this.uri.with({ path: paths.join(this.uri.path, relativePath) }); + return resources.joinPath(this.uri, relativePath); } toJSON(): IWorkspaceFolderData { @@ -278,7 +278,7 @@ function toUri(path: string, relativeTo: URI): URI { return URI.file(path); } if (relativeTo) { - return relativeTo.with({ path: paths.join(relativeTo.path, path) }); + return resources.joinPath(relativeTo, path); } } return null; diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 85c30a83a6b..71081b8f94a 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -3552,6 +3552,7 @@ declare module 'vscode' { * [Region](#FoldingRangeKind.Region). The kind is used to categorize folding ranges and used by commands * like 'Fold all comments'. See * [FoldingRangeKind](#FoldingRangeKind) for an enumeration of all kinds. + * If not set, the range is originated from a syntax element. */ kind?: FoldingRangeKind; @@ -3566,7 +3567,10 @@ declare module 'vscode' { } /** - * An enumeration of all folding range kinds. The kind is used to categorize folding ranges. + * An enumeration of specific folding range kinds. The kind is an optional field of a [FoldingRange](#FoldingRange) + * and is used to distinguish specific folding ranges such as ranges originated from comments. The kind is used by commands like + * `Fold all comments` or `Fold all regions`. + * If the kind is not set on the range, the range originated from a syntax element other than comments, imports or region markers. */ export enum FoldingRangeKind { /** @@ -3578,7 +3582,7 @@ declare module 'vscode' { */ Imports = 2, /** - * Kind for folding range representing regions (for example a folding range marked by `#region` and `#endregion`). + * Kind for folding range representing regions originating from folding markers like `#region` and `#endregion`. */ Region = 3 } @@ -6888,7 +6892,7 @@ declare module 'vscode' { /** * Predefined buttons for [QuickPick](#QuickPick) and [InputBox](#InputBox). */ - export namespace QuickInputButtons { + export class QuickInputButtons { /** * A back button for [QuickPick](#QuickPick) and [InputBox](#InputBox). @@ -6896,7 +6900,12 @@ declare module 'vscode' { * When a navigation 'back' button is needed this one should be used for consistency. * It comes with a predefined icon, tooltip and location. */ - export const Back: QuickInputButton; + static readonly Back: QuickInputButton; + + /** + * @hidden + */ + private constructor(); } /** diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index efd2ed56157..03f3b993f60 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -70,24 +70,24 @@ declare module 'vscode' { * Whether external files that exclude files, like .gitignore, should be respected. * See the vscode setting `"search.useIgnoreFiles"`. */ - useIgnoreFiles?: boolean; + useIgnoreFiles: boolean; /** * Whether symlinks should be followed while searching. * See the vscode setting `"search.followSymlinks"`. */ - followSymlinks?: boolean; - - /** - * The maximum number of results to be returned. - */ - maxResults?: number; + followSymlinks: boolean; } /** * Options that apply to text search. */ export interface TextSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + /** * TODO@roblou - total length? # of context lines? leading and trailing # of chars? */ @@ -118,7 +118,17 @@ declare module 'vscode' { /** * Options that apply to file search. */ - export interface FileSearchOptions extends SearchOptions { } + export interface FileSearchOptions extends SearchOptions { + /** + * The maximum number of results to be returned. + */ + maxResults: number; + } + + /** + * Options that apply to requesting the file index. + */ + export interface FileIndexOptions extends SearchOptions { } export interface TextSearchResultPreview { /** @@ -153,23 +163,36 @@ declare module 'vscode' { preview: TextSearchResultPreview; } + /** + * A FileIndexProvider provides a list of files in the given folder. VS Code will filter that list for searching with quickopen or from other extensions. + * + * A FileIndexProvider is the simpler of two ways to implement file search in VS Code. Use a FileIndexProvider if you are able to provide a listing of all files + * in a folder, and want VS Code to filter them according to the user's search query. + * + * The FileIndexProvider will be invoked once when quickopen is opened, and VS Code will filter the returned list. It will also be invoked when + * `workspace.findFiles` is called. + * + * If a [`FileSearchProvider`](#FileSearchProvider) is registered for the scheme, that provider will be used instead. + */ export interface FileIndexProvider { - provideFileIndex(options: FileSearchOptions, token: CancellationToken): Thenable; - } - - export interface TextSearchProvider { /** - * Provide results that match the given text pattern. - * @param query The parameters for this query. + * Provide the set of files in the folder. * @param options A set of options to consider while searching. - * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileIndex(options: FileIndexOptions, token: CancellationToken): Thenable; } /** - * A FileSearchProvider provides search results for files or text in files. It can be invoked by quickopen and other extensions. + * A FileSearchProvider provides search results for files in the given folder that match a query string. It can be invoked by quickopen or other extensions. + * + * A FileSearchProvider is the more powerful of two ways to implement file search in VS Code. Use a FileSearchProvider if you wish to search within a folder for + * all files that match the user's query. + * + * The FileSearchProvider will be invoked on every keypress in quickopen. When `workspace.findFiles` is called, it will be invoked with an empty query string, + * and in that case, every file in the folder should be returned. + * + * @see [FileIndexProvider](#FileIndexProvider) */ export interface FileSearchProvider { /** @@ -179,7 +202,21 @@ declare module 'vscode' { * @param progress A progress callback that must be invoked for all results. * @param token A cancellation token. */ - provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, progress: Progress, token: CancellationToken): Thenable; + provideFileSearchResults(query: FileSearchQuery, options: FileSearchOptions, token: CancellationToken): Thenable; + } + + /** + * A TextSearchProvider provides search results for text results inside files in the workspace. + */ + export interface TextSearchProvider { + /** + * Provide results that match the given text pattern. + * @param query The parameters for this query. + * @param options A set of options to consider while searching. + * @param progress A progress callback that must be invoked for all results. + * @param token A cancellation token. + */ + provideTextSearchResults(query: TextSearchQuery, options: TextSearchOptions, progress: Progress, token: CancellationToken): Thenable; } /** @@ -230,6 +267,17 @@ declare module 'vscode' { */ export function registerSearchProvider(): Disposable; + /** + * Register a file index provider. + * + * Only one provider can be registered per scheme. + * + * @param scheme The provider will be invoked for workspace folders that have this file scheme. + * @param provider The provider. + * @return A [disposable](#Disposable) that unregisters this provider when being disposed. + */ + export function registerFileIndexProvider(scheme: string, provider: FileIndexProvider): Disposable; + /** * Register a search provider. * @@ -252,18 +300,6 @@ declare module 'vscode' { */ export function registerTextSearchProvider(scheme: string, provider: TextSearchProvider): Disposable; - /** - * Register a file index provider. - * - * Only one provider can be registered per scheme. - * - * @param scheme The provider will be invoked for workspace folders that have this file scheme. - * @param provider The provider. - * @return A [disposable](#Disposable) that unregisters this provider when being disposed. - */ - export function registerFileIndexProvider(scheme: string, provider: FileIndexProvider): Disposable; - - /** * Search text in files across all [workspace folders](#workspace.workspaceFolders) in the workspace. * @param query The query parameters for the search - the search string, whether it's case-sensitive, or a regex, or matches whole words. @@ -326,9 +362,9 @@ declare module 'vscode' { priority?: number; title?: string; bubble?: boolean; - abbreviation?: string; + abbreviation?: string; // letter, not optional color?: ThemeColor; - source?: string; + source?: string; // hacky... we should remove it and use equality under the hood } export interface SourceControlResourceDecorations { @@ -487,6 +523,23 @@ declare module 'vscode' { //#endregion + //#region Joao: SCM selected provider + + export interface SourceControl { + + /** + * Whether the source control is selected. + */ + readonly selected: boolean; + + /** + * An event signaling when the selection state changes. + */ + readonly onDidChangeSelection: Event; + } + + //#endregion + //#region Comments /** * Comments provider related APIs are still in early stages, they may be changed significantly during our API experiments. diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index 7e32ff27498..334c62d7251 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -92,36 +92,36 @@ class RemoteFileSystemProvider implements IFileSystemProvider { }); } - readFile(resource: URI): TPromise { + readFile(resource: URI): TPromise { return this._proxy.$readFile(this._handle, resource).then(encoded => { return Buffer.from(encoded, 'base64'); }); } - writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): TPromise { + writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): TPromise { let encoded = Buffer.isBuffer(content) ? content.toString('base64') : Buffer.from(content.buffer, content.byteOffset, content.byteLength).toString('base64'); return this._proxy.$writeFile(this._handle, resource, encoded, opts); } - delete(resource: URI, opts: FileDeleteOptions): TPromise { + delete(resource: URI, opts: FileDeleteOptions): TPromise { return this._proxy.$delete(this._handle, resource, opts); } - mkdir(resource: URI): TPromise { + mkdir(resource: URI): TPromise { return this._proxy.$mkdir(this._handle, resource); } - readdir(resource: URI): TPromise<[string, FileType][], any> { + readdir(resource: URI): TPromise<[string, FileType][]> { return this._proxy.$readdir(this._handle, resource); } - rename(resource: URI, target: URI, opts: FileOverwriteOptions): TPromise { + rename(resource: URI, target: URI, opts: FileOverwriteOptions): TPromise { return this._proxy.$rename(this._handle, resource, target, opts); } - copy(resource: URI, target: URI, opts: FileOverwriteOptions): TPromise { + copy(resource: URI, target: URI, opts: FileOverwriteOptions): TPromise { return this._proxy.$copy(this._handle, resource, target, opts); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 8ee8daaee7a..a6c6f1caa69 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI, { UriComponents } from 'vs/base/common/uri'; -import { Event, Emitter } from 'vs/base/common/event'; +import { Event, Emitter, debounceEvent } from 'vs/base/common/event'; import { assign } from 'vs/base/common/objects'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, IInputValidation } from 'vs/workbench/services/scm/common/scm'; @@ -270,6 +270,9 @@ export class MainThreadSCM implements MainThreadSCMShape { @ISCMService private scmService: ISCMService ) { this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSCM); + + debounceEvent(scmService.onDidChangeSelectedRepositories, (_, e) => e, 100) + (this.onDidChangeSelectedRepositories, this, this._disposables); } dispose(): void { @@ -417,4 +420,12 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.validateInput = () => TPromise.as(undefined); } } + + private onDidChangeSelectedRepositories(repositories: ISCMRepository[]): void { + const handles = repositories + .filter(r => r.provider instanceof MainThreadSCMProvider) + .map(r => (r.provider as MainThreadSCMProvider).handle); + + this._proxy.$setSelectedSourceControls(handles); + } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 59f93eb7f2f..aa936863ad3 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -37,6 +37,7 @@ import { applyCodeAction } from 'vs/editor/contrib/codeAction/codeActionCommands import { getCodeActions } from 'vs/editor/contrib/codeAction/codeAction'; import { ICodeActionsOnSaveOptions } from 'vs/editor/common/config/editorOptions'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; +import { CancellationTokenSource } from 'vs/base/common/cancellation'; export interface ISaveParticipantParticipant extends ISaveParticipant { // progressMessage: string; @@ -215,11 +216,12 @@ class FormatOnSaveParticipant implements ISaveParticipantParticipant { const timeout = this._configurationService.getValue('editor.formatOnSaveTimeout', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }); return new Promise((resolve, reject) => { - let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces }); + let source = new CancellationTokenSource(); + let request = getDocumentFormattingEdits(model, { tabSize, insertSpaces }, source.token); setTimeout(() => { reject(localize('timeout.formatOnSave', "Aborted format on save after {0}ms", timeout)); - request.cancel(); + source.cancel(); }, timeout); request.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)).then(resolve, err => { diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index 8db9fa2331f..e797ba2ba0b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -11,7 +11,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { localize } from 'vs/nls'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IFileMatch, IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchQuery, ISearchService, QueryType, ISearchProgressItem } from 'vs/platform/search/common/search'; +import { IFolderQuery, IPatternInfo, IQueryOptions, ISearchConfiguration, ISearchQuery, ISearchService, QueryType, ISearchProgressItem } from 'vs/platform/search/common/search'; import { IStatusbarService } from 'vs/platform/statusbar/common/statusbar'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -165,36 +165,36 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { return search; } - $startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number): TPromise { + $startTextSearch(pattern: IPatternInfo, options: IQueryOptions, requestId: number): TPromise { const workspace = this._contextService.getWorkspace(); const folders = workspace.folders.map(folder => folder.uri); const queryBuilder = this._instantiationService.createInstance(QueryBuilder); const query = queryBuilder.text(pattern, folders, options); - return new TPromise((resolve, reject) => { - const onProgress = (p: ISearchProgressItem) => { - if (p.lineMatches) { - this._proxy.$handleTextSearchResult(p, requestId); + const onProgress = (p: ISearchProgressItem) => { + if (p.lineMatches) { + this._proxy.$handleTextSearchResult(p, requestId); + } + }; + + const search = this._searchService.search(query, onProgress).then( + () => { + delete this._activeSearches[requestId]; + return null; + }, + err => { + delete this._activeSearches[requestId]; + if (!isPromiseCanceledError(err)) { + return TPromise.wrapError(err); } - }; - const search = this._searchService.search(query, onProgress).then( - () => { - delete this._activeSearches[requestId]; - resolve(null); - }, - err => { - delete this._activeSearches[requestId]; - if (!isPromiseCanceledError(err)) { - reject(TPromise.wrapError(err)); - } + return undefined; + }); - return undefined; - }); + this._activeSearches[requestId] = search; - this._activeSearches[requestId] = search; - }); + return search; } $cancelSearch(requestId: number): Thenable { diff --git a/src/vs/workbench/api/node/apiCommands.ts b/src/vs/workbench/api/node/apiCommands.ts index 98eaf7888ca..d04d6b2334c 100644 --- a/src/vs/workbench/api/node/apiCommands.ts +++ b/src/vs/workbench/api/node/apiCommands.ts @@ -5,6 +5,7 @@ 'use strict'; import URI from 'vs/base/common/uri'; +import { isMalformedFileUri } from 'vs/base/common/resources'; import * as vscode from 'vscode'; import * as typeConverters from 'vs/workbench/api/node/extHostTypeConverters'; import { CommandsRegistry, ICommandService, ICommandHandler } from 'vs/platform/commands/common/commands'; @@ -48,6 +49,12 @@ export class OpenFolderAPICommand { if (!uri) { return executor.executeCommand('_files.pickFolderAndOpen', forceNewWindow); } + let correctedUri = isMalformedFileUri(uri); + if (correctedUri) { + // workaround for #55916 and #55891, will be removed in 1.28 + console.warn(`'vscode.openFolder' command invoked with an invalid URI (file:// scheme missing): '${uri}'. Converted to a 'file://' URI: ${correctedUri}`); + uri = correctedUri; + } return executor.executeCommand('_files.windowOpen', [uri], forceNewWindow); } diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index c6daee8c6ca..bc631fe2d4d 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -463,11 +463,6 @@ export function createApiFactory( }, }; - // namespace: QuickInputButtons - const QuickInputButtons: typeof vscode.QuickInputButtons = { - Back: extHostQuickOpen.backButton, - }; - // namespace: workspace const workspace: typeof vscode.workspace = { get rootPath() { @@ -732,7 +727,7 @@ export function createApiFactory( OverviewRulerLane: OverviewRulerLane, ParameterInformation: extHostTypes.ParameterInformation, Position: extHostTypes.Position, - QuickInputButtons, + QuickInputButtons: extHostTypes.QuickInputButtons, Range: extHostTypes.Range, Selection: extHostTypes.Selection, SignatureHelp: extHostTypes.SignatureHelp, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index 3d55724ba44..0afcb94f377 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -882,6 +882,7 @@ export interface ExtHostSCMShape { $onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise; $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise; $validateInput(sourceControlHandle: number, value: string, cursorPosition: number): TPromise<[string, number] | undefined>; + $setSelectedSourceControls(selectedSourceControlHandles: number[]): TPromise; } export interface ExtHostTaskShape { diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index d40aad904b9..4c29149c845 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -146,11 +146,11 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { return { type, ctime, mtime, size }; } - $stat(handle: number, resource: UriComponents): TPromise { + $stat(handle: number, resource: UriComponents): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).stat(URI.revive(resource))).then(ExtHostFileSystem._asIStat); } - $readdir(handle: number, resource: UriComponents): TPromise<[string, files.FileType][], any> { + $readdir(handle: number, resource: UriComponents): TPromise<[string, files.FileType][]> { return asWinJsPromise(() => this._fsProvider.get(handle).readDirectory(URI.revive(resource))); } @@ -162,23 +162,23 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { }); } - $writeFile(handle: number, resource: UriComponents, base64Content: string, opts: files.FileWriteOptions): TPromise { + $writeFile(handle: number, resource: UriComponents, base64Content: string, opts: files.FileWriteOptions): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).writeFile(URI.revive(resource), Buffer.from(base64Content, 'base64'), opts)); } - $delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise { + $delete(handle: number, resource: UriComponents, opts: files.FileDeleteOptions): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).delete(URI.revive(resource), opts)); } - $rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise { + $rename(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).rename(URI.revive(oldUri), URI.revive(newUri), opts)); } - $copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise { + $copy(handle: number, oldUri: UriComponents, newUri: UriComponents, opts: files.FileOverwriteOptions): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).copy(URI.revive(oldUri), URI.revive(newUri), opts)); } - $mkdir(handle: number, resource: UriComponents): TPromise { + $mkdir(handle: number, resource: UriComponents): TPromise { return asWinJsPromise(() => this._fsProvider.get(handle).createDirectory(URI.revive(resource))); } diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index 43c5f784178..29928df44f9 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -14,9 +14,7 @@ import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { InputBox, InputBoxOptions, QuickInput, QuickInputButton, QuickPick, QuickPickItem, QuickPickOptions, WorkspaceFolder, WorkspaceFolderPickOptions } from 'vscode'; import { ExtHostQuickOpenShape, IMainContext, MainContext, MainThreadQuickOpenShape, TransferQuickPickItems, TransferQuickInput, TransferQuickInputButton } from './extHost.protocol'; import URI from 'vs/base/common/uri'; -import { ThemeIcon } from 'vs/workbench/api/node/extHostTypes'; - -const backButton: QuickInputButton = { iconPath: 'back.svg' }; +import { ThemeIcon, QuickInputButtons } from 'vs/workbench/api/node/extHostTypes'; export type Item = string | QuickPickItem; @@ -153,8 +151,6 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { // ---- QuickInput - backButton = backButton; - createQuickPick(extensionId: string): QuickPick { const session = new ExtHostQuickPick(this._proxy, extensionId, () => this._sessions.delete(session._id)); this._sessions.set(session._id, session); @@ -328,14 +324,14 @@ class ExtHostQuickInput implements QuickInput { this._buttons = buttons.slice(); this._handlesToButtons.clear(); buttons.forEach((button, i) => { - const handle = button === backButton ? -1 : i; + const handle = button === QuickInputButtons.Back ? -1 : i; this._handlesToButtons.set(handle, button); }); this.update({ buttons: buttons.map((button, i) => ({ iconPath: getIconUris(button.iconPath), tooltip: button.tooltip, - handle: button === backButton ? -1 : i, + handle: button === QuickInputButtons.Back ? -1 : i, })) }); } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index eeafdc4a416..2ce26e50530 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -395,6 +395,15 @@ class ExtHostSourceControl implements vscode.SourceControl { this._proxy.$updateSourceControl(this.handle, { statusBarCommands: internal }); } + private _selected: boolean = false; + + get selected(): boolean { + return this._selected; + } + + private _onDidChangeSelection = new Emitter(); + readonly onDidChangeSelection = this._onDidChangeSelection.event; + private handle: number = ExtHostSourceControl._handlePool++; constructor( @@ -454,6 +463,11 @@ class ExtHostSourceControl implements vscode.SourceControl { return this._groups.get(handle); } + setSelectionState(selected: boolean): void { + this._selected = selected; + this._onDidChangeSelection.fire(selected); + } + dispose(): void { this._groups.forEach(group => group.dispose()); this._proxy.$unregisterSourceControl(this.handle); @@ -471,6 +485,8 @@ export class ExtHostSCM implements ExtHostSCMShape { private _onDidChangeActiveProvider = new Emitter(); get onDidChangeActiveProvider(): Event { return this._onDidChangeActiveProvider.event; } + private _selectedSourceControlHandles = new Set(); + constructor( mainContext: IMainContext, private _commands: ExtHostCommands, @@ -607,4 +623,41 @@ export class ExtHostSCM implements ExtHostSCMShape { return TPromise.as<[string, number]>([result.message, result.type]); }); } + + $setSelectedSourceControls(selectedSourceControlHandles: number[]): TPromise { + this.logService.trace('ExtHostSCM#$setSelectedSourceControls', selectedSourceControlHandles); + + const set = new Set(); + + for (const handle of selectedSourceControlHandles) { + set.add(handle); + } + + set.forEach(handle => { + if (!this._selectedSourceControlHandles.has(handle)) { + const sourceControl = this._sourceControls.get(handle); + + if (!sourceControl) { + return; + } + + sourceControl.setSelectionState(true); + } + }); + + this._selectedSourceControlHandles.forEach(handle => { + if (!set.has(handle)) { + const sourceControl = this._sourceControls.get(handle); + + if (!sourceControl) { + return; + } + + sourceControl.setSelectionState(false); + } + }); + + this._selectedSourceControlHandles = set; + return TPromise.as(null); + } } diff --git a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts index 89422c20368..90b8a578f1c 100644 --- a/src/vs/workbench/api/node/extHostSearch.fileIndex.ts +++ b/src/vs/workbench/api/node/extHostSearch.fileIndex.ts @@ -276,7 +276,7 @@ export class FileIndexSearchEngine { }); } - private getSearchOptionsForFolder(fq: IFolderQuery): vscode.FileSearchOptions { + private getSearchOptionsForFolder(fq: IFolderQuery): vscode.FileIndexOptions { const includes = resolvePatternsForProvider(this.config.includePattern, fq.includePattern); const excludes = resolvePatternsForProvider(this.config.excludePattern, fq.excludePattern); @@ -416,7 +416,7 @@ export class FileIndexSearchManager { sortedSearch.then(complete => { this.sendAsBatches(complete.results, onBatch, FileIndexSearchManager.BATCH_SIZE); c(complete); - }, e, onBatch); + }, e); }, () => { sortedSearch.cancel(); }); diff --git a/src/vs/workbench/api/node/extHostSearch.ts b/src/vs/workbench/api/node/extHostSearch.ts index a4be83baa25..47b7f055eee 100644 --- a/src/vs/workbench/api/node/extHostSearch.ts +++ b/src/vs/workbench/api/node/extHostSearch.ts @@ -488,24 +488,6 @@ class FileSearchEngine { const queryTester = new QueryGlobTester(this.config, fq); const noSiblingsClauses = !queryTester.hasSiblingExcludeClauses(); - const onProviderResult = (result: URI) => { - if (this.isCanceled) { - return; - } - - const relativePath = path.relative(fq.folder.fsPath, result.fsPath); - - if (noSiblingsClauses) { - const basename = path.basename(result.fsPath); - this.matchFile(onResult, { base: fq.folder, relativePath, basename }); - - return; - } - - // TODO: Optimize siblings clauses with ripgrep here. - this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); - }; - new TPromise(_resolve => process.nextTick(_resolve)) .then(() => { this.activeCancellationTokens.add(cancellation); @@ -515,10 +497,29 @@ class FileSearchEngine { pattern: this.config.filePattern || '' }, options, - { report: onProviderResult }, cancellation.token); }) - .then(() => { + .then(results => { + if (this.isCanceled) { + return; + } + + if (results) { + results.forEach(result => { + const relativePath = path.relative(fq.folder.fsPath, result.fsPath); + + if (noSiblingsClauses) { + const basename = path.basename(result.fsPath); + this.matchFile(onResult, { base: fq.folder, relativePath, basename }); + + return; + } + + // TODO: Optimize siblings clauses with ripgrep here. + this.addDirectoryEntries(tree, fq.folder, relativePath, onResult); + }); + } + this.activeCancellationTokens.delete(cancellation); if (this.isCanceled) { return null; @@ -668,9 +669,16 @@ class FileSearchManager { } private rawMatchToSearchItem(match: IInternalFileMatch): IFileMatch { - return { - resource: resources.joinPath(match.base, match.relativePath) - }; + if (match.relativePath) { + return { + resource: resources.joinPath(match.base, match.relativePath) + }; + } else { + // extraFileResources + return { + resource: match.base + }; + } } private doSearch(engine: FileSearchEngine, batchSize: number, onResultBatch: (matches: IInternalFileMatch[]) => void): TPromise { diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index 4bf2cd070ba..7e7572fe66c 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -1956,3 +1956,10 @@ export enum CommentThreadCollapsibleState { */ Expanded = 1 } + +export class QuickInputButtons { + + static readonly Back: vscode.QuickInputButton = { iconPath: 'back.svg' }; + + private constructor() { } +} diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index ae1f6739927..56e9d91c15c 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { posix, relative, join } from 'path'; +import { relative, join } from 'path'; import { delta as arrayDelta } from 'vs/base/common/arrays'; import { Emitter, Event } from 'vs/base/common/event'; import { TernarySearchTree } from 'vs/base/common/map'; import { normalize } from 'vs/base/common/paths'; import { isLinux } from 'vs/base/common/platform'; -import { basenameOrAuthority, isEqual } from 'vs/base/common/resources'; +import { basenameOrAuthority, isEqual, dirname } from 'vs/base/common/resources'; import { compare } from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -124,7 +124,7 @@ class ExtHostWorkspaceImpl extends Workspace { getWorkspaceFolder(uri: URI, resolveParent?: boolean): vscode.WorkspaceFolder { if (resolveParent && this._structure.get(uri.toString())) { // `uri` is a workspace folder so we check for its parent - uri = uri.with({ path: posix.dirname(uri.path) }); + uri = dirname(uri); } return this._structure.findSubstr(uri.toString()); } @@ -395,7 +395,13 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { excludePattern: options.exclude && globPatternToString(options.exclude) }; + let isCanceled = false; + this._activeSearchCallbacks[requestId] = p => { + if (isCanceled) { + return; + } + p.lineMatches.forEach(lineMatch => { lineMatch.offsetAndLengths.forEach(offsetAndLength => { const range = new Range(lineMatch.lineNumber, offsetAndLength[0], lineMatch.lineNumber, offsetAndLength[0] + offsetAndLength[1]); @@ -409,7 +415,10 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { }; if (token) { - token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId)); + token.onCancellationRequested(() => { + isCanceled = true; + this._proxy.$cancelSearch(requestId); + }); } return this._proxy.$startTextSearch(query, queryOptions, requestId).then( diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts index 9ead6f9f1fb..955717730e0 100644 --- a/src/vs/workbench/browser/actions/workspaceCommands.ts +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -14,7 +14,6 @@ import URI from 'vs/base/common/uri'; import * as resources from 'vs/base/common/resources'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { dirname } from 'vs/base/common/paths'; -import { IQuickOpenService, IFilePickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { CancellationToken } from 'vs/base/common/cancellation'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -24,6 +23,10 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isLinux } from 'vs/base/common/platform'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; +import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { getIconClasses } from 'vs/workbench/browser/labels'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; export const ADD_ROOT_FOLDER_COMMAND_ID = 'addRootFolder'; export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); @@ -158,10 +161,12 @@ CommandsRegistry.registerCommand({ } }); -CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (accessor, args?: [IPickOptions, CancellationToken]) { - const quickOpenService = accessor.get(IQuickOpenService); +CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (accessor, args?: [IPickOptions, CancellationToken]) { + const quickInputService = accessor.get(IQuickInputService); const uriDisplayService = accessor.get(IUriDisplayService); const contextService = accessor.get(IWorkspaceContextService); + const modelService = accessor.get(IModelService); + const modeService = accessor.get(IModeService); const folders = contextService.getWorkspace().folders; if (!folders.length) { @@ -173,12 +178,11 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (acc label: folder.name, description: uriDisplayService.getLabel(resources.dirname(folder.uri), true), folder, - resource: folder.uri, - fileKind: FileKind.ROOT_FOLDER - } as IFilePickOpenEntry; + iconClasses: getIconClasses(modelService, modeService, folder.uri, FileKind.ROOT_FOLDER) + } as IQuickPickItem; }); - let options: IPickOptions; + let options: IPickOptions; if (args) { options = args[0]; } @@ -187,8 +191,8 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (acc options = Object.create(null); } - if (!options.autoFocus) { - options.autoFocus = { autoFocusFirstEntry: true }; + if (!options.activeItem) { + options.activeItem = folderPicks[0]; } if (!options.placeHolder) { @@ -208,7 +212,7 @@ CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (acc token = CancellationToken.None; } - return quickOpenService.pick(folderPicks, options, token).then(pick => { + return quickInputService.pick(folderPicks, options, token).then(pick => { if (!pick) { return void 0; } diff --git a/src/vs/workbench/browser/labels.ts b/src/vs/workbench/browser/labels.ts index 7357d3a8b6b..bf04e217fa7 100644 --- a/src/vs/workbench/browser/labels.ts +++ b/src/vs/workbench/browser/labels.ts @@ -191,7 +191,7 @@ export class ResourceLabel extends IconLabel { iconLabelOptions.title = this.options.title; } else if (resource && resource.scheme !== Schemas.data /* do not accidentally inline Data URIs */) { if (!this.computedPathLabel) { - this.computedPathLabel = this.uriDisplayService.getLabel(resource, true); + this.computedPathLabel = this.uriDisplayService.getLabel(resource); } iconLabelOptions.title = this.computedPathLabel; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 6d1fa885470..773bbe0d763 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -21,7 +21,7 @@ import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colo import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; -import { ActivityAction, ActivityActionItem, ICompositeBarColors, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { ActivityAction, ActivityActionItem, ICompositeBarColors, ToggleCompositePinnedAction, ICompositeBar } from 'vs/workbench/browser/parts/compositeBarActions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import URI from 'vs/base/common/uri'; diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index 298c940d475..52d0ff4ccf3 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/activitybarpart'; import * as nls from 'vs/nls'; import { illegalArgument } from 'vs/base/common/errors'; -import { $ } from 'vs/base/browser/builder'; import { ActionsOrientation, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { GlobalActivityExtensions, IGlobalActivityRegistry } from 'vs/workbench/common/activity'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -23,15 +22,15 @@ import { ToggleActivityBarVisibilityAction } from 'vs/workbench/browser/actions/ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar'; +import { CompositeBar } from 'vs/workbench/browser/parts/compositeBar'; import { isMacintosh } from 'vs/base/common/platform'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { scheduleAtNextAnimationFrame, Dimension } from 'vs/base/browser/dom'; +import { scheduleAtNextAnimationFrame, Dimension, addClass } from 'vs/base/browser/dom'; import { Color } from 'vs/base/common/color'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import URI from 'vs/base/common/uri'; -import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; interface IPlaceholderComposite { @@ -156,14 +155,19 @@ export class ActivitybarPart extends Part { } createContentArea(parent: HTMLElement): HTMLElement { - const $el = $(parent); - const $result = $('.content').appendTo($el); + const content = document.createElement('div'); + addClass(content, 'content'); + parent.appendChild(content); // Top Actionbar with action items for each viewlet action - this.compositeBar.create($result.getHTMLElement()); + this.compositeBar.create(content); // Top Actionbar with action items for each viewlet action - this.createGlobalActivityActionBar($('.global-activity').appendTo($result).getHTMLElement()); + const globalActivities = document.createElement('div'); + addClass(globalActivities, 'global-activity'); + content.appendChild(globalActivities); + + this.createGlobalActivityActionBar(globalActivities); // TODO@Ben: workaround for https://github.com/Microsoft/vscode/issues/45700 // It looks like there are rendering glitches on macOS with Chrome 61 when @@ -186,26 +190,26 @@ export class ActivitybarPart extends Part { }); } - return $result.getHTMLElement(); + return content; } updateStyles(): void { super.updateStyles(); // Part container - const container = $(this.getContainer()); + const container = this.getContainer(); const background = this.getColor(ACTIVITY_BAR_BACKGROUND); - container.style('background-color', background); + container.style.backgroundColor = background; const borderColor = this.getColor(ACTIVITY_BAR_BORDER) || this.getColor(contrastBorder); const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT; - container.style('box-sizing', borderColor && isPositionLeft ? 'border-box' : null); - container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null); - container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null); - container.style('border-right-color', isPositionLeft ? borderColor : null); - container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null); - container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null); - container.style('border-left-color', !isPositionLeft ? borderColor : null); + container.style.boxSizing = borderColor && isPositionLeft ? 'border-box' : null; + container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; + container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; + container.style.borderRightColor = isPositionLeft ? borderColor : null; + container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null; + container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null; + container.style.borderLeftColor = !isPositionLeft ? borderColor : null; } private createGlobalActivityActionBar(container: HTMLElement): void { diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts similarity index 99% rename from src/vs/workbench/browser/parts/compositebar/compositeBar.ts rename to src/vs/workbench/browser/parts/compositeBar.ts index 54f2748037a..8731222e1fe 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -14,7 +14,7 @@ import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { CompositeActionItem, CompositeOverflowActivityAction, ICompositeActivity, CompositeOverflowActivityActionItem, ActivityAction, ICompositeBar, ICompositeBarColors } from 'vs/workbench/browser/parts/compositeBarActions'; import { TPromise } from 'vs/base/common/winjs.base'; import { Dimension, $, addDisposableListener, EventType, EventHelper } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositeBarActions.ts similarity index 98% rename from src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts rename to src/vs/workbench/browser/parts/compositeBarActions.ts index 6d6647e8c5a..eed75df278b 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositeBarActions.ts @@ -604,8 +604,10 @@ export class CompositeActionItem extends ActivityActionItem { protected _updateChecked(): void { if (this.getAction().checked) { this.$container.addClass('checked'); + this.$container.attr('aria-label', nls.localize('compositeActive', "{0} active", this.$container.getHTMLElement().title)); } else { this.$container.removeClass('checked'); + this.$container.attr('aria-label', this.$container.getHTMLElement().title); } } diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts index c076583eb44..e0e5dfa0fc8 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbs.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbs.ts @@ -120,7 +120,7 @@ Registry.as(Extensions.Configuration).registerConfigurat // default: false // }, 'breadcrumbs.filePath': { - description: localize('filepath', "Controls if and how file paths are shown in the breadcrumbs view."), + description: localize('filepath', "Controls whether and how file paths are shown in the breadcrumbs view."), type: 'string', default: 'on', enum: ['on', 'off', 'last'], @@ -131,7 +131,7 @@ Registry.as(Extensions.Configuration).registerConfigurat ] }, 'breadcrumbs.symbolPath': { - description: localize('symbolpath', "Controls if and how symbols are shown in the breadcrumbs view."), + description: localize('symbolpath', "Controls whether and how symbols are shown in the breadcrumbs view."), type: 'string', default: 'on', enum: ['on', 'off', 'last'], diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts index 555823a6d83..ad040a10a27 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsControl.ts @@ -362,16 +362,11 @@ export class BreadcrumbsControl { //#region commands -MenuRegistry.appendMenuItem(MenuId.CommandPalette, { - command: { - id: 'breadcrumbs.focusAndSelect', - title: localize('cmd.focus', "Focus Breadcrumbs") - } -}); MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id: 'breadcrumbs.toggle', - title: localize('cmd.toggle', "Toggle Breadcrumbs") + title: localize('cmd.toggle', "Toggle Breadcrumbs"), + category: localize('cmd.category', "View") } }); MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { @@ -388,17 +383,11 @@ CommandsRegistry.registerCommand('breadcrumbs.toggle', accessor => { BreadcrumbsConfig.IsEnabled.bindTo(config).value = !value; }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'breadcrumbs.focus', - weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON, - when: BreadcrumbsControl.CK_BreadcrumbsVisible, - handler(accessor) { - const groups = accessor.get(IEditorGroupsService); - const breadcrumbs = accessor.get(IBreadcrumbsService); - const widget = breadcrumbs.getWidget(groups.activeGroup.id); - const item = tail(widget.getItems()); - widget.setFocused(item); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: 'breadcrumbs.focusAndSelect', + title: localize('cmd.focus', "Focus Breadcrumbs"), + precondition: BreadcrumbsControl.CK_BreadcrumbsVisible } }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -410,11 +399,29 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const groups = accessor.get(IEditorGroupsService); const breadcrumbs = accessor.get(IBreadcrumbsService); const widget = breadcrumbs.getWidget(groups.activeGroup.id); - const item = tail(widget.getItems()); - widget.setFocused(item); - widget.setSelection(item, BreadcrumbsControl.Payload_Pick); + if (widget) { + const item = tail(widget.getItems()); + widget.setFocused(item); + widget.setSelection(item, BreadcrumbsControl.Payload_Pick); + } } }); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'breadcrumbs.focus', + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_SEMICOLON, + when: BreadcrumbsControl.CK_BreadcrumbsVisible, + handler(accessor) { + const groups = accessor.get(IEditorGroupsService); + const breadcrumbs = accessor.get(IBreadcrumbsService); + const widget = breadcrumbs.getWidget(groups.activeGroup.id); + if (widget) { + const item = tail(widget.getItems()); + widget.setFocused(item); + } + } +}); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'breadcrumbs.focusNext', weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts index 40d9babb573..142765257c1 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsModel.ts @@ -12,8 +12,7 @@ import { size } from 'vs/base/common/collections'; import { onUnexpectedError } from 'vs/base/common/errors'; import { debounceEvent, Emitter, Event } from 'vs/base/common/event'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import * as paths from 'vs/base/common/paths'; -import { isEqual } from 'vs/base/common/resources'; +import { isEqual, dirname } from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IPosition } from 'vs/editor/common/core/position'; @@ -117,7 +116,7 @@ export class EditorBreadcrumbsModel { break; } info.path.unshift(new FileElement(uri, info.path.length === 0 ? FileKind.FILE : FileKind.FOLDER)); - uri = uri.with({ path: paths.dirname(uri.path) }); + uri = dirname(uri); } if (info.folder && workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE) { diff --git a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts index 3ee60065b1c..e6eef49b484 100644 --- a/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts +++ b/src/vs/workbench/browser/parts/editor/breadcrumbsPicker.ts @@ -123,12 +123,20 @@ export abstract class BreadcrumbsPicker { } layout(height: number, width: number, arrowSize: number, arrowOffset: number) { - this._domNode.style.height = `${height}px`; + + let treeHeight = height - 2 * arrowSize; + let elementHeight = 22; + let elementCount = treeHeight / elementHeight; + if (elementCount % 2 !== 1) { + treeHeight = elementHeight * (elementCount + 1); + } + let totalHeight = treeHeight + 2 + arrowSize; + + this._domNode.style.height = `${totalHeight}px`; this._domNode.style.width = `${width}px`; this._arrow.style.borderWidth = `${arrowSize}px`; this._arrow.style.marginLeft = `${arrowOffset}px`; - - this._treeContainer.style.height = `${height - 2 * arrowSize}px`; + this._treeContainer.style.height = `${treeHeight}px`; this._treeContainer.style.width = `${width}px`; this._tree.layout(); } @@ -231,7 +239,8 @@ export class FileRenderer implements IRenderer, IHighlightingRenderer { fileKind, hidePath: true, fileDecorations: fileDecorations, - matches: createMatches((this._scores.get(resource.toString()) || [, []])[1]) + matches: createMatches((this._scores.get(resource.toString()) || [, []])[1]), + extraClasses: ['picker-item'] }); } diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index f7865c8bb40..c5dd8e397d1 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -671,3 +671,177 @@ MenuRegistry.appendMenuItem(MenuId.MenubarLayoutMenu, { }, order: 9 }); + +// Main Menu Bar Contributions: + +// Forward/Back +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '1_fwd_back', + command: { + id: 'workbench.action.navigateBack', + title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), + precondition: ContextKeyExpr.has('canNavigateBack') + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '1_fwd_back', + command: { + id: 'workbench.action.navigateForward', + title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), + precondition: ContextKeyExpr.has('canNavigateForward') + }, + order: 2 +}); + +// Switch Editor +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '1_any', + command: { + id: 'workbench.action.nextEditor', + title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '1_any', + command: { + id: 'workbench.action.previousEditor', + title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '2_used', + command: { + id: 'workbench.action.openNextRecentlyUsedEditorInGroup', + title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { + group: '2_used', + command: { + id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup', + title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '2_switch', + title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"), + submenu: MenuId.MenubarSwitchEditorMenu, + order: 1 +}); + +// Switch Group +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '1_focus_index', + command: { + id: 'workbench.action.focusFirstEditorGroup', + title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '1_focus_index', + command: { + id: 'workbench.action.focusSecondEditorGroup', + title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '1_focus_index', + command: { + id: 'workbench.action.focusThirdEditorGroup', + title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '1_focus_index', + command: { + id: 'workbench.action.focusFourthEditorGroup', + title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '1_focus_index', + command: { + id: 'workbench.action.focusFifthEditorGroup', + title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5") + }, + order: 5 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '2_next_prev', + command: { + id: 'workbench.action.focusNextGroup', + title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '2_next_prev', + command: { + id: 'workbench.action.focusPreviousGroup', + title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '3_directional', + command: { + id: 'workbench.action.focusLeftGroup', + title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '3_directional', + command: { + id: 'workbench.action.focusRightGroup', + title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '3_directional', + command: { + id: 'workbench.action.focusAboveGroup', + title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { + group: '3_directional', + command: { + id: 'workbench.action.focusBelowGroup', + title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below") + }, + order: 4 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: '2_switch', + title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"), + submenu: MenuId.MenubarSwitchGroupMenu, + order: 2 +}); \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 600ece0164f..b6545f0bdc6 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -23,6 +23,7 @@ import { IEditorGroupsService, IEditorGroup, GroupDirection, GroupLocation, Grou import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; export const CLOSE_SAVED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; @@ -263,6 +264,14 @@ function registerDiffEditorCommands(): void { } } }); + + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id: TOGGLE_DIFF_INLINE_MODE, + title: nls.localize('toggleInlineView', "Compare: Toggle Inline View") + }, + when: ContextKeyExpr.has('textCompareEditorActive') + }); } function registerOpenEditorAtIndexCommands(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index c55b73e43ca..2f4c869314a 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -958,7 +958,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this.doCloseInactiveEditor(editor); } - // Forward to title control & breadcrumbs + // Forward to title control this.titleAreaControl.closeEditor(editor); } @@ -1373,6 +1373,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { this._onWillDispose.fire(); this.titleAreaControl.dispose(); + // this.editorControl = null; super.dispose(); } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 36d3ffb96b5..553ff7e0c65 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -10,20 +10,20 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Part } from 'vs/workbench/browser/part'; import { Dimension, isAncestor, toggleClass, addClass, $ } from 'vs/base/browser/dom'; import { Event, Emitter, once, Relay, anyEvent } from 'vs/base/common/event'; -import { contrastBorder, editorBackground, registerColor } from 'vs/platform/theme/common/colorRegistry'; +import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { GroupDirection, IAddGroupOptions, GroupsArrangement, GroupOrientation, IMergeGroupOptions, MergeGroupMode, ICopyEditorOptions, GroupsOrder, GroupChangeKind, GroupLocation, IFindGroupScope, EditorGroupLayout, GroupLayoutArgument } from 'vs/workbench/services/group/common/editorGroupsService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, ISerializedNode, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; +import { Direction, SerializableGrid, Sizing, ISerializedGrid, Orientation, GridBranchNode, isGridBranchNode, GridNode, createSerializedGrid, Grid } from 'vs/base/browser/ui/grid/grid'; import { GroupIdentifier, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { values } from 'vs/base/common/map'; -import { EDITOR_GROUP_BORDER } from 'vs/workbench/common/theme'; +import { EDITOR_GROUP_BORDER, EDITOR_PANE_BACKGROUND } from 'vs/workbench/common/theme'; import { distinct } from 'vs/base/common/arrays'; import { IEditorGroupsAccessor, IEditorGroupView, IEditorPartOptions, getEditorPartOptions, impactsEditorPartOptions, IEditorPartOptionsChangeEvent, EditorGroupsServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; import { EditorGroupView } from 'vs/workbench/browser/parts/editor/editorGroupView'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { Scope } from 'vs/workbench/common/memento'; import { ISerializedEditorGroup, isSerializedEditorGroup } from 'vs/workbench/common/editor/editorGroup'; import { TValueCallback, TPromise } from 'vs/base/common/winjs.base'; @@ -82,12 +82,6 @@ class GridWidgetView implements IView { } } -export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { - dark: editorBackground, - light: editorBackground, - hc: editorBackground -}, localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); - export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditorGroupsAccessor { _serviceBrand: any; @@ -708,7 +702,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor // Move/Copy editors over into target let index = (options && typeof options.index === 'number') ? options.index : targetView.count; sourceView.editors.forEach(editor => { - const inactive = !sourceView.isActive(editor); + const inactive = !sourceView.isActive(editor) || this._activeGroup !== sourceView; const copyOptions: ICopyEditorOptions = { index, inactive, preserveFocus: inactive }; if (options && options.mode === MergeGroupMode.COPY_EDITORS) { @@ -828,7 +822,7 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor } private doCreateGridControlWithPreviousState(): void { - const uiState = this.doGetPreviousState(); + const uiState = this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState; if (uiState && uiState.serializedGrid) { // MRU @@ -889,123 +883,6 @@ export class EditorPart extends Part implements EditorGroupsServiceImpl, IEditor this.onDidSetGridWidget.fire(); } - private doGetPreviousState(): IEditorPartUIState { - const legacyState = this.doGetPreviousLegacyState(); - if (legacyState) { - return legacyState; // TODO@ben remove after a while - } - - return this.memento[EditorPart.EDITOR_PART_UI_STATE_STORAGE_KEY] as IEditorPartUIState; - } - - private doGetPreviousLegacyState(): IEditorPartUIState { - const LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY = 'editorpart.uiState'; - const LEGACY_STACKS_MODEL_STORAGE_KEY = 'editorStacks.model'; - - interface ILegacyEditorPartUIState { - ratio: number[]; - groupOrientation: 'vertical' | 'horizontal'; - } - - interface ISerializedLegacyEditorStacksModel { - groups: ISerializedEditorGroup[]; - active: number; - } - - let legacyUIState: ISerializedLegacyEditorStacksModel; - const legacyUIStateRaw = this.storageService.get(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE); - if (legacyUIStateRaw) { - try { - legacyUIState = JSON.parse(legacyUIStateRaw); - } catch (error) { /* ignore */ } - } - - if (legacyUIState) { - this.storageService.remove(LEGACY_STACKS_MODEL_STORAGE_KEY, StorageScope.WORKSPACE); - } - - const legacyPartState = this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY] as ILegacyEditorPartUIState; - if (legacyPartState) { - delete this.memento[LEGACY_EDITOR_PART_UI_STATE_STORAGE_KEY]; - } - - if (legacyUIState && Array.isArray(legacyUIState.groups) && legacyUIState.groups.length > 0) { - const splitHorizontally = legacyPartState && legacyPartState.groupOrientation === 'horizontal'; - - const legacyState: IEditorPartUIState = Object.create(null); - - const positionOneGroup = legacyUIState.groups[0]; - const positionTwoGroup = legacyUIState.groups[1]; - const positionThreeGroup = legacyUIState.groups[2]; - - legacyState.activeGroup = legacyUIState.active; - legacyState.mostRecentActiveGroups = [legacyUIState.active]; - - if (positionTwoGroup || positionThreeGroup) { - if (!positionThreeGroup) { - legacyState.mostRecentActiveGroups.push(legacyState.activeGroup === 0 ? 1 : 0); - } else { - if (legacyState.activeGroup === 0) { - legacyState.mostRecentActiveGroups.push(1, 2); - } else if (legacyState.activeGroup === 1) { - legacyState.mostRecentActiveGroups.push(0, 2); - } else { - legacyState.mostRecentActiveGroups.push(0, 1); - } - } - } - - const toNode = function (group: ISerializedEditorGroup, size: number): ISerializedNode { - return { - data: group, - size, - type: 'leaf' - }; - }; - - const baseSize = 1200; // just some number because layout() was not called yet, but we only need the proportions - - // No split editor - if (!positionTwoGroup) { - legacyState.serializedGrid = { - width: baseSize, - height: baseSize, - orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL, - root: toNode(positionOneGroup, baseSize) - }; - } - - // Split editor (2 or 3 columns) - else { - const children: ISerializedNode[] = []; - - const size = positionThreeGroup ? baseSize / 3 : baseSize / 2; - - children.push(toNode(positionOneGroup, size)); - children.push(toNode(positionTwoGroup, size)); - - if (positionThreeGroup) { - children.push(toNode(positionThreeGroup, size)); - } - - legacyState.serializedGrid = { - width: baseSize, - height: baseSize, - orientation: splitHorizontally ? Orientation.VERTICAL : Orientation.HORIZONTAL, - root: { - data: children, - size: baseSize, - type: 'branch' - } - }; - } - - return legacyState; - } - - return void 0; - } - private updateContainer(): void { toggleClass(this.container, 'empty', this.isEmpty()); } diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 7b24eca3f73..20a1bc2d68d 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -58,6 +58,7 @@ import { Schemas } from 'vs/base/common/network'; import { IAnchor } from 'vs/base/browser/ui/contextview/contextview'; import { Themable } from 'vs/workbench/common/theme'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private master: IEncodingSupport, private details: IEncodingSupport) { } @@ -834,6 +835,7 @@ export class ChangeModeAction extends Action { @IEditorService private editorService: IEditorService, @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService, @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @IPreferencesService private preferencesService: IPreferencesService, @IInstantiationService private instantiationService: IInstantiationService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService @@ -844,7 +846,7 @@ export class ChangeModeAction extends Action { run(): TPromise { const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); if (!activeTextEditorWidget) { - return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } const textModel = activeTextEditorWidget.getModel(); @@ -987,10 +989,10 @@ export class ChangeModeAction extends Action { const currentAssociation = this.modeService.getModeIdByFilenameOrFirstLine(basename); const languages = this.modeService.getRegisteredLanguageNames(); - const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => { + const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { const id = this.modeService.getModeIdForLanguageName(lang.toLowerCase()); - return { + return { id, label: lang, description: (id === currentAssociation) ? nls.localize('currentAssociation', "Current Association") : void 0 @@ -998,7 +1000,7 @@ export class ChangeModeAction extends Action { }); TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).done(() => { - this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => { + this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguageToConfigure', "Select Language Mode to Associate with '{0}'", extension || basename) }).done(language => { if (language) { const fileAssociationsConfig = this.configurationService.inspect(FILES_ASSOCIATIONS_CONFIG); @@ -1030,7 +1032,7 @@ export class ChangeModeAction extends Action { } } -export interface IChangeEOLEntry extends IPickOpenEntry { +export interface IChangeEOLEntry extends IQuickPickItem { eol: EndOfLineSequence; } @@ -1043,7 +1045,8 @@ class ChangeIndentationAction extends Action { actionId: string, actionLabel: string, @IEditorService private editorService: IEditorService, - @IQuickOpenService private quickOpenService: IQuickOpenService + @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService ) { super(actionId, actionLabel); } @@ -1051,11 +1054,11 @@ class ChangeIndentationAction extends Action { run(): TPromise { const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); if (!activeTextEditorWidget) { - return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } if (!isWritableCodeEditor(activeTextEditorWidget)) { - return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); } const picks = [ @@ -1093,7 +1096,7 @@ export class ChangeEOLAction extends Action { actionId: string, actionLabel: string, @IEditorService private editorService: IEditorService, - @IQuickOpenService private quickOpenService: IQuickOpenService + @IQuickInputService private quickInputService: IQuickInputService ) { super(actionId, actionLabel); } @@ -1101,11 +1104,11 @@ export class ChangeEOLAction extends Action { run(): TPromise { const activeTextEditorWidget = getCodeEditor(this.editorService.activeTextEditorWidget); if (!activeTextEditorWidget) { - return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } if (!isWritableCodeEditor(activeTextEditorWidget)) { - return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); + return this.quickInputService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); } const textModel = activeTextEditorWidget.getModel(); @@ -1117,7 +1120,7 @@ export class ChangeEOLAction extends Action { const selectedIndex = (textModel && textModel.getEOL() === '\n') ? 0 : 1; - return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then(eol => { + return this.quickInputService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), activeItem: EOLOptions[selectedIndex] }).then(eol => { if (eol) { const activeCodeEditor = getCodeEditor(this.editorService.activeTextEditorWidget); if (activeCodeEditor && isWritableCodeEditor(activeCodeEditor)) { @@ -1139,6 +1142,7 @@ export class ChangeEncodingAction extends Action { actionLabel: string, @IEditorService private editorService: IEditorService, @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService, @IFileService private fileService: IFileService ) { @@ -1147,19 +1151,19 @@ export class ChangeEncodingAction extends Action { run(): TPromise { if (!getCodeEditor(this.editorService.activeTextEditorWidget)) { - return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); + return this.quickInputService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } let activeControl = this.editorService.activeControl; let encodingSupport: IEncodingSupport = toEditorWithEncodingSupport(activeControl.input); if (!encodingSupport) { - return this.quickOpenService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); + return this.quickInputService.pick([{ label: nls.localize('noFileEditor', "No file active at this time") }]); } - let pickActionPromise: TPromise; + let pickActionPromise: TPromise; - let saveWithEncodingPick: IPickOpenEntry; - let reopenWithEncodingPick: IPickOpenEntry; + let saveWithEncodingPick: IQuickPickItem; + let reopenWithEncodingPick: IQuickPickItem; if (language === LANGUAGE_DEFAULT) { saveWithEncodingPick = { label: nls.localize('saveWithEncoding', "Save with Encoding") }; reopenWithEncodingPick = { label: nls.localize('reopenWithEncoding', "Reopen with Encoding") }; @@ -1173,7 +1177,7 @@ export class ChangeEncodingAction extends Action { } else if (!isWritableBaseEditor(activeControl)) { pickActionPromise = TPromise.as(reopenWithEncodingPick); } else { - pickActionPromise = this.quickOpenService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); + pickActionPromise = this.quickInputService.pick([reopenWithEncodingPick, saveWithEncodingPick], { placeHolder: nls.localize('pickAction', "Select Action"), matchOnDetail: true }); } return pickActionPromise.then(action => { diff --git a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css index 2aa84dbc6ec..e03eee35663 100644 --- a/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/breadcrumbscontrol.css @@ -23,6 +23,10 @@ /* todo@joh move somewhere else */ +.monaco-workbench .monaco-breadcrumbs-picker .picker-item { + line-height: 22px; +} + .monaco-workbench .monaco-breadcrumbs-picker .highlighting-tree { height: 100%; overflow: hidden; diff --git a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css index 5c323a06753..efd03a02391 100644 --- a/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/notabstitlecontrol.css @@ -79,6 +79,7 @@ display: flex; flex: initial; opacity: 0.5; + height: 35px; } .monaco-workbench > .part.editor > .content .editor-group-container.active > .title .title-actions { diff --git a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts index e4cd8fb91df..a5a99f0c2bd 100644 --- a/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/noTabsTitleControl.ts @@ -147,6 +147,10 @@ export class NoTabsTitleControl extends TitleControl { this.redraw(); } + protected handleBreadcrumbsEnablementChange(): void { + this.redraw(); + } + private ifActiveEditorChanged(fn: () => void): void { if ( !this.lastRenderedActiveEditor && this.group.activeEditor || // active editor changed from null => editor diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 605ac959776..2cf73b3bf5a 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -145,6 +145,11 @@ export class TabsTitleControl extends TitleControl { } } + protected handleBreadcrumbsEnablementChange(): void { + // relayout when breadcrumbs are enable/disabled + this.group.relayout(); + } + private registerContainerListeners(): void { // Group dragging diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 3a123c9c218..91d2e0b3e83 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -101,11 +101,11 @@ export abstract class TitleControl extends Themable { if (!value && this.breadcrumbsControl) { this.breadcrumbsControl.dispose(); this.breadcrumbsControl = undefined; - this.group.relayout(); + this.handleBreadcrumbsEnablementChange(); } else if (value && !this.breadcrumbsControl) { this.breadcrumbsControl = this.instantiationService.createInstance(BreadcrumbsControl, container, options, this.group); this.breadcrumbsControl.update(); - this.group.relayout(); + this.handleBreadcrumbsEnablementChange(); } }); if (config.value) { @@ -113,6 +113,8 @@ export abstract class TitleControl extends Themable { } } + protected abstract handleBreadcrumbsEnablementChange(): void; + protected createEditorActionsToolBar(container: HTMLElement): void { const context = { groupId: this.group.id } as IEditorCommandsContext; diff --git a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css b/src/vs/workbench/browser/parts/menubar/media/menubarpart.css deleted file mode 100644 index 39912f3dd35..00000000000 --- a/src/vs/workbench/browser/parts/menubar/media/menubarpart.css +++ /dev/null @@ -1,48 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .part.menubar { - display: flex; - flex-shrink: 1; - box-sizing: border-box; - height: 30px; - -webkit-app-region: no-drag; - overflow-x: hidden; -} - -.monaco-workbench.fullscreen .part.menubar { - margin: 0px; - padding: 0px 5px; -} - -.monaco-workbench .part.menubar > .menubar-menu-button { - align-items: center; - box-sizing: border-box; - padding: 0px 8px; - cursor: default; - -webkit-app-region: no-drag; - zoom: 1; -} - -.monaco-workbench .part.menubar .menubar-menu-items-holder { - position: absolute; - left: 0px; - opacity: 1; - z-index: 2000; -} - -.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container { - font-family: "Segoe WPC", "Segoe UI", ".SFNSDisplay-Light", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback"; - outline: 0; - border: none; -} - -.monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container :focus { - outline: 0; -} - -.hc-black .monaco-workbench .part.menubar .menubar-menu-items-holder.monaco-menu-container { - border: 2px solid #6FC3DF; -} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts b/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts deleted file mode 100644 index 57e3026f0eb..00000000000 --- a/src/vs/workbench/browser/parts/menubar/menubar.contribution.ts +++ /dev/null @@ -1,411 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; -import { isMacintosh } from 'vs/base/common/platform'; - -goMenuRegistration(); - -if (isMacintosh) { - windowMenuRegistration(); -} - -helpMenuRegistration(); - -// Menu registration -function goMenuRegistration() { - // Forward/Back - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '1_fwd_back', - command: { - id: 'workbench.action.navigateBack', - title: nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '1_fwd_back', - command: { - id: 'workbench.action.navigateForward', - title: nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward") - }, - order: 2 - }); - - // Switch Editor - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '1_any', - command: { - id: 'workbench.action.nextEditor', - title: nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '1_any', - command: { - id: 'workbench.action.previousEditor', - title: nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '2_used', - command: { - id: 'workbench.action.openNextRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchEditorMenu, { - group: '2_used', - command: { - id: 'workbench.action.openPreviousRecentlyUsedEditorInGroup', - title: nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '2_switch', - title: nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor"), - submenu: MenuId.MenubarSwitchEditorMenu, - order: 1 - }); - - // Switch Group - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '1_focus_index', - command: { - id: 'workbench.action.focusFirstEditorGroup', - title: nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "Group &&1") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '1_focus_index', - command: { - id: 'workbench.action.focusSecondEditorGroup', - title: nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "Group &&2") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '1_focus_index', - command: { - id: 'workbench.action.focusThirdEditorGroup', - title: nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "Group &&3") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '1_focus_index', - command: { - id: 'workbench.action.focusFourthEditorGroup', - title: nls.localize({ key: 'miFocusFourthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&4") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '1_focus_index', - command: { - id: 'workbench.action.focusFifthEditorGroup', - title: nls.localize({ key: 'miFocusFifthGroup', comment: ['&& denotes a mnemonic'] }, "Group &&5") - }, - order: 5 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '2_next_prev', - command: { - id: 'workbench.action.focusNextGroup', - title: nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '2_next_prev', - command: { - id: 'workbench.action.focusPreviousGroup', - title: nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '3_directional', - command: { - id: 'workbench.action.focusLeftGroup', - title: nls.localize({ key: 'miFocusLeftGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Left") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '3_directional', - command: { - id: 'workbench.action.focusRightGroup', - title: nls.localize({ key: 'miFocusRightGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Right") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarSwitchGroupMenu, { - group: '3_directional', - command: { - id: 'workbench.action.focusAboveGroup', - title: nls.localize({ key: 'miFocusAboveGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Above") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '3_directional', - command: { - id: 'workbench.action.focusBelowGroup', - title: nls.localize({ key: 'miFocusBelowGroup', comment: ['&& denotes a mnemonic'] }, "Group &&Below") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: '2_switch', - title: nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group"), - submenu: MenuId.MenubarSwitchGroupMenu, - order: 2 - }); - - // Go to - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'workbench.action.quickOpen', - title: nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File...") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'workbench.action.gotoSymbol', - title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'workbench.action.showAllSymbols', - title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'editor.action.goToDeclaration', - title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") - }, - order: 4 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'editor.action.goToTypeDefinition', - title: nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition") - }, - order: 5 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'editor.action.goToImplementation', - title: nls.localize({ key: 'miGotoImplementation', comment: ['&& denotes a mnemonic'] }, "Go to &&Implementation") - }, - order: 6 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { - group: 'z_go_to', - command: { - id: 'workbench.action.gotoLine', - title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line...") - }, - order: 7 - }); -} - -function windowMenuRegistration() { - -} - -function helpMenuRegistration() { - // Welcome - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'workbench.action.showWelcomePage', - title: nls.localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'workbench.action.showInteractivePlayground', - title: nls.localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "&&Interactive Playground") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'workbench.action.openDocumentationUrl', - title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") - }, - order: 3 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '1_welcome', - command: { - id: 'update.showCurrentReleaseNotes', - title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") - }, - order: 4 - }); - - // Reference - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: 'workbench.action.keybindingsReference', - title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: 'workbench.action.openIntroductoryVideosUrl', - title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '2_reference', - command: { - id: 'workbench.action.openTipsAndTricksUrl', - title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "&&Tips and Tricks") - }, - order: 3 - }); - - // Feedback - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openTwitterUrl', - title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openRequestFeatureUrl', - title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") - }, - order: 2 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '3_feedback', - command: { - id: 'workbench.action.openIssueReporter', - title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") - }, - order: 3 - }); - - // Legal - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: 'workbench.action.openLicenseUrl', - title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '4_legal', - command: { - id: 'workbench.action.openPrivacyStatementUrl', - title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement") - }, - order: 2 - }); - - // Tools - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.toggleDevTools', - title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") - }, - order: 1 - }); - - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.openProcessExplorer', - title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") - }, - order: 2 - }); - - if (!isMacintosh) { - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: '5_tools', - command: { - id: 'workbench.action.showAccessibilityOptions', - title: nls.localize({ key: 'miAccessibilityOptions', comment: ['&& denotes a mnemonic'] }, "Accessibility &&Options") - }, - order: 3 - }); - - // About - MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { - group: 'z_about', - command: { - id: 'workbench.action.showAboutDialog', - title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") - }, - order: 1 - }); - } -} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index d3933cb7b9c..54d4f61d040 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -14,7 +14,7 @@ import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/ import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService, Parts, Position } from 'vs/workbench/services/part/common/partService'; -import { ActivityAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { ActivityAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IActivity } from 'vs/workbench/common/activity'; export class ClosePanelAction extends Action { diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 40c3fa928ea..13df252c82f 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -7,7 +7,6 @@ import 'vs/css!./media/panelpart'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAction } from 'vs/base/common/actions'; import { Event } from 'vs/base/common/event'; -import { $ } from 'vs/base/browser/builder'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPanel } from 'vs/workbench/common/panel'; @@ -24,8 +23,8 @@ import { ClosePanelAction, TogglePanelPositionAction, PanelActivityAction, Toggl import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme'; import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry'; -import { CompositeBar } from 'vs/workbench/browser/parts/compositebar/compositeBar'; -import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositebar/compositeBarActions'; +import { CompositeBar } from 'vs/workbench/browser/parts/compositeBar'; +import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions'; import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { Dimension } from 'vs/base/browser/dom'; @@ -151,12 +150,12 @@ export class PanelPart extends CompositePart implements IPanelService { updateStyles(): void { super.updateStyles(); - const container = $(this.getContainer()); - container.style('background-color', this.getColor(PANEL_BACKGROUND)); - container.style('border-left-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder)); + const container = this.getContainer(); + container.style.backgroundColor = this.getColor(PANEL_BACKGROUND); + container.style.borderLeftColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); - const title = $(this.getTitleArea()); - title.style('border-top-color', this.getColor(PANEL_BORDER) || this.getColor(contrastBorder)); + const title = this.getTitleArea(); + title.style.borderTopColor = this.getColor(PANEL_BORDER) || this.getColor(contrastBorder); } openPanel(id: string, focus?: boolean): TPromise { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.css b/src/vs/workbench/browser/parts/quickinput/quickInput.css index 133ce19e8fc..35094940935 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.css +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.css @@ -168,4 +168,27 @@ .quick-input-list .monaco-highlighted-label .highlight { font-weight: bold; -} \ No newline at end of file +} + +.quick-input-list .quick-input-list-entry-action-bar { + display: none; + flex: 0; + overflow: visible; +} + +.quick-input-list .quick-input-list-entry-action-bar .action-label.icon { + margin: 0; + width: 19px; + height: 100%; + background-position: center; + background-repeat: no-repeat; +} + +.quick-input-list .quick-input-list-entry-action-bar ul:last-child .action-label.icon { + margin-right: 3px; +} + +.quick-input-list .quick-input-list-entry:hover .quick-input-list-entry-action-bar, +.quick-input-list .monaco-list-row.focused .quick-input-list-entry-action-bar { + display: flex; +} diff --git a/src/vs/workbench/browser/parts/quickinput/quickInput.ts b/src/vs/workbench/browser/parts/quickinput/quickInput.ts index 83145223bde..0746e309b9e 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInput.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInput.ts @@ -7,7 +7,7 @@ import 'vs/css!./quickInput'; import { Component } from 'vs/workbench/common/component'; -import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickInputService, IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent } from 'vs/platform/quickinput/common/quickInput'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import * as dom from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -39,10 +39,10 @@ import { inQuickOpenContext } from 'vs/workbench/browser/parts/quickopen/quickop import { ActionBar, ActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { Action } from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; -import { IdGenerator } from 'vs/base/common/idGenerator'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { equals } from 'vs/base/common/arrays'; import { TimeoutTimer } from 'vs/base/common/async'; +import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; const $ = dom.$; @@ -73,6 +73,7 @@ interface QuickInputUI { show(controller: QuickInput): void; setVisibilities(visibilities: Visibilities): void; setEnabled(enabled: boolean): void; + setContextKey(contextKey?: string): void; hide(): void; } @@ -94,6 +95,7 @@ class QuickInput implements IQuickInput { private _totalSteps: number; protected visible = false; private _enabled = true; + private _contextKey: string; private _busy = false; private _ignoreFocusOut = false; private _buttons: IQuickInputButton[] = []; @@ -148,6 +150,15 @@ class QuickInput implements IQuickInput { this.update(); } + get contextKey() { + return this._contextKey; + } + + set contextKey(contextKey: string) { + this._contextKey = contextKey; + this.update(); + } + get busy() { return this._busy; } @@ -182,7 +193,7 @@ class QuickInput implements IQuickInput { if (this.visible) { return; } - this.disposables.push( + this.visibleDisposables.push( this.ui.onDidTriggerButton(button => { if (this.buttons.indexOf(button) !== -1) { this.onDidTriggerButtonEmitter.fire(button); @@ -235,20 +246,21 @@ class QuickInput implements IQuickInput { this.ui.leftActionBar.clear(); const leftButtons = this.buttons.filter(button => button === backButton); this.ui.leftActionBar.push(leftButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); action.tooltip = button.tooltip; return action; }), { icon: true, label: false }); this.ui.rightActionBar.clear(); const rightButtons = this.buttons.filter(button => button !== backButton); this.ui.rightActionBar.push(rightButtons.map((button, index) => { - const action = new Action(`id-${index}`, '', getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => this.onDidTriggerButtonEmitter.fire(button)); action.tooltip = button.tooltip; return action; }), { icon: true, label: false }); } this.ui.ignoreFocusOut = this.ignoreFocusOut; this.ui.setEnabled(this.enabled); + this.ui.setContextKey(this.contextKey); } private getTitle() { @@ -299,7 +311,9 @@ class QuickPick extends QuickInput implements IQuickPi private selectedItemsUpdated = false; private selectedItemsToConfirm: T[] = []; private onDidChangeSelectionEmitter = new Emitter(); - private quickNavigate = false; + private onDidTriggerItemButtonEmitter = new Emitter>(); + + quickNavigate: IQuickNavigateConfiguration; constructor(ui: QuickInputUI) { super(ui); @@ -308,6 +322,7 @@ class QuickPick extends QuickInput implements IQuickPi this.onDidAcceptEmitter, this.onDidChangeActiveEmitter, this.onDidChangeSelectionEmitter, + this.onDidTriggerItemButtonEmitter, ); } @@ -394,6 +409,8 @@ class QuickPick extends QuickInput implements IQuickPi onDidChangeSelection = this.onDidChangeSelectionEmitter.event; + onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event; + show() { if (!this.visible) { this.visibleDisposables.push( @@ -486,11 +503,61 @@ class QuickPick extends QuickInput implements IQuickPi this._selectedItems = checkedItems as T[]; this.onDidChangeSelectionEmitter.fire(checkedItems as T[]); }), + this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent)), + this.registerQuickNavigation() ); } super.show(); } + private registerQuickNavigation() { + return dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => { + if (this.canSelectMany || !this.quickNavigate) { + return; + } + + const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent); + const keyCode = keyboardEvent.keyCode; + + // Select element when keys are pressed that signal it + const quickNavKeys = this.quickNavigate.keybindings; + const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => { + const [firstPart, chordPart] = k.getParts(); + if (chordPart) { + return false; + } + + if (firstPart.shiftKey && keyCode === KeyCode.Shift) { + if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { + return false; // this is an optimistic check for the shift key being used to navigate back in quick open + } + + return true; + } + + if (firstPart.altKey && keyCode === KeyCode.Alt) { + return true; + } + + if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { + return true; + } + + if (firstPart.metaKey && keyCode === KeyCode.Meta) { + return true; + } + + return false; + }); + + if (wasTriggerKeyPressed && this.activeItems[0]) { + this._selectedItems = [this.activeItems[0]]; + this.onDidChangeSelectionEmitter.fire(this.selectedItems); + this.onDidAcceptEmitter.fire(); + } + }); + } + protected update() { super.update(); if (!this.visible) { @@ -544,55 +611,6 @@ class QuickPick extends QuickInput implements IQuickPi this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.setVisibilities(this.canSelectMany ? { title: !!this.title || !!this.step, checkAll: true, inputBox: true, visibleCount: true, count: true, ok: true, list: true } : { title: !!this.title || !!this.step, inputBox: true, visibleCount: true, list: true }); } - - configureQuickNavigate(quickNavigate: IQuickNavigateConfiguration) { - if (this.canSelectMany || this.quickNavigate) { - return; - } - this.quickNavigate = true; - - this.disposables.push(dom.addDisposableListener(this.ui.container, dom.EventType.KEY_UP, (e: KeyboardEvent) => { - const keyboardEvent: StandardKeyboardEvent = new StandardKeyboardEvent(e as KeyboardEvent); - const keyCode = keyboardEvent.keyCode; - - // Select element when keys are pressed that signal it - const quickNavKeys = quickNavigate.keybindings; - const wasTriggerKeyPressed = keyCode === KeyCode.Enter || quickNavKeys.some(k => { - const [firstPart, chordPart] = k.getParts(); - if (chordPart) { - return false; - } - - if (firstPart.shiftKey && keyCode === KeyCode.Shift) { - if (keyboardEvent.ctrlKey || keyboardEvent.altKey || keyboardEvent.metaKey) { - return false; // this is an optimistic check for the shift key being used to navigate back in quick open - } - - return true; - } - - if (firstPart.altKey && keyCode === KeyCode.Alt) { - return true; - } - - if (firstPart.ctrlKey && keyCode === KeyCode.Ctrl) { - return true; - } - - if (firstPart.metaKey && keyCode === KeyCode.Meta) { - return true; - } - - return false; - }); - - if (wasTriggerKeyPressed && this.activeItems[0]) { - this._selectedItems = [this.activeItems[0]]; - this.onDidChangeSelectionEmitter.fire(this.selectedItems); - this.onDidAcceptEmitter.fire(); - } - })); - } } class InputBox extends QuickInput implements IInputBox { @@ -740,6 +758,7 @@ export class QuickInputService extends Component implements IQuickInputService { private enabled = true; private inQuickOpenWidgets: Record = {}; private inQuickOpenContext: IContextKey; + private contexts: { [id: string]: IContextKey; } = Object.create(null); private onDidAcceptEmitter = this._register(new Emitter()); private onDidTriggerButtonEmitter = this._register(new Emitter()); @@ -753,7 +772,7 @@ export class QuickInputService extends Component implements IQuickInputService { @IQuickOpenService private quickOpenService: IQuickOpenService, @IEditorGroupsService private editorGroupService: IEditorGroupsService, @IKeybindingService private keybindingService: IKeybindingService, - @IContextKeyService contextKeyService: IContextKeyService, + @IContextKeyService private contextKeyService: IContextKeyService, @IThemeService themeService: IThemeService ) { super(QuickInputService.ID, themeService); @@ -779,13 +798,43 @@ export class QuickInputService extends Component implements IQuickInputService { } } + private setContextKey(id?: string) { + let key: IContextKey; + if (id) { + key = this.contexts[id]; + if (!key) { + key = new RawContextKey(id, false) + .bindTo(this.contextKeyService); + this.contexts[id] = key; + } + } + + if (key && key.get()) { + return; // already active context + } + + this.resetContextKeys(); + + if (key) { + key.set(true); + } + } + + private resetContextKeys() { + for (const key in this.contexts) { + if (this.contexts[key].get()) { + this.contexts[key].reset(); + } + } + } + private create() { if (this.ui) { return; } const workbench = document.getElementById(this.partService.getWorkbenchElementId()); - const container = dom.append(workbench, $('.quick-input-widget')); + const container = dom.append(workbench, $('.quick-input-widget.show-file-icons')); container.tabIndex = -1; container.style.display = 'none'; @@ -923,11 +972,12 @@ export class QuickInputService extends Component implements IQuickInputService { hide: () => this.hide(), setVisibilities: visibilities => this.setVisibilities(visibilities), setEnabled: enabled => this.setEnabled(enabled), + setContextKey: contextKey => this.setContextKey(contextKey), }; this.updateStyles(); } - pick>(picks: TPromise, options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { + pick>(picks: TPromise | T[], options: O = {}, token: CancellationToken = CancellationToken.None): TPromise { return new TPromise((resolve, reject) => { if (token.isCancellationRequested) { resolve(undefined); @@ -963,6 +1013,17 @@ export class QuickInputService extends Component implements IQuickInputService { } } }), + input.onDidTriggerItemButton(event => options.onDidTriggerItemButton && options.onDidTriggerItemButton({ + ...event, + removeItem: () => { + const index = input.items.indexOf(event.item); + if (index !== -1) { + const items = input.items.slice(); + items.splice(index, 1); + input.items = items; + } + } + })), token.onCancellationRequested(() => { input.hide(); }), @@ -976,16 +1037,22 @@ export class QuickInputService extends Component implements IQuickInputService { input.ignoreFocusOut = options.ignoreFocusLost; input.matchOnDescription = options.matchOnDescription; input.matchOnDetail = options.matchOnDetail; + input.quickNavigate = options.quickNavigate; + input.contextKey = options.contextKey; input.busy = true; - picks.then(items => { - input.busy = false; - input.items = items; - if (input.canSelectMany) { - input.selectedItems = items.filter(item => item.picked); - } - }); + TPromise.join([picks, options.activeItem]) + .then(([items, activeItem]) => { + input.busy = false; + input.items = items; + if (input.canSelectMany) { + input.selectedItems = items.filter(item => item.picked); + } + if (activeItem) { + input.activeItems = [activeItem]; + } + }); input.show(); - picks.then(null, err => { + TPromise.wrap(picks).then(null, err => { reject(err); input.hide(); }); @@ -1092,6 +1159,7 @@ export class QuickInputService extends Component implements IQuickInputService { backButton.tooltip = keybinding ? localize('quickInput.backWithKeybinding', "Back ({0})", keybinding.getLabel()) : localize('quickInput.back', "Back"); this.inQuickOpen('quickInput', true); + this.resetContextKeys(); this.ui.container.style.display = ''; this.updateLayout(); @@ -1132,6 +1200,7 @@ export class QuickInputService extends Component implements IQuickInputService { if (controller) { this.controller = null; this.inQuickOpen('quickInput', false); + this.resetContextKeys(); this.ui.container.style.display = 'none'; if (!focusLost) { this.editorGroupService.activeGroup.focus(); @@ -1156,7 +1225,7 @@ export class QuickInputService extends Component implements IQuickInputService { if (this.isDisplayed() && this.ui.list.isDisplayed()) { this.ui.list.focus(next ? 'Next' : 'Previous'); if (quickNavigate && this.controller instanceof QuickPick) { - this.controller.configureQuickNavigate(quickNavigate); + this.controller.quickNavigate = quickNavigate; } } } @@ -1219,25 +1288,6 @@ export class QuickInputService extends Component implements IQuickInputService { } } -const iconPathToClass = {}; -const iconClassGenerator = new IdGenerator('quick-input-button-icon-'); - -function getIconClass(iconPath: { dark: URI; light?: URI; }) { - let iconClass: string; - - const key = iconPath.dark.toString(); - if (iconPathToClass[key]) { - iconClass = iconPathToClass[key]; - } else { - iconClass = iconClassGenerator.nextId(); - dom.createCSSRule(`.${iconClass}`, `background-image: url("${(iconPath.light || iconPath.dark).toString()}")`); - dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${iconPath.dark.toString()}")`); - iconPathToClass[key] = iconClass; - } - - return iconClass; -} - export const QuickPickManyToggle: ICommandAndKeybindingRule = { id: 'workbench.action.quickPickManyToggle', weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts index f4a3a8fdf99..ff832f65ebe 100644 --- a/src/vs/workbench/browser/parts/quickinput/quickInputList.ts +++ b/src/vs/workbench/browser/parts/quickinput/quickInputList.ts @@ -11,7 +11,7 @@ import * as dom from 'vs/base/browser/dom'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { IQuickPickItem, IQuickPickItemButtonEvent } from 'vs/platform/quickinput/common/quickInput'; import { IMatch } from 'vs/base/common/filters'; import { matchesFuzzyOcticonAware, parseOcticons } from 'vs/base/common/octicon'; import { compareAnything } from 'vs/base/common/comparers'; @@ -26,6 +26,9 @@ import { range } from 'vs/base/common/arrays'; import * as platform from 'vs/base/common/platform'; import { listFocusBackground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { Action } from 'vs/base/common/actions'; +import { getIconClass } from 'vs/workbench/browser/parts/quickinput/quickInputUtils'; const $ = dom.$; @@ -33,6 +36,7 @@ interface IListElement { index: number; item: IQuickPickItem; checked: boolean; + fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; } class ListElement implements IListElement { @@ -55,6 +59,7 @@ class ListElement implements IListElement { labelHighlights?: IMatch[]; descriptionHighlights?: IMatch[]; detailHighlights?: IMatch[]; + fireButtonTriggered: (event: IQuickPickItemButtonEvent) => void; constructor(init: IListElement) { assign(this, init); @@ -65,6 +70,7 @@ interface IListElementTemplateData { checkbox: HTMLInputElement; label: IconLabel; detail: HighlightedLabel; + actionBar: ActionBar; element: ListElement; toDisposeElement: IDisposable[]; toDisposeTemplate: IDisposable[]; @@ -105,6 +111,11 @@ class ListElementRenderer implements IRenderer { + const action = new Action(`id-${index}`, '', button.iconClass || getIconClass(button.iconPath), true, () => { + element.fireButtonTriggered({ + button, + item: element.item + }); + return null; + }); + action.tooltip = button.tooltip; + return action; + }), { icon: true, label: false }); + } } - disposeElement(): void { - // noop + disposeElement(element: ListElement, index: number, data: IListElementTemplateData): void { + data.toDisposeElement = dispose(data.toDisposeElement); } disposeTemplate(data: IListElementTemplateData): void { @@ -164,6 +192,8 @@ export class QuickInputList { onChangedVisibleCount: Event = this._onChangedVisibleCount.event; private _onChangedCheckedElements = new Emitter(); onChangedCheckedElements: Event = this._onChangedCheckedElements.event; + private _onButtonTriggered = new Emitter>(); + onButtonTriggered = this._onButtonTriggered.event; private _onLeave = new Emitter(); onLeave: Event = this._onLeave.event; private _fireCheckedEvents = true; @@ -286,10 +316,12 @@ export class QuickInputList { setElements(elements: IQuickPickItem[]): void { this.elementDisposables = dispose(this.elementDisposables); + const fireButtonTriggered = (event: IQuickPickItemButtonEvent) => this.fireButtonTriggered(event); this.elements = elements.map((item, index) => new ListElement({ index, item, - checked: false + checked: false, + fireButtonTriggered })); this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); @@ -468,6 +500,10 @@ export class QuickInputList { this._onChangedCheckedElements.fire(this.getCheckedElements()); } } + + private fireButtonTriggered(event: IQuickPickItemButtonEvent) { + this._onButtonTriggered.fire(event); + } } function compareEntries(elementA: ListElement, elementB: ListElement, lookFor: string): number { diff --git a/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts new file mode 100644 index 00000000000..ef37fbf76dc --- /dev/null +++ b/src/vs/workbench/browser/parts/quickinput/quickInputUtils.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./quickInput'; +import * as dom from 'vs/base/browser/dom'; +import URI from 'vs/base/common/uri'; +import { IdGenerator } from 'vs/base/common/idGenerator'; + +const iconPathToClass = {}; +const iconClassGenerator = new IdGenerator('quick-input-button-icon-'); + +export function getIconClass(iconPath: { dark: URI; light?: URI; }) { + let iconClass: string; + + const key = iconPath.dark.toString(); + if (iconPathToClass[key]) { + iconClass = iconPathToClass[key]; + } else { + iconClass = iconClassGenerator.nextId(); + dom.createCSSRule(`.${iconClass}`, `background-image: url("${(iconPath.light || iconPath.dark).toString()}")`); + dom.createCSSRule(`.vs-dark .${iconClass}, .hc-black .${iconClass}`, `background-image: url("${iconPath.dark.toString()}")`); + iconPathToClass[key] = iconClass; + } + + return iconClass; +} diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index a63062b1035..e35dd586dd8 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -1074,7 +1074,17 @@ class EditorHistoryHandler { // Massage search for scoring const query = prepareQuery(searchValue); - const history = this.historyService.getHistory() + // Just return all if we are not searching + const history = this.historyService.getHistory(); + if (!query.value) { + return history.map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); + } + + // Otherwise filter by search value and sort by score. Include matches on description + // in case the user is explicitly including path separators. + const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription; + return history + // For now, only support to match on inputs that provide resource information .filter(input => { let resource: URI; @@ -1088,17 +1098,8 @@ class EditorHistoryHandler { }) // Conver to quick open entries - .map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)); + .map(input => this.instantiationService.createInstance(EditorHistoryEntry, input)) - // Just return all if we are not searching - if (!query.value) { - return history; - } - - // Otherwise filter by search value and sort by score. Include matches on description - // in case the user is explicitly including path separators. - const accessor = query.containsPathSeparator ? MatchOnDescription : DoNotMatchOnDescription; - return history // Make sure the search value is matching .filter(e => { const itemScore = scoreItem(e, query, false, accessor, this.scorerCache); diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 88e7acb9c01..556cdfd222e 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -26,8 +26,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER } from 'vs/workbench/common/theme'; import { INotificationService } from 'vs/platform/notification/common/notification'; -import { Dimension, EventType } from 'vs/base/browser/dom'; -import { $ } from 'vs/base/browser/builder'; +import { Dimension, EventType, addDisposableListener } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; export class SidebarPart extends CompositePart { @@ -77,7 +76,10 @@ export class SidebarPart extends CompositePart { createTitleArea(parent: HTMLElement): HTMLElement { const titleArea = super.createTitleArea(parent); - $(titleArea).on(EventType.CONTEXT_MENU, (e: MouseEvent) => this.onTitleAreaContextMenu(new StandardMouseEvent(e)), this.toDispose); + + this._register(addDisposableListener(titleArea, EventType.CONTEXT_MENU, e => { + this.onTitleAreaContextMenu(new StandardMouseEvent(e)); + })); return titleArea; } @@ -86,19 +88,19 @@ export class SidebarPart extends CompositePart { super.updateStyles(); // Part container - const container = $(this.getContainer()); + const container = this.getContainer(); - container.style('background-color', this.getColor(SIDE_BAR_BACKGROUND)); - container.style('color', this.getColor(SIDE_BAR_FOREGROUND)); + container.style.backgroundColor = this.getColor(SIDE_BAR_BACKGROUND); + container.style.color = this.getColor(SIDE_BAR_FOREGROUND); const borderColor = this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder); const isPositionLeft = this.partService.getSideBarPosition() === SideBarPosition.LEFT; - container.style('border-right-width', borderColor && isPositionLeft ? '1px' : null); - container.style('border-right-style', borderColor && isPositionLeft ? 'solid' : null); - container.style('border-right-color', isPositionLeft ? borderColor : null); - container.style('border-left-width', borderColor && !isPositionLeft ? '1px' : null); - container.style('border-left-style', borderColor && !isPositionLeft ? 'solid' : null); - container.style('border-left-color', !isPositionLeft ? borderColor : null); + container.style.borderRightWidth = borderColor && isPositionLeft ? '1px' : null; + container.style.borderRightStyle = borderColor && isPositionLeft ? 'solid' : null; + container.style.borderRightColor = isPositionLeft ? borderColor : null; + container.style.borderLeftWidth = borderColor && !isPositionLeft ? '1px' : null; + container.style.borderLeftStyle = borderColor && !isPositionLeft ? 'solid' : null; + container.style.borderLeftColor = !isPositionLeft ? borderColor : null; } openViewlet(id: string, focus?: boolean): TPromise { diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index 175276033e2..d0c0302f07c 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -134,22 +134,22 @@ export class StatusbarPart extends Part implements IStatusbarService { protected updateStyles(): void { super.updateStyles(); - const container = $(this.getContainer()); + const container = this.getContainer(); // Background colors const backgroundColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BACKGROUND : STATUS_BAR_NO_FOLDER_BACKGROUND); - container.style('background-color', backgroundColor); - container.style('color', this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND)); + container.style.backgroundColor = backgroundColor; + container.style.color = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_FOREGROUND : STATUS_BAR_NO_FOLDER_FOREGROUND); // Border color const borderColor = this.getColor(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? STATUS_BAR_BORDER : STATUS_BAR_NO_FOLDER_BORDER) || this.getColor(contrastBorder); - container.style('border-top-width', borderColor ? '1px' : null); - container.style('border-top-style', borderColor ? 'solid' : null); - container.style('border-top-color', borderColor); + container.style.borderTopWidth = borderColor ? '1px' : null; + container.style.borderTopStyle = borderColor ? 'solid' : null; + container.style.borderTopColor = borderColor; // Notification Beak if (!this.styleElement) { - this.styleElement = createStyleSheet(container.getHTMLElement()); + this.styleElement = createStyleSheet(container); } this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor}; }`; diff --git a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css index 29bb9ba3abf..42965aae65c 100644 --- a/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css +++ b/src/vs/workbench/browser/parts/titlebar/media/titlebarpart.css @@ -51,6 +51,10 @@ overflow: visible; } +.monaco-workbench.linux > .part.titlebar > .window-title { + font-size: inherit; +} + .monaco-workbench.windows > .part.titlebar > .resizer, .monaco-workbench.linux > .part.titlebar > .resizer { -webkit-app-region: no-drag; @@ -146,4 +150,52 @@ .monaco-workbench > .part.titlebar > .window-controls-container .window-icon.window-close:hover { background-color: white; +} + +/* Menubar styles */ + +.monaco-workbench .menubar { + display: flex; + flex-shrink: 1; + box-sizing: border-box; + height: 30px; + -webkit-app-region: no-drag; + overflow: hidden; + flex-wrap: wrap; +} + +.monaco-workbench.fullscreen .menubar { + margin: 0px; + padding: 0px 5px; +} + +.monaco-workbench .menubar > .menubar-menu-button { + align-items: center; + box-sizing: border-box; + padding: 0px 8px; + cursor: default; + -webkit-app-region: no-drag; + zoom: 1; + white-space: nowrap; +} + +.monaco-workbench .menubar .menubar-menu-items-holder { + position: absolute; + left: 0px; + opacity: 1; + z-index: 2000; +} + +.monaco-workbench .menubar .menubar-menu-items-holder.monaco-menu-container { + font-family: "Segoe WPC", "Segoe UI", ".SFNSDisplay-Light", "SFUIText-Light", "HelveticaNeue-Light", sans-serif, "Droid Sans Fallback"; + outline: 0; + border: none; +} + +.monaco-workbench .menubar .menubar-menu-items-holder.monaco-menu-container :focus { + outline: 0; +} + +.hc-black .monaco-workbench .menubar .menubar-menu-items-holder.monaco-menu-container { + border: 2px solid #6FC3DF; } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/menubar/menubarPart.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts similarity index 86% rename from src/vs/workbench/browser/parts/menubar/menubarPart.ts rename to src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 09f72b0bc8e..3568c42c54b 100644 --- a/src/vs/workbench/browser/parts/menubar/menubarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -5,12 +5,9 @@ 'use strict'; -import 'vs/workbench/browser/parts/menubar/menubar.contribution'; -import 'vs/css!./media/menubarpart'; import * as nls from 'vs/nls'; import * as browser from 'vs/base/browser/browser'; -import { Part } from 'vs/workbench/browser/part'; -import { IMenubarService, IMenubarMenu, IMenubarMenuItemAction, IMenubarData, IMenubarMenuItemSubmenu, IMenubarKeybinding } from 'vs/platform/menubar/common/menubar'; +import { IMenubarMenu, IMenubarMenuItemAction, IMenubarMenuItemSubmenu, IMenubarKeybinding } from 'vs/platform/menubar/common/menubar'; import { IMenuService, MenuId, IMenu, SubmenuItemAction } from 'vs/platform/actions/common/actions'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; import { IWindowService, MenuBarVisibility, IWindowsService } from 'vs/platform/windows/common/windows'; @@ -26,7 +23,7 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable, dispose } from 'vs/base/common/lifecycle'; import { domEvent } from 'vs/base/browser/event'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { IWorkspaceIdentifier, getWorkspaceLabel, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, isWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; @@ -35,6 +32,8 @@ import { RunOnceScheduler } from 'vs/base/common/async'; import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, MENU_BACKGROUND, MENU_FOREGROUND, MENU_SELECTION_BACKGROUND, MENU_SELECTION_FOREGROUND, MENU_SELECTION_BORDER } from 'vs/workbench/common/theme'; import URI from 'vs/base/common/uri'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; +import { foreground } from 'vs/platform/theme/common/colorRegistry'; +import { IUpdateService, StateType } from 'vs/platform/update/common/update'; interface CustomMenu { title: string; @@ -50,7 +49,7 @@ enum MenubarState { OPEN } -export class MenubarPart extends Part { +export class MenubarControl extends Disposable { private keys = [ 'files.autoSave', @@ -60,7 +59,7 @@ export class MenubarPart extends Part { 'workbench.statusBar.visible', 'workbench.activityBar.visible', 'window.enableMenuBarMnemonics', - // 'window.nativeTabs' + 'window.nativeTabs' ]; private topLevelMenus: { @@ -105,15 +104,15 @@ export class MenubarPart extends Part { private updatePending: boolean; private _modifierKeyStatus: IModifierKeyStatus; private _focusState: MenubarState; + private openedViaKeyboard: boolean; private _onVisibilityChange: Emitter; - private static MAX_MENU_RECENT_ENTRIES = 5; + private static MAX_MENU_RECENT_ENTRIES = 10; constructor( id: string, @IThemeService themeService: IThemeService, - @IMenubarService private menubarService: IMenubarService, @IMenuService private menuService: IMenuService, @IWindowService private windowService: IWindowService, @IWindowsService private windowsService: IWindowsService, @@ -121,9 +120,11 @@ export class MenubarPart extends Part { @IKeybindingService private keybindingService: IKeybindingService, @IConfigurationService private configurationService: IConfigurationService, @IEnvironmentService private environmentService: IEnvironmentService, - @IUriDisplayService private uriDisplayService: IUriDisplayService + @IUriDisplayService private uriDisplayService: IUriDisplayService, + @IUpdateService private updateService: IUpdateService ) { - super(id, { hasTitle: false }, themeService); + + super(); this.topLevelMenus = { 'File': this._register(this.menuService.createMenu(MenuId.MenubarFileMenu, this.contextKeyService)), @@ -139,10 +140,9 @@ export class MenubarPart extends Part { if (isMacintosh) { this.topLevelMenus['Preferences'] = this._register(this.menuService.createMenu(MenuId.MenubarPreferencesMenu, this.contextKeyService)); - this.topLevelMenus['Window'] = this._register(this.menuService.createMenu(MenuId.MenubarWindowMenu, this.contextKeyService)); } - this.menuUpdater = this._register(new RunOnceScheduler(() => this.doSetupMenubar(), 0)); + this.menuUpdater = this._register(new RunOnceScheduler(() => this.doSetupMenubar(), 100)); this.actionRunner = this._register(new ActionRunner()); this._register(this.actionRunner.onDidBeforeRun(() => { @@ -291,7 +291,7 @@ export class MenubarPart extends Part { } if (this.focusedMenu) { - this.showCustomMenu(this.focusedMenu.index, !!this._modifierKeyStatus && this._modifierKeyStatus.altKey); + this.showCustomMenu(this.focusedMenu.index, this.openedViaKeyboard); } break; } @@ -312,7 +312,7 @@ export class MenubarPart extends Part { } private onDidChangeFullscreen(): void { - this.updateStyles(); + this.setUnfocusedState(); } private onDidChangeWindowFocus(hasFocus: boolean): void { @@ -352,22 +352,12 @@ export class MenubarPart extends Part { private onModifierKeyToggled(modifierKeyStatus: IModifierKeyStatus): void { this._modifierKeyStatus = modifierKeyStatus; - const altKeyAlone = modifierKeyStatus.lastKeyPressed === 'alt' && !modifierKeyStatus.ctrlKey && !modifierKeyStatus.shiftKey; const allModifiersReleased = !modifierKeyStatus.altKey && !modifierKeyStatus.ctrlKey && !modifierKeyStatus.shiftKey; if (this.currentMenubarVisibility === 'hidden') { return; } - if (this.currentMenubarVisibility === 'toggle') { - if (altKeyAlone) { - if (!this.isVisible) { - this.focusState = MenubarState.VISIBLE; - } - } else if (!allModifiersReleased && !this.isFocused) { - this.focusState = MenubarState.HIDDEN; - } - } if (allModifiersReleased && modifierKeyStatus.lastKeyPressed === 'alt' && modifierKeyStatus.lastKeyReleased === 'alt') { if (!this.isFocused) { @@ -400,7 +390,7 @@ export class MenubarPart extends Part { this._register(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e))); // Listen to update service - // this.updateService.onStateChange(() => this.setupMenubar()); + this.updateService.onStateChange(() => this.setupMenubar()); // Listen for context changes this._register(this.contextKeyService.onDidChangeContext(() => this.setupMenubar())); @@ -427,11 +417,16 @@ export class MenubarPart extends Part { private doSetupMenubar(): void { if (!isMacintosh && this.currentTitlebarStyleSetting === 'custom') { this.setupCustomMenubar(); - } else { - // Send menus to main process to be rendered by Electron - this.menubarService.updateMenubar(this.windowService.getCurrentWindowId(), this.getMenubarMenus()); - } + + // TODO@sbatten Uncomment to bring back dynamic menubar + // else { + // // Send menus to main process to be rendered by Electron + // const menubarData = {}; + // if (this.getMenubarMenus(menubarData)) { + // this.menubarService.updateMenubar(this.windowService.getCurrentWindowId(), menubarData, this.getAdditionalKeybindings()); + // } + // } } private setupMenubar(): void { @@ -527,7 +522,7 @@ export class MenubarPart extends Part { const result: IAction[] = []; if (workspaces.length > 0) { - for (let i = 0; i < MenubarPart.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { + for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < workspaces.length; i++) { result.push(this.createOpenRecentMenuAction(workspaces[i], 'openRecentWorkspace', false)); } @@ -535,7 +530,7 @@ export class MenubarPart extends Part { } if (files.length > 0) { - for (let i = 0; i < MenubarPart.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { + for (let i = 0; i < MenubarControl.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { result.push(this.createOpenRecentMenuAction(files[i], 'openRecentFile', false)); } @@ -545,12 +540,58 @@ export class MenubarPart extends Part { return result; } + private getUpdateAction(): IAction | null { + const state = this.updateService.state; + + switch (state.type) { + case StateType.Uninitialized: + return null; + + case StateType.Idle: + const windowId = this.windowService.getCurrentWindowId(); + return new Action('update.check', nls.localize('checkForUpdates', "Check for Updates..."), undefined, true, () => + this.updateService.checkForUpdates({ windowId })); + + case StateType.CheckingForUpdates: + return new Action('update.checking', nls.localize('checkingForUpdates', "Checking For Updates..."), undefined, false); + + case StateType.AvailableForDownload: + return new Action('update.downloadNow', nls.localize('download now', "Download Now"), null, true, () => + this.updateService.downloadUpdate()); + + case StateType.Downloading: + return new Action('update.downloading', nls.localize('DownloadingUpdate', "Downloading Update..."), undefined, false); + + case StateType.Downloaded: + return new Action('update.install', nls.localize('installUpdate...', "Install Update..."), undefined, true, () => + this.updateService.applyUpdate()); + + case StateType.Updating: + return new Action('update.updating', nls.localize('installingUpdate', "Installing Update..."), undefined, false); + + case StateType.Ready: + return new Action('update.restart', nls.localize('restartToUpdate', "Restart to Update..."), undefined, true, () => + this.updateService.quitAndInstall()); + } + } + private insertActionsBefore(nextAction: IAction, target: IAction[]): void { switch (nextAction.id) { case 'workbench.action.openRecent': target.push(...this.getOpenRecentActions()); break; + case 'workbench.action.showAboutDialog': + if (!isMacintosh) { + const updateAction = this.getUpdateAction(); + if (updateAction) { + target.push(updateAction); + target.push(new Separator()); + } + } + + break; + default: break; } @@ -644,6 +685,7 @@ export class MenubarPart extends Part { if ((event.equals(KeyCode.DownArrow) || event.equals(KeyCode.Enter)) && !this.isOpen) { this.focusedMenu = { index: menuIndex }; + this.openedViaKeyboard = true; this.focusState = MenubarState.OPEN; } else { eventHandled = false; @@ -656,6 +698,11 @@ export class MenubarPart extends Part { }); this.customMenus[menuIndex].buttonElement.on(EventType.CLICK, (e) => { + // This should only happen for mnemonics and we shouldn't trigger them + if (this.currentMenubarVisibility === 'hidden') { + return; + } + if (this._modifierKeyStatus && (this._modifierKeyStatus.shiftKey || this._modifierKeyStatus.ctrlKey)) { return; // supress keyboard shortcuts that shouldn't conflict } @@ -665,10 +712,11 @@ export class MenubarPart extends Part { this.setUnfocusedState(); } else { this.cleanupCustomMenu(); - this.showCustomMenu(menuIndex, !!this._modifierKeyStatus && this._modifierKeyStatus.altKey); + this.showCustomMenu(menuIndex, this.openedViaKeyboard); } } else { this.focusedMenu = { index: menuIndex }; + this.openedViaKeyboard = (e as MouseEvent).detail === 0; // Indicates mouse was not clicked this.focusState = MenubarState.OPEN; } @@ -845,18 +893,33 @@ export class MenubarPart extends Part { } } - private getMenubarMenus(): IMenubarData { - let ret: IMenubarData = {}; + // private getAdditionalKeybindings(): Array { + // const keybindings = []; + // if (isMacintosh) { + // keybindings.push(this.getMenubarKeybinding('workbench.action.quit')); + // } - for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { - const menu = this.topLevelMenus[topLevelMenuName]; - let menubarMenu: IMenubarMenu = { items: [] }; - this.populateMenuItems(menu, menubarMenu); - ret[topLevelMenuName] = menubarMenu; - } + // return keybindings; + // } - return ret; - } + // private getMenubarMenus(menubarData: IMenubarData): boolean { + // if (!menubarData) { + // return false; + // } + + // for (let topLevelMenuName of Object.keys(this.topLevelMenus)) { + // const menu = this.topLevelMenus[topLevelMenuName]; + // let menubarMenu: IMenubarMenu = { items: [] }; + // this.populateMenuItems(menu, menubarMenu); + // if (menubarMenu.items.length === 0) { + // // Menus are incomplete + // return false; + // } + // menubarData[topLevelMenuName] = menubarMenu; + // } + + // return true; + // } private isCurrentMenu(menuIndex: number): boolean { if (!this.focusedMenu) { @@ -925,14 +988,16 @@ export class MenubarPart extends Part { return this._onVisibilityChange.event; } - public layout(dimension: Dimension): Dimension[] { + public layout(dimension: Dimension) { + if (this.container) { + this.container.style({ height: `${dimension.height}px` }); + } + if (!this.isVisible) { this.hideMenubar(); } else { this.showMenubar(); } - - return super.layout(dimension); } public getMenubarItemsDimensions(): Dimension { @@ -945,7 +1010,7 @@ export class MenubarPart extends Part { return new Dimension(0, 0); } - public createContentArea(parent: HTMLElement): HTMLElement { + public create(parent: HTMLElement): HTMLElement { this.container = $(parent); // Build the menubar @@ -965,7 +1030,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarActiveWindowFgColor = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (menubarActiveWindowFgColor) { collector.addRule(` - .monaco-workbench .part.menubar > .menubar-menu-button { + .monaco-workbench .menubar > .menubar-menu-button { color: ${menubarActiveWindowFgColor}; } `); @@ -974,7 +1039,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarInactiveWindowFgColor = theme.getColor(TITLE_BAR_INACTIVE_FOREGROUND); if (menubarInactiveWindowFgColor) { collector.addRule(` - .monaco-workbench .part.menubar.inactive > .menubar-menu-button { + .monaco-workbench .menubar.inactive > .menubar-menu-button { color: ${menubarInactiveWindowFgColor}; } `); @@ -984,9 +1049,9 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarSelectedFgColor = theme.getColor(MENUBAR_SELECTION_FOREGROUND); if (menubarSelectedFgColor) { collector.addRule(` - .monaco-workbench .part.menubar > .menubar-menu-button.open, - .monaco-workbench .part.menubar > .menubar-menu-button:focus, - .monaco-workbench .part.menubar > .menubar-menu-button:hover { + .monaco-workbench .menubar > .menubar-menu-button.open, + .monaco-workbench .menubar > .menubar-menu-button:focus, + .monaco-workbench .menubar > .menubar-menu-button:hover { color: ${menubarSelectedFgColor}; } `); @@ -995,9 +1060,9 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarSelectedBgColor = theme.getColor(MENUBAR_SELECTION_BACKGROUND); if (menubarSelectedBgColor) { collector.addRule(` - .monaco-workbench .part.menubar > .menubar-menu-button.open, - .monaco-workbench .part.menubar > .menubar-menu-button:focus, - .monaco-workbench .part.menubar > .menubar-menu-button:hover { + .monaco-workbench .menubar > .menubar-menu-button.open, + .monaco-workbench .menubar > .menubar-menu-button:focus, + .monaco-workbench .menubar > .menubar-menu-button:hover { background-color: ${menubarSelectedBgColor}; } `); @@ -1006,18 +1071,18 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const menubarSelectedBorderColor = theme.getColor(MENUBAR_SELECTION_BORDER); if (menubarSelectedBorderColor) { collector.addRule(` - .monaco-workbench .part.menubar > .menubar-menu-button:hover { + .monaco-workbench .menubar > .menubar-menu-button:hover { outline: dashed 1px; } - .monaco-workbench .part.menubar > .menubar-menu-button.open, - .monaco-workbench .part.menubar > .menubar-menu-button:focus { + .monaco-workbench .menubar > .menubar-menu-button.open, + .monaco-workbench .menubar > .menubar-menu-button:focus { outline: solid 1px; } - .monaco-workbench .part.menubar > .menubar-menu-button.open, - .monaco-workbench .part.menubar > .menubar-menu-button:focus, - .monaco-workbench .part.menubar > .menubar-menu-button:hover { + .monaco-workbench .menubar > .menubar-menu-button.open, + .monaco-workbench .menubar > .menubar-menu-button:focus, + .monaco-workbench .menubar > .menubar-menu-button:hover { outline-offset: -1px; outline-color: ${menubarSelectedBorderColor}; } @@ -1043,7 +1108,11 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { `); } - const menuFgColor = theme.getColor(MENU_FOREGROUND); + let menuFgColor = theme.getColor(MENU_FOREGROUND); + if (!menuFgColor) { + menuFgColor = theme.getColor(foreground); + } + if (menuFgColor) { collector.addRule(` .monaco-shell .monaco-menu .monaco-action-bar.vertical, diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 4fae75a06ea..fe01f90cbe0 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -31,7 +31,7 @@ import URI from 'vs/base/common/uri'; import { Color } from 'vs/base/common/color'; import { trim } from 'vs/base/common/strings'; import { addDisposableListener, EventType, EventHelper, Dimension } from 'vs/base/browser/dom'; -import { MenubarPart } from 'vs/workbench/browser/parts/menubar/menubarPart'; +import { MenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { template, getBaseLabel } from 'vs/base/common/labels'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; @@ -52,7 +52,7 @@ export class TitlebarPart extends Part implements ITitleService { private windowControls: Builder; private maxRestoreControl: Builder; private appIcon: Builder; - private menubarPart: MenubarPart; + private menubarPart: MenubarControl; private menubar: Builder; private resizer: Builder; @@ -273,10 +273,10 @@ export class TitlebarPart extends Part implements ITitleService { } // Menubar: the menubar part which is responsible for populating both the custom and native menubars - this.menubarPart = this.instantiationService.createInstance(MenubarPart, 'workbench.parts.menubar'); + this.menubarPart = this.instantiationService.createInstance(MenubarControl, 'workbench.parts.titlebar.menubar'); this.menubar = $(this.titleContainer).div({ - 'class': ['part', 'menubar'], - id: 'workbench.parts.menubar', + 'class': ['menubar'], + id: 'workbench.parts.titlebar.menubar', role: 'menubar' }); @@ -295,11 +295,13 @@ export class TitlebarPart extends Part implements ITitleService { } // Maximize/Restore on doubleclick - this.title.on(EventType.DBLCLICK, (e) => { - EventHelper.stop(e); + if (isMacintosh) { + this.titleContainer.on(EventType.DBLCLICK, (e) => { + EventHelper.stop(e); - this.onTitleDoubleclick(); - }); + this.onTitleDoubleclick(); + }); + } // Context menu on title this.title.on([EventType.CONTEXT_MENU, EventType.MOUSE_DOWN], (e: MouseEvent) => { diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index 183b36edfb6..36268d9a636 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -377,9 +377,8 @@ export abstract class ViewContainerViewlet extends PanelViewlet implements IView private computeInitialSizes(): { [id: string]: number } { let sizes = {}; if (this.dimension) { - let totalWeight = 0; for (const viewDescriptor of this.viewsModel.visibleViewDescriptors) { - sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / totalWeight; + sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / 100; } } return sizes; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index e49e2d26fc5..8162f48a02e 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -25,6 +25,7 @@ export const EditorsVisibleContext = new RawContextKey('editorIsOpen', export const EditorGroupActiveEditorDirtyContext = new RawContextKey('groupActiveEditorDirty', false); export const NoEditorsVisibleContext: ContextKeyExpr = EditorsVisibleContext.toNegated(); export const TextCompareEditorVisibleContext = new RawContextKey('textCompareEditorVisible', false); +export const TextCompareEditorActiveContext = new RawContextKey('textCompareEditorActive', false); export const ActiveEditorGroupEmptyContext = new RawContextKey('activeEditorGroupEmpty', false); export const MultipleEditorGroupsContext = new RawContextKey('multipleEditorGroups', false); export const SingleEditorGroupsContext = MultipleEditorGroupsContext.toNegated(); diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 1801c5ad38c..f58e4867ebe 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -9,7 +9,6 @@ import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IFileService } from 'vs/platform/files/common/files'; export class ResourceContextKey implements IContextKey { @@ -19,7 +18,6 @@ export class ResourceContextKey implements IContextKey { static Resource = new RawContextKey('resource', undefined); static Extension = new RawContextKey('resourceExtname', undefined); static HasResource = new RawContextKey('resourceSet', false); - static IsFile = new RawContextKey('resourceIsFile', false); private _resourceKey: IContextKey; private _schemeKey: IContextKey; @@ -27,12 +25,10 @@ export class ResourceContextKey implements IContextKey { private _langIdKey: IContextKey; private _extensionKey: IContextKey; private _hasResource: IContextKey; - private _isFile: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IModeService private readonly _modeService: IModeService, - @IFileService private readonly _fileService: IFileService + @IModeService private readonly _modeService: IModeService ) { this._schemeKey = ResourceContextKey.Scheme.bindTo(contextKeyService); this._filenameKey = ResourceContextKey.Filename.bindTo(contextKeyService); @@ -40,7 +36,6 @@ export class ResourceContextKey implements IContextKey { this._resourceKey = ResourceContextKey.Resource.bindTo(contextKeyService); this._extensionKey = ResourceContextKey.Extension.bindTo(contextKeyService); this._hasResource = ResourceContextKey.HasResource.bindTo(contextKeyService); - this._isFile = ResourceContextKey.IsFile.bindTo(contextKeyService); } set(value: URI) { @@ -50,7 +45,6 @@ export class ResourceContextKey implements IContextKey { this._langIdKey.set(value && this._modeService.getModeIdByFilenameOrFirstLine(value.fsPath)); this._extensionKey.set(value && paths.extname(value.fsPath)); this._hasResource.set(!!value); - this._isFile.set(value && this._fileService.canHandleResource(value)); } reset(): void { @@ -60,7 +54,6 @@ export class ResourceContextKey implements IContextKey { this._langIdKey.reset(); this._extensionKey.reset(); this._hasResource.reset(); - this._isFile.reset(); } get(): URI { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 8dfe3cd10c7..8dbf7693c79 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -117,6 +117,12 @@ export const TAB_UNFOCUSED_INACTIVE_FOREGROUND = registerColor('tab.unfocusedIna // < --- Editors --- > +export const EDITOR_PANE_BACKGROUND = registerColor('editorPane.background', { + dark: editorBackground, + light: editorBackground, + hc: editorBackground +}, nls.localize('editorPaneBackground', "Background color of the editor pane visible on the left and right side of the centered editor layout.")); + registerColor('editorGroup.background', { dark: null, light: null, diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 63426fa881a..79a86a957c9 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -19,7 +19,6 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; -import * as paths from 'vs/base/common/paths'; import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen'; import * as browser from 'vs/base/browser/browser'; @@ -52,6 +51,10 @@ import { IWorkbenchIssueService } from 'vs/workbench/services/issue/common/issue import { INotificationService } from 'vs/platform/notification/common/notification'; import { IUriDisplayService } from 'vs/platform/uriDisplay/common/uriDisplay'; import { dirname } from 'vs/base/common/resources'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IQuickInputService, IQuickPickItem, IQuickInputButton } from 'vs/platform/quickinput/common/quickInput'; +import { getIconClasses } from 'vs/workbench/browser/labels'; // --- actions @@ -493,7 +496,9 @@ export class ShowStartupPerformance extends Action { } i += 1; - ticks[stat.type].push(new Tick(stat, nextStat)); + if (ticks[stat.type]) { + ticks[stat.type].push(new Tick(stat, nextStat)); + } } ticks[LoaderEventType.BeginInvokeFactory].sort(Tick.compareUsingStartTimestamp); @@ -575,20 +580,24 @@ export class ReloadWindowWithExtensionsDisabledAction extends Action { } export abstract class BaseSwitchWindow extends Action { - private closeWindowAction: CloseWindowAction; + + private closeWindowAction: IQuickInputButton = { + iconClass: 'action-remove-from-recently-opened', + tooltip: nls.localize('close', "Close Window") + }; constructor( id: string, label: string, private windowsService: IWindowsService, private windowService: IWindowService, - private quickOpenService: IQuickOpenService, + private quickInputService: IQuickInputService, private keybindingService: IKeybindingService, - private instantiationService: IInstantiationService + private modelService: IModelService, + private modeService: IModeService, ) { super(id, label); - this.closeWindowAction = this.instantiationService.createInstance(CloseWindowAction); } protected abstract isQuickNavigate(): boolean; @@ -598,56 +607,35 @@ export abstract class BaseSwitchWindow extends Action { return this.windowsService.getWindows().then(windows => { const placeHolder = nls.localize('switchWindowPlaceHolder', "Select a window to switch to"); - const picks = windows.map(win => ({ - payload: win.id, - resource: win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? URI.file(win.workspace.configPath) : void 0, - fileKind: win.filename ? FileKind.FILE : win.workspace ? FileKind.ROOT_FOLDER : win.folderUri ? FileKind.FOLDER : FileKind.FILE, - label: win.title, - description: (currentWindowId === win.id) ? nls.localize('current', "Current Window") : void 0, - run: () => { - setTimeout(() => { - // Bug: somehow when not running this code in a timeout, it is not possible to use this picker - // with quick navigate keys (not able to trigger quick navigate once running it once). - this.windowsService.showWindow(win.id).done(null, errors.onUnexpectedError); - }); - }, - action: (!this.isQuickNavigate() && currentWindowId !== win.id) ? this.closeWindowAction : void 0 - } as IFilePickOpenEntry)); - - this.quickOpenService.pick(picks, { - contextKey: 'inWindowsPicker', - autoFocus: { autoFocusFirstEntry: true }, - placeHolder, - quickNavigateConfiguration: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : void 0 + const picks = windows.map(win => { + const resource = win.filename ? URI.file(win.filename) : win.folderUri ? win.folderUri : win.workspace ? URI.file(win.workspace.configPath) : void 0; + const fileKind = win.filename ? FileKind.FILE : win.workspace ? FileKind.ROOT_FOLDER : win.folderUri ? FileKind.FOLDER : FileKind.FILE; + return { + payload: win.id, + label: win.title, + iconClasses: getIconClasses(this.modelService, this.modeService, resource, fileKind), + description: (currentWindowId === win.id) ? nls.localize('current', "Current Window") : void 0, + buttons: (!this.isQuickNavigate() && currentWindowId !== win.id) ? [this.closeWindowAction] : void 0 + } as (IQuickPickItem & { payload: number }); }); - }); - } - dispose(): void { - super.dispose(); + const autoFocusIndex = (picks.indexOf(picks.filter(pick => pick.payload === currentWindowId)[0]) + 1) % picks.length; - this.closeWindowAction.dispose(); - } -} - -class CloseWindowAction extends Action implements IPickOpenAction { - - static readonly ID = 'workbench.action.closeWindow'; - static readonly LABEL = nls.localize('close', "Close Window"); - - constructor( - @IWindowsService private windowsService: IWindowsService - ) { - super(CloseWindowAction.ID, CloseWindowAction.LABEL); - - this.class = 'action-remove-from-recently-opened'; - } - - run(item: IPickOpenItem): TPromise { - return this.windowsService.closeWindow(item.getPayload()).then(() => { - item.remove(); - - return true; + return this.quickInputService.pick(picks, { + contextKey: 'inWindowsPicker', + activeItem: picks[autoFocusIndex], + placeHolder, + quickNavigate: this.isQuickNavigate() ? { keybindings: this.keybindingService.lookupKeybindings(this.id) } : void 0, + onDidTriggerItemButton: context => { + this.windowsService.closeWindow(context.item.payload).then(() => { + context.removeItem(); + }); + } + }); + }).then(pick => { + if (pick) { + this.windowsService.showWindow(pick.payload).done(null, errors.onUnexpectedError); + } }); } } @@ -662,11 +650,13 @@ export class SwitchWindow extends BaseSwitchWindow { label: string, @IWindowsService windowsService: IWindowsService, @IWindowService windowService: IWindowService, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, windowsService, windowService, quickOpenService, keybindingService, instantiationService); + super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService); } protected isQuickNavigate(): boolean { @@ -684,11 +674,13 @@ export class QuickSwitchWindow extends BaseSwitchWindow { label: string, @IWindowsService windowsService: IWindowsService, @IWindowService windowService: IWindowService, - @IQuickOpenService quickOpenService: IQuickOpenService, + @IQuickInputService quickInputService: IQuickInputService, @IKeybindingService keybindingService: IKeybindingService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, @IInstantiationService instantiationService: IInstantiationService ) { - super(id, label, windowsService, windowService, quickOpenService, keybindingService, instantiationService); + super(id, label, windowsService, windowService, quickInputService, keybindingService, modelService, modeService); } protected isQuickNavigate(): boolean { @@ -730,7 +722,7 @@ export abstract class BaseOpenRecentAction extends Action { if (isSingleFolderWorkspaceIdentifier(workspace) && fileKind !== FileKind.FILE) { resource = workspace; label = getWorkspaceLabel(workspace, environmentService, uriDisplayService); - description = uriDisplayService.getLabel(resource.with({ path: paths.dirname(resource.path) })); + description = uriDisplayService.getLabel(dirname(resource)); } else if (isWorkspaceIdentifier(workspace)) { resource = URI.file(workspace.configPath); label = getWorkspaceLabel(workspace, environmentService, uriDisplayService); @@ -1455,6 +1447,24 @@ export class DecreaseViewSizeAction extends BaseResizeViewAction { } } +export class NewWindowTab extends Action { + + static readonly ID = 'workbench.action.newWindowTab'; + static readonly LABEL = nls.localize('newTab', "New Window Tab"); + + constructor( + id: string, + label: string, + @IWindowsService private windowsService: IWindowsService + ) { + super(NewWindowTab.ID, NewWindowTab.LABEL); + } + + run(): TPromise { + return this.windowsService.newWindowTab().then(() => true); + } +} + export class ShowPreviousWindowTab extends Action { static readonly ID = 'workbench.action.showPreviousWindowTab'; @@ -1641,25 +1651,6 @@ export class OpenPrivacyStatementUrlAction extends Action { } } -export class ShowAccessibilityOptionsAction extends Action { - - static readonly ID = 'workbench.action.showAccessibilityOptions'; - static LABEL = nls.localize('accessibilityOptions', "Accessibility Options"); - - constructor( - id: string, - label: string, - @IWindowsService private windowsService: IWindowsService - ) { - super(id, label); - } - - run(): TPromise { - return this.windowsService.openAccessibilityOptions(); - } -} - - export class ShowAboutDialogAction extends Action { static readonly ID = 'workbench.action.showAboutDialog'; diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index e2bf1cb5113..c8a4525a481 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -14,7 +14,7 @@ import { IConfigurationRegistry, Extensions as ConfigurationExtensions, Configur import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; -import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, ShowAccessibilityOptionsAction, OpenRecentAction } from 'vs/workbench/electron-browser/actions'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, OpenIssueReporterAction, ReportPerformanceIssueUsingReporterAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ShowAboutDialogAction, InspectContextKeysAction, OpenProcessExplorer, OpenTwitterUrlAction, OpenRequestFeatureUrlAction, OpenPrivacyStatementUrlAction, OpenLicenseUrlAction, OpenRecentAction } from 'vs/workbench/electron-browser/actions'; import { registerCommands, QUIT_ID } from 'vs/workbench/electron-browser/commands'; import { AddRootFolderAction, GlobalRemoveRootFolderAction, OpenWorkspaceAction, SaveWorkspaceAsAction, OpenWorkspaceConfigFileAction, DuplicateWorkspaceInNewWindowAction, OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; @@ -70,7 +70,6 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenTw workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRequestFeatureUrlAction, OpenRequestFeatureUrlAction.ID, OpenRequestFeatureUrlAction.LABEL), 'Help: Search Feature Requests', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLicenseUrlAction, OpenLicenseUrlAction.ID, OpenLicenseUrlAction.LABEL), 'Help: View License', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenPrivacyStatementUrlAction, OpenPrivacyStatementUrlAction.ID, OpenPrivacyStatementUrlAction.LABEL), 'Help: Privacy Statement', helpCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAccessibilityOptionsAction, ShowAccessibilityOptionsAction.ID, ShowAccessibilityOptionsAction.LABEL), 'Help: Accessibility Options', helpCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowAboutDialogAction, ShowAboutDialogAction.ID, ShowAboutDialogAction.LABEL), 'Help: About', helpCategory); workbenchActionsRegistry.registerWorkbenchAction( @@ -250,9 +249,21 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { group: '6_close', command: { id: CloseWorkspaceAction.ID, - title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder") + title: nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), + precondition: new RawContextKey('workspaceFolderCount', 0).notEqualsTo('0') }, - order: 3 + order: 3, + when: new RawContextKey('workbenchState', '').notEqualsTo('workspace') +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { + group: '6_close', + command: { + id: CloseWorkspaceAction.ID, + title: nls.localize({ key: 'miCloseWorkspace', comment: ['&& denotes a mnemonic'] }, "Close &&Workspace") + }, + order: 3, + when: new RawContextKey('workbenchState', '').isEqualTo('workspace') }); MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { @@ -330,6 +341,131 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 3 }); +// Help + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'workbench.action.openDocumentationUrl', + title: nls.localize({ key: 'miDocumentation', comment: ['&& denotes a mnemonic'] }, "&&Documentation") + }, + order: 3 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'update.showCurrentReleaseNotes', + title: nls.localize({ key: 'miReleaseNotes', comment: ['&& denotes a mnemonic'] }, "&&Release Notes") + }, + order: 4 +}); + +// Reference +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.keybindingsReference', + title: nls.localize({ key: 'miKeyboardShortcuts', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts Reference") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.openIntroductoryVideosUrl', + title: nls.localize({ key: 'miIntroductoryVideos', comment: ['&& denotes a mnemonic'] }, "Introductory &&Videos") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '2_reference', + command: { + id: 'workbench.action.openTipsAndTricksUrl', + title: nls.localize({ key: 'miTipsAndTricks', comment: ['&& denotes a mnemonic'] }, "&&Tips and Tricks") + }, + order: 3 +}); + +// Feedback +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openTwitterUrl', + title: nls.localize({ key: 'miTwitter', comment: ['&& denotes a mnemonic'] }, "&&Join us on Twitter") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openRequestFeatureUrl', + title: nls.localize({ key: 'miUserVoice', comment: ['&& denotes a mnemonic'] }, "&&Search Feature Requests") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '3_feedback', + command: { + id: 'workbench.action.openIssueReporter', + title: nls.localize({ key: 'miReportIssue', comment: ['&& denotes a mnemonic', 'Translate this to "Report Issue in English" in all languages please!'] }, "Report &&Issue") + }, + order: 3 +}); + +// Legal +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: 'workbench.action.openLicenseUrl', + title: nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "View &&License") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '4_legal', + command: { + id: 'workbench.action.openPrivacyStatementUrl', + title: nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement") + }, + order: 2 +}); + +// Tools +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '5_tools', + command: { + id: 'workbench.action.toggleDevTools', + title: nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools") + }, + order: 1 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '5_tools', + command: { + id: 'workbench.action.openProcessExplorer', + title: nls.localize({ key: 'miOpenProcessExplorerer', comment: ['&& denotes a mnemonic'] }, "Open &&Process Explorer") + }, + order: 2 +}); + +// About +if (!isMacintosh) { + MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: 'z_about', + command: { + id: 'workbench.action.showAboutDialog', + title: nls.localize({ key: 'miAbout', comment: ['&& denotes a mnemonic'] }, "&&About") + }, + order: 1 + }); +} // Configuration: Workbench const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -342,7 +478,7 @@ configurationRegistry.registerConfiguration({ 'properties': { 'workbench.editor.showTabs': { 'type': 'boolean', - 'description': nls.localize('showEditorTabs', "Controls if opened editors should show in tabs or not."), + 'description': nls.localize('showEditorTabs', "Controls whether opened editors should show in tabs or not."), 'default': true }, 'workbench.editor.labelFormat': { @@ -355,52 +491,58 @@ configurationRegistry.registerConfiguration({ nls.localize('workbench.editor.labelFormat.long', "Show the name of the file followed by it's absolute path.") ], 'default': 'default', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'tabDescription' }, - "Controls the format of the label for an editor. Changing this setting can for example make it easier to understand the location of a file:\n- short: 'parent'\n- medium: 'workspace/src/parent'\n- long: '/home/user/workspace/src/parent'\n- default: '.../parent', when another tab shares the same title, or the relative workspace path if tabs are disabled"), + 'description': nls.localize({ + comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], + key: 'tabDescription' + }, "Controls the format of the label for an editor."), }, 'workbench.editor.tabCloseButton': { 'type': 'string', 'enum': ['left', 'right', 'off'], 'default': 'right', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons or disables them when set to 'off'.") + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorTabCloseButton' }, "Controls the position of the editor's tabs close buttons, or disables them when set to 'off'.") }, 'workbench.editor.tabSizing': { 'type': 'string', 'enum': ['fit', 'shrink'], 'default': 'fit', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs. Set to 'fit' to keep tabs always large enough to show the full editor label. Set to 'shrink' to allow tabs to get smaller when the available space is not enough to show all tabs at once.") + 'enumDescriptions': [ + nls.localize('workbench.editor.tabSizing.fit', "Always keep tabs large enough to show the full editor label."), + nls.localize('workbench.editor.tabSizing.shrink', "Allow tabs to get smaller when the available space is not enough to show all tabs at once.") + ], + 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'tabSizing' }, "Controls the sizing of editor tabs.") }, 'workbench.editor.showIcons': { 'type': 'boolean', - 'description': nls.localize('showIcons', "Controls if opened editors should show with an icon or not. This requires an icon theme to be enabled as well."), + 'description': nls.localize('showIcons', "Controls whether opened editors should show with an icon or not. This requires an icon theme to be enabled as well."), 'default': true }, 'workbench.editor.enablePreview': { 'type': 'boolean', - 'description': nls.localize('enablePreview', "Controls if opened editors show as preview. Preview editors are reused until they are kept (e.g. via double click or editing) and show up with an italic font style."), + 'description': nls.localize('enablePreview', "Controls whether opened editors show as preview. Preview editors are reused until they are kept (e.g. via double click or editing) and show up with an italic font style."), 'default': true }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls if opened editors from Quick Open show as preview. Preview editors are reused until they are kept (e.g. via double click or editing)."), + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls whether opened editors from Quick Open show as preview. Preview editors are reused until they are kept (e.g. via double click or editing)."), 'default': true }, 'workbench.editor.closeOnFileDelete': { 'type': 'boolean', - 'description': nls.localize('closeOnFileDelete', "Controls whether editors showing a file should close automatically when the file is deleted or renamed by some other process. Disabling this will keep the editor open as dirty on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), - 'default': true + 'description': nls.localize('closeOnFileDelete', "Controls whether editors showing a file that was opened during the session should close automatically when getting deleted or renamed by some other process. Disabling this will keep the editor open on such an event. Note that deleting from within the application will always close the editor and that dirty files will never close to preserve your data."), + 'default': false }, 'workbench.editor.openPositioning': { 'type': 'string', 'enum': ['left', 'right', 'first', 'last'], 'default': 'right', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select 'left' or 'right' to open editors to the left or right of the currently active one. Select 'first' or 'last' to open editors independently from the currently active one.") + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'editorOpenPositioning' }, "Controls where editors open. Select `left` or `right` to open editors to the left or right of the currently active one. Select `first` or `last` to open editors independently from the currently active one.") }, 'workbench.editor.openSideBySideDirection': { 'type': 'string', 'enum': ['right', 'down'], 'default': 'right', - 'description': nls.localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to open down, the editors will open below the currently active one.") + 'markdownDescription': nls.localize('sideBySideDirection', "Controls the default direction of editors that are opened side by side (e.g. from the explorer). By default, editors will open on the right hand side of the currently active one. If changed to `down`, the editors will open below the currently active one.") }, 'workbench.editor.closeEmptyGroups': { 'type': 'boolean', @@ -409,7 +551,7 @@ configurationRegistry.registerConfiguration({ }, 'workbench.editor.revealIfOpen': { 'type': 'boolean', - 'description': nls.localize('revealIfOpen', "Controls if an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), + 'description': nls.localize('revealIfOpen', "Controls whether an editor is revealed in any of the visible groups if opened. If disabled, an editor will prefer to open in the currently active editor group. If enabled, an already opened editor will be revealed instead of opened again in the currently active editor group. Note that there are some cases where this setting is ignored, e.g. when forcing an editor to open in a specific group or to the side of the currently active group."), 'default': false }, 'workbench.editor.swipeToNavigate': { @@ -425,22 +567,22 @@ configurationRegistry.registerConfiguration({ }, 'workbench.commandPalette.preserveInput': { 'type': 'boolean', - 'description': nls.localize('preserveInput', "Controls if the last typed input to the command palette should be restored when opening it the next time."), + 'description': nls.localize('preserveInput', "Controls whether the last typed input to the command palette should be restored when opening it the next time."), 'default': false }, 'workbench.quickOpen.closeOnFocusLost': { 'type': 'boolean', - 'description': nls.localize('closeOnFocusLost', "Controls if Quick Open should close automatically once it loses focus."), + 'description': nls.localize('closeOnFocusLost', "Controls whether Quick Open should close automatically once it loses focus."), 'default': true }, 'workbench.settings.openDefaultSettings': { 'type': 'boolean', - 'description': nls.localize('openDefaultSettings', "Controls if opening settings also opens an editor showing all default settings."), + 'description': nls.localize('openDefaultSettings', "Controls whether opening settings also opens an editor showing all default settings."), 'default': true }, 'workbench.settings.openDefaultKeybindings': { 'type': 'boolean', - 'description': nls.localize('openDefaultKeybindings', "Controls if opening keybinding settings also opens an editor showing all default keybindings."), + 'description': nls.localize('openDefaultKeybindings', "Controls whether opening keybinding settings also opens an editor showing all default keybindings."), 'default': true }, 'workbench.sideBar.location': { @@ -475,7 +617,7 @@ configurationRegistry.registerConfiguration({ 'enum': ['default', 'antialiased', 'none', 'auto'], 'default': 'default', 'description': - nls.localize('fontAliasing', "Controls font aliasing method in the workbench.\n- default: Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text\n- antialiased: Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall\n- none: Disables font smoothing. Text will show with jagged sharp edges\n- auto: Applies `default` or `antialiased` automatically based on the DPI of displays."), + nls.localize('fontAliasing', "Controls font aliasing method in the workbench."), 'enumDescriptions': [ nls.localize('workbench.fontAliasing.default', "Sub-pixel font smoothing. On most non-retina displays this will give the sharpest text."), nls.localize('workbench.fontAliasing.antialiased', "Smooth the font on the level of the pixel, as opposed to the subpixel. Can make the font appear lighter overall."), @@ -486,7 +628,7 @@ configurationRegistry.registerConfiguration({ }, 'workbench.settings.enableNaturalLanguageSearch': { 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings."), + 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings. The natural language search is provided by an online service."), 'default': true, 'scope': ConfigurationScope.WINDOW, 'tags': ['usesOnlineServices'] @@ -495,7 +637,7 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': ['hide', 'filter', 'show'], 'description': nls.localize('settingsSearchTocBehavior', "Controls the behavior of the settings editor Table of Contents while searching."), - 'default': 'hide', + 'default': 'filter', 'scope': ConfigurationScope.WINDOW }, 'workbench.settings.tocVisible': { @@ -503,6 +645,12 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('settingsTocVisible', "Controls whether the settings editor Table of Contents is visible."), 'default': true, 'scope': ConfigurationScope.WINDOW + }, + 'workbench.enableExperiments': { + 'type': 'boolean', + 'description': nls.localize('workbench.enableExperiments', "Fetches experiments to run from a Microsoft online service."), + 'default': true, + 'tags': ['usesOnlineServices'] } } }); @@ -519,41 +667,41 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': ['on', 'off', 'default'], 'enumDescriptions': [ - nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window"), - nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window"), + nls.localize('window.openFilesInNewWindow.on', "Files will open in a new window."), + nls.localize('window.openFilesInNewWindow.off', "Files will open in the window with the files' folder open or the last active window."), isMacintosh ? - nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder") : - nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu)") + nls.localize('window.openFilesInNewWindow.defaultMac', "Files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder.") : + nls.localize('window.openFilesInNewWindow.default', "Files will open in a new window unless picked from within the application (e.g. via the File menu).") ], 'default': 'off', 'scope': ConfigurationScope.APPLICATION, - 'description': + 'markdownDescription': isMacintosh ? - nls.localize('openFilesInNewWindowMac', "Controls if files should open in a new window.\n- default: files will open in the window with the files' folder open or the last active window unless opened via the Dock or from Finder\n- on: files will open in a new window\n- off: files will open in the window with the files' folder open or 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).") : - nls.localize('openFilesInNewWindow', "Controls if files should open in a new window.\n- default: files will open in a new window unless picked from within the application (e.g. via the File menu)\n- on: files will open in a new window\n- off: files will open in the window with the files' folder open or 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).") + 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)") + 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, - 'description': nls.localize('openFoldersInNewWindow', "Controls if folders should open in a new window or replace the last active window.\n- default: folders will open in a new window unless a folder is picked from within the application (e.g. via the File menu)\n- on: folders will open in a new window\n- off: folders will 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).") + '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'], 'enumDescriptions': [ - nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window"), - nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance") + nls.localize('window.openWithoutArgumentsInNewWindow.on', "Open a new empty window."), + nls.localize('window.openWithoutArgumentsInNewWindow.off', "Focus the last active running instance.") ], 'default': isMacintosh ? 'off' : 'on', 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('openWithoutArgumentsInNewWindow', "Controls if a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\n- on: open a new empty window\n- off: the last active running instance will get focus\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).") + 'description': nls.localize('openWithoutArgumentsInNewWindow', "Controls whether a new empty window should open when starting a second instance without arguments or if the last running instance should get focus.\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.restoreWindows': { 'type': 'string', @@ -582,7 +730,7 @@ configurationRegistry.registerConfiguration({ 'window.title': { 'type': 'string', 'default': isMacintosh ? '${activeEditorShort}${separator}${rootName}' : '${dirty}${activeEditorShort}${separator}${rootName}${separator}${appName}', - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'title' }, + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by parenthesis are not to be translated.'], key: 'title' }, "Controls the window title based on the active editor. Variables are substituted based on the context:\n- `\${activeEditorShort}`: the file name (e.g. myFile.txt).\n- `\${activeEditorMedium}`: the path of the file relative to the workspace folder (e.g. myFolder/myFile.txt).\n- `\${activeEditorLong}`: the full path of the file (e.g. /Users/Development/myProject/myFolder/myFile.txt).\n- `\${folderName}`: name of the workspace folder the file is contained in (e.g. myFolder).\n- `\${folderPath}`: file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder).\n- `\${rootName}`: name of the workspace (e.g. myFolder or myWorkspace).\n- `\${rootPath}`: file path of the workspace (e.g. /Users/Development/myWorkspace).\n- `\${appName}`: e.g. VS Code.\n- `\${dirty}`: a dirty indicator if the active editor is dirty.\n- `\${separator}`: a conditional separator (\" - \") that only shows when surrounded by variables with values or static text.") }, 'window.newWindowDimensions': { @@ -601,7 +749,7 @@ configurationRegistry.registerConfiguration({ 'window.closeWhenEmpty': { 'type': 'boolean', 'default': false, - 'description': nls.localize('closeWhenEmpty', "Controls if closing the last editor should also close the window. This setting only applies for windows that do not show folders.") + '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', @@ -648,7 +796,7 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': false, 'scope': ConfigurationScope.APPLICATION, - 'description': nls.localize('window.smoothScrollingWorkaround', "Enable this workaround if scrolling is no longer smooth after restoring a minimized VS Code window. This is a workaround for an issue (https://github.com/Microsoft/vscode/issues/13612) where scrolling starts to lag on devices with precision trackpads like the Surface devices from Microsoft. Enabling this workaround can result in a little bit of layout flickering after restoring the window from minimized state but is otherwise harmless. Note: in order for this workaround to function, make sure to also set `#window.titleBarStyle#` to `native`."), + 'markdownDescription': nls.localize('window.smoothScrollingWorkaround', "Enable this workaround if scrolling is no longer smooth after restoring a minimized VS Code window. This is a workaround for an issue (https://github.com/Microsoft/vscode/issues/13612) where scrolling starts to lag on devices with precision trackpads like the Surface devices from Microsoft. Enabling this workaround can result in a little bit of layout flickering after restoring the window from minimized state but is otherwise harmless. Note: in order for this workaround to function, make sure to also set `#window.titleBarStyle#` to `native`."), 'included': isWindows }, 'window.clickThroughInactive': { @@ -671,12 +819,12 @@ configurationRegistry.registerConfiguration({ 'zenMode.fullScreen': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.fullScreen', "Controls if turning on Zen Mode also puts the workbench into full screen mode.") + 'description': nls.localize('zenMode.fullScreen', "Controls whether turning on Zen Mode also puts the workbench into full screen mode.") }, 'zenMode.centerLayout': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.centerLayout', "Controls if turning on Zen Mode also centers the layout.") + 'description': nls.localize('zenMode.centerLayout', "Controls whether turning on Zen Mode also centers the layout.") }, 'zenMode.hideTabs': { 'type': 'boolean', @@ -691,7 +839,7 @@ configurationRegistry.registerConfiguration({ 'zenMode.hideActivityBar': { 'type': 'boolean', 'default': true, - 'description': nls.localize('zenMode.hideActivityBar', "Controls if turning on Zen Mode also hides the activity bar at the left of the workbench.") + 'description': nls.localize('zenMode.hideActivityBar', "Controls whether turning on Zen Mode also hides the activity bar at the left of the workbench.") }, 'zenMode.restore': { 'type': 'boolean', diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 4911010c77c..7a2b1a8207e 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -15,11 +15,16 @@ /* Font Families (with CJK support) */ -.monaco-shell { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif; } -.monaco-shell:lang(zh-Hans) { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } -.monaco-shell:lang(zh-Hant) { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; } -.monaco-shell:lang(ja) { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } -.monaco-shell:lang(ko) { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } +.monaco-shell, +.monaco-shell .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif; } +.monaco-shell:lang(zh-Hans), +.monaco-shell:lang(zh-Hans) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Microsoft YaHei", "PingFang SC", "Hiragino Sans GB", "Source Han Sans SC", "Source Han Sans CN", "Source Han Sans", sans-serif; } +.monaco-shell:lang(zh-Hant), +.monaco-shell:lang(zh-Hant) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Microsoft Jhenghei", "PingFang TC", "Source Han Sans TC", "Source Han Sans", "Source Han Sans TW", sans-serif; } +.monaco-shell:lang(ja), +.monaco-shell:lang(ja) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Meiryo", "Hiragino Kaku Gothic Pro", "Source Han Sans J", "Source Han Sans JP", "Source Han Sans", "Sazanami Gothic", "IPA Gothic", sans-serif; } +.monaco-shell:lang(ko), +.monaco-shell:lang(ko) .monaco-menu-container .monaco-menu { font-family: -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Noto Sans", "Malgun Gothic", "Nanum Gothic", "Dotom", "Apple SD Gothic Neo", "AppleGothic", "Source Han Sans K", "Source Han Sans JR", "Source Han Sans", "UnDotum", "FBaekmuk Gulim", sans-serif; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index c3ead3cacd8..cb1958138b4 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -12,7 +12,6 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose, toDisposable, Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; import * as DOM from 'vs/base/browser/dom'; -import { Builder, $ } from 'vs/base/browser/builder'; import { RunOnceScheduler } from 'vs/base/common/async'; import * as browser from 'vs/base/browser/browser'; import * as perf from 'vs/base/common/performance'; @@ -23,7 +22,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { isWindows, isLinux, isMacintosh } from 'vs/base/common/platform'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; -import { IEditorInputFactoryRegistry, Extensions as EditorExtensions, TextCompareEditorVisibleContext, TEXT_DIFF_EDITOR_ID, EditorsVisibleContext, InEditorZenModeContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, IUntitledResourceInput, IResourceDiffInput, SplitEditorsVertically } from 'vs/workbench/common/editor'; +import { IEditorInputFactoryRegistry, Extensions as EditorExtensions, TextCompareEditorVisibleContext, TEXT_DIFF_EDITOR_ID, EditorsVisibleContext, InEditorZenModeContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, IUntitledResourceInput, IResourceDiffInput, SplitEditorsVertically, TextCompareEditorActiveContext } from 'vs/workbench/common/editor'; import { HistoryService } from 'vs/workbench/services/history/electron-browser/history'; import { ActivitybarPart } from 'vs/workbench/browser/parts/activitybar/activitybarPart'; import { SidebarPart } from 'vs/workbench/browser/parts/sidebar/sidebarPart'; @@ -85,7 +84,7 @@ import { MenuService } from 'vs/workbench/services/actions/common/menuService'; import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { OpenRecentAction, ToggleDevToolsAction, ReloadWindowAction, ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, ReloadWindowWithExtensionsDisabledAction } from 'vs/workbench/electron-browser/actions'; +import { OpenRecentAction, ToggleDevToolsAction, ReloadWindowAction, ShowPreviousWindowTab, MoveWindowTabToNewWindow, MergeAllWindowTabs, ShowNextWindowTab, ToggleWindowTabsBar, ReloadWindowWithExtensionsDisabledAction, NewWindowTab } from 'vs/workbench/electron-browser/actions'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { WorkspaceEditingService } from 'vs/workbench/services/workspace/node/workspaceEditingService'; @@ -190,7 +189,7 @@ export class Workbench extends Disposable implements IPartService { _serviceBrand: any; private workbenchParams: WorkbenchParams; - private workbench: Builder; + private workbench: HTMLElement; private workbenchStarted: boolean; private workbenchCreated: boolean; private workbenchShutdown: boolean; @@ -296,10 +295,13 @@ export class Workbench extends Disposable implements IPartService { } private createWorkbench(): void { - this.workbench = $().div({ - 'class': `monaco-workbench ${isWindows ? 'windows' : isLinux ? 'linux' : 'mac'}`, - id: Identifiers.WORKBENCH_CONTAINER - }); + this.workbench = document.createElement('div'); + this.workbench.id = Identifiers.WORKBENCH_CONTAINER; + DOM.addClasses(this.workbench, 'monaco-workbench', isWindows ? 'windows' : isLinux ? 'linux' : 'mac'); + + this._register(DOM.addDisposableListener(this.workbench, DOM.EventType.SCROLL, () => { + this.workbench.scrollTop = 0; // Prevent workbench from scrolling #55456 + })); } private createGlobalActions(): void { @@ -315,6 +317,7 @@ export class Workbench extends Disposable implements IPartService { // Actions for macOS native tabs management (only when enabled) const windowConfig = this.configurationService.getValue(); if (windowConfig && windowConfig.window && windowConfig.window.nativeTabs) { + registry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowTab, NewWindowTab.ID, NewWindowTab.LABEL), 'New Window Tab'); registry.registerWorkbenchAction(new SyncActionDescriptor(ShowPreviousWindowTab, ShowPreviousWindowTab.ID, ShowPreviousWindowTab.LABEL), 'Show Previous Window Tab'); registry.registerWorkbenchAction(new SyncActionDescriptor(ShowNextWindowTab, ShowNextWindowTab.ID, ShowNextWindowTab.LABEL), 'Show Next Window Tab'); registry.registerWorkbenchAction(new SyncActionDescriptor(MoveWindowTabToNewWindow, MoveWindowTabToNewWindow.ID, MoveWindowTabToNewWindow.LABEL), 'Move Window Tab to New Window'); @@ -356,7 +359,7 @@ export class Workbench extends Disposable implements IPartService { serviceCollection.set(IListService, this.instantiationService.createInstance(ListService)); // Context view service - this.contextViewService = this.instantiationService.createInstance(ContextViewService, this.workbench.getHTMLElement()); + this.contextViewService = this.instantiationService.createInstance(ContextViewService, this.workbench); serviceCollection.set(IContextViewService, this.contextViewService); // Use themable context menus when custom titlebar is enabled to match custom menubar @@ -512,9 +515,10 @@ export class Workbench extends Disposable implements IPartService { // Apply as CSS class const isFullscreen = browser.isFullscreen(); if (isFullscreen) { - this.workbench.addClass('fullscreen'); + DOM.addClass(this.workbench, 'fullscreen'); } else { - this.workbench.removeClass('fullscreen'); + DOM.removeClass(this.workbench, 'fullscreen'); + if (this.zenMode.transitionedToFullScreen && this.zenMode.active) { this.toggleZenMode(); } @@ -595,17 +599,24 @@ export class Workbench extends Disposable implements IPartService { private handleContextKeys(): void { this.inZenMode = InEditorZenModeContext.bindTo(this.contextKeyService); + (new RawContextKey('isMac', isMacintosh)).bindTo(this.contextKeyService); + (new RawContextKey('isLinux', isLinux)).bindTo(this.contextKeyService); + (new RawContextKey('isWindows', isWindows)).bindTo(this.contextKeyService); + const sidebarVisibleContextRaw = new RawContextKey('sidebarVisible', false); this.sideBarVisibleContext = sidebarVisibleContextRaw.bindTo(this.contextKeyService); const editorsVisibleContext = EditorsVisibleContext.bindTo(this.contextKeyService); const textCompareEditorVisible = TextCompareEditorVisibleContext.bindTo(this.contextKeyService); + const textCompareEditorActive = TextCompareEditorActiveContext.bindTo(this.contextKeyService); const activeEditorGroupEmpty = ActiveEditorGroupEmptyContext.bindTo(this.contextKeyService); const multipleEditorGroups = MultipleEditorGroupsContext.bindTo(this.contextKeyService); const updateEditorContextKeys = () => { + const activeControl = this.editorService.activeControl; const visibleEditors = this.editorService.visibleControls; + textCompareEditorActive.set(activeControl && activeControl.getId() === TEXT_DIFF_EDITOR_ID); textCompareEditorVisible.set(visibleEditors.some(control => control.getId() === TEXT_DIFF_EDITOR_ID)); if (visibleEditors.length > 0) { @@ -916,9 +927,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - this.workbench.addClass('nostatusbar'); + DOM.addClass(this.workbench, 'nostatusbar'); } else { - this.workbench.removeClass('nostatusbar'); + DOM.removeClass(this.workbench, 'nostatusbar'); } // Layout @@ -943,7 +954,7 @@ export class Workbench extends Disposable implements IPartService { this.workbenchLayout = this.instantiationService.createInstance( WorkbenchLayout, this.container, - this.workbench.getHTMLElement(), + this.workbench, { titlebar: this.titlebarPart, activitybar: this.activitybarPart, @@ -963,13 +974,15 @@ export class Workbench extends Disposable implements IPartService { // Apply sidebar state as CSS class if (this.sideBarHidden) { - this.workbench.addClass('nosidebar'); + DOM.addClass(this.workbench, 'nosidebar'); } + if (this.panelHidden) { - this.workbench.addClass('nopanel'); + DOM.addClass(this.workbench, 'nopanel'); } + if (this.statusBarHidden) { - this.workbench.addClass('nostatusbar'); + DOM.addClass(this.workbench, 'nostatusbar'); } // Apply font aliasing @@ -977,7 +990,7 @@ export class Workbench extends Disposable implements IPartService { // Apply fullscreen state if (browser.isFullscreen()) { - this.workbench.addClass('fullscreen'); + DOM.addClass(this.workbench, 'fullscreen'); } // Create Parts @@ -992,80 +1005,63 @@ export class Workbench extends Disposable implements IPartService { this.createNotificationsHandlers(); // Add Workbench to DOM - this.workbench.appendTo(this.container); + this.container.appendChild(this.workbench); } private createTitlebarPart(): void { - const titlebarContainer = $(this.workbench).div({ - 'class': ['part', 'titlebar'], - id: Identifiers.TITLEBAR_PART, - role: 'contentinfo' - }); + const titlebarContainer = this.createPart(Identifiers.TITLEBAR_PART, ['part', 'titlebar'], 'contentinfo'); - this.titlebarPart.create(titlebarContainer.getHTMLElement()); + this.titlebarPart.create(titlebarContainer); } private createActivityBarPart(): void { - const activitybarPartContainer = $(this.workbench) - .div({ - 'class': ['part', 'activitybar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], - id: Identifiers.ACTIVITYBAR_PART, - role: 'navigation' - }); + const activitybarPartContainer = this.createPart(Identifiers.ACTIVITYBAR_PART, ['part', 'activitybar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], 'navigation'); - this.activitybarPart.create(activitybarPartContainer.getHTMLElement()); + this.activitybarPart.create(activitybarPartContainer); } private createSidebarPart(): void { - const sidebarPartContainer = $(this.workbench) - .div({ - 'class': ['part', 'sidebar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], - id: Identifiers.SIDEBAR_PART, - role: 'complementary' - }); + const sidebarPartContainer = this.createPart(Identifiers.SIDEBAR_PART, ['part', 'sidebar', this.sideBarPosition === Position.LEFT ? 'left' : 'right'], 'complementary'); - this.sidebarPart.create(sidebarPartContainer.getHTMLElement()); + this.sidebarPart.create(sidebarPartContainer); } private createPanelPart(): void { - const panelPartContainer = $(this.workbench) - .div({ - 'class': ['part', 'panel', this.panelPosition === Position.BOTTOM ? 'bottom' : 'right'], - id: Identifiers.PANEL_PART, - role: 'complementary' - }); + const panelPartContainer = this.createPart(Identifiers.PANEL_PART, ['part', 'panel', this.panelPosition === Position.BOTTOM ? 'bottom' : 'right'], 'complementary'); - this.panelPart.create(panelPartContainer.getHTMLElement()); + this.panelPart.create(panelPartContainer); } private createEditorPart(): void { - const editorContainer = $(this.workbench) - .div({ - 'class': ['part', 'editor'], - id: Identifiers.EDITOR_PART, - role: 'main' - }); + const editorContainer = this.createPart(Identifiers.EDITOR_PART, ['part', 'editor'], 'main'); - this.editorPart.create(editorContainer.getHTMLElement()); + this.editorPart.create(editorContainer); } private createStatusbarPart(): void { - const statusbarContainer = $(this.workbench).div({ - 'class': ['part', 'statusbar'], - id: Identifiers.STATUSBAR_PART, - role: 'contentinfo' - }); + const statusbarContainer = this.createPart(Identifiers.STATUSBAR_PART, ['part', 'statusbar'], 'contentinfo'); - this.statusbarPart.create(statusbarContainer.getHTMLElement()); + this.statusbarPart.create(statusbarContainer); + } + + private createPart(id: string, classes: string[], role: string): HTMLElement { + const part = document.createElement('div'); + classes.forEach(clazz => DOM.addClass(part, clazz)); + part.id = id; + part.setAttribute('role', role); + + this.workbench.appendChild(part); + + return part; } private createNotificationsHandlers(): void { // Notifications Center - this.notificationsCenter = this._register(this.instantiationService.createInstance(NotificationsCenter, this.workbench.getHTMLElement(), this.notificationService.model)); + this.notificationsCenter = this._register(this.instantiationService.createInstance(NotificationsCenter, this.workbench, this.notificationService.model)); // Notifications Toasts - this.notificationsToasts = this._register(this.instantiationService.createInstance(NotificationsToasts, this.workbench.getHTMLElement(), this.notificationService.model)); + this.notificationsToasts = this._register(this.instantiationService.createInstance(NotificationsToasts, this.workbench, this.notificationService.model)); // Notifications Alerts this._register(this.instantiationService.createInstance(NotificationsAlerts, this.notificationService.model)); @@ -1122,7 +1118,7 @@ export class Workbench extends Disposable implements IPartService { get onEditorLayout(): Event { return this.editorPart.onDidLayout; } isCreated(): boolean { - return this.workbenchCreated && this.workbenchStarted; + return !!(this.workbenchCreated && this.workbenchStarted); } hasFocus(part: Parts): boolean { @@ -1315,9 +1311,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - this.workbench.addClass('nosidebar'); + DOM.addClass(this.workbench, 'nosidebar'); } else { - this.workbench.removeClass('nosidebar'); + DOM.removeClass(this.workbench, 'nosidebar'); } // If sidebar becomes hidden, also hide the current active Viewlet if any @@ -1366,9 +1362,9 @@ export class Workbench extends Disposable implements IPartService { // Adjust CSS if (hidden) { - this.workbench.addClass('nopanel'); + DOM.addClass(this.workbench, 'nopanel'); } else { - this.workbench.removeClass('nopanel'); + DOM.removeClass(this.workbench, 'nopanel'); } // If panel part becomes hidden, also hide the current active panel if any diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts index 54006b5ff83..513c3d605ce 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -10,7 +10,6 @@ import * as pfs from 'vs/base/node/pfs'; import * as platform from 'vs/base/common/platform'; import { nfcall } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -20,6 +19,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import Severity from 'vs/base/common/severity'; import { ILogService } from 'vs/platform/log/common/log'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function ignore(code: string, value: T = null): (err: any) => TPromise { return err => err.code === code ? TPromise.as(value) : TPromise.wrapError(err); @@ -28,7 +28,7 @@ function ignore(code: string, value: T = null): (err: any) => TPromise { let _source: string = null; function getSource(): string { if (!_source) { - const root = URI.parse(require.toUrl('')).fsPath; + const root = getPathFromAmdModule(require, ''); _source = path.resolve(root, '..', 'bin', 'code'); } return _source; diff --git a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts b/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts index 15a56872cdc..4b1038f2039 100644 --- a/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts +++ b/src/vs/workbench/parts/codeEditor/electron-browser/languageConfiguration/languageConfigurationExtensionPoint.ts @@ -89,7 +89,7 @@ export class LanguageConfigurationFileHandler { } private _handleConfigFile(languageIdentifier: LanguageIdentifier, configFileLocation: URI): void { - this._fileService.resolveContent(configFileLocation).then((contents) => { + this._fileService.resolveContent(configFileLocation, { encoding: 'utf8' }).then((contents) => { const errors: ParseError[] = []; const configuration = parse(contents.value.toString(), errors); if (errors.length) { diff --git a/src/vs/workbench/parts/comments/common/commentModel.ts b/src/vs/workbench/parts/comments/common/commentModel.ts index 4fb5b74843c..305acad3ce4 100644 --- a/src/vs/workbench/parts/comments/common/commentModel.ts +++ b/src/vs/workbench/parts/comments/common/commentModel.ts @@ -67,8 +67,12 @@ export class CommentsModel { this.resourceCommentThreads = flatten(values(this.commentThreadsMap)); } - public updateCommentThreads(event: CommentThreadChangedEvent): void { + public updateCommentThreads(event: CommentThreadChangedEvent): boolean { const { owner, removed, changed, added } = event; + if (!this.commentThreadsMap.has(owner)) { + return false; + } + let threadsForOwner = this.commentThreadsMap.get(owner); removed.forEach(thread => { @@ -96,10 +100,20 @@ export class CommentsModel { matchingResourceData.commentThreads[index] = ResourceWithCommentThreads.createCommentNode(URI.parse(matchingResourceData.id), thread); }); - threadsForOwner = threadsForOwner.concat(this.groupByResource(added)); + added.forEach(thread => { + const existingResource = threadsForOwner.filter(resourceWithThreads => resourceWithThreads.resource.toString() === thread.resource); + if (existingResource.length) { + const resource = existingResource[0]; + resource.commentThreads.push(ResourceWithCommentThreads.createCommentNode(resource.resource, thread)); + } else { + threadsForOwner.push(new ResourceWithCommentThreads(URI.parse(thread.resource), [thread])); + } + }); this.commentThreadsMap.set(owner, threadsForOwner); this.resourceCommentThreads = flatten(values(this.commentThreadsMap)); + + return removed.length > 0 || changed.length > 0 || added.length > 0; } public hasCommentThreads(): boolean { diff --git a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts index 41fd94bf268..de324d7d2f0 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentThreadWidget.ts @@ -25,7 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IModelService } from 'vs/editor/common/services/modelService'; import { SimpleCommentEditor } from './simpleCommentEditor'; import URI from 'vs/base/common/uri'; -import { transparent, editorForeground, inputValidationErrorBorder, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; +import { transparent, editorForeground, textLinkActiveForeground, textLinkForeground, focusBorder, textBlockQuoteBackground, textBlockQuoteBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; @@ -354,9 +354,14 @@ export class ReviewZoneWidget extends ZoneWidget { this._localToDispose.push(this._commentEditor.onKeyDown((ev: IKeyboardEvent) => { const hasExistingComments = this._commentThread.comments.length > 0; - if (this._commentEditor.getModel().getValueLength() === 0 && ev.keyCode === KeyCode.Escape && hasExistingComments) { - if (dom.hasClass(this._commentForm, 'expand')) { - dom.removeClass(this._commentForm, 'expand'); + + if (this._commentEditor.getModel().getValueLength() === 0 && ev.keyCode === KeyCode.Escape) { + if (hasExistingComments) { + if (dom.hasClass(this._commentForm, 'expand')) { + dom.removeClass(this._commentForm, 'expand'); + } + } else { + this.dispose(); } } })); @@ -366,23 +371,17 @@ export class ReviewZoneWidget extends ZoneWidget { const button = new Button(formActions); attachButtonStyler(button, this.themeService); button.label = 'Add comment'; - button.onDidClick(async () => { - if (!this._commentEditor.getValue()) { - this._commentEditor.focus(); - this._commentEditor.getDomNode().style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - - this._disposables.push(this._commentEditor.onDidChangeModelContent(_ => { - if (!this._commentEditor.getValue()) { - this._commentEditor.getDomNode().style.outline = `1px solid ${this.themeService.getTheme().getColor(inputValidationErrorBorder)}`; - } else { - this._commentEditor.getDomNode().style.outline = ''; - } - })); - - return; + button.enabled = false; + this._localToDispose.push(this._commentEditor.onDidChangeModelContent(_ => { + if (this._commentEditor.getValue()) { + button.enabled = true; + } else { + button.enabled = false; } + })); + button.onDidClick(async () => { let newCommentThread; if (this._commentThread.threadId) { // reply diff --git a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts b/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts index c916bec1da8..4701e552987 100644 --- a/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts +++ b/src/vs/workbench/parts/comments/electron-browser/commentsPanel.ts @@ -262,7 +262,9 @@ export class CommentsPanel extends Panel { } private onCommentsUpdated(e: CommentThreadChangedEvent): void { - this.commentsModel.updateCommentThreads(e); - this.refresh(); + const didUpdate = this.commentsModel.updateCommentThreads(e); + if (didUpdate) { + this.refresh(); + } } } diff --git a/src/vs/workbench/parts/comments/electron-browser/media/panel.css b/src/vs/workbench/parts/comments/electron-browser/media/panel.css index 45268e182f1..3c2b37bcf3f 100644 --- a/src/vs/workbench/parts/comments/electron-browser/media/panel.css +++ b/src/vs/workbench/parts/comments/electron-browser/media/panel.css @@ -30,8 +30,15 @@ opacity: 0.5; } +.comments-panel .comments-panel-container .tree-container .comment-container .text { + flex: 1; + min-width: 0; +} + .comments-panel .comments-panel-container .tree-container .comment-container .text * { margin: 0; + text-overflow: ellipsis; + overflow: hidden; } .comments-panel .comments-panel-container .message-box-container { @@ -49,6 +56,5 @@ .comments-panel .comments-panel-container .tree-container .comment-container { line-height: 22px; - text-overflow: ellipsis; - overflow: hidden; + margin-right: 5px; } \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 42d3ff235fd..8590c1bc66b 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -72,6 +72,7 @@ export class StartDebugActionItem implements IActionItem { dom.addClass(container, 'start-debug-action-item'); this.start = dom.append(container, $('.icon')); this.start.title = this.action.label; + this.start.setAttribute('role', 'button'); this.start.tabIndex = 0; this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => { diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index 545751a774d..19f6fb170af 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -94,7 +94,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi this.updateScheduler = this._register(new RunOnceScheduler(() => { const state = this.debugService.state; const toolBarLocation = this.configurationService.getValue('debug').toolBarLocation; - if (state === State.Inactive || toolBarLocation === 'docked' || toolBarLocation === 'hidden') { + if (state === State.Inactive || state === State.Initializing || toolBarLocation === 'docked' || toolBarLocation === 'hidden') { return this.hide(); } diff --git a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts index da803391a97..d5f424be629 100644 --- a/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts +++ b/src/vs/workbench/parts/debug/browser/loadedScriptsView.ts @@ -7,18 +7,293 @@ import * as nls from 'vs/nls'; import { TreeViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { TPromise } from 'vs/base/common/winjs.base'; import * as dom from 'vs/base/browser/dom'; +import * as errors from 'vs/base/common/errors'; +import { normalize, isAbsolute, sep } from 'vs/base/common/paths'; import { IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { WorkbenchTree } from 'vs/platform/list/browser/listService'; +import { WorkbenchTree, TreeResourceNavigator } from 'vs/platform/list/browser/listService'; import { renderViewTree, twistiePixels } from 'vs/workbench/parts/debug/browser/baseDebugView'; import { IAccessibilityProvider, ITree, IRenderer, IDataSource } from 'vs/base/parts/tree/browser/tree'; +import { ISession, IDebugService, IModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/parts/debug/common/debug'; +import { Source } from 'vs/workbench/parts/debug/common/debugSource'; +import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { tildify } from 'vs/base/common/labels'; +import { isWindows } from 'vs/base/common/platform'; +import URI from 'vs/base/common/uri'; +import { ltrim } from 'vs/base/common/strings'; + +const SMART = true; + +const $ = dom.$; + +const SESSION_TEMPLATE_ID = 'session'; +const SOURCE_TEMPLATE_ID = 'source'; +const ROOT_FOLDER_TEMPLATE_ID = 'node'; + +class BaseTreeItem { + + private _id: string; + private _children: { [key: string]: BaseTreeItem; }; + private _source: Source; + + constructor(private _parent: BaseTreeItem, private _label: string) { + this._id = this._parent ? `${this._parent._id}/${this._label}` : this._label; + this._children = {}; + } + + getLabel() { + const child = this.oneChild(); + if (child) { + const sep = this instanceof RootFolderTreeItem ? ' • ' : '/'; + return `${this._label}${sep}${child.getLabel()}`; + } + return this._label; + } + + getId(): string { + return this._id; + } + + getTemplateId(): string { + return SOURCE_TEMPLATE_ID; + } + + getChildren(): TPromise { + const child = this.oneChild(); + if (child) { + return child.getChildren(); + } + const array = Object.keys(this._children).map(key => this._children[key]); + return TPromise.as(array.sort((a, b) => this.compare(a, b))); + } + + hasChildren(): boolean { + const child = this.oneChild(); + if (child) { + return child.hasChildren(); + } + return Object.keys(this._children).length > 0; + } + + getSource() { + const child = this.oneChild(); + if (child) { + return child.getSource(); + } + return this._source; + } + + setSource(session: ISession, source: Source): void { + this._source = source; + } + + createIfNeeded(key: string, factory: (parent: BaseTreeItem, label: string) => T): T { + let child = this._children[key]; + if (!child) { + child = factory(this, key); + this._children[key] = child; + } + return child; + } + + remove(key: string): void { + delete this._children[key]; + } + + protected compare(a: BaseTreeItem, b: BaseTreeItem): number { + if (a._label && b._label) { + return a._label.localeCompare(b._label); + } + return 0; + } + + private oneChild(): BaseTreeItem { + if (SMART && !(this instanceof RootTreeItem)) { + const keys = Object.keys(this._children); + if (keys.length === 1) { + return this._children[keys[0]]; + } + } + return undefined; + } +} + +class RootFolderTreeItem extends BaseTreeItem { + + constructor(parent: BaseTreeItem, public folder: IWorkspaceFolder) { + super(parent, folder.name); + } + + getTemplateId(): string { + return ROOT_FOLDER_TEMPLATE_ID; + } +} + +class RootTreeItem extends BaseTreeItem { + + private _showedMoreThanOne: boolean; + + constructor(private _debugModel: IModel, private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService) { + super(undefined, 'Root'); + this._showedMoreThanOne = false; + this._debugModel.getSessions().forEach(session => { + this.add(session); + }); + } + + hasChildren(): boolean { + return true; + } + + getChildren(): TPromise { + return super.getChildren().then(children => { + const size = children.length; + if (!this._showedMoreThanOne && size === 1) { + // skip session if there is only one + return children[0].getChildren(); + } + this._showedMoreThanOne = size > 1; + return children; + }); + } + + add(session: ISession): SessionTreeItem { + return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this, session, this._environmentService, this._contextService)); + } +} + +class SessionTreeItem extends BaseTreeItem { + + private static URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/; + + private _session: ISession; + private _initialized: boolean; + + constructor(parent: BaseTreeItem, session: ISession, private _environmentService: IEnvironmentService, private rootProvider: IWorkspaceContextService) { + super(parent, session.getName(true)); + this._initialized = false; + this._session = session; + } + + getTemplateId(): string { + return SESSION_TEMPLATE_ID; + } + + hasChildren(): boolean { + return true; + } + + getChildren(): TPromise { + + if (!this._initialized) { + this._initialized = true; + return this._session.getLoadedSources().then(paths => { + paths.forEach(path => this.addPath(path)); + return super.getChildren(); + }); + } + + return super.getChildren(); + } + + protected compare(a: BaseTreeItem, b: BaseTreeItem): number { + const acat = this.category(a); + const bcat = this.category(b); + if (acat !== bcat) { + return acat - bcat; + } + return super.compare(a, b); + } + + /** + * Return an ordinal number for folders + */ + private category(item: BaseTreeItem): number { + + // workspace scripts come at the beginning in "folder" order + if (item instanceof RootFolderTreeItem) { + return item.folder.index; + } + + // <...> come at the very end + const l = item.getLabel(); + if (l && /^<.+>$/.test(l)) { + return 1000; + } + + // everything else in between + return 999; + } + + addPath(source: Source): void { + + let folder: IWorkspaceFolder; + let url: string; + + let path = source.raw.path; + + const match = SessionTreeItem.URL_REGEXP.exec(path); + if (match && match.length === 3) { + url = match[1]; + path = decodeURI(match[2]); + } else { + if (isAbsolute(path)) { + const resource = URI.file(path); + + // return early if we can resolve a relative path label from the root folder + folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null; + if (folder) { + // strip off the root folder path + path = normalize(ltrim(resource.path.substr(folder.uri.path.length), sep), true); + const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1; + if (hasMultipleRoots) { + path = '/' + path; + } else { + // don't show root folder + folder = undefined; + } + } else { + // on unix try to tildify absolute paths + path = normalize(path, true); + if (!isWindows) { + path = tildify(path, this._environmentService.userHome); + } + } + } + } + + let x: BaseTreeItem = this; + path.split(/[\/\\]/).forEach((segment, i) => { + if (segment.length === 0) { // macOS or unix path + segment = '/'; + } + if (i === 0 && folder) { + x = x.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, folder)); + } else if (i === 0 && url) { + x = x.createIfNeeded(url, parent => new BaseTreeItem(parent, url)); + } else { + x = x.createIfNeeded(segment, parent => new BaseTreeItem(parent, segment)); + } + }); + + x.setSource(this._session, source); + } +} export class LoadedScriptsView extends TreeViewsViewletPanel { + private static readonly MEMENTO = 'loadedscriptsview.memento'; + private treeContainer: HTMLElement; + private loadedScriptsItemType: IContextKey; + private settings: any; + constructor( options: IViewletViewOptions, @@ -26,22 +301,77 @@ export class LoadedScriptsView extends TreeViewsViewletPanel { @IKeybindingService keybindingService: IKeybindingService, @IInstantiationService private instantiationService: IInstantiationService, @IConfigurationService configurationService: IConfigurationService, + @IEditorService private editorService: IEditorService, + @IContextKeyService contextKeyService: IContextKeyService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IDebugService private debugService: IDebugService ) { super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService); + this.settings = options.viewletSettings; + this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService); } protected renderBody(container: HTMLElement): void { dom.addClass(container, 'debug-loaded-scripts'); + this.treeContainer = renderViewTree(container); - this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, { - dataSource: new LoadedScriptsDataSource(), - renderer: this.instantiationService.createInstance(LoadedScriptsRenderer), - accessibilityProvider: new LoadedSciptsAccessibilityProvider(), - }, { + this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, + { + dataSource: new LoadedScriptsDataSource(), + renderer: this.instantiationService.createInstance(LoadedScriptsRenderer), + accessibilityProvider: new LoadedSciptsAccessibilityProvider(), + }, + { ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"), twistiePixels - }); + } + ); + + const callstackNavigator = new TreeResourceNavigator(this.tree); + this.disposables.push(callstackNavigator); + this.disposables.push(callstackNavigator.openResource(e => { + + const element = e.element; + + if (element instanceof BaseTreeItem) { + const source = element.getSource(); + if (source && source.available) { + const nullRange = { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 }; + source.openInEditor(this.editorService, nullRange, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError); + } + } + })); + + this.disposables.push(this.tree.onDidChangeFocus(() => { + const focus = this.tree.getFocus(); + if (focus instanceof SessionTreeItem) { + this.loadedScriptsItemType.set('session'); + } else { + this.loadedScriptsItemType.reset(); + } + })); + + const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService); + this.tree.setInput(root); + + let timeout: number; + + this.disposables.push(this.debugService.onDidLoadedSource(event => { + const sessionRoot = root.add(event.session); + sessionRoot.addPath(event.source); + + clearTimeout(timeout); + timeout = setTimeout(() => { + this.tree.refresh(root, true); + }, 300); + })); + + this.disposables.push(this.debugService.onDidEndSession(session => { + root.remove(session.getId()); + this.tree.refresh(root, false); + })); } layoutBody(size: number): void { @@ -50,6 +380,11 @@ export class LoadedScriptsView extends TreeViewsViewletPanel { } super.layoutBody(size); } + + public shutdown(): void { + this.settings[LoadedScriptsView.MEMENTO] = !this.isExpanded(); + super.shutdown(); + } } // A good example of data source, renderers, action providers and accessibilty providers can be found in the callStackView.ts @@ -57,42 +392,94 @@ export class LoadedScriptsView extends TreeViewsViewletPanel { class LoadedScriptsDataSource implements IDataSource { getId(tree: ITree, element: any): string { - throw new Error('Method not implemented.'); + return element.getId(); } hasChildren(tree: ITree, element: any): boolean { - throw new Error('Method not implemented.'); + return element.hasChildren(); } getChildren(tree: ITree, element: any): TPromise { - throw new Error('Method not implemented.'); + return element.getChildren(); } getParent(tree: ITree, element: any): TPromise { - throw new Error('Method not implemented.'); + return TPromise.as(null); } + + shouldAutoexpand?(tree: ITree, element: any): boolean { + return element instanceof RootTreeItem || element instanceof SessionTreeItem; + } +} + +interface ISessionTemplateData { + session: HTMLElement; +} + +interface ISourceTemplateData { + source: HTMLElement; +} + +interface INodeTemplateData { + node: HTMLElement; } class LoadedScriptsRenderer implements IRenderer { getHeight(tree: ITree, element: any): number { - throw new Error('Method not implemented.'); + return 22; } getTemplateId(tree: ITree, element: any): string { - throw new Error('Method not implemented.'); + return element.getTemplateId(); } renderTemplate(tree: ITree, templateId: string, container: HTMLElement) { - throw new Error('Method not implemented.'); + + if (templateId === SESSION_TEMPLATE_ID) { + let data: ISessionTemplateData = Object.create(null); + data.session = dom.append(container, $('.session')); + return data; + } + + if (templateId === SOURCE_TEMPLATE_ID) { + let data: ISourceTemplateData = Object.create(null); + data.source = dom.append(container, $('.source')); + return data; + } + + let data: INodeTemplateData = Object.create(null); + data.node = dom.append(container, $('.node')); + return data; } renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { - throw new Error('Method not implemented.'); + if (templateId === SESSION_TEMPLATE_ID) { + this.renderSession(element, templateData); + } else if (templateId === SOURCE_TEMPLATE_ID) { + this.renderSource(element, templateData); + } else if (templateId === ROOT_FOLDER_TEMPLATE_ID) { + this.renderNode(element, templateData); + } } disposeTemplate(tree: ITree, templateId: string, templateData: any): void { - throw new Error('Method not implemented.'); + // noop + } + + private renderSession(session: SessionTreeItem, data: ISessionTemplateData): void { + data.session.title = 'session'; + data.session.textContent = session.getLabel(); + } + + private renderSource(source: BaseTreeItem, data: ISourceTemplateData): void { + data.source.title = 'source'; + data.source.textContent = source.getLabel(); + } + + private renderNode(node: BaseTreeItem, data: INodeTemplateData): void { + data.node.title = 'node'; + data.node.textContent = node.getLabel(); } } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 8db643264cd..6af33b36e60 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -37,8 +37,8 @@ export const REPL_ID = 'workbench.panel.repl'; export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_DEBUG_TYPE = new RawContextKey('debugType', undefined); export const CONTEXT_DEBUG_STATE = new RawContextKey('debugState', 'inactive'); -export const CONTEXT_NOT_IN_DEBUG_MODE = CONTEXT_DEBUG_STATE.isEqualTo('inactive'); -export const CONTEXT_IN_DEBUG_MODE = CONTEXT_DEBUG_STATE.notEqualsTo('inactive'); +export const CONTEXT_IN_DEBUG_MODE = new RawContextKey('inDebugMode', false); +export const CONTEXT_NOT_IN_DEBUG_MODE = CONTEXT_IN_DEBUG_MODE.toNegated(); export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey('breakpointWidgetVisible', false); export const CONTEXT_IN_BREAKPOINT_WIDGET = new RawContextKey('inBreakpointWidget', false); @@ -49,13 +49,14 @@ export const CONTEXT_EXPRESSION_SELECTED = new RawContextKey('expressio export const CONTEXT_BREAKPOINT_SELECTED = new RawContextKey('breakpointSelected', false); export const CONTEXT_CALLSTACK_ITEM_TYPE = new RawContextKey('callStackItemType', undefined); export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey('loadedScriptsSupported', false); +export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey('loadedScriptsItemType', undefined); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const DEBUG_SCHEME = 'debug'; export const INTERNAL_CONSOLE_OPTIONS_SCHEMA = { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'], default: 'openOnFirstSessionStart', - description: nls.localize('internalConsoleOptions', "Controls behavior of the internal debug console.") + description: nls.localize('internalConsoleOptions', "Controls when the internal debug console should open.") }; // raw @@ -135,6 +136,8 @@ export interface IRawSession { completions(args: DebugProtocol.CompletionsArguments): TPromise; setVariable(args: DebugProtocol.SetVariableArguments): TPromise; source(args: DebugProtocol.SourceArguments): TPromise; + loadedSources(args: DebugProtocol.LoadedSourcesArguments): TPromise; + } export enum SessionState { @@ -151,6 +154,7 @@ export interface ISession extends ITreeElement { getThread(threadId: number): IThread; getAllThreads(): ReadonlyArray; getSource(raw: DebugProtocol.Source): Source; + getLoadedSources(): TPromise; completions(frameId: number, text: string, position: Position, overwriteBefore: number): TPromise; } @@ -572,6 +576,12 @@ export interface DebugEvent extends DebugProtocol.Event { sessionId?: string; } +export interface LoadedSourceEvent { + session: ISession; + reason: string; + source: Source; +} + export interface IDebugService { _serviceBrand: any; @@ -595,6 +605,11 @@ export interface IDebugService { */ onDidEndSession: Event; + /** + * Allows to register on loaded source events. + */ + onDidLoadedSource: Event; + /** * Allows to register on custom DAP events. */ diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index 6e8edd9f7cf..3a58268ec72 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -616,6 +616,14 @@ export class Session implements ISession { return result; } + public getLoadedSources(): TPromise { + return this.raw.loadedSources({}).then(response => { + return response.body.sources.map(src => this.getSource(src)); + }, error => { + return []; + }); + } + public getId(): string { return this.session.getId(); } diff --git a/src/vs/workbench/parts/debug/common/debugViewModel.ts b/src/vs/workbench/parts/debug/common/debugViewModel.ts index d1d7cc88b50..c5816ddb628 100644 --- a/src/vs/workbench/parts/debug/common/debugViewModel.ts +++ b/src/vs/workbench/parts/debug/common/debugViewModel.ts @@ -69,8 +69,6 @@ export class ViewModel implements IViewModel { this._focusedStackFrame = stackFrame; this.loadedScriptsSupportedContextKey.set(session && session.raw.capabilities.supportsLoadedSourcesRequest); - // @weinand remove the next line which always disables the context for the view to be shown - this.loadedScriptsSupportedContextKey.set(false); if (shouldEmit) { this._onDidFocusStackFrame.fire({ stackFrame, explicit }); diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index 398a15c9a86..c8faf5c0e38 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -178,39 +178,39 @@ configurationRegistry.registerConfiguration({ properties: { 'debug.allowBreakpointsEverywhere': { type: 'boolean', - description: nls.localize({ comment: ['This is the description for a setting'], key: 'allowBreakpointsEverywhere' }, "Allows setting breakpoint in any file"), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'allowBreakpointsEverywhere' }, "Allow setting breakpoints in any file."), default: false }, 'debug.openExplorerOnEnd': { type: 'boolean', - description: nls.localize({ comment: ['This is the description for a setting'], key: 'openExplorerOnEnd' }, "Automatically open explorer view on the end of a debug session"), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'openExplorerOnEnd' }, "Automatically open the explorer view at the end of a debug session"), default: false }, 'debug.inlineValues': { type: 'boolean', - description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging"), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging."), default: false }, 'debug.toolBarLocation': { enum: ['floating', 'docked', 'hidden'], - description: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either \"floating\" in all views, \"docked\" in the debug view, or \"hidden\""), + markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, or `hidden`"), default: 'floating' }, 'debug.showInStatusBar': { enum: ['never', 'always', 'onFirstSessionStart'], enumDescriptions: [nls.localize('never', "Never show debug in status bar"), nls.localize('always', "Always show debug in status bar"), nls.localize('onFirstSessionStart', "Show debug in status bar only after debug was started for the first time")], - description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInStatusBar' }, "Controls when the debug status bar should be visible"), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInStatusBar' }, "Controls when the debug status bar should be visible."), default: 'onFirstSessionStart' }, 'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA, 'debug.openDebug': { enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'], default: 'openOnFirstSessionStart', - description: nls.localize('openDebug', "Controls whether debug view should be open on debugging session start.") + description: nls.localize('openDebug', "Controls when the debug view should open.") }, 'debug.enableAllHovers': { type: 'boolean', - description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls if the non debug hovers should be enabled while debugging. If true the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is true."), + description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."), default: false }, 'launch': { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 3daf1bd37ca..f847d1a6830 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -10,7 +10,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as strings from 'vs/base/common/strings'; import * as objects from 'vs/base/common/objects'; import uri from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { ITextModel } from 'vs/editor/common/model'; import { IEditor } from 'vs/workbench/common/editor'; @@ -390,7 +390,7 @@ class Launch implements ILaunch { } public get uri(): uri { - return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') }); + return resources.joinPath(this.workspace.uri, '/.vscode/launch.json'); } public get name(): string { @@ -442,14 +442,12 @@ class Launch implements ILaunch { } public openConfigFile(sideBySide: boolean, type?: string): TPromise<{ editor: IEditor, created: boolean }> { - return this.configurationManager.activateDebuggers().then(() => { - const resource = this.uri; - let created = false; - - return this.fileService.resolveContent(resource).then(content => content.value, err => { - - // launch.json not found: create one by collecting launch configs from debugConfigProviders + const resource = this.uri; + let created = false; + return this.fileService.resolveContent(resource).then(content => content.value, err => { + // launch.json not found: create one by collecting launch configs from debugConfigProviders + return this.configurationManager.activateDebuggers().then(() => { return this.configurationManager.guessDebugger(type).then(adapter => { if (adapter) { return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => { @@ -470,30 +468,30 @@ class Launch implements ILaunch { return content; }); }); - }).then(content => { - if (!content) { - return { editor: undefined, created: false }; - } - const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); - let startLineNumber = 1; - for (let i = 0; i < index; i++) { - if (content.charAt(i) === '\n') { - startLineNumber++; - } - } - const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; - - return this.editorService.openEditor({ - resource: resource, - options: { - selection, - pinned: created, - revealIfVisible: true - }, - }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created })); - }, (error) => { - throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error)); }); + }).then(content => { + if (!content) { + return { editor: undefined, created: false }; + } + const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`); + let startLineNumber = 1; + for (let i = 0; i < index; i++) { + if (content.charAt(i) === '\n') { + startLineNumber++; + } + } + const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined; + + return this.editorService.openEditor({ + resource, + options: { + selection, + pinned: created, + revealIfVisible: true + }, + }, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created })); + }, (error) => { + throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error)); }); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 3ec47d67994..a5e2dcee7a0 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -69,6 +69,7 @@ export class DebugService implements debug.IDebugService { private readonly _onDidChangeState: Emitter; private readonly _onDidNewSession: Emitter; private readonly _onDidEndSession: Emitter; + private readonly _onDidLoadedSource: Emitter; private readonly _onDidCustomEvent: Emitter; private model: Model; private viewModel: ViewModel; @@ -78,6 +79,7 @@ export class DebugService implements debug.IDebugService { private toDisposeOnSessionEnd: Map; private debugType: IContextKey; private debugState: IContextKey; + private inDebugMode: IContextKey; private breakpointsToSendOnResourceSaved: Set; private firstSessionStart: boolean; private skipRunningTask: boolean; @@ -112,6 +114,7 @@ export class DebugService implements debug.IDebugService { this._onDidChangeState = new Emitter(); this._onDidNewSession = new Emitter(); this._onDidEndSession = new Emitter(); + this._onDidLoadedSource = new Emitter(); this._onDidCustomEvent = new Emitter(); this.sessionStates = new Map(); this.allSessions = new Map(); @@ -121,6 +124,7 @@ export class DebugService implements debug.IDebugService { this.toDispose.push(this.configurationManager); this.debugType = debug.CONTEXT_DEBUG_TYPE.bindTo(contextKeyService); this.debugState = debug.CONTEXT_DEBUG_STATE.bindTo(contextKeyService); + this.inDebugMode = debug.CONTEXT_IN_DEBUG_MODE.bindTo(contextKeyService); this.model = new Model(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(), this.loadExceptionBreakpoints(), this.loadWatchExpressions()); @@ -448,6 +452,14 @@ export class DebugService implements debug.IDebugService { } })); + this.toDisposeOnSessionEnd.get(session.getId()).push(raw.onDidLoadedSource(event => { + this._onDidLoadedSource.fire({ + session: session, + reason: event.body.reason, + source: session.getSource(event.body.source) + }); + })); + this.toDisposeOnSessionEnd.get(session.getId()).push(raw.onDidCustomEvent(event => { this._onDidCustomEvent.fire(event); })); @@ -540,6 +552,10 @@ export class DebugService implements debug.IDebugService { return this._onDidEndSession.event; } + public get onDidLoadedSource(): Event { + return this._onDidLoadedSource.event; + } + public get onDidCustomEvent(): Event { return this._onDidCustomEvent.event; } @@ -558,6 +574,7 @@ export class DebugService implements debug.IDebugService { const stateLabel = debug.State[state]; if (stateLabel) { this.debugState.set(stateLabel.toLowerCase()); + this.inDebugMode.set(state !== debug.State.Inactive); } this.previousState = state; this._onDidChangeState.fire(state); @@ -1102,7 +1119,8 @@ export class DebugService implements debug.IDebugService { const unresolvedConfiguration = (session).unresolvedConfiguration; if (session.raw.capabilities.supportsRestartRequest) { return this.runTask(session.getId(), session.raw.root, session.configuration.postDebugTask, session.configuration, unresolvedConfiguration, - () => session.raw.custom('restart', null)); + () => this.runTask(session.getId(), session.raw.root, session.configuration.preLaunchTask, session.configuration, unresolvedConfiguration, + () => session.raw.custom('restart', null))); } const focusedSession = this.viewModel.focusedSession; diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts index 1e7a2fd9db2..5e9785e36c8 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts @@ -58,6 +58,7 @@ export class RawDebugSession implements IRawSession { private readonly _onDidThread: Emitter; private readonly _onDidOutput: Emitter; private readonly _onDidBreakpoint: Emitter; + private readonly _onDidLoadedSource: Emitter; private readonly _onDidCustomEvent: Emitter; private readonly _onDidEvent: Emitter; @@ -85,6 +86,7 @@ export class RawDebugSession implements IRawSession { this._onDidThread = new Emitter(); this._onDidOutput = new Emitter(); this._onDidBreakpoint = new Emitter(); + this._onDidLoadedSource = new Emitter(); this._onDidCustomEvent = new Emitter(); this._onDidEvent = new Emitter(); } @@ -129,6 +131,10 @@ export class RawDebugSession implements IRawSession { return this._onDidBreakpoint.event; } + public get onDidLoadedSource(): Event { + return this._onDidLoadedSource.event; + } + public get onDidCustomEvent(): Event { return this._onDidCustomEvent.event; } @@ -234,7 +240,9 @@ export class RawDebugSession implements IRawSession { private onDapEvent(event: DebugEvent): void { event.sessionId = this.id; - if (event.event === 'initialized') { + if (event.event === 'loadedSource') { // most frequent comes first + this._onDidLoadedSource.fire(event); + } else if (event.event === 'initialized') { this.readyForBreakpoints = true; this._onDidInitialize.fire(event); } else if (event.event === 'capabilities' && event.body) { @@ -387,6 +395,11 @@ export class RawDebugSession implements IRawSession { return this.send('source', args); } + public loadedSources(args: DebugProtocol.LoadedSourcesArguments): TPromise { + return this.send('loadedSources', args); + } + + public threads(): TPromise { return this.send('threads', null); } diff --git a/src/vs/workbench/parts/debug/node/debugger.ts b/src/vs/workbench/parts/debug/node/debugger.ts index cde530adaef..a341ff73572 100644 --- a/src/vs/workbench/parts/debug/node/debugger.ts +++ b/src/vs/workbench/parts/debug/node/debugger.ts @@ -19,10 +19,10 @@ import { IOutputService } from 'vs/workbench/parts/output/common/output'; import { DebugAdapter, SocketDebugAdapter } from 'vs/workbench/parts/debug/node/debugAdapter'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; -import uri from 'vs/base/common/uri'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { memoize } from 'vs/base/common/decorators'; import { TaskDefinitionRegistry } from 'vs/workbench/parts/tasks/common/taskDefinitionRegistry'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class Debugger { @@ -173,7 +173,7 @@ export class Debugger { return telemetryInfo; }).then(data => { const client = new TelemetryClient( - uri.parse(require.toUrl('bootstrap')).fsPath, + getPathFromAmdModule(require, 'bootstrap'), { serverName: 'Debug Telemetry', timeout: 1000 * 60 * 5, diff --git a/src/vs/workbench/parts/debug/node/terminals.ts b/src/vs/workbench/parts/debug/node/terminals.ts index a46ea642248..00ff6960cb4 100644 --- a/src/vs/workbench/parts/debug/node/terminals.ts +++ b/src/vs/workbench/parts/debug/node/terminals.ts @@ -11,8 +11,8 @@ import * as env from 'vs/base/common/platform'; import * as pfs from 'vs/base/node/pfs'; import { assign } from 'vs/base/common/objects'; import { TPromise } from 'vs/base/common/winjs.base'; -import uri from 'vs/base/common/uri'; import { ITerminalLauncher, ITerminalSettings } from 'vs/workbench/parts/debug/common/debug'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -132,7 +132,7 @@ class MacTerminalService extends TerminalLauncher { // and then launches the program inside that window. const script = terminalApp === MacTerminalService.DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper'; - const scriptpath = uri.parse(require.toUrl(`vs/workbench/parts/execution/electron-browser/${script}.scpt`)).fsPath; + const scriptpath = getPathFromAmdModule(require, `vs/workbench/parts/execution/electron-browser/${script}.scpt`); const osaArgs = [ scriptpath, @@ -415,4 +415,4 @@ export function prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments } return command; -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index ceb4cc25a49..f92a322a3a0 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -7,7 +7,7 @@ import uri from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { ILaunch, IDebugService, State, DebugEvent, ISession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IModel, IViewModel, IRawSession, IBreakpoint } from 'vs/workbench/parts/debug/common/debug'; +import { ILaunch, IDebugService, State, DebugEvent, ISession, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IModel, IViewModel, IRawSession, IBreakpoint, LoadedSourceEvent } from 'vs/workbench/parts/debug/common/debug'; export class MockDebugService implements IDebugService { public _serviceBrand: any; @@ -32,6 +32,10 @@ export class MockDebugService implements IDebugService { return null; } + public get onDidLoadedSource(): Event { + return null; + } + public getConfigurationManager(): IConfigurationManager { return null; } @@ -218,7 +222,7 @@ export class MockSession implements IRawSession { return TPromise.as(null); } - public terminateThreads(args: DebugProtocol.TerminateThreadsArguments): TPromise { + public terminateThreads(args: DebugProtocol.TerminateThreadsArguments): TPromise { return TPromise.as(null); } @@ -242,6 +246,10 @@ export class MockSession implements IRawSession { return TPromise.as(null); } + public loadedSources(args: DebugProtocol.LoadedSourcesArguments): TPromise { + return TPromise.as(null); + } + public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): TPromise { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts b/src/vs/workbench/parts/execution/electron-browser/terminalService.ts index 275f11a662e..b1835830812 100644 --- a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/execution/electron-browser/terminalService.ts @@ -15,8 +15,8 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { ITerminalService } from 'vs/workbench/parts/execution/common/execution'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalConfiguration, getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal'; -import uri from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); @@ -143,7 +143,7 @@ export class MacTerminalService implements ITerminalService { // and then launches the program inside that window. const script = terminalApp === DEFAULT_TERMINAL_OSX ? 'TerminalHelper' : 'iTermHelper'; - const scriptpath = uri.parse(require.toUrl(`vs/workbench/parts/execution/electron-browser/${script}.scpt`)).fsPath; + const scriptpath = getPathFromAmdModule(require, `vs/workbench/parts/execution/electron-browser/${script}.scpt`); const osaArgs = [ scriptpath, diff --git a/src/vs/workbench/parts/experiments/node/experimentService.ts b/src/vs/workbench/parts/experiments/node/experimentService.ts index ea5ff884fe7..96777a2416c 100644 --- a/src/vs/workbench/parts/experiments/node/experimentService.ts +++ b/src/vs/workbench/parts/experiments/node/experimentService.ts @@ -10,20 +10,17 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; - +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IExtensionManagementService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IRequestService } from 'vs/platform/request/node/request'; - import { TPromise } from 'vs/base/common/winjs.base'; import { language } from 'vs/base/common/platform'; import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; 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/parts/stats/node/workspaceStats'; -import { Emitter, Event } from 'vs/base/common/event'; - interface IExperimentStorageState { enabled: boolean; @@ -123,7 +120,8 @@ export class ExperimentService extends Disposable implements IExperimentService @IEnvironmentService private environmentService: IEnvironmentService, @ITelemetryService private telemetryService: ITelemetryService, @ILifecycleService private lifecycleService: ILifecycleService, - @IRequestService private requestService: IRequestService + @IRequestService private requestService: IRequestService, + @IConfigurationService private configurationService: IConfigurationService ) { super(); @@ -167,7 +165,7 @@ export class ExperimentService extends Disposable implements IExperimentService } protected getExperiments(): TPromise { - if (!product.experimentsUrl) { + if (!product.experimentsUrl || this.configurationService.getValue('workbench.enableExperiments') === false) { return TPromise.as([]); } return this.requestService.request({ type: 'GET', url: product.experimentsUrl }).then(context => { diff --git a/src/vs/workbench/parts/extensions/common/extensionQuery.ts b/src/vs/workbench/parts/extensions/common/extensionQuery.ts index aa271d1498f..b966d438028 100644 --- a/src/vs/workbench/parts/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/parts/extensions/common/extensionQuery.ts @@ -12,7 +12,7 @@ export class Query { this.value = value.trim(); } - static autocompletions(): string[] { + static autocompletions(query: string): string[] { const commands = ['installed', 'outdated', 'enabled', 'disabled', 'builtin', 'recommended', 'sort', 'category', 'tag', 'ext']; const subcommands = { 'sort': ['installs', 'rating', 'name'], @@ -21,11 +21,23 @@ export class Query { 'ext': [''] }; + let queryContains = (substr: string) => query.indexOf(substr) > -1; + let hasSort = subcommands.sort.some(subcommand => queryContains(`@sort:${subcommand}`)); + let hasCategory = subcommands.category.some(subcommand => queryContains(`@category:${subcommand}`)); + return flatten( - commands.map(command => - subcommands[command] - ? subcommands[command].map(subcommand => `@${command}:${subcommand}${subcommand === '' ? '' : ' '}`) - : [`@${command} `])); + commands.map(command => { + if (hasSort && command === 'sort' || hasCategory && command === 'category') { + return []; + } + if (subcommands[command]) { + return subcommands[command].map(subcommand => `@${command}:${subcommand}${subcommand === '' ? '' : ' '}`); + } + else { + return [`@${command} `]; + } + })); + } static parse(value: string): Query { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 5550d05036c..79af9338ba2 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -673,8 +673,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } }, { - label: choiceNever, - isSecondary: true, + label: localize('dontShowAgainExtension', "Don't Show Again for '.{0}' files", fileExtension), run: () => { fileExtensionSuggestionIgnoreList.push(fileExtension); this.storageService.store( diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index 25ced8dabe0..d14b8666b9d 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -204,14 +204,14 @@ Registry.as(ConfigurationExtensions.Configuration) properties: { 'extensions.autoUpdate': { type: 'boolean', - description: localize('extensionsAutoUpdate', "Automatically update extensions."), + description: localize('extensionsAutoUpdate', "When enabled, automatically installs updates for extensions. The updates are fetched from an online service."), default: true, scope: ConfigurationScope.APPLICATION, tags: ['usesOnlineServices'] }, 'extensions.autoCheckUpdates': { type: 'boolean', - description: localize('extensionsCheckUpdates', "Automatically checks for extension updates. If an extension update is available and the extension auto update feature is disabled, then the extension will appear as outdated in the Extensions view."), + description: localize('extensionsCheckUpdates', "When enabled, automatically checks extensions for updates. If an extension has an update, it is marked as outdated in the Extensions view. The updates are fetched from an online service."), default: true, scope: ConfigurationScope.APPLICATION, tags: ['usesOnlineServices'] @@ -223,7 +223,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'extensions.showRecommendationsOnlyOnDemand': { type: 'boolean', - description: localize('extensionsShowRecommendationsOnlyOnDemand', "When enabled, recommendations will not be fetched or shown unless specifically requested by the user."), + description: localize('extensionsShowRecommendationsOnlyOnDemand', "When enabled, recommendations will not be fetched or shown unless specifically requested by the user. Some recommendations are fetched from an online service."), default: false, tags: ['usesOnlineServices'] }, diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index 35077d1000f..0d74ff218bd 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -46,12 +46,12 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/ import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/group/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import product from 'vs/platform/node/product'; import { ContextSubMenu } from 'vs/base/browser/contextmenu'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; const promptDownloadManually = (extension: IGalleryExtension, message: string, instantiationService: IInstantiationService, notificationService: INotificationService, openerService: IOpenerService) => { const downloadUrl = `${product.extensionsGallery.serviceUrl}/publishers/${extension.publisher}/vsextensions/${extension.name}/${extension.version}/vspackage`; @@ -2694,7 +2694,7 @@ export class ReinstallAction extends Action { constructor( id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, - @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @INotificationService private notificationService: INotificationService, @IWindowService private windowService: IWindowService ) { @@ -2706,21 +2706,22 @@ export class ReinstallAction extends Action { } run(): TPromise { - return this.quickOpenService.pick(this.getEntries(), { placeHolder: localize('selectExtension', "Select Extension to Reinstall") }); + return this.quickInputService.pick(this.getEntries(), { placeHolder: localize('selectExtension', "Select Extension to Reinstall") }) + .then(pick => pick && this.reinstallExtension(pick.extension)); } - private getEntries(): TPromise { + private getEntries() { return this.extensionsWorkbenchService.queryLocal() .then(local => { - const entries: IPickOpenEntry[] = local + const entries = local .filter(extension => extension.type === LocalExtensionType.User) .map(extension => { - return { + return { id: extension.id, label: extension.displayName, description: extension.id, - run: () => this.reinstallExtension(extension), - }; + extension, + } as (IQuickPickItem & { extension: IExtension }); }); return entries; }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts index 1153fd57fb8..846d0185cd5 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsList.ts @@ -191,7 +191,7 @@ export class Renderer implements IPagedRenderer { extension.getManifest().then(manifest => { const name = manifest && manifest.contributes && manifest.contributes.localizations && manifest.contributes.localizations.length > 0 && manifest.contributes.localizations[0].localizedLanguageName; if (name) { data.description.textContent = name[0].toLocaleUpperCase() + name.slice(1); } - }); + }, () => { }); } disposeElement(): void { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 5a17cd2c003..334c02d10d7 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -65,7 +65,12 @@ import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { ITextModel } from 'vs/editor/common/model'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; -import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions'; +import { getSimpleEditorOptions } from 'vs/workbench/parts/codeEditor/electron-browser/simpleEditorOptions'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { ContextMenuController } from 'vs/editor/contrib/contextmenu/contextmenu'; +import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; +import { isMacintosh } from 'vs/base/common/platform'; interface SearchInputEvent extends Event { target: HTMLInputElement; @@ -128,7 +133,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio container: VIEW_CONTAINER, ctor: EnabledExtensionsView, when: ContextKeyExpr.not('searchExtensions'), - weight: 30, + weight: 40, canToggleVisibility: true, order: 1 }; @@ -177,7 +182,7 @@ export class ExtensionsViewletViewsContribution implements IWorkbenchContributio container: VIEW_CONTAINER, ctor: DefaultRecommendedExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')), - weight: 70, + weight: 60, order: 2, canToggleVisibility: true }; @@ -342,7 +347,14 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.monacoStyleContainer = append(header, $('.monaco-container')); this.searchBox = this.instantiationService.createInstance(CodeEditorWidget, this.monacoStyleContainer, mixinHTMLInputStyleOptions(getSimpleEditorOptions(), localize('searchExtensions', "Search Extensions in Marketplace")), - getSimpleCodeEditorWidgetOptions()); + { + isSimpleWidget: true, contributions: [ + SuggestController, + SnippetController2, + ContextMenuController, + MenuPreventer + ] + }); this.placeholderText = append(this.monacoStyleContainer, $('.search-placeholder', null, localize('searchExtensions', "Search Extensions in Marketplace"))); @@ -351,24 +363,30 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio this.searchBox.setModel(this.modelService.createModel('', null, uri.parse('extensions:searchinput'), true)); this.disposables.push(this.searchBox.onDidPaste(() => { - this.searchBox.setValue(this.searchBox.getValue().replace(/\s+/g, ' ')); + let trimmed = this.searchBox.getValue().replace(/\s+/g, ' '); + this.searchBox.setValue(trimmed); this.searchBox.setScrollTop(0); + this.searchBox.setPosition(new Position(1, trimmed.length + 1)); })); + this.disposables.push(this.searchBox.onDidFocusEditorText(() => addClass(this.monacoStyleContainer, 'synthetic-focus'))); this.disposables.push(this.searchBox.onDidBlurEditorText(() => removeClass(this.monacoStyleContainer, 'synthetic-focus'))); const onKeyDownMonaco = chain(this.searchBox.onKeyDown); onKeyDownMonaco.filter(e => e.keyCode === KeyCode.Enter).on(e => e.preventDefault(), this, this.disposables); - onKeyDownMonaco.filter(e => e.keyCode === KeyCode.DownArrow && e.ctrlKey).on(() => this.focusListView(), this, this.disposables); + onKeyDownMonaco.filter(e => e.keyCode === KeyCode.DownArrow && (isMacintosh ? e.metaKey : e.ctrlKey)).on(() => this.focusListView(), this, this.disposables); const searchChangeEvent = new Emitter(); this.onSearchChange = searchChangeEvent.event; + let existingContent = this.searchBox.getValue().trim(); this.disposables.push(this.searchBox.getModel().onDidChangeContent(() => { + this.placeholderText.style.visibility = this.searchBox.getValue() ? 'hidden' : 'visible'; + let content = this.searchBox.getValue().trim(); + if (existingContent === content) { return; } this.triggerSearch(); - const content = this.searchBox.getValue(); searchChangeEvent.fire(content); - this.placeholderText.style.visibility = content ? 'hidden' : 'visible'; + existingContent = content; })); return super.create(this.extensionsBox) @@ -417,7 +435,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio layout(dimension: Dimension): void { toggleClass(this.root, 'narrow', dimension.width <= 300); - this.searchBox.layout({ height: 20, width: dimension.width - 30 }); + this.searchBox.layout({ height: 20, width: dimension.width - 34 }); this.placeholderText.style.width = '' + (dimension.width - 30) + 'px'; super.layout(new Dimension(dimension.width, dimension.height - 38)); @@ -475,6 +493,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio event.immediate = true; this.searchBox.setValue(value); + this.searchBox.setPosition(new Position(1, value.length + 1)); } private triggerSearch(immediate = false): void { @@ -527,7 +546,7 @@ export class ExtensionsViewlet extends ViewContainerViewlet implements IExtensio // dont show autosuggestions if the user has typed something, but hasn't used the trigger character if (alreadyTypedCount > 0 && query[wordStart] !== '@') { return []; } - return Query.autocompletions().map(replacement => ({ fullText: replacement, overwrite: alreadyTypedCount })); + return Query.autocompletions(query).map(replacement => ({ fullText: replacement, overwrite: alreadyTypedCount })); } private count(): number { @@ -679,7 +698,10 @@ function mixinHTMLInputStyleOptions(config: IEditorOptions, ariaLabel?: string): config.wordWrap = 'off'; config.scrollbar.vertical = 'hidden'; config.ariaLabel = ariaLabel || ''; + config.renderIndentGuides = false; config.cursorWidth = 1; + config.snippetSuggestions = 'none'; + config.suggest = { filterGraceful: false }; config.fontFamily = ' -apple-system, BlinkMacSystemFont, "Segoe WPC", "Segoe UI", "HelveticaNeue-Light", "Ubuntu", "Droid Sans", sans-serif'; return config; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts index 8fc5ed3bca1..70edc1d5fab 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViews.ts @@ -801,7 +801,8 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView { } async show(query: string): Promise> { - let model = await ((query && query.trim() !== '@recommended') ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery)); + let shouldShowEmptyView = query && query.trim() !== '@recommended' && query.trim() !== '@recommended:workspace'; + let model = await (shouldShowEmptyView ? this.showEmptyModel() : super.show(this.recommendedExtensionsQuery)); this.setExpanded(model.length > 0); return model; } diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 5930310b95e..eaf82c0f793 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -38,7 +38,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { groupBy } from 'vs/base/common/collections'; import { Schemas } from 'vs/base/common/network'; -import { posix } from 'path'; +import * as resources from 'vs/base/common/resources'; interface IExtensionStateProvider { (extension: Extension): T; @@ -131,7 +131,7 @@ class Extension implements IExtension { private get localIconUrl(): string { if (this.local && this.local.manifest.icon) { - return this.local.location.with({ path: posix.join(this.local.location.path, this.local.manifest.icon) }).toString(); + return resources.joinPath(this.local.location, this.local.manifest.icon).toString(); } return null; } diff --git a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts b/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts index 1d1031053eb..a3ba946041f 100644 --- a/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts +++ b/src/vs/workbench/parts/extensions/test/common/extensionQuery.test.ts @@ -140,4 +140,12 @@ suite('Extension query', () => { query2 = new Query('hello', 'installs', ''); assert(!query1.equals(query2)); }); + + test('autocomplete', () => { + Query.autocompletions('@sort:in').some(x => x === '@sort:installs '); + Query.autocompletions('@sort:installs').every(x => x !== '@sort:rating '); + + Query.autocompletions('@category:blah').some(x => x === '@category:"extension packs" '); + Query.autocompletions('@category:"extension packs"').every(x => x !== '@category:formatters '); + }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 411ed8b209f..7da387ac880 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -218,7 +218,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.equal('1.2.0', actual.version); assert.equal('1.2.0', actual.latestVersion); assert.equal('localDescription2', actual.description); - assert.ok(fs.existsSync(actual.iconUrl)); + assert.ok(fs.existsSync(URI.parse(actual.iconUrl).fsPath)); assert.equal(null, actual.licenseUrl); assert.equal(ExtensionState.Installed, actual.state); assert.equal(null, actual.installCount); @@ -311,7 +311,7 @@ suite('ExtensionsWorkbenchServiceTest', () => { assert.equal('1.2.0', actual.version); assert.equal('1.2.0', actual.latestVersion); assert.equal('localDescription2', actual.description); - assert.ok(fs.existsSync(actual.iconUrl)); + assert.ok(fs.existsSync(URI.parse(actual.iconUrl).fsPath)); assert.equal(null, actual.licenseUrl); assert.equal(ExtensionState.Installed, actual.state); assert.equal(null, actual.installCount); @@ -1282,4 +1282,4 @@ suite('ExtensionsWorkbenchServiceTest', () => { }); }); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index 8b16509b50c..9f6bd55fff9 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import URI from 'vs/base/common/uri'; -import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { toResource, SideBySideEditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor'; import { ITextFileService, ITextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles'; @@ -149,7 +149,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut // Do NOT close any opened editor that matches the resource path (either equal or being parent) of the // resource we move to (movedTo). Otherwise we would close a resource that has been renamed to the same // path but different casing. - if (movedTo && paths.isEqualOrParent(resource.fsPath, movedTo.fsPath, !isLinux /* ignorecase */) && resource.fsPath.indexOf(movedTo.fsPath) === 0) { + if (movedTo && resources.isEqualOrParent(resource, movedTo, resources.hasToIgnoreCase(resource)) && resource.path.indexOf(movedTo.path) === 0) { return; } @@ -157,7 +157,7 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut if (arg1 instanceof FileChangesEvent) { matches = arg1.contains(resource, FileChangeType.DELETED); } else { - matches = paths.isEqualOrParent(resource.fsPath, arg1.fsPath, !isLinux /* ignorecase */); + matches = resources.isEqualOrParent(resource, arg1, resources.hasToIgnoreCase(resource)); } if (!matches) { @@ -224,13 +224,13 @@ export class FileEditorTracker extends Disposable implements IWorkbenchContribut const resource = input.getResource(); // Update Editor if file (or any parent of the input) got renamed or moved - if (paths.isEqualOrParent(resource.fsPath, oldResource.fsPath, !isLinux /* ignorecase */)) { + if (resources.isEqualOrParent(resource, oldResource, resources.hasToIgnoreCase(resource))) { let reopenFileResource: URI; if (oldResource.toString() === resource.toString()) { reopenFileResource = newResource; // file got moved } else { const index = this.getIndexOfPath(resource.path, oldResource.path); - reopenFileResource = newResource.with({ path: paths.join(newResource.path, resource.path.substr(index + oldResource.path.length + 1)) }); // parent folder got moved + reopenFileResource = resources.joinPath(newResource, resource.path.substr(index + oldResource.path.length + 1)); // parent folder got moved } // Reopen diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index 1d0e661bdb6..5eea47892d2 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -127,7 +127,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { this.name = resources.basenameOrAuthority(this.resource); } - return this.decorateOrphanedFiles(this.name); + return this.decorateLabel(this.name); } @memoize @@ -192,14 +192,17 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { break; } - return this.decorateOrphanedFiles(title); + return this.decorateLabel(title); } - private decorateOrphanedFiles(label: string): string { + private decorateLabel(label: string): string { const model = this.textFileService.models.get(this.resource); if (model && model.hasState(ModelState.ORPHAN)) { return localize('orphanedFile', "{0} (deleted from disk)", label); } + if (model && model.isReadonly()) { + return localize('readonlyFile', "{0} (read-only)", label); + } return label; } diff --git a/src/vs/workbench/parts/files/common/explorerModel.ts b/src/vs/workbench/parts/files/common/explorerModel.ts index 92490a50246..49d64d0a561 100644 --- a/src/vs/workbench/parts/files/common/explorerModel.ts +++ b/src/vs/workbench/parts/files/common/explorerModel.ts @@ -15,7 +15,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { toResource, IEditorIdentifier, IEditorInput } from 'vs/workbench/common/editor'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; -import { startsWith, startsWithIgnoreCase, rtrim } from 'vs/base/common/strings'; +import { rtrim } from 'vs/base/common/strings'; import { IEditorGroup } from 'vs/workbench/services/group/common/editorGroupsService'; export class Model { @@ -157,7 +157,7 @@ export class ExplorerItem { // the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo // array of resource path to resolve. stat.isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => { - return resources.isEqualOrParent(r, stat.resource, !isLinux /* ignorecase */); + return resources.isEqualOrParent(r, stat.resource, resources.hasToIgnoreCase(r)); })); // Recurse into children @@ -311,7 +311,7 @@ export class ExplorerItem { } private updateResource(recursive: boolean): void { - this.resource = this.parent.resource.with({ path: paths.join(this.parent.resource.path, this.name) }); + this.resource = resources.joinPath(this.parent.resource, this.name); if (recursive) { if (this.isDirectory && this.children) { @@ -342,9 +342,7 @@ export class ExplorerItem { */ public find(resource: URI): ExplorerItem { // Return if path found - if (resource && this.resource.scheme === resource.scheme && this.resource.authority === resource.authority && - (isLinux ? startsWith(resource.path, this.resource.path) : startsWithIgnoreCase(resource.path, this.resource.path)) - ) { + if (resource && resources.isEqualOrParent(resource, this.resource)) { return this.findByPath(rtrim(resource.path, paths.sep), this.resource.path.length); } diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts index dbca8fb34ef..1fd97d3e912 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -114,8 +114,8 @@ const copyRelativePathCommand = { // Editor Title Context Menu appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); -appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFile, copyRelativePathCommand); -appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFile); +appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.HasResource, copyRelativePathCommand); +appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.HasResource); function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr, alt?: { id: string, title: string }): void { @@ -186,7 +186,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: 'navigation', order: 10, command: openToSideCommand, - when: ResourceContextKey.IsFile + when: ResourceContextKey.HasResource }); const revealInOsCommand = { @@ -205,7 +205,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { order: 40, command: copyPathCommand, alt: copyRelativePathCommand, - when: ResourceContextKey.IsFile + when: ResourceContextKey.HasResource }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -216,7 +216,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: SAVE_FILE_LABEL, precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFile, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -227,7 +227,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('revert', "Revert File"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFile, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo('')) }); MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { @@ -256,7 +256,7 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { title: nls.localize('compareWithSaved', "Compare with Saved"), precondition: DirtyEditorContext }, - when: ContextKeyExpr.and(ResourceContextKey.IsFile, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo(''), WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ResourceContextKey.HasResource, AutoSaveContext.notEqualsTo('afterDelay') && AutoSaveContext.notEqualsTo(''), WorkbenchListDoubleSelection.toNegated()) }); const compareResourceCommand = { @@ -372,21 +372,21 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, command: compareResourceCommand, - when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.IsFile, ResourceSelectedForCompareContext, WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource, ResourceSelectedForCompareContext, WorkbenchListDoubleSelection.toNegated()) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 30, command: selectForCompareCommand, - when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.IsFile, WorkbenchListDoubleSelection.toNegated()) + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource, WorkbenchListDoubleSelection.toNegated()) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 30, command: compareSelectedCommand, - when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.IsFile, WorkbenchListDoubleSelection) + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource, WorkbenchListDoubleSelection) }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -415,7 +415,7 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { order: 30, command: copyPathCommand, alt: copyRelativePathCommand, - when: ResourceContextKey.IsFile + when: ResourceContextKey.HasResource }); MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { @@ -544,3 +544,14 @@ MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, { }, order: 2 }); + +// Go to menu + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'workbench.action.quickOpen', + title: nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File...") + }, + order: 1 +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 04b6594db4e..ba7e84bb2f5 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -14,7 +14,6 @@ import { sequence, ITask, always } from 'vs/base/common/async'; import * as paths from 'vs/base/common/paths'; import * as resources from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; -import { posix } from 'path'; import * as errors from 'vs/base/common/errors'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as strings from 'vs/base/common/strings'; @@ -300,7 +299,7 @@ class RenameFileAction extends BaseRenameAction { public runAction(newName: string): TPromise { const parentResource = this.element.parent.resource; - const targetResource = parentResource.with({ path: paths.join(parentResource.path, newName) }); + const targetResource = resources.joinPath(parentResource, newName); return this.textFileService.move(this.element.resource, targetResource); } @@ -496,7 +495,7 @@ class CreateFileAction extends BaseCreateAction { public runAction(fileName: string): TPromise { const resource = this.element.parent.resource; - return this.fileService.createFile(resource.with({ path: paths.join(resource.path, fileName) })).then(stat => { + return this.fileService.createFile(resources.joinPath(resource, fileName)).then(stat => { return this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); }, (error) => { this.onErrorWithRetry(error, () => this.runAction(fileName)); @@ -523,7 +522,7 @@ class CreateFolderAction extends BaseCreateAction { public runAction(fileName: string): TPromise { const resource = this.element.parent.resource; - return this.fileService.createFolder(resource.with({ path: paths.join(resource.path, fileName) })).then(null, (error) => { + return this.fileService.createFolder(resources.joinPath(resource, fileName)).then(null, (error) => { this.onErrorWithRetry(error, () => this.runAction(fileName)); }); } @@ -784,9 +783,9 @@ export class AddFilesAction extends BaseFileAction { this._updateEnablement(); } - public run(resources: URI[]): TPromise { + public run(resourcesToAdd: URI[]): TPromise { const addPromise = TPromise.as(null).then(() => { - if (resources && resources.length > 0) { + if (resourcesToAdd && resourcesToAdd.length > 0) { // Find parent to add to let targetElement: ExplorerItem; @@ -811,8 +810,8 @@ export class AddFilesAction extends BaseFileAction { }); let overwritePromise: TPromise = TPromise.as({ confirmed: true }); - if (resources.some(resource => { - return targetNames.has(isLinux ? paths.basename(resource.fsPath) : paths.basename(resource.fsPath).toLowerCase()); + if (resourcesToAdd.some(resource => { + return targetNames.has(!resources.hasToIgnoreCase(resource) ? resources.basename(resource) : resources.basename(resource).toLowerCase()); })) { const confirm: IConfirmation = { message: nls.localize('confirmOverwrite', "A file or folder with the same name already exists in the destination folder. Do you want to replace it?"), @@ -831,10 +830,10 @@ export class AddFilesAction extends BaseFileAction { // Run add in sequence const addPromisesFactory: ITask>[] = []; - resources.forEach(resource => { + resourcesToAdd.forEach(resource => { addPromisesFactory.push(() => { const sourceFile = resource; - const targetFile = targetElement.resource.with({ path: paths.join(targetElement.resource.path, paths.basename(sourceFile.path)) }); + const targetFile = resources.joinPath(targetElement.resource, resources.basename(sourceFile)); // if the target exists and is dirty, make sure to revert it. otherwise the dirty contents // of the target file would replace the contents of the added file. since we already @@ -845,11 +844,11 @@ export class AddFilesAction extends BaseFileAction { } return revertPromise.then(() => { - const target = targetElement.resource.with({ path: posix.join(targetElement.resource.path, posix.basename(sourceFile.path)) }); + const target = resources.joinPath(targetElement.resource, resources.basename(sourceFile)); return this.fileService.copyFile(sourceFile, target, true).then(stat => { // if we only add one file, just open it directly - if (resources.length === 1) { + if (resourcesToAdd.length === 1) { this.editorService.openEditor({ resource: stat.resource, options: { pinned: true } }); } }, error => this.onError(error)); @@ -1020,14 +1019,14 @@ export class DuplicateFileAction extends BaseFileAction { function findValidPasteFileTarget(targetFolder: ExplorerItem, fileToPaste: { resource: URI, isDirectory?: boolean }): URI { let name = resources.basenameOrAuthority(fileToPaste.resource); - let candidate = targetFolder.resource.with({ path: paths.join(targetFolder.resource.path, name) }); + let candidate = resources.joinPath(targetFolder.resource, name); while (true) { if (!targetFolder.root.find(candidate)) { break; } name = incrementFileName(name, fileToPaste.isDirectory); - candidate = targetFolder.resource.with({ path: paths.join(targetFolder.resource.path, name) }); + candidate = resources.joinPath(targetFolder.resource, name); } return candidate; @@ -1545,7 +1544,7 @@ export class CompareWithClipboardAction extends Action { this.registrationDisposal = this.textModelService.registerTextModelContentProvider(CompareWithClipboardAction.SCHEME, provider); } - const name = paths.basename(resource.fsPath); + const name = resources.basename(resource); const editorLabel = nls.localize('clipboardComparisonLabel', "Clipboard ↔ {0}", name); const cleanUp = () => { diff --git a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts index a1ff10697c8..a34d3ded080 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.contribution.ts @@ -186,7 +186,7 @@ configurationRegistry.registerConfiguration({ 'properties': { 'files.exclude': { 'type': 'object', - 'description': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting."), + 'markdownDescription': nls.localize('exclude', "Configure glob patterns for excluding files and folders. For example, the files explorer decides which files and folders to show or hide based on this setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), 'default': { '**/.git': true, '**/.svn': true, '**/.hg': true, '**/CVS': true, '**/.DS_Store': true }, 'scope': ConfigurationScope.RESOURCE, 'additionalProperties': { @@ -211,7 +211,7 @@ configurationRegistry.registerConfiguration({ }, 'files.associations': { 'type': 'object', - 'description': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), + 'markdownDescription': nls.localize('associations', "Configure file associations to languages (e.g. `\"*.extension\": \"html\"`). These have precedence over the default associations of the languages installed."), }, 'files.encoding': { 'type': 'string', @@ -267,19 +267,19 @@ configurationRegistry.registerConfiguration({ 'files.autoSave': { 'type': 'string', 'enum': [AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE], - 'enumDescriptions': [ + 'markdownEnumDescriptions': [ nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.off' }, "A dirty file is never automatically saved."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.afterDelay' }, "A dirty file is automatically saved after the configured `#files.autoSaveDelay#`."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onFocusChange' }, "A dirty file is automatically saved when the editor loses focus."), nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'files.autoSave.onWindowChange' }, "A dirty file is automatically saved when the window loses focus.") ], 'default': AutoSaveConfiguration.OFF, - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty files. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSave' }, "Controls auto save of dirty files. Read more about autosave [here](https://code.visualstudio.com/docs/editor/codebasics#_save-auto-save).", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { 'type': 'number', 'default': 1000, - 'description': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty file is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY) + 'markdownDescription': nls.localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'autoSaveDelay' }, "Controls the delay in ms after which a dirty file is saved automatically. Only applies when `#files.autoSave#` is set to `{0}`.", AutoSaveConfiguration.AFTER_DELAY) }, 'files.watcherExclude': { 'type': 'object', @@ -291,10 +291,10 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': [HotExitConfiguration.OFF, HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE], 'default': HotExitConfiguration.ON_EXIT, - 'enumDescriptions': [ + 'markdownEnumDescriptions': [ nls.localize('hotExit.off', 'Disable hot exit.'), - nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit command` is triggered (command palette, keybinding, menu). All windows with backups will be restored upon next launch.'), - nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit command` is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. To restore folder windows as they were before shutdown set `#window.restoreWindows#` to `all`.') + nls.localize('hotExit.onExit', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu). All windows with backups will be restored upon next launch.'), + nls.localize('hotExit.onExitAndWindowClose', 'Hot exit will be triggered when the last window is closed on Windows/Linux or when the `workbench.action.quit` command is triggered (command palette, keybinding, menu), and also for any window with a folder opened regardless of whether it\'s the last window. All windows without folders opened will be restored upon next launch. To restore folder windows as they were before shutdown set `#window.restoreWindows#` to `all`.') ], 'description': nls.localize('hotExit', "Controls whether unsaved files are remembered between sessions, allowing the save prompt when exiting the editor to be skipped.", HotExitConfiguration.ON_EXIT, HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) }, @@ -310,7 +310,7 @@ configurationRegistry.registerConfiguration({ 'files.maxMemoryForLargeFilesMB': { 'type': 'number', 'default': 4096, - 'description': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to VS Code after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line.") + 'markdownDescription': nls.localize('maxMemoryForLargeFilesMB', "Controls the memory available to VS Code after restart when trying to open large files. Same effect as specifying `--max-memory=NEWSIZE` on the command line.") } } }); @@ -351,22 +351,22 @@ configurationRegistry.registerConfiguration({ }, 'explorer.autoReveal': { 'type': 'boolean', - 'description': nls.localize('autoReveal', "Controls if the explorer should automatically reveal and select files when opening them."), + 'description': nls.localize('autoReveal', "Controls whether the explorer should automatically reveal and select files when opening them."), 'default': true }, 'explorer.enableDragAndDrop': { 'type': 'boolean', - 'description': nls.localize('enableDragAndDrop', "Controls if the explorer should allow to move files and folders via drag and drop."), + 'description': nls.localize('enableDragAndDrop', "Controls whether the explorer should allow to move files and folders via drag and drop."), 'default': true }, 'explorer.confirmDragAndDrop': { 'type': 'boolean', - 'description': nls.localize('confirmDragAndDrop', "Controls if the explorer should ask for confirmation to move files and folders via drag and drop."), + 'description': nls.localize('confirmDragAndDrop', "Controls whether the explorer should ask for confirmation to move files and folders via drag and drop."), 'default': true }, 'explorer.confirmDelete': { 'type': 'boolean', - 'description': nls.localize('confirmDelete', "Controls if the explorer should ask for confirmation when deleting a file via the trash."), + 'description': nls.localize('confirmDelete', "Controls whether the explorer should ask for confirmation when deleting a file via the trash."), 'default': true }, 'explorer.sortOrder': { @@ -384,12 +384,12 @@ configurationRegistry.registerConfiguration({ }, 'explorer.decorations.colors': { type: 'boolean', - description: nls.localize('explorer.decorations.colors', "Controls if file decorations should use colors."), + description: nls.localize('explorer.decorations.colors', "Controls whether file decorations should use colors."), default: true }, 'explorer.decorations.badges': { type: 'boolean', - description: nls.localize('explorer.decorations.badges', "Controls if file decorations should use badges."), + description: nls.localize('explorer.decorations.badges', "Controls whether file decorations should use badges."), default: true }, } diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts index cadc88c5131..40850fe8f72 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -196,6 +196,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(e => this.refreshFromEvent(e.added))); this.disposables.push(this.contextService.onDidChangeWorkbenchState(e => this.refreshFromEvent())); + this.disposables.push(this.fileService.onDidChangeFileSystemProviderRegistrations(() => this.refreshFromEvent())); } layoutBody(size: number): void { diff --git a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts index a59212c44df..af257952938 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -279,7 +279,7 @@ export class FileRenderer implements IRenderer { const parent = stat.name ? resources.dirname(stat.resource) : stat.resource; const value = stat.name || ''; - label.setFile(parent.with({ path: paths.join(parent.path, value || ' ') }), labelOptions); // Use icon for ' ' if name is empty. + label.setFile(resources.joinPath(parent, value || ' '), labelOptions); // Use icon for ' ' if name is empty. // Input field for name const inputBox = new InputBox(label.element, this.contextViewService, { @@ -291,7 +291,7 @@ export class FileRenderer implements IRenderer { const styler = attachInputBoxStyler(inputBox, this.themeService); inputBox.onDidChange(value => { - label.setFile(parent.with({ path: paths.join(parent.path, value || ' ') }), labelOptions); // update label icon while typing! + label.setFile(resources.joinPath(parent, value || ' '), labelOptions); // update label icon while typing! }); const lastDot = value.lastIndexOf('.'); @@ -390,7 +390,7 @@ export class FileRenderer implements IRenderer { export class FileAccessibilityProvider implements IAccessibilityProvider { public getAriaLabel(tree: ITree, stat: ExplorerItem): string { - return nls.localize('filesExplorerViewerAriaLabel', "{0}, Files Explorer", stat.name); + return stat.name; } } @@ -1058,7 +1058,7 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { } // Otherwise move - const targetResource = target.resource.with({ path: paths.join(target.resource.path, source.name) }); + const targetResource = resources.joinPath(target.resource, source.name); return this.textFileService.move(source.resource, targetResource).then(null, error => { diff --git a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts b/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts index 210ff1bdb78..e88d9dadf02 100644 --- a/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts +++ b/src/vs/workbench/parts/files/test/electron-browser/explorerModel.test.ts @@ -18,7 +18,12 @@ function createStat(path: string, name: string, isFolder: boolean, hasChildren: } function toResource(path) { - return URI.file(join('C:\\', path)); + if (isWindows) { + return URI.file(join('C:\\', path)); + } else { + return URI.file(join('/home/john', path)); + } + } suite('Files - View Model', () => { diff --git a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts index 2fcdc413e3d..e9695b3cde0 100644 --- a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts +++ b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts @@ -9,13 +9,13 @@ import * as paths from 'vs/base/common/paths'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; import { IOutputService, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output'; import * as Constants from 'vs/workbench/parts/logs/common/logConstants'; import { ICommandService } from 'vs/platform/commands/common/commands'; import URI from 'vs/base/common/uri'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; export class OpenLogsFolderAction extends Action { @@ -40,7 +40,7 @@ export class ShowLogsAction extends Action { static LABEL = nls.localize('showLogs', "Show Logs..."); constructor(id: string, label: string, - @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @IOutputService private outputService: IOutputService, @IWorkspaceContextService private contextService: IWorkspaceContextService ) { @@ -48,14 +48,14 @@ export class ShowLogsAction extends Action { } run(): TPromise { - const entries: IPickOpenEntry[] = [ + const entries: IQuickPickItem[] = [ { id: Constants.rendererLogChannelId, label: this.contextService.getWorkspace().name ? nls.localize('rendererProcess', "Window ({0})", this.contextService.getWorkspace().name) : nls.localize('emptyWindow', "Window") }, { id: Constants.extHostLogChannelId, label: nls.localize('extensionHost', "Extension Host") }, { id: Constants.sharedLogChannelId, label: nls.localize('sharedProcess', "Shared") }, { id: Constants.mainLogChannelId, label: nls.localize('mainProcess', "Main") } ]; - return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select Log for Process") }) + return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select Log for Process") }) .then(entry => { if (entry) { return this.outputService.showChannel(entry.id); @@ -71,7 +71,7 @@ export class OpenLogFileAction extends Action { static LABEL = nls.localize('openLogFile', "Open Log File..."); constructor(id: string, label: string, - @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @IEnvironmentService private environmentService: IEnvironmentService, @ICommandService private commandService: ICommandService, @IWindowService private windowService: IWindowService, @@ -81,7 +81,7 @@ export class OpenLogFileAction extends Action { } run(): TPromise { - const entries: IPickOpenEntry[] = [ + const entries: IQuickPickItem[] = [ { id: URI.file(paths.join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)).fsPath, label: this.contextService.getWorkspace().name ? nls.localize('rendererProcess', "Window ({0})", this.contextService.getWorkspace().name) : nls.localize('emptyWindow', "Window") }, { id: URI.file(paths.join(this.environmentService.logsPath, `exthost${this.windowService.getCurrentWindowId()}.log`)).fsPath, label: nls.localize('extensionHost', "Extension Host") }, { id: URI.file(paths.join(this.environmentService.logsPath, `sharedprocess.log`)).fsPath, label: nls.localize('sharedProcess', "Shared") }, @@ -89,7 +89,7 @@ export class OpenLogFileAction extends Action { { id: URI.file(paths.join(this.environmentService.logsPath, `telemetry.log`)).fsPath, label: nls.localize('telemetry', "Telemetry") } ]; - return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select Log for Process") }) + return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select Log for Process") }) .then(entry => { if (entry) { return this.commandService.executeCommand(COMMAND_OPEN_LOG_VIEWER, URI.file(entry.id)); @@ -105,7 +105,7 @@ export class SetLogLevelAction extends Action { static LABEL = nls.localize('setLogLevel', "Set Log Level..."); constructor(id: string, label: string, - @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @ILogService private logService: ILogService ) { super(id, label); @@ -123,7 +123,7 @@ export class SetLogLevelAction extends Action { { label: nls.localize('off', "Off"), level: LogLevel.Off, description: this.getDescription(LogLevel.Off, current) }, ]; - return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => { + return this.quickInputService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), activeItem: entries[this.logService.getLevel()] }).then(entry => { if (entry) { this.logService.setLevel(entry.level); } diff --git a/src/vs/workbench/parts/markers/electron-browser/messages.ts b/src/vs/workbench/parts/markers/electron-browser/messages.ts index c5285fae726..09e3eddbb5d 100644 --- a/src/vs/workbench/parts/markers/electron-browser/messages.ts +++ b/src/vs/workbench/parts/markers/electron-browser/messages.ts @@ -16,7 +16,7 @@ export default class Messages { public static MARKERS_PANEL_SHOW_LABEL: string = nls.localize('problems.view.focus.label', "Focus Problems (Errors, Warnings, Infos)"); public static PROBLEMS_PANEL_CONFIGURATION_TITLE: string = nls.localize('problems.panel.configuration.title', "Problems View"); - public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls if Problems view should automatically reveal files when opening them"); + public static PROBLEMS_PANEL_CONFIGURATION_AUTO_REVEAL: string = nls.localize('problems.panel.configuration.autoreveal', "Controls whether Problems view should automatically reveal files when opening them."); public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems"); public static MARKERS_PANEL_ARIA_LABEL_PROBLEMS_TREE: string = nls.localize('markers.panel.aria.label.problems.tree', "Problems grouped by files"); diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css index 858ba8e3266..23afd397253 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.css @@ -71,11 +71,17 @@ display: none; } -.monaco-tree.focused .selected .outline-element-label, .monaco-tree.focused .selected .outline-element-decoration { +.monaco-tree.focused .selected .outline-element-label, .monaco-tree.focused .selected .outline-element-decoration{ /* make sure selection color wins when a label is being selected */ color: inherit !important; } +.monaco-tree.focused .selected .outline-element-label .monaco-highlighted-label .highlight, +.monaco-tree.focused .selected .monaco-icon-label .monaco-highlighted-label .highlight{ + /* allows text color to use the default when selected */ + color: inherit !important; +} + .monaco-workbench .outline-panel.no-icons .outline-element .outline-element-icon { display: none; } diff --git a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts index c61097b03d3..4123498f29d 100644 --- a/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts +++ b/src/vs/workbench/parts/outline/electron-browser/outlinePanel.ts @@ -50,7 +50,6 @@ import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewl import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IViewsService } from 'vs/workbench/common/views'; import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService'; -import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/keybindingService'; import { OutlineConfigKeys, OutlineViewFiltered, OutlineViewFocused, OutlineViewId } from './outline'; import { OutlineController, OutlineDataSource, OutlineItemComparator, OutlineItemCompareType, OutlineItemFilter, OutlineRenderer, OutlineTreeState } from '../../../../editor/contrib/documentSymbols/outlineTree'; import { IResourceInput } from 'vs/platform/editor/common/editor'; @@ -256,12 +255,12 @@ export class OutlinePanel extends ViewletPanel { @IEditorService private readonly _editorService: IEditorService, @IMarkerService private readonly _markerService: IMarkerService, @IConfigurationService private readonly _configurationService: IConfigurationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, @IConfigurationService configurationService: IConfigurationService, - @IKeybindingService keybindingService: IKeybindingService, @IContextKeyService contextKeyService: IContextKeyService, @IContextMenuService contextMenuService: IContextMenuService, ) { - super(options, keybindingService, contextMenuService, configurationService); + super(options, _keybindingService, contextMenuService, configurationService); this._outlineViewState.restore(this._storageService); this._contextKeyFocused = OutlineViewFocused.bindTo(contextKeyService); this._contextKeyFiltered = OutlineViewFiltered.bindTo(contextKeyService); @@ -326,8 +325,6 @@ export class OutlinePanel extends ViewletPanel { const $this = this; const controller = new class extends OutlineController { - private readonly _mapper = KeyboardMapperFactory.INSTANCE; - constructor() { super({}, $this.configurationService); } @@ -340,22 +337,10 @@ export class OutlinePanel extends ViewletPanel { if (this.upKeyBindingDispatcher.has(event.keyCode)) { return false; } - if (event.ctrlKey || event.metaKey) { - // ignore ctrl/cmd-combination but not shift/alt-combinatios - return false; - } // crazy -> during keydown focus moves to the input box // and because of that the keyup event is handled by the // input field - const mapping = this._mapper.getRawKeyboardMapping(); - if (!mapping) { - return false; - } - const keyInfo = mapping[event.code]; - if (!keyInfo) { - return false; - } - if (keyInfo.value) { + if ($this._keybindingService.mightProducePrintableCharacter(event)) { $this._input.focus(); return true; } diff --git a/src/vs/workbench/parts/output/common/outputLinkComputer.ts b/src/vs/workbench/parts/output/common/outputLinkComputer.ts index 54334478c76..71cba0a3ce8 100644 --- a/src/vs/workbench/parts/output/common/outputLinkComputer.ts +++ b/src/vs/workbench/parts/output/common/outputLinkComputer.ts @@ -9,6 +9,7 @@ import { ILink } from 'vs/editor/common/modes'; import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; import * as strings from 'vs/base/common/strings'; import * as arrays from 'vs/base/common/arrays'; import { Range } from 'vs/editor/common/core/range'; @@ -70,7 +71,7 @@ export class OutputLinkComputer { const resourceCreator: IResourceCreator = { toResource: (folderRelativePath: string): URI => { if (typeof folderRelativePath === 'string') { - return folderUri.with({ path: paths.join(folderUri.path, folderRelativePath) }); + return resources.joinPath(folderUri, folderRelativePath); } return null; diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css index 3be8f0ad66d..30a6e934f54 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsEditor2.css @@ -17,6 +17,7 @@ .settings-editor > .settings-header { box-sizing: border-box; margin: auto; + overflow: hidden; } .settings-editor > .settings-header > .settings-preview-header { @@ -25,6 +26,7 @@ .settings-editor > .settings-header > .settings-preview-header .settings-preview-label { opacity: .7; + white-space: nowrap; } .settings-editor > .settings-header > .settings-preview-header > .settings-preview-warning { @@ -177,11 +179,22 @@ display: none; } +.settings-editor > .settings-body .settings-toc-container .monaco-tree-row .content { + display: flex; +} + .settings-editor > .settings-body .settings-toc-container .monaco-tree-row .settings-toc-entry { overflow: hidden; text-overflow: ellipsis; line-height: 22px; opacity: 0.9; + flex-shrink: 1; +} + +.settings-editor > .settings-body .settings-toc-container .monaco-tree-row .settings-toc-count { + line-height: 22px; + opacity: 0.7; + margin-left: 3px; } .settings-editor > .settings-body .settings-toc-container .monaco-tree-row.has-children > .content:before { @@ -211,26 +224,14 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item { - padding-top: 8px; - padding-bottom: 14px; + padding-top: 12px; + padding-bottom: 18px; box-sizing: border-box; cursor: default; white-space: normal; height: 100%; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-expand-indicator { - display: none; -} - -.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expandable:not(.is-expanded) .setting-expand-indicator { - display: block; - position: absolute; - left: 7px; - top: 0px; - opacity: .9; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-title { white-space: nowrap; overflow: hidden; @@ -254,28 +255,34 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-label, .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { - font-weight: bold; + font-weight: 600; } .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-category { opacity: 0.9; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-container { - margin-top: 3px; - position: relative; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description { + margin-top: 3px; overflow: hidden; + text-overflow: ellipsis; height: 18px; + display: -webkit-box; + -webkit-line-clamp: 1; + -webkit-box-orient: vertical; + transform: translate3d(0px, 0px, 0px); } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description * { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown * { margin: 0px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description code { +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a:focus { + outline: 1px solid -webkit-focus-ring-color; + outline-offset: -1px; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { line-height: 15px; /** For some reason, this is needed, otherwise will take up 20px height */ font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback"; } @@ -288,11 +295,16 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-description, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-description { height: initial; + -webkit-line-clamp: initial; } -.settings-editor > .settings-body > .settings-tree-container .setting-description-measure-container .setting-item .setting-item-description, -.settings-editor > .settings-body > .settings-tree-container .setting-description-measure-container .setting-item .setting-item-description * { - display: inline; +.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-enumDescription { + display: none; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.is-expanded .setting-item-enumDescription, +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-measure-helper .setting-item-enumDescription { + display: block; } .settings-editor > .settings-body > .settings-tree-container .setting-item-bool .setting-item-value-description { @@ -328,17 +340,15 @@ min-width: 200px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-value { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-control { width: 500px; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-enum .setting-item-value > .setting-item-control, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-text .setting-item-value > .setting-item-control { - flex: 1; min-width: initial; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-enum .setting-item-value, .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-enum .setting-item-value > .setting-item-control > select { width: 320px; } @@ -379,7 +389,7 @@ .settings-editor > .settings-body > .settings-tree-container .settings-group-title-label { margin: 0px; - font-weight: 500; + font-weight: 600; } .settings-editor > .settings-body > .settings-tree-container .settings-group-level-1 { diff --git a/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css b/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css index f6b08e12198..b34fee0b67b 100644 --- a/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css +++ b/src/vs/workbench/parts/preferences/browser/media/settingsWidgets.css @@ -37,8 +37,12 @@ position: relative; } +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:focus { + outline: none; +} + .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover .monaco-action-bar, -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.focused .monaco-action-bar { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected .monaco-action-bar { display: block; } @@ -71,20 +75,20 @@ .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .monaco-text-button { width: initial; - padding: 4px 10px; + padding: 2px 14px; +} + +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-item-control.setting-exclude-new-mode .setting-exclude-new-row { + display: none; } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .monaco-text-button.setting-exclude-addButton { margin-right: 10px; } -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-edit-row.setting-exclude-newExcludeItem { - display: flex; -} - .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-patternInput, -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-siblingInput, -.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-newPatternInput { +.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-siblingInput { + height: 22px; max-width: 300px; display: inline-block; margin-right: 10px; @@ -95,5 +99,5 @@ } .settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-widget { - margin-bottom: 10px; + margin-bottom: 1px; } diff --git a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts index 70beb50fe4f..95acd99a978 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts @@ -10,11 +10,13 @@ import URI from 'vs/base/common/uri'; import { Action } from 'vs/base/common/actions'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; +import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; +import { getIconClasses } from 'vs/workbench/browser/labels'; +import { IModelService } from 'vs/editor/common/services/modelService'; export class OpenRawDefaultSettingsAction extends Action { @@ -225,8 +227,9 @@ export class ConfigureLanguageBasedSettingsAction extends Action { constructor( id: string, label: string, + @IModelService private modelService: IModelService, @IModeService private modeService: IModeService, - @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @IPreferencesService private preferencesService: IPreferencesService ) { super(id, label); @@ -234,7 +237,7 @@ export class ConfigureLanguageBasedSettingsAction extends Action { public run(): TPromise { const languages = this.modeService.getRegisteredLanguageNames(); - const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => { + const picks: IQuickPickItem[] = languages.sort().map((lang, index) => { let description: string = nls.localize('languageDescriptionConfigured', "({0})", this.modeService.getModeIdForLanguageName(lang.toLowerCase())); // construct a fake resource to be able to show nice icons if any let fakeResource: URI; @@ -247,14 +250,14 @@ export class ConfigureLanguageBasedSettingsAction extends Action { fakeResource = URI.file(filenames[0]); } } - return { + return { label: lang, - resource: fakeResource, + iconClasses: getIconClasses(this.modelService, this.modeService, fakeResource), description - }; + } as IQuickPickItem; }); - return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") }) + return this.quickInputService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language") }) .then(pick => { if (pick) { return this.modeService.getOrCreateModeByLanguageName(pick.label) diff --git a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts index 79fef29f2a4..4f94c79cb9e 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsEditor2.ts @@ -15,6 +15,7 @@ import * as collections from 'vs/base/common/collections'; import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { collapseAll, expandAll } from 'vs/base/parts/tree/browser/treeUtils'; import 'vs/css!./media/settingsEditor2'; import { localize } from 'vs/nls'; @@ -35,7 +36,7 @@ import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbenc import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout'; import { ISettingsEditorViewState, MODIFIED_SETTING_TAG, ONLINE_SERVICES_SETTING_TAG, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsRenderer, SettingsTree, SettingsTreeElement, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; import { TOCRenderer, TOCTree, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree'; -import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; +import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences'; import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences'; import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput'; import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels'; @@ -55,7 +56,8 @@ export class SettingsEditor2 extends BaseEditor { private toolbar: ToolBar; private settingsTreeContainer: HTMLElement; - private settingsTree: WorkbenchTree; + private settingsTree: Tree; + private settingsTreeRenderer: SettingsRenderer; private tocTreeModel: TOCTreeModel; private settingsTreeModel: SettingsTreeModel; @@ -66,25 +68,20 @@ export class SettingsEditor2 extends BaseEditor { private localSearchDelayer: Delayer; private remoteSearchThrottle: ThrottledDelayer; private searchInProgress: TPromise; + private delayRefreshOnLayout: Delayer; private settingUpdateDelayer: Delayer; private pendingSettingUpdate: { key: string, value: any }; - private selectedElement: SettingsTreeElement; - private viewState: ISettingsEditorViewState; private searchResultModel: SearchResultModel; - private firstRowFocused: IContextKey; - private rowFocused: IContextKey; private tocRowFocused: IContextKey; private inSettingsEditorContextKey: IContextKey; private searchFocusContextKey: IContextKey; private tagRegex = /(^|\s)@tag:("([^"]*)"|[^"]\S*)/g; - private layoutDelayer: Delayer; - /** Don't spam warnings */ private hasWarnedMissingSettings: boolean; @@ -105,14 +102,12 @@ export class SettingsEditor2 extends BaseEditor { this.localSearchDelayer = new Delayer(100); this.remoteSearchThrottle = new ThrottledDelayer(200); this.viewState = { settingsTarget: ConfigurationTarget.USER }; - this.layoutDelayer = new Delayer(100); + this.delayRefreshOnLayout = new Delayer(100); this.settingUpdateDelayer = new Delayer(500); this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService); this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService); - this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService); - this.rowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService); this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService); this._register(configurationService.onDidChangeConfiguration(e => { @@ -135,9 +130,10 @@ export class SettingsEditor2 extends BaseEditor { setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable { this.inSettingsEditorContextKey.set(true); return super.setInput(input, options, token) + .then(() => new Promise(process.nextTick)) // Force setInput to be async .then(() => { return this.render(token); - }).then(() => new Promise(process.nextTick)); // Force setInput to be async + }); } clearInput(): void { @@ -151,7 +147,7 @@ export class SettingsEditor2 extends BaseEditor { DOM.toggleClass(this.rootElement, 'narrow', dimension.width < 600); - this.layoutDelayer.trigger(() => this.refreshTreeAndMaintainFocus()); + this.delayRefreshOnLayout.trigger(() => this.refreshTreeAndMaintainFocus()); } focus(): void { @@ -159,28 +155,16 @@ export class SettingsEditor2 extends BaseEditor { } focusSettings(): void { - const selection = this.settingsTree.getSelection(); - if (selection && selection[0]) { - this.settingsTree.setFocus(selection[0]); - } else { - this.settingsTree.focusFirst(); + const firstFocusable = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR); + if (firstFocusable) { + (firstFocusable).focus(); } - - this.settingsTree.domFocus(); } focusSearch(): void { this.searchWidget.focus(); } - editSelectedSetting(): void { - const focus = this.settingsTree.getFocus(); - if (focus instanceof SettingsTreeSettingElement) { - const itemId = focus.id.replace(/\./g, '_'); - this.focusEditControlForRow(itemId); - } - } - clearSearchResults(): void { this.searchWidget.clear(); } @@ -230,24 +214,28 @@ export class SettingsEditor2 extends BaseEditor { private createHeaderControls(parent: HTMLElement): void { const headerControlsContainerRight = DOM.append(parent, $('.settings-header-controls-right')); - this.toolbar = new ToolBar(headerControlsContainerRight, this.contextMenuService, { + this.toolbar = this._register(new ToolBar(headerControlsContainerRight, this.contextMenuService, { ariaLabel: localize('settingsToolbarLabel', "Settings Editor Actions"), actionRunner: this.actionRunner - }); + })); - const actions = [ + const actions: Action[] = [ this.instantiationService.createInstance(FilterByTagAction, localize('filterModifiedLabel', "Show modified settings"), MODIFIED_SETTING_TAG, - this), - this.instantiationService.createInstance( - FilterByTagAction, - localize('filterOnlineServicesLabel', "Show settings for online services"), - ONLINE_SERVICES_SETTING_TAG, - this), - new Separator(), - this.instantiationService.createInstance(OpenSettingsAction) + this) ]; + if (this.environmentService.appQuality !== 'stable') { + actions.push( + this.instantiationService.createInstance( + FilterByTagAction, + localize('filterOnlineServicesLabel', "Show settings for online services"), + ONLINE_SERVICES_SETTING_TAG, + this)); + actions.push(new Separator()); + } + actions.push(this.instantiationService.createInstance(OpenSettingsAction)); + this.toolbar.setActions([], actions)(); this.toolbar.context = { target: this.settingsTargetsWidget.settingsTarget }; } @@ -255,13 +243,19 @@ export class SettingsEditor2 extends BaseEditor { private revealSetting(settingName: string): void { const element = this.settingsTreeModel.getElementByName(settingName); if (element) { - this.settingsTree.setSelection([element]); - this.settingsTree.setFocus(element); - this.settingsTree.reveal(element, 0); - this.settingsTree.domFocus(); + this.settingsTree.reveal(element, .1); } } + private revealSettingElement(element: SettingsTreeElement): void { + const top = this.settingsTree.getRelativeTop(element); + const clampedTop = Math.max( + Math.min(top, .9), + .1); + + this.settingsTree.reveal(element, clampedTop); + } + private openSettingsFile(): TPromise { const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget; @@ -277,7 +271,50 @@ export class SettingsEditor2 extends BaseEditor { private createBody(parent: HTMLElement): void { const bodyContainer = DOM.append(parent, $('.settings-body')); + this.createFocusSink( + bodyContainer, + e => { + if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { + if (this.settingsTree.getScrollPosition() > 0) { + const firstElement = this.settingsTree.getFirstVisibleElement(); + this.settingsTree.reveal(firstElement, 0.1); + return true; + } + } else { + const firstControl = this.settingsTree.getHTMLElement().querySelector(SettingsRenderer.CONTROL_SELECTOR); + if (firstControl) { + (firstControl).focus(); + } + } + + return false; + }, + 'settings list focus helper'); + this.createSettingsTree(bodyContainer); + + this.createFocusSink( + bodyContainer, + e => { + if (DOM.findParentWithClass(e.relatedTarget, 'settings-editor-tree')) { + if (this.settingsTree.getScrollPosition() < 1) { + const lastElement = this.settingsTree.getLastVisibleElement(); + this.settingsTree.reveal(lastElement, 0.9); + return true; + } + } else { + const controls = this.settingsTree.getHTMLElement().querySelectorAll(SettingsRenderer.CONTROL_SELECTOR); + const lastControl = controls && controls[controls.length]; + if (lastControl) { + (lastControl).focus(); + } + } + + return false; + }, + 'settings list focus helper' + ); + this.createTOC(bodyContainer); if (this.environmentService.appQuality !== 'stable') { @@ -285,29 +322,40 @@ export class SettingsEditor2 extends BaseEditor { } } + private createFocusSink(container: HTMLElement, callback: (e: any) => boolean, label: string): HTMLElement { + const listFocusSink = DOM.append(container, $('.settings-tree-focus-sink')); + listFocusSink.setAttribute('aria-label', label); + listFocusSink.tabIndex = 0; + this._register(DOM.addDisposableListener(listFocusSink, 'focus', (e: any) => { + if (e.relatedTarget && callback(e)) { + e.relatedTarget.focus(); + } + })); + + return listFocusSink; + } + private createTOC(parent: HTMLElement): void { this.tocTreeModel = new TOCTreeModel(); this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container')); const tocRenderer = this.instantiationService.createInstance(TOCRenderer); - this.tocTree = this.instantiationService.createInstance(TOCTree, this.tocTreeContainer, + this.tocTree = this._register(this.instantiationService.createInstance(TOCTree, this.tocTreeContainer, this.viewState, { renderer: tocRenderer - }); + })); this._register(this.tocTree.onDidChangeFocus(e => { const element = e.focus; if (this.searchResultModel) { this.viewState.filterToCategory = element; this.refreshTreeAndMaintainFocus(); - } else if (this.settingsTreeModel) { - if (element && !e.payload.fromScroll) { - this.settingsTree.reveal(element, 0); - this.settingsTree.setSelection([element]); - this.settingsTree.setFocus(element); - } + } + + if (element && (!e.payload || !e.payload.fromScroll)) { + this.settingsTree.reveal(element, 0); } })); @@ -330,61 +378,25 @@ export class SettingsEditor2 extends BaseEditor { private createSettingsTree(parent: HTMLElement): void { this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container')); - const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer); - this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value))); - this._register(renderer.onDidOpenSettings(settingKey => { + this.settingsTreeRenderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer); + this._register(this.settingsTreeRenderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value))); + this._register(this.settingsTreeRenderer.onDidOpenSettings(settingKey => { this.openSettingsFile().then(editor => { if (editor instanceof PreferencesEditor && settingKey) { editor.focusSearch(settingKey); } }); })); - this._register(renderer.onDidClickSettingLink(settingName => this.revealSetting(settingName))); + this._register(this.settingsTreeRenderer.onDidClickSettingLink(settingName => this.revealSetting(settingName))); + this._register(this.settingsTreeRenderer.onDidFocusSetting(element => this.revealSettingElement(element))); - this.settingsTree = this.instantiationService.createInstance(SettingsTree, + this.settingsTree = this._register(this.instantiationService.createInstance(SettingsTree, this.settingsTreeContainer, this.viewState, { - renderer - }); - - this._register(this.settingsTree.onDidChangeFocus(e => { - this.settingsTree.setSelection([e.focus]); - if (this.selectedElement) { - this.settingsTree.refresh(this.selectedElement); - } - - if (e.focus) { - this.settingsTree.refresh(e.focus); - } - - this.selectedElement = e.focus; - })); - - this._register(this.settingsTree.onDidBlur(() => { - this.rowFocused.set(false); - this.firstRowFocused.set(false); - })); - - this._register(this.settingsTree.onDidChangeSelection(e => { - this.updateTreeScrollSync(); - - let firstRowFocused = false; - let rowFocused = false; - const selection: SettingsTreeElement = e.selection[0]; - if (selection) { - rowFocused = true; - if (this.searchResultModel) { - firstRowFocused = selection.id === this.searchResultModel.getChildren()[0].id; - } else { - const firstRowId = this.settingsTreeModel.root.children[0] && this.settingsTreeModel.root.children[0].id; - firstRowFocused = selection.id === firstRowId; - } - } - - this.rowFocused.set(rowFocused); - this.firstRowFocused.set(firstRowFocused); - })); + renderer: this.settingsTreeRenderer + })); + this.settingsTree.getHTMLElement().attributes.removeNamedItem('tabindex'); this._register(this.settingsTree.onDidScroll(() => { this.updateTreeScrollSync(); @@ -421,15 +433,7 @@ export class SettingsEditor2 extends BaseEditor { return; } - let elementToSync = this.settingsTree.getFirstVisibleElement(); - const selection = this.settingsTree.getSelection()[0]; - if (selection) { - const selectionPos = this.settingsTree.getRelativeTop(selection); - if (selectionPos >= 0 && selectionPos <= 1) { - elementToSync = selection; - } - } - + const elementToSync = this.settingsTree.getFirstVisibleElement(); const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent : elementToSync instanceof SettingsTreeGroupElement ? elementToSync : null; @@ -589,6 +593,7 @@ export class SettingsEditor2 extends BaseEditor { if (this.settingsTreeModel) { this.settingsTreeModel.update(resolvedSettingsRoot); + return this.refreshTreeAndMaintainFocus(); } else { this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, resolvedSettingsRoot); this.settingsTree.setInput(this.settingsTreeModel.root); @@ -601,7 +606,7 @@ export class SettingsEditor2 extends BaseEditor { } } - return this.refreshTreeAndMaintainFocus(); + return TPromise.wrap(null); } private refreshTreeAndMaintainFocus(): TPromise { @@ -619,13 +624,16 @@ export class SettingsEditor2 extends BaseEditor { } }) .then(() => { + // TODO@roblou - hack + this.tocTreeModel.update(); + return this.tocTree.refresh(); }); } private focusEditControlForRow(id: string, selection?: number): void { const rowSelector = `.setting-item#${id}`; - const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a, ${rowSelector} .monaco-custom-checkbox`); + const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} .monaco-custom-checkbox`); if (inputElementToFocus) { inputElementToFocus.focus(); if (typeof selection === 'number') { @@ -789,14 +797,11 @@ export class SettingsEditor2 extends BaseEditor { this.settingsTreeContainer.style.height = `${settingsTreeHeight}px`; this.settingsTree.layout(settingsTreeHeight, 800); - const selectedSetting = this.settingsTree.getSelection()[0]; - if (selectedSetting) { - this.settingsTree.refresh(selectedSetting); - } - const tocTreeHeight = listHeight - 16; this.tocTreeContainer.style.height = `${tocTreeHeight}px`; this.tocTree.layout(tocTreeHeight, 175); + + this.settingsTreeRenderer.updateWidth(dimension.width); } } diff --git a/src/vs/workbench/parts/preferences/browser/settingsTree.ts b/src/vs/workbench/parts/preferences/browser/settingsTree.ts index a11bf419339..59737f81ccb 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsTree.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsTree.ts @@ -23,20 +23,21 @@ import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IAccessibilityProvider, IDataSource, IFilter, IRenderer as ITreeRenderer, ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree'; import { DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { localize } from 'vs/nls'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IListService, WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; +import { IListService, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { editorBackground, focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler, attachSelectBoxStyler, attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { SettingsTarget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets'; import { ITOCEntry } from 'vs/workbench/parts/preferences/browser/settingsLayout'; -import { ExcludeSettingWidget, IExcludeDataItem, settingItemInactiveSelectionBorder, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets'; +import { ExcludeSettingWidget, IExcludeDataItem, settingsHeaderForeground, settingsNumberInputBackground, settingsNumberInputBorder, settingsNumberInputForeground, settingsSelectBackground, settingsSelectBorder, settingsSelectForeground, settingsTextInputBackground, settingsTextInputBorder, settingsTextInputForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets'; import { IExtensionSetting, ISearchResult, ISetting, ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences'; const $ = DOM.$; @@ -145,7 +146,8 @@ export class SettingsTreeModel { if (tocEntry.children) { element.children = tocEntry.children.map(child => this.createSettingsTreeGroupElement(child, element)); } else if (tocEntry.settings) { - element.children = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(s, element)); + element.children = tocEntry.settings.map(s => this.createSettingsTreeSettingElement(s, element)) + .filter(el => el.setting.deprecationMessage ? el.isConfigured : true); } this._treeElementsById.set(element.id, element); @@ -377,7 +379,7 @@ export class SettingsDataSource implements IDataSource { return false; } - getChildren(tree: ITree, element: SettingsTreeElement): TPromise { + getChildren(tree: ITree, element: SettingsTreeElement): TPromise { return TPromise.as(this._getChildren(element)); } @@ -392,8 +394,8 @@ export class SettingsDataSource implements IDataSource { } } - getParent(tree: ITree, element: SettingsTreeElement): TPromise { - return TPromise.wrap(element.parent); + getParent(tree: ITree, element: SettingsTreeElement): TPromise { + return TPromise.wrap(element && element.parent); } shouldAutoexpand(): boolean { @@ -402,17 +404,18 @@ export class SettingsDataSource implements IDataSource { } export function settingKeyToDisplayFormat(key: string, groupId = ''): { category: string, label: string } { - let label = wordifyKey(key); - const lastDotIdx = label.lastIndexOf('.'); + const lastDotIdx = key.lastIndexOf('.'); let category = ''; if (lastDotIdx >= 0) { - category = label.substr(0, lastDotIdx); - label = label.substr(lastDotIdx + 1); + category = key.substr(0, lastDotIdx); + key = key.substr(lastDotIdx + 1); } - groupId = wordifyKey(groupId.replace(/\//g, '.')); + groupId = groupId.replace(/\//g, '.'); category = trimCategoryForGroup(category, groupId); + category = wordifyKey(category); + const label = wordifyKey(key); return { category, label }; } @@ -467,11 +470,11 @@ interface IDisposableTemplate { interface ISettingItemTemplate extends IDisposableTemplate { onChange?: (value: T) => void; + context?: SettingsTreeSettingElement; containerElement: HTMLElement; categoryElement: HTMLElement; labelElement: HTMLElement; descriptionElement: HTMLElement; - expandIndicatorElement: HTMLElement; controlElement: HTMLElement; isConfiguredElement: HTMLElement; otherOverridesElement: HTMLElement; @@ -489,6 +492,7 @@ type ISettingNumberItemTemplate = ISettingTextItemTemplate; interface ISettingEnumItemTemplate extends ISettingItemTemplate { selectBox: SelectBox; + enumDescriptionElement: HTMLElement; } interface ISettingComplexItemTemplate extends ISettingItemTemplate { @@ -497,7 +501,6 @@ interface ISettingComplexItemTemplate extends ISettingItemTemplate { interface ISettingExcludeItemTemplate extends ISettingItemTemplate { excludeWidget: ExcludeSettingWidget; - context?: SettingsTreeSettingElement; } interface ISettingNewExtensionsTemplate extends IDisposableTemplate { @@ -531,10 +534,11 @@ export interface ISettingChangeEvent { export class SettingsRenderer implements ITreeRenderer { - private static readonly SETTING_ROW_HEIGHT = 96; - private static readonly SETTING_BOOL_ROW_HEIGHT = 65; public static readonly MAX_ENUM_DESCRIPTIONS = 10; + private static readonly CONTROL_CLASS = 'setting-control-focus-target'; + public static readonly CONTROL_SELECTOR = '.' + SettingsRenderer.CONTROL_CLASS; + private readonly _onDidChangeSetting: Emitter = new Emitter(); public readonly onDidChangeSetting: Event = this._onDidChangeSetting.event; @@ -544,9 +548,13 @@ export class SettingsRenderer implements ITreeRenderer { private readonly _onDidClickSettingLink: Emitter = new Emitter(); public readonly onDidClickSettingLink: Event = this._onDidClickSettingLink.event; + private readonly _onDidFocusSetting: Emitter = new Emitter(); + public readonly onDidFocusSetting: Event = this._onDidFocusSetting.event; + private measureContainer: HTMLElement; - // private measureDescriptionContainer: HTMLElement; - // private measureDescriptionTemplates = new Map(); + private measureTemplatesPool = new Map(); + private rowHeightCache = new Map(); + private lastRenderedWidth: number; constructor( _measureContainer: HTMLElement, @@ -557,10 +565,27 @@ export class SettingsRenderer implements ITreeRenderer { @ICommandService private readonly commandService: ICommandService, ) { this.measureContainer = DOM.append(_measureContainer, $('.setting-measure-container.monaco-tree-row')); - // this.measureDescriptionContainer = DOM.append(_measureContainer, $('.setting-measure-container.setting-description-measure-container.monaco-tree-row')); + } + + updateWidth(width: number): void { + if (this.lastRenderedWidth !== width) { + this.rowHeightCache = new Map(); + } + + this.lastRenderedWidth = width; } getHeight(tree: ITree, element: SettingsTreeElement): number { + if (this.rowHeightCache.has(element.id)) { + return this.rowHeightCache.get(element.id); + } + + const h = this._getHeight(tree, element); + this.rowHeightCache.set(element.id, h); + return h; + } + + _getHeight(tree: ITree, element: SettingsTreeElement): number { if (element instanceof SettingsTreeGroupElement) { if (element.isFirstGroup) { return 31; @@ -570,13 +595,10 @@ export class SettingsRenderer implements ITreeRenderer { } if (element instanceof SettingsTreeSettingElement) { - const isSelected = this.elementIsSelected(tree, element); - if (isSelected) { - return this.measureSettingElementHeight(tree, element); - } else if (isExcludeSetting(element.setting)) { + if (isExcludeSetting(element.setting)) { return this._getExcludeSettingHeight(element); } else { - return this._getUnexpandedSettingHeight(element); + return this.measureSettingElementHeight(tree, element); } } @@ -592,60 +614,17 @@ export class SettingsRenderer implements ITreeRenderer { return (displayValue.length + 1) * 22 + 80; } - _getUnexpandedSettingHeight(element: SettingsTreeSettingElement): number { - if (element.valueType === 'boolean') { - return SettingsRenderer.SETTING_BOOL_ROW_HEIGHT; - } else { - return SettingsRenderer.SETTING_ROW_HEIGHT; - } - } - private measureSettingElementHeight(tree: ITree, element: SettingsTreeSettingElement): number { - const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper')); - const templateId = this.getTemplateId(tree, element); - const template = this.renderTemplate(tree, templateId, measureHelper); + const template: ISettingItemTemplate = this.measureTemplatesPool.get(templateId) || this.renderTemplate(tree, templateId, $('.setting-measure-helper')) as ISettingItemTemplate; this.renderElement(tree, element, templateId, template); + this.measureContainer.appendChild(template.containerElement); const height = this.measureContainer.offsetHeight; this.measureContainer.removeChild(this.measureContainer.firstChild); - return Math.max(height, this._getUnexpandedSettingHeight(element)); + return height; } - // private measureSettingDescriptionHeight(tree: ITree, element: SettingsTreeSettingElement): number { - // const measureHelper = DOM.append(this.measureContainer, $('.setting-measure-helper')); - - // const templateId = this.getTemplateId(tree, element); - // const template = this.renderTemplate(tree, templateId, measureHelper); - // this.renderDescription(element.description, template, true); - - // const height = (template).descriptionElement.offsetHeight; - // this.measureContainer.removeChild(this.measureContainer.firstChild); - // return height; - // } - - // private measureSettingDescription(tree: ITree, element: SettingsTreeSettingElement, text: string): { height: number, width: number } { - // const templateId = this.getTemplateId(tree, element); - // if (!this.measureDescriptionTemplates.has(templateId)) { - // const measureHelper = $('.setting-measure-helper'); - // this.measureDescriptionTemplates.set(templateId, this.renderTemplate(tree, templateId, measureHelper)); - // } - - // const template = this.measureDescriptionTemplates.get(templateId); - // this.measureDescriptionContainer.appendChild(template.containerElement); - // this.renderDescription(text, template, true); - - // const descriptionElement = (template).descriptionElement; - // const width = descriptionElement.offsetWidth; - // const height = descriptionElement.offsetHeight; - - // if (this.measureDescriptionContainer.firstChild) { - // this.measureDescriptionContainer.removeChild(this.measureDescriptionContainer.firstChild); - // } - - // return { height, width }; - // } - getTemplateId(tree: ITree, element: SettingsTreeElement): string { if (element instanceof SettingsTreeGroupElement) { @@ -734,21 +713,16 @@ export class SettingsRenderer implements ITreeRenderer { private renderCommonTemplate(tree: ITree, container: HTMLElement, typeClass: string): ISettingItemTemplate { DOM.addClass(container, 'setting-item'); DOM.addClass(container, 'setting-item-' + typeClass); - const titleElement = DOM.append(container, $('.setting-item-title')); const categoryElement = DOM.append(titleElement, $('span.setting-item-category')); const labelElement = DOM.append(titleElement, $('span.setting-item-label')); const isConfiguredElement = DOM.append(titleElement, $('span.setting-item-is-configured-label')); const otherOverridesElement = DOM.append(titleElement, $('span.setting-item-overrides')); - const descriptionContainerElement = DOM.append(container, $('.setting-item-description-container')); - const descriptionElement = DOM.append(descriptionContainerElement, $('.setting-item-description')); + const descriptionElement = DOM.append(container, $('.setting-item-description')); const valueElement = DOM.append(container, $('.setting-item-value')); const controlElement = DOM.append(valueElement, $('div.setting-item-control')); - const expandIndicatorElement = DOM.append(descriptionContainerElement, $('.setting-expand-indicator')); - expandIndicatorElement.textContent = '…'; - const toDispose = []; const template: ISettingItemTemplate = { toDispose, @@ -759,7 +733,6 @@ export class SettingsRenderer implements ITreeRenderer { descriptionElement, controlElement, isConfiguredElement, - expandIndicatorElement, otherOverridesElement }; @@ -776,6 +749,14 @@ export class SettingsRenderer implements ITreeRenderer { return template; } + private addSettingElementFocusHandler(template: ISettingItemTemplate): void { + template.toDispose.push(DOM.addDisposableListener(template.containerElement, 'focus', e => { + if (template.context) { + this._onDidFocusSetting.fire(template.context); + } + }, true)); + } + private renderSettingTextTemplate(tree: ITree, container: HTMLElement, type = 'text'): ISettingTextItemTemplate { const common = this.renderCommonTemplate(tree, container, 'text'); @@ -793,12 +774,15 @@ export class SettingsRenderer implements ITreeRenderer { } })); common.toDispose.push(inputBox); + inputBox.inputElement.classList.add(SettingsRenderer.CONTROL_CLASS); const template: ISettingTextItemTemplate = { ...common, inputBox }; + this.addSettingElementFocusHandler(template); + return template; } @@ -819,12 +803,15 @@ export class SettingsRenderer implements ITreeRenderer { } })); common.toDispose.push(inputBox); + inputBox.inputElement.classList.add(SettingsRenderer.CONTROL_CLASS); const template: ISettingNumberItemTemplate = { ...common, inputBox }; + this.addSettingElementFocusHandler(template); + return template; } @@ -840,10 +827,7 @@ export class SettingsRenderer implements ITreeRenderer { const descriptionAndValueElement = DOM.append(container, $('.setting-item-value-description')); const controlElement = DOM.append(descriptionAndValueElement, $('.setting-item-bool-control')); - const descriptionContainerElement = DOM.append(descriptionAndValueElement, $('.setting-item-description-container')); - const descriptionElement = DOM.append(descriptionContainerElement, $('.setting-item-description')); - const expandIndicatorElement = DOM.append(descriptionContainerElement, $('.setting-expand-indicator')); - expandIndicatorElement.textContent = '…'; + const descriptionElement = DOM.append(descriptionAndValueElement, $('.setting-item-description')); const toDispose = []; const checkbox = new Checkbox({ actionClassName: 'setting-value-checkbox', isChecked: true, title: '', inputActiveOptionBorder: null }); @@ -854,6 +838,7 @@ export class SettingsRenderer implements ITreeRenderer { template.onChange(checkbox.checked); } })); + checkbox.domNode.classList.add(SettingsRenderer.CONTROL_CLASS); const template: ISettingBoolItemTemplate = { toDispose, @@ -864,11 +849,12 @@ export class SettingsRenderer implements ITreeRenderer { controlElement, checkbox, descriptionElement, - expandIndicatorElement, isConfiguredElement, otherOverridesElement }; + this.addSettingElementFocusHandler(template); + // Prevent clicks from being handled by list toDispose.push(DOM.addDisposableListener(controlElement, 'mousedown', (e: IMouseEvent) => e.stopPropagation())); @@ -893,6 +879,10 @@ export class SettingsRenderer implements ITreeRenderer { selectBorder: settingsSelectBorder })); selectBox.render(common.controlElement); + const selectElement = common.controlElement.querySelector('select'); + if (selectElement) { + selectElement.classList.add(SettingsRenderer.CONTROL_CLASS); + } common.toDispose.push( selectBox.onDidSelect(e => { @@ -901,11 +891,16 @@ export class SettingsRenderer implements ITreeRenderer { } })); + const enumDescriptionElement = common.containerElement.insertBefore($('.setting-item-enumDescription'), common.descriptionElement.nextSibling); + const template: ISettingEnumItemTemplate = { ...common, - selectBox + selectBox, + enumDescriptionElement }; + this.addSettingElementFocusHandler(template); + return template; } @@ -913,6 +908,7 @@ export class SettingsRenderer implements ITreeRenderer { const common = this.renderCommonTemplate(tree, container, 'exclude'); const excludeWidget = this.instantiationService.createInstance(ExcludeSettingWidget, common.controlElement); + excludeWidget.domNode.classList.add(SettingsRenderer.CONTROL_CLASS); common.toDispose.push(excludeWidget); const template: ISettingExcludeItemTemplate = { @@ -920,6 +916,8 @@ export class SettingsRenderer implements ITreeRenderer { excludeWidget }; + this.addSettingElementFocusHandler(template); + common.toDispose.push(excludeWidget.onDidChangeExclude(e => { if (template.context) { const newValue = { @@ -979,6 +977,8 @@ export class SettingsRenderer implements ITreeRenderer { button: openSettingsButton }; + this.addSettingElementFocusHandler(template); + return template; } @@ -1003,6 +1003,8 @@ export class SettingsRenderer implements ITreeRenderer { toDispose }; + // this.addSettingElementFocusHandler(template); + return template; } @@ -1029,65 +1031,17 @@ export class SettingsRenderer implements ITreeRenderer { } } - private elementIsSelected(tree: ITree, element: SettingsTreeElement): boolean { - const selection = tree.getSelection(); - const selectedElement: SettingsTreeElement = selection && selection[0]; - return selectedElement && selectedElement.id === element.id; - } - private renderNewExtensionsElement(element: SettingsTreeNewExtensionsElement, template: ISettingNewExtensionsTemplate): void { template.context = element; } - // private isSettingExpandable(tree: ITree, element: SettingsTreeSettingElement): boolean { - // // Shortcuts before measuring - // if (element.valueType === 'enum' && element.setting.enumDescriptions && element.setting.enum && element.setting.enum.length < SettingsRenderer.MAX_ENUM_DESCRIPTIONS) { - // return true; - // } - - // if (element.setting.description.indexOf('\n') >= 0) { - // return true; - // } - - // const height = this.measureSettingDescriptionHeight(tree, element); - // return height > 18; - // } - - // private settingDescriptionFirstLineLength(tree: ITree, element: SettingsTreeSettingElement): number { - // const fullDescription = element.description - // .replace(/\[(.*)\]\(.*\)/, '$1') - // .split('\n')[0]; - - // // Add characters one at a time, measure the width. Start from some safe number. - // // const startPos = Math.min(50, fullDescription.length - 1); - // let size: { height: number, width: number }; - // for (let i = 10; i <= fullDescription.length;) { - // let description = fullDescription.substr(0, i); - // size = this.measureSettingDescription(tree, element, description); - // if (size.height > 20) { - // // It wrapped - // return size.width; - // } - - // const nextBreakMatch = fullDescription.slice(i + 1).match(/([\s.,]|$)/); - // if (nextBreakMatch) { - // i = nextBreakMatch.index + i + 1; - // } else { - // return size.width; - // } - // } - - // return size ? size.width : 0; - // } - private renderSettingElement(tree: ITree, element: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate | ISettingBoolItemTemplate): void { - const isSelected = !!this.elementIsSelected(tree, element); + template.context = element; + const setting = element.setting; - // const isExpandable = this.isSettingExpandable(tree, element); - // DOM.toggleClass(template.containerElement, 'is-expandable', isExpandable); - DOM.toggleClass(template.containerElement, 'is-expanded', isSelected); DOM.toggleClass(template.containerElement, 'is-configured', element.isConfigured); + DOM.toggleClass(template.containerElement, 'is-expanded', true); template.containerElement.id = element.id.replace(/\./g, '_'); const titleTooltip = setting.key; @@ -1097,19 +1051,19 @@ export class SettingsRenderer implements ITreeRenderer { template.labelElement.textContent = element.displayLabel; template.labelElement.title = titleTooltip; - // if (isExpandable) { - // const widthInFirstLine = this.settingDescriptionFirstLineLength(tree, element); - // template.expandIndicatorElement.style.left = (widthInFirstLine + 8) + 'px'; - // } - - const descriptionText = element.description + this.getEnumDescriptionText(element); - this.renderDescription(descriptionText, template, isSelected); - this.renderValue(element, isSelected, templateId, template); + this.renderValue(element, templateId, template); + template.descriptionElement.innerHTML = ''; + if (element.setting.descriptionIsMarkdown) { + const renderedDescription = this.renderDescriptionMarkdown(element.description, template.toDispose); + template.descriptionElement.appendChild(renderedDescription); + } else { + template.descriptionElement.innerText = element.description; + } template.isConfiguredElement.textContent = element.isConfigured ? localize('configured', "Modified") : ''; if (element.overriddenScopeList.length) { - const otherOverridesLabel = element.isConfigured ? + let otherOverridesLabel = element.isConfigured ? localize('alsoConfiguredIn', "Also modified in") : localize('configuredIn', "Modified in"); @@ -1119,89 +1073,76 @@ export class SettingsRenderer implements ITreeRenderer { } } - private getEnumDescriptionText(element: SettingsTreeSettingElement): string { - const setting = element.setting; - let enumDescriptionText = ''; - if (element.valueType === 'enum' && setting.enumDescriptions && setting.enum && setting.enum.length < SettingsRenderer.MAX_ENUM_DESCRIPTIONS) { - enumDescriptionText = '\n' + setting.enumDescriptions - .map((desc, i) => { - const displayEnum = escapeInvisibleChars(setting.enum[i]); - return desc ? - ` - \`${displayEnum}\`: ${desc}` : - ` - \`${setting.enum[i]}\``; - }) - .filter(desc => !!desc) - .join('\n'); - } - - return enumDescriptionText; - } - - private renderDescription(text: string, template: ISettingItemTemplate | ISettingBoolItemTemplate, isSelected: boolean, measuring = false): void { + private renderDescriptionMarkdown(text: string, disposeables: IDisposable[]): HTMLElement { // Rewrite `#editor.fontSize#` to link format - const descriptionText = text - .replace(/`#(.*)#`/g, (match, settingName) => `[\`${settingName}\`](#${settingName})`); + text = fixSettingLinks(text); - const renderedDescription = renderMarkdown({ value: descriptionText }, { - actionHandler: measuring ? - undefined : - { - callback: (content: string) => { - if (startsWith(content, '#')) { - this._onDidClickSettingLink.fire(content.substr(1)); - } else { - this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError); - } - }, - disposeables: template.toDispose - } + const renderedMarkdown = renderMarkdown({ value: text }, { + actionHandler: { + callback: (content: string) => { + if (startsWith(content, '#')) { + this._onDidClickSettingLink.fire(content.substr(1)); + } else { + this.openerService.open(URI.parse(content)).then(void 0, onUnexpectedError); + } + }, + disposeables + } }); - if (!measuring) { - cleanRenderedMarkdown(renderedDescription); - } - renderedDescription.classList.add('setting-item-description-markdown'); - template.descriptionElement.innerHTML = ''; - template.descriptionElement.appendChild(renderedDescription); - - if (!measuring) { - (renderedDescription.querySelectorAll('a')).forEach(aElement => { - aElement.tabIndex = isSelected ? 0 : -1; - }); - } + renderedMarkdown.classList.add('setting-item-description-markdown'); + cleanRenderedMarkdown(renderedMarkdown); + return renderedMarkdown; } - private renderValue(element: SettingsTreeSettingElement, isSelected: boolean, templateId: string, template: ISettingItemTemplate | ISettingBoolItemTemplate): void { + private renderValue(element: SettingsTreeSettingElement, templateId: string, template: ISettingItemTemplate | ISettingBoolItemTemplate): void { const onChange = value => this._onDidChangeSetting.fire({ key: element.setting.key, value }); if (templateId === SETTINGS_ENUM_TEMPLATE_ID) { - this.renderEnum(element, isSelected, template, onChange); + this.renderEnum(element, template, onChange); } else if (templateId === SETTINGS_TEXT_TEMPLATE_ID) { - this.renderText(element, isSelected, template, onChange); + this.renderText(element, template, onChange); } else if (templateId === SETTINGS_NUMBER_TEMPLATE_ID) { - this.renderNumber(element, isSelected, template, onChange); + this.renderNumber(element, template, onChange); } else if (templateId === SETTINGS_BOOL_TEMPLATE_ID) { - this.renderBool(element, isSelected, template, onChange); + this.renderBool(element, template, onChange); } else if (templateId === SETTINGS_EXCLUDE_TEMPLATE_ID) { - this.renderExcludeSetting(element, isSelected, template); + this.renderExcludeSetting(element, template); } else if (templateId === SETTINGS_COMPLEX_TEMPLATE_ID) { - this.renderComplexSetting(element, isSelected, template); + this.renderComplexSetting(element, template); } } - private renderBool(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void { + private renderBool(dataElement: SettingsTreeSettingElement, template: ISettingBoolItemTemplate, onChange: (value: boolean) => void): void { template.onChange = null; template.checkbox.checked = dataElement.value; template.onChange = onChange; - template.checkbox.domNode.tabIndex = isSelected ? 0 : -1; + // Setup and add ARIA attributes + // Create id and label for control/input element - parent is wrapper div + const id = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_'); + const label = ' ' + dataElement.displayCategory + ' ' + dataElement.displayLabel + ' checkbox ' + (dataElement.value ? 'checked ' : 'unchecked ') + template.isConfiguredElement.textContent; + + // We use the parent control div for the aria-labelledby target + // Does not appear you can use the direct label on the element itself within a tree + template.checkbox.domNode.parentElement.setAttribute('id', id); + template.checkbox.domNode.parentElement.setAttribute('aria-label', label); + + // Labels will not be read on descendent input elements of the parent treeitem + // unless defined as role=treeitem and indirect aria-labelledby approach + // TODO: Determine method to normally label input items with value read last + template.checkbox.domNode.setAttribute('id', id + 'item'); + template.checkbox.domNode.setAttribute('role', 'treeitem'); + template.checkbox.domNode.setAttribute('aria-labelledby', id + 'item ' + id); + } - private renderEnum(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void { + private renderEnum(dataElement: SettingsTreeSettingElement, template: ISettingEnumItemTemplate, onChange: (value: string) => void): void { const displayOptions = getDisplayEnumOptions(dataElement.setting); template.selectBox.setOptions(displayOptions); - const label = dataElement.displayCategory + ' ' + dataElement.displayLabel; + const label = ' ' + dataElement.displayCategory + ' ' + dataElement.displayLabel + ' combobox ' + template.isConfiguredElement.textContent; + template.selectBox.setAriaLabel(label); const idx = dataElement.setting.enum.indexOf(dataElement.value); @@ -1210,34 +1151,89 @@ export class SettingsRenderer implements ITreeRenderer { template.onChange = idx => onChange(dataElement.setting.enum[idx]); if (template.controlElement.firstElementChild) { - template.controlElement.firstElementChild.setAttribute('tabindex', isSelected ? '0' : '-1'); + // SelectBox needs to be treeitem to read correctly within tree + template.controlElement.firstElementChild.setAttribute('role', 'treeitem'); } + + template.enumDescriptionElement.innerHTML = ''; + // if (dataElement.setting.enumDescriptions && dataElement.setting.enum && dataElement.setting.enum.length < SettingsRenderer.MAX_ENUM_DESCRIPTIONS) { + // if (isSelected) { + // let enumDescriptionText = '\n' + dataElement.setting.enumDescriptions + // .map((desc, i) => { + // const displayEnum = escapeInvisibleChars(dataElement.setting.enum[i]); + // return desc ? + // ` - \`${displayEnum}\`: ${desc}` : + // ` - \`${dataElement.setting.enum[i]}\``; + // }) + // .filter(desc => !!desc) + // .join('\n'); + + // const renderedMarkdown = this.renderDescriptionMarkdown(fixSettingLinks(enumDescriptionText), template.toDispose); + // template.enumDescriptionElement.appendChild(renderedMarkdown); + // } + + // return { overflows: true }; + // } } - private renderText(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingTextItemTemplate, onChange: (value: string) => void): void { + private renderText(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: string) => void): void { template.onChange = null; template.inputBox.value = dataElement.value; template.onChange = value => onChange(value); - template.inputBox.inputElement.tabIndex = isSelected ? 0 : -1; + + // Setup and add ARIA attributes + // Create id and label for control/input element - parent is wrapper div + const id = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_'); + const label = ' ' + dataElement.displayCategory + ' ' + dataElement.displayLabel + ' ' + template.isConfiguredElement.textContent; + + // We use the parent control div for the aria-labelledby target + // Does not appear you can use the direct label on the element itself within a tree + template.inputBox.inputElement.parentElement.setAttribute('id', id); + template.inputBox.inputElement.parentElement.setAttribute('aria-label', label); + + // Labels will not be read on descendent input elements of the parent treeitem + // unless defined as role=treeitem and indirect aria-labelledby approach + // TODO: Determine method to normally label input items with value read last + template.inputBox.inputElement.setAttribute('id', id + 'item'); + template.inputBox.inputElement.setAttribute('role', 'treeitem'); + template.inputBox.inputElement.setAttribute('aria-labelledby', id + 'item ' + id); + } - private renderNumber(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingTextItemTemplate, onChange: (value: number) => void): void { + + private renderNumber(dataElement: SettingsTreeSettingElement, template: ISettingTextItemTemplate, onChange: (value: number) => void): void { template.onChange = null; template.inputBox.value = dataElement.value; template.onChange = value => onChange(parseFn(value)); - template.inputBox.inputElement.tabIndex = isSelected ? 0 : -1; const parseFn = dataElement.valueType === 'integer' ? parseInt : parseFloat; + + // Setup and add ARIA attributes + // Create id and label for control/input element - parent is wrapper div + const id = (dataElement.displayCategory + '_' + dataElement.displayLabel).replace(/ /g, '_'); + const label = ' ' + dataElement.displayCategory + ' ' + dataElement.displayLabel + ' number ' + template.isConfiguredElement.textContent; + + // We use the parent control div for the aria-labelledby target + // Does not appear you can use the direct label on the element itself within a tree + template.inputBox.inputElement.parentElement.setAttribute('id', id); + template.inputBox.inputElement.parentElement.setAttribute('aria-label', label); + + // Labels will not be read on descendent input elements of the parent treeitem + // unless defined as role=treeitem and indirect aria-labelledby approach + // TODO: Determine method to normally label input items with value read last + template.inputBox.inputElement.setAttribute('id', id + 'item'); + template.inputBox.inputElement.setAttribute('role', 'treeitem'); + template.inputBox.inputElement.setAttribute('aria-labelledby', id + 'item ' + id); + } - private renderExcludeSetting(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingExcludeItemTemplate): void { + private renderExcludeSetting(dataElement: SettingsTreeSettingElement, template: ISettingExcludeItemTemplate): void { const value = getExcludeDisplayValue(dataElement); template.excludeWidget.setValue(value); template.context = dataElement; } - private renderComplexSetting(dataElement: SettingsTreeSettingElement, isSelected: boolean, template: ISettingComplexItemTemplate): void { - template.button.element.tabIndex = isSelected ? 0 : -1; + private renderComplexSetting(dataElement: SettingsTreeSettingElement, template: ISettingComplexItemTemplate): void { template.onChange = () => this._onDidOpenSettings.fire(dataElement.setting.key); } @@ -1259,6 +1255,10 @@ function cleanRenderedMarkdown(element: Node): void { } } +function fixSettingLinks(text: string): string { + return text.replace(/`#([^#]*)#`/g, (match, settingName) => `[\`${settingName}\`](#${settingName})`); +} + function getDisplayEnumOptions(setting: ISetting): string[] { if (setting.enum.length > SettingsRenderer.MAX_ENUM_DESCRIPTIONS && setting.enumDescriptions) { return setting.enum @@ -1270,7 +1270,9 @@ function getDisplayEnumOptions(setting: ISetting): string[] { }); } - return setting.enum.map(escapeInvisibleChars); + return setting.enum + .map(String) + .map(escapeInvisibleChars); } function escapeInvisibleChars(enumValue: string): string { @@ -1431,7 +1433,8 @@ export class SearchResultModel { updateChildren(): void { this.children = this.getFlatSettings() - .map(s => createSettingsTreeSettingElement(s, this, this._viewState.settingsTarget, this._configurationService)); + .map(s => createSettingsTreeSettingElement(s, this, this._viewState.settingsTarget, this._configurationService)) + .filter(el => el.setting.deprecationMessage ? el.isConfigured : true); if (this.newExtensionSearchResults) { const newExtElement = new SettingsTreeNewExtensionsElement(); @@ -1459,17 +1462,31 @@ export class SearchResultModel { } } -class NonExpandableTree extends WorkbenchTree { - expand(): TPromise { +class NonExpandableOrSelectableTree extends Tree { + expand(): TPromise { return TPromise.wrap(null); } - collapse(): TPromise { + collapse(): TPromise { return TPromise.wrap(null); } + + public setFocus(element?: any, eventPayload?: any): void { + return; + } + + public focusNext(count?: number, eventPayload?: any): void { + return; + } + + public focusPrevious(count?: number, eventPayload?: any): void { + return; + } } -export class SettingsTree extends NonExpandableTree { +export class SettingsTree extends NonExpandableOrSelectableTree { + protected disposables: IDisposable[]; + constructor( container: HTMLElement, viewState: ISettingsEditorViewState, @@ -1482,12 +1499,13 @@ export class SettingsTree extends NonExpandableTree { ) { const treeClass = 'settings-editor-tree'; + const controller = instantiationService.createInstance(SettingsTreeController); const fullConfiguration = { dataSource: instantiationService.createInstance(SettingsDataSource, viewState), - controller: instantiationService.createInstance(SettingsTreeController), + controller, accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider), filter: instantiationService.createInstance(SettingsTreeFilter, viewState), - styler: new DefaultTreestyler(DOM.createStyleSheet(), treeClass), + styler: new DefaultTreestyler(DOM.createStyleSheet(container), treeClass), ...configuration }; @@ -1501,27 +1519,18 @@ export class SettingsTree extends NonExpandableTree { super(container, fullConfiguration, - options, - contextKeyService, - listService, - themeService, - instantiationService, - configurationService); + options); + + this.disposables = []; + this.disposables.push(controller); this.disposables.push(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const activeBorderColor = theme.getColor(focusBorder); if (activeBorderColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`); - // TODO@rob - why isn't this applied when added to the stylesheet from tocTree.ts? Seems like a chromium glitch. collector.addRule(`.settings-editor > .settings-body > .settings-toc-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`); } - const inactiveBorderColor = theme.getColor(settingItemInactiveSelectionBorder); - if (inactiveBorderColor) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`); - } - const foregroundColor = theme.getColor(foreground); if (foregroundColor) { // Links appear inside other elements in markdown. CSS opacity acts like a mask. So we have to dynamically compute the description color to avoid @@ -1553,45 +1562,4 @@ export class SettingsTree extends NonExpandableTree { this.style(colors); })); } - - public setFocus(element?: any, eventPayload?: any): void { - if (element instanceof SettingsTreeGroupElement) { - const nav = this.getNavigator(element, false); - do { - element = nav.next(); - } while (element instanceof SettingsTreeGroupElement); - } - - super.setFocus(element, eventPayload); - } - - public focusNext(count?: number, eventPayload?: any): void { - const focus = this.getFocus(); - if (!focus) { - return super.focusFirst(); - } - - const nav = this.getNavigator(focus, false); - let current; - do { - current = nav.next(); - } while (current instanceof SettingsTreeGroupElement); - - this.setFocus(current, eventPayload); - } - - public focusPrevious(count?: number, eventPayload?: any): void { - const focus = this.getFocus(); - if (!focus) { - return super.focusFirst(); - } - - const nav = this.getNavigator(focus, false); - let current; - do { - current = nav.previous(); - } while (current instanceof SettingsTreeGroupElement); - - this.setFocus(current, eventPayload); - } } diff --git a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts b/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts index 706e5bdd9f6..862ec4dde3b 100644 --- a/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts +++ b/src/vs/workbench/parts/preferences/browser/settingsWidgets.ts @@ -15,14 +15,13 @@ import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import 'vs/css!./media/settingsWidgets'; import { localize } from 'vs/nls'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { foreground, inputBackground, inputBorder, inputForeground, listHoverBackground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; +import { foreground, inputBackground, inputBorder, inputForeground, listActiveSelectionBackground, listActiveSelectionForeground, listHoverBackground, listHoverForeground, listInactiveSelectionBackground, listInactiveSelectionForeground, registerColor, selectBackground, selectBorder, selectForeground, textLinkForeground, textPreformatForeground } from 'vs/platform/theme/common/colorRegistry'; import { attachButtonStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; const $ = DOM.$; export const settingsHeaderForeground = registerColor('settings.headerForeground', { light: '#444444', dark: '#e7e7e7', hc: '#ffffff' }, localize('headerForeground', "(For settings editor preview) The foreground color for a section header or active title.")); export const modifiedItemForeground = registerColor('settings.modifiedItemForeground', { light: '#018101', dark: '#73C991', hc: '#73C991' }, localize('modifiedItemForeground', "(For settings editor preview) The foreground color for a the modified setting indicator.")); -export const settingItemInactiveSelectionBorder = registerColor('settings.inactiveSelectedItemBorder', { dark: '#3F3F46', light: '#CCCEDB', hc: null }, localize('settingItemInactiveSelectionBorder', "(For settings editor preview) The color of the selected setting row border, when the settings list does not have focus.")); // Enum control colors export const settingsSelectBackground = registerColor('settings.dropdownBackground', { dark: selectBackground, light: selectBackground, hc: selectBackground }, localize('settingsDropdownBackground', "(For settings editor preview) Settings editor dropdown background.")); @@ -62,8 +61,8 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const link = theme.getColor(textLinkForeground); if (link) { - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description a { color: ${link}; }`); - collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description a > code { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a { color: ${link}; }`); + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown a > code { color: ${link}; }`); } const headerForegroundColor = theme.getColor(settingsHeaderForeground); @@ -76,23 +75,68 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { collector.addRule(`.settings-editor > .settings-header > .settings-header-controls .settings-tabs-widget .action-label { color: ${foregroundColor}; }`); } + // Exclude control const listHoverBackgroundColor = theme.getColor(listHoverBackground); if (listHoverBackgroundColor) { collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover { background-color: ${listHoverBackgroundColor}; }`); } + + const listHoverForegroundColor = theme.getColor(listHoverForeground); + if (listHoverForegroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row:hover { color: ${listHoverForegroundColor}; }`); + } + + const listSelectBackgroundColor = theme.getColor(listActiveSelectionBackground); + if (listSelectBackgroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:focus { background-color: ${listSelectBackgroundColor}; }`); + } + + const listInactiveSelectionBackgroundColor = theme.getColor(listInactiveSelectionBackground); + if (listInactiveSelectionBackgroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:not(:focus) { background-color: ${listInactiveSelectionBackgroundColor}; }`); + } + + const listInactiveSelectionForegroundColor = theme.getColor(listInactiveSelectionForeground); + if (listInactiveSelectionForegroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:not(:focus) { color: ${listInactiveSelectionForegroundColor}; }`); + } + + const listSelectForegroundColor = theme.getColor(listActiveSelectionForeground); + if (listSelectForegroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item.setting-item-exclude .setting-exclude-row.selected:focus { color: ${listSelectForegroundColor}; }`); + } + + const codeTextForegroundColor = theme.getColor(textPreformatForeground); + if (codeTextForegroundColor) { + collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .setting-item .setting-item-description-markdown code { color: ${codeTextForegroundColor} }`); + } }); export class ExcludeSettingListModel { private _dataItems: IExcludeDataItem[] = []; - private _editKey: string; + private _editKey: string | null; + private _selectedIdx: number | null; get items(): IExcludeViewItem[] { - return this._dataItems.map(item => { + const items = this._dataItems.map((item, i) => { + const editing = item.pattern === this._editKey; return { ...item, - editing: item.pattern === this._editKey + editing, + selected: i === this._selectedIdx || editing }; }); + + if (this._editKey === '') { + items.push({ + editing: true, + selected: true, + pattern: '', + sibling: '' + }); + } + + return items; } setEditKey(key: string): void { @@ -102,6 +146,30 @@ export class ExcludeSettingListModel { setValue(excludeData: IExcludeDataItem[]): void { this._dataItems = excludeData; } + + select(idx: number): void { + this._selectedIdx = idx; + } + + getSelected(): number { + return this._selectedIdx; + } + + selectNext(): void { + if (typeof this._selectedIdx === 'number') { + this._selectedIdx = Math.min(this._selectedIdx + 1, this._dataItems.length - 1); + } else { + this._selectedIdx = 0; + } + } + + selectPrevious(): void { + if (typeof this._selectedIdx === 'number') { + this._selectedIdx = Math.max(this._selectedIdx - 1, 0); + } else { + this._selectedIdx = 0; + } + } } interface IExcludeChangeEvent { @@ -113,37 +181,87 @@ interface IExcludeChangeEvent { export class ExcludeSettingWidget extends Disposable { private listElement: HTMLElement; private listDisposables: IDisposable[] = []; - private patternInput: InputBox; private model = new ExcludeSettingListModel(); private readonly _onDidChangeExclude: Emitter = new Emitter(); public readonly onDidChangeExclude: Event = this._onDidChangeExclude.event; + get domNode(): HTMLElement { + return this.listElement; + } + constructor( - container: HTMLElement, + private container: HTMLElement, @IThemeService private themeService: IThemeService, @IContextViewService private contextViewService: IContextViewService ) { super(); this.listElement = DOM.append(container, $('.setting-exclude-widget')); - DOM.append(container, this.renderAddItem()); - this.update(); + this.listElement.setAttribute('tabindex', '0'); + DOM.append(container, this.renderAddButton()); + this.renderList(); + + this._register(DOM.addDisposableListener(this.listElement, 'click', (e: MouseEvent) => { + if (!e.target) { + return; + } + + const element = DOM.findParentWithClass((e.target), 'setting-exclude-row'); + if (!element) { + return; + } + + const targetIdxStr = element.getAttribute('data-index'); + if (!targetIdxStr) { + return; + } + + const targetIdx = parseInt(targetIdxStr); + + if (this.model.getSelected() === targetIdx) { + return; + } + + this.model.select(targetIdx); + this.renderList(); + e.preventDefault(); + e.stopPropagation(); + })); + + + this._register(DOM.addStandardDisposableListener(this.listElement, 'keydown', (e: KeyboardEvent) => { + if (e.keyCode === KeyCode.UpArrow) { + this.model.selectPrevious(); + this.renderList(); + e.preventDefault(); + e.stopPropagation(); + } else if (e.keyCode === KeyCode.DownArrow) { + this.model.selectNext(); + this.renderList(); + e.preventDefault(); + e.stopPropagation(); + } + })); } setValue(excludeData: IExcludeDataItem[]): void { this.model.setValue(excludeData); - this.patternInput.value = ''; - this.update(); + this.renderList(); } - private update(): void { + private renderList(): void { + const focused = DOM.isAncestor(document.activeElement, this.listElement); + DOM.clearNode(this.listElement); this.listDisposables = dispose(this.listDisposables); + const newMode = this.model.items.some(item => item.editing && !item.pattern); + DOM.toggleClass(this.container, 'setting-exclude-new-mode', newMode); + this.model.items - .map(item => this.renderItem(item)) + .map((item, i) => this.renderItem(item, i, focused)) .forEach(itemElement => this.listElement.appendChild(itemElement)); const listHeight = 22 * this.model.items.length; @@ -168,19 +286,23 @@ export class ExcludeSettingWidget extends Disposable { tooltip: localize('editExcludeItem', "Edit Exclude Item"), run: () => { this.model.setEditKey(key); - this.update(); + this.renderList(); } }; } - private renderItem(item: IExcludeViewItem): HTMLElement { + private renderItem(item: IExcludeViewItem, idx: number, listFocused: boolean): HTMLElement { return item.editing ? this.renderEditItem(item) : - this.renderDataItem(item); + this.renderDataItem(item, idx, listFocused); } - private renderDataItem(item: IExcludeDataItem): HTMLElement { + private renderDataItem(item: IExcludeViewItem, idx: number, listFocused: boolean): HTMLElement { const rowElement = $('.setting-exclude-row'); + rowElement.setAttribute('data-index', idx + ''); + rowElement.setAttribute('tabindex', item.selected ? '0' : '-1'); + DOM.toggleClass(rowElement, 'selected', item.selected); + const actionBar = new ActionBar(rowElement); this.listDisposables.push(actionBar); @@ -198,40 +320,29 @@ export class ExcludeSettingWidget extends Disposable { localize('excludeSiblingHintLabel', "Exclude files matching `{0}`, only when a file matching `{1}` is present", item.pattern, item.sibling) : localize('excludePatternHintLabel', "Exclude files matching `{0}`", item.pattern); + if (item.selected) { + if (listFocused) { + setTimeout(() => { + rowElement.focus(); + }, 10); + } + } + return rowElement; } - private renderAddItem(): HTMLElement { + private renderAddButton(): HTMLElement { const rowElement = $('.setting-exclude-new-row'); - this.patternInput = new InputBox(rowElement, this.contextViewService, { - placeholder: localize('excludePatternInputPlaceholder', "Exclude Pattern...") - }); - this.patternInput.element.classList.add('setting-exclude-newPatternInput'); - this._register(attachInputBoxStyler(this.patternInput, this.themeService, { - inputBackground: settingsTextInputBackground, - inputForeground: settingsTextInputForeground, - inputBorder: settingsTextInputBorder + + const startAddButton = this._register(new Button(rowElement)); + startAddButton.label = localize('addPattern', "Add Pattern"); + startAddButton.element.classList.add('setting-exclude-addButton'); + this._register(attachButtonStyler(startAddButton, this.themeService)); + + this._register(startAddButton.onDidClick(() => { + this.model.setEditKey(''); + this.renderList(); })); - this._register(this.patternInput); - - const addPatternButton = this._register(new Button(rowElement)); - addPatternButton.label = localize('addPattern', "Add Pattern"); - addPatternButton.element.classList.add('setting-exclude-addButton'); - this._register(attachButtonStyler(addPatternButton, this.themeService)); - - const addItem = () => this._onDidChangeExclude.fire({ - originalPattern: undefined, - pattern: this.patternInput.value - }); - - this._register(addPatternButton.onDidClick(addItem)); - - const onKeydown = (e: StandardKeyboardEvent) => { - if (e.equals(KeyCode.Enter)) { - addItem(); - } - }; - this._register(DOM.addStandardDisposableListener(this.patternInput.inputElement, DOM.EventType.KEY_DOWN, onKeydown)); return rowElement; } @@ -241,20 +352,24 @@ export class ExcludeSettingWidget extends Disposable { const onSubmit = edited => { this.model.setEditKey(null); - if (edited) { + const pattern = patternInput.value.trim(); + if (edited && pattern) { this._onDidChangeExclude.fire({ originalPattern: item.pattern, - pattern: patternInput.value, - sibling: siblingInput && siblingInput.value + pattern, + sibling: siblingInput && siblingInput.value.trim() }); } else { - this.update(); + this.renderList(); } }; const onKeydown = (e: StandardKeyboardEvent) => { if (e.equals(KeyCode.Enter)) { onSubmit(true); + } else if (e.equals(KeyCode.Escape)) { + onSubmit(false); + e.preventDefault(); } }; @@ -320,4 +435,5 @@ export interface IExcludeDataItem { interface IExcludeViewItem extends IExcludeDataItem { editing?: boolean; + selected?: boolean; } diff --git a/src/vs/workbench/parts/preferences/browser/tocTree.ts b/src/vs/workbench/parts/preferences/browser/tocTree.ts index d81dc3bcac6..1b71bcdb72b 100644 --- a/src/vs/workbench/parts/preferences/browser/tocTree.ts +++ b/src/vs/workbench/parts/preferences/browser/tocTree.ts @@ -11,7 +11,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListService, WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService'; -import { editorBackground, focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, focusBorder } from 'vs/platform/theme/common/colorRegistry'; import { attachStyler } from 'vs/platform/theme/common/styler'; import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { ISettingsEditorViewState, SearchResultModel, SettingsAccessibilityProvider, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeSettingElement } from 'vs/workbench/parts/preferences/browser/settingsTree'; @@ -91,7 +91,7 @@ export class TOCDataSource implements IDataSource { (element instanceof SettingsTreeGroupElement && element.children && element.children.every(child => child instanceof SettingsTreeGroupElement)); } - getChildren(tree: ITree, element: TOCTreeElement): TPromise { + getChildren(tree: ITree, element: TOCTreeElement): TPromise { return TPromise.as(this._getChildren(element)); } @@ -107,7 +107,7 @@ export class TOCDataSource implements IDataSource { return element.children; } - getParent(tree: ITree, element: TOCTreeElement): TPromise { + getParent(tree: ITree, element: TOCTreeElement): TPromise { return TPromise.wrap(element instanceof SettingsTreeGroupElement && element.parent); } } @@ -115,7 +115,8 @@ export class TOCDataSource implements IDataSource { const TOC_ENTRY_TEMPLATE_ID = 'settings.toc.entry'; interface ITOCEntryTemplate { - element: HTMLElement; + labelElement: HTMLElement; + countElement: HTMLElement; } export class TOCRenderer implements IRenderer { @@ -129,17 +130,23 @@ export class TOCRenderer implements IRenderer { renderTemplate(tree: ITree, templateId: string, container: HTMLElement): ITOCEntryTemplate { return { - element: DOM.append(container, $('.settings-toc-entry')) + labelElement: DOM.append(container, $('.settings-toc-entry')), + countElement: DOM.append(container, $('.settings-toc-count')) }; } renderElement(tree: ITree, element: SettingsTreeGroupElement, templateId: string, template: ITOCEntryTemplate): void { - const label = (element).count ? - `${element.label} (${(element).count})` : - element.label; + const count = (element).count; + const label = element.label; - DOM.toggleClass(template.element, 'no-results', (element).count === 0); - template.element.textContent = label; + DOM.toggleClass(template.labelElement, 'no-results', count === 0); + template.labelElement.textContent = label; + + if (count) { + template.countElement.textContent = ` (${count})`; + } else { + template.countElement.textContent = ''; + } } disposeTemplate(tree: ITree, templateId: string, templateData: any): void { @@ -162,7 +169,7 @@ export class TOCTree extends WorkbenchTree { const fullConfiguration = { controller: instantiationService.createInstance(WorkbenchTreeController, { openMode: OpenMode.DOUBLE_CLICK }), filter: instantiationService.createInstance(SettingsTreeFilter, viewState), - styler: new DefaultTreestyler(DOM.createStyleSheet(), treeClass), + styler: new DefaultTreestyler(DOM.createStyleSheet(container), treeClass), dataSource: instantiationService.createInstance(TOCDataSource), accessibilityProvider: instantiationService.createInstance(SettingsAccessibilityProvider), @@ -199,7 +206,7 @@ export class TOCTree extends WorkbenchTree { listFocusAndSelectionForeground: settingsHeaderForeground, listFocusBackground: editorBackground, listFocusForeground: settingsHeaderForeground, - listHoverForeground: foreground, + listHoverForeground: settingsHeaderForeground, listHoverBackground: editorBackground, listInactiveSelectionBackground: editorBackground, listInactiveSelectionForeground: settingsHeaderForeground, diff --git a/src/vs/workbench/parts/preferences/common/preferences.ts b/src/vs/workbench/parts/preferences/common/preferences.ts index efbbd723b00..f77fc18a455 100644 --- a/src/vs/workbench/parts/preferences/common/preferences.ts +++ b/src/vs/workbench/parts/preferences/common/preferences.ts @@ -61,8 +61,6 @@ export interface IKeybindingsEditor extends IEditor { export const CONTEXT_SETTINGS_EDITOR = new RawContextKey('inSettingsEditor', false); export const CONTEXT_SETTINGS_SEARCH_FOCUS = new RawContextKey('inSettingsSearch', false); -export const CONTEXT_SETTINGS_FIRST_ROW_FOCUS = new RawContextKey('firstSettingRowFocused', false); -export const CONTEXT_SETTINGS_ROW_FOCUS = new RawContextKey('settingRowFocused', false); export const CONTEXT_TOC_ROW_FOCUS = new RawContextKey('settingsTocRowFocus', false); export const CONTEXT_KEYBINDINGS_EDITOR = new RawContextKey('inKeybindings', false); export const CONTEXT_KEYBINDINGS_SEARCH_FOCUS = new RawContextKey('inKeybindingsSearch', false); @@ -74,7 +72,6 @@ export const SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING = 'settings.action.focus export const SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING = 'settings.action.focusPreviousSetting'; export const SETTINGS_EDITOR_COMMAND_FOCUS_FILE = 'settings.action.focusSettingsFile'; export const SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING = 'settings.action.editFocusedSetting'; -export const SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS = 'settings.action.focusSearchFromSettings'; export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH = 'settings.action.focusSettingsFromSearch'; export const SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST = 'settings.action.focusSettingsList'; diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts index c8ab50f2ba7..12f455b6224 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -22,7 +22,7 @@ import { KeybindingsEditor } from 'vs/workbench/parts/preferences/browser/keybin import { OpenDefaultKeybindingsFileAction, OpenRawDefaultSettingsAction, OpenSettingsAction, OpenGlobalSettingsAction, OpenGlobalKeybindingsFileAction, OpenWorkspaceSettingsAction, OpenFolderSettingsAction, ConfigureLanguageBasedSettingsAction, OPEN_FOLDER_SETTINGS_COMMAND, OpenGlobalKeybindingsAction, OpenSettings2Action } from 'vs/workbench/parts/preferences/browser/preferencesActions'; import { IKeybindingsEditor, IPreferencesSearchService, CONTEXT_KEYBINDING_FOCUS, CONTEXT_KEYBINDINGS_EDITOR, CONTEXT_KEYBINDINGS_SEARCH_FOCUS, KEYBINDINGS_EDITOR_COMMAND_DEFINE, KEYBINDINGS_EDITOR_COMMAND_REMOVE, KEYBINDINGS_EDITOR_COMMAND_SEARCH, - KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_TOC_ROW_FOCUS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST + KEYBINDINGS_EDITOR_COMMAND_COPY, KEYBINDINGS_EDITOR_COMMAND_RESET, KEYBINDINGS_EDITOR_COMMAND_COPY_COMMAND, KEYBINDINGS_EDITOR_COMMAND_SHOW_SIMILAR, KEYBINDINGS_EDITOR_COMMAND_FOCUS_KEYBINDINGS, KEYBINDINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_SEARCH, CONTEXT_SETTINGS_EDITOR, SETTINGS_EDITOR_COMMAND_FOCUS_FILE, CONTEXT_SETTINGS_SEARCH_FOCUS, SETTINGS_EDITOR_COMMAND_CLEAR_SEARCH_RESULTS, SETTINGS_EDITOR_COMMAND_FOCUS_NEXT_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_PREVIOUS_SETTING, SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_FROM_SEARCH, CONTEXT_TOC_ROW_FOCUS, SETTINGS_EDITOR_COMMAND_FOCUS_SETTINGS_LIST } from 'vs/workbench/parts/preferences/common/preferences'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; @@ -191,8 +191,8 @@ Registry.as(EditorInputExtensions.EditorInputFactor const category = nls.localize('preferences', "Preferences"); const registry = Registry.as(Extensions.WorkbenchActions); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenRawDefaultSettingsAction, OpenRawDefaultSettingsAction.ID, OpenRawDefaultSettingsAction.LABEL), 'Preferences: Open Raw Default Settings', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsAction, OpenSettingsAction.ID, OpenSettingsAction.LABEL), 'Preferences: Open Settings', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_COMMA }), 'Preferences: Open Settings (Preview)', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettingsAction, OpenSettingsAction.ID, OpenSettingsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_COMMA }), 'Preferences: Open Settings', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(OpenSettings2Action, OpenSettings2Action.ID, OpenSettings2Action.LABEL), 'Preferences: Open Settings (Preview)', category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), 'Preferences: Open User Settings', category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenGlobalKeybindingsAction, OpenGlobalKeybindingsAction.ID, OpenGlobalKeybindingsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_S) }), 'Preferences: Open Keyboard Shortcuts', category); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDefaultKeybindingsFileAction, OpenDefaultKeybindingsFileAction.ID, OpenDefaultKeybindingsFileAction.LABEL), 'Preferences: Open Default Keyboard Shortcuts File', category); @@ -355,23 +355,6 @@ const startSearchCommand = new StartSearchDefaultSettingsCommand({ }); startSearchCommand.register(); -class FocusSearchFromSettingsCommand extends SettingsCommand { - - public runCommand(accessor: ServicesAccessor, args: any): void { - const preferencesEditor = this.getPreferencesEditor(accessor); - if (preferencesEditor) { - preferencesEditor.focusSearch(); - } - } -} -const focusSearchFromSettingsCommand = new FocusSearchFromSettingsCommand({ - id: SETTINGS_EDITOR_COMMAND_FOCUS_SEARCH_FROM_SETTINGS, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS), - kbOpts: { primary: KeyCode.UpArrow, weight: KeybindingWeight.WorkbenchContrib } -}); -focusSearchFromSettingsCommand.register(); - - class ClearSearchResultsCommand extends SettingsCommand { public runCommand(accessor: ServicesAccessor, args: any): void { @@ -461,23 +444,6 @@ const editFocusedSettingCommand = new EditFocusedSettingCommand({ }); editFocusedSettingCommand.register(); -class EditFocusedSettingCommand2 extends SettingsCommand { - - public runCommand(accessor: ServicesAccessor, args: any): void { - const preferencesEditor = this.getPreferencesEditor(accessor); - if (preferencesEditor instanceof SettingsEditor2) { - preferencesEditor.editSelectedSetting(); - } - } -} - -const editFocusedSettingCommand2 = new EditFocusedSettingCommand2({ - id: SETTINGS_EDITOR_COMMAND_EDIT_FOCUSED_SETTING, - precondition: ContextKeyExpr.and(CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_ROW_FOCUS), - kbOpts: { primary: KeyCode.Enter, weight: KeybindingWeight.WorkbenchContrib } -}); -editFocusedSettingCommand2.register(); - class FocusSettingsListCommand extends SettingsCommand { public runCommand(accessor: ServicesAccessor, args: any): void { @@ -500,7 +466,7 @@ focusSettingsListCommand.register(); MenuRegistry.appendMenuItem(MenuId.MenubarPreferencesMenu, { group: '1_settings', command: { - id: OpenSettings2Action.ID, + id: OpenSettingsAction.ID, title: nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&Settings") }, order: 1 diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts index ac1fc3c1c1b..1fffc1a2884 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts @@ -405,6 +405,7 @@ function escapeSpecialChars(query: string): string { function remoteSettingToISetting(remoteSetting: IRemoteSetting): IExtensionSetting { return { description: remoteSetting.description.split('\n'), + descriptionIsMarkdown: false, descriptionRanges: null, key: remoteSetting.key, keyRange: null, diff --git a/src/vs/workbench/parts/preferences/test/browser/settingsTree.test.ts b/src/vs/workbench/parts/preferences/test/browser/settingsTree.test.ts index a1d32f71ac6..06517a42795 100644 --- a/src/vs/workbench/parts/preferences/test/browser/settingsTree.test.ts +++ b/src/vs/workbench/parts/preferences/test/browser/settingsTree.test.ts @@ -47,6 +47,13 @@ suite('SettingsTree', () => { label: 'Bar' }); + assert.deepEqual( + settingKeyToDisplayFormat('disableligatures.ligatures', 'disableligatures'), + { + category: '', + label: 'Ligatures' + }); + assert.deepEqual( settingKeyToDisplayFormat('foo.bar.etc', 'foo'), { @@ -62,14 +69,14 @@ suite('SettingsTree', () => { }); assert.deepEqual( - settingKeyToDisplayFormat('foo.bar.etc', 'foo.bar'), + settingKeyToDisplayFormat('foo.bar.etc', 'foo/bar'), { category: '', label: 'Etc' }); assert.deepEqual( - settingKeyToDisplayFormat('foo.bar.etc', 'something.foo'), + settingKeyToDisplayFormat('foo.bar.etc', 'something/foo'), { category: 'Bar', label: 'Etc' diff --git a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts index 1ce6dc74391..638f3f36c58 100644 --- a/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts +++ b/src/vs/workbench/parts/quickopen/browser/quickopen.contribution.ts @@ -165,3 +165,23 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { }, order: 2 }); + +// Go to menu + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'workbench.action.gotoSymbol', + title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...") + }, + order: 2 +}); + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'workbench.action.gotoLine', + title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line...") + }, + order: 7 +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts index 6a37c940969..453a5aa6da3 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -71,7 +71,7 @@ Registry.as(ConfigurationExtensions.Configuration).regis properties: { 'scm.alwaysShowProviders': { type: 'boolean', - description: localize('alwaysShowProviders', "Whether to always show the Source Control Provider section."), + description: localize('alwaysShowProviders', "Controls whether to always show the Source Control Provider section."), default: false }, 'scm.diffDecorations': { diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 102c393dddb..4a9da7a25f7 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -1321,6 +1321,11 @@ export class SCMViewlet extends PanelViewlet implements IViewModel, IViewsViewle this.updateTitleArea(); } + + if (this.isVisible()) { + panelsToRemove.forEach(p => p.repository.setSelected(false)); + newRepositoryPanels.forEach(p => p.repository.setSelected(true)); + } } private getContributableViewsSize(): number { diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index 8f3f69372cd..4a27ccf12d5 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -263,7 +263,7 @@ export class SearchRenderer extends Disposable implements IRenderer { private renderFileMatch(tree: ITree, fileMatch: FileMatch, templateData: IFileMatchTemplate): void { templateData.el.setAttribute('data-resource', fileMatch.resource().toString()); - templateData.label.setFile(fileMatch.resource()); + templateData.label.setFile(fileMatch.resource(), { hideIcon: false }); let count = fileMatch.count(); templateData.badge.setCount(count); templateData.badge.setTitleFormat(count > 1 ? nls.localize('searchMatches', "{0} matches found", count) : nls.localize('searchMatch', "{0} match found", count)); diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 85bd5d5d603..fe81d90a0f5 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -1069,7 +1069,6 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { isRegExp: isRegex, isCaseSensitive: isCaseSensitive, isWordMatch: isWholeWords, - wordSeparators: this.configurationService.getValue().editor.wordSeparators, isSmartCase: this.configurationService.getValue().search.smartCase }; diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 525753bcae8..08bea8ac1cd 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -11,6 +11,7 @@ import * as collections from 'vs/base/common/collections'; import * as strings from 'vs/base/common/strings'; import * as glob from 'vs/base/common/glob'; import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; import uri from 'vs/base/common/uri'; import { untildify } from 'vs/base/common/labels'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -73,6 +74,8 @@ export class QueryBuilder { if (contentPattern) { this.resolveSmartCaseToCaseSensitive(contentPattern); + + contentPattern.wordSeparators = this.configurationService.getValue().editor.wordSeparators; } const query: ISearchQuery = { @@ -88,7 +91,7 @@ export class QueryBuilder { maxResults: options.maxResults, sortByScore: options.sortByScore, cacheKey: options.cacheKey, - contentPattern: contentPattern, + contentPattern, useRipgrep, disregardIgnoreFiles: options.disregardIgnoreFiles || !useIgnoreFiles, disregardExcludeSettings: options.disregardExcludeSettings, @@ -272,18 +275,18 @@ export class QueryBuilder { if (this.workspaceContextService.getWorkbenchState() === WorkbenchState.FOLDER) { // TODO: @Sandy Try checking workspace folders length instead. const workspaceUri = this.workspaceContextService.getWorkspace().folders[0].uri; - return [workspaceUri.with({ path: paths.normalize(paths.join(workspaceUri.path, searchPath)) })]; + return [resources.joinPath(workspaceUri, searchPath)]; } else if (searchPath === './') { return []; // ./ or ./**/foo makes sense for single-folder but not multi-folder workspaces } else { const relativeSearchPathMatch = searchPath.match(/\.[\/\\]([^\/\\]+)([\/\\].+)?/); if (relativeSearchPathMatch) { const searchPathRoot = relativeSearchPathMatch[1]; - const matchingRoots = this.workspaceContextService.getWorkspace().folders.filter(folder => paths.basename(folder.uri.fsPath) === searchPathRoot || folder.name === searchPathRoot); + const matchingRoots = this.workspaceContextService.getWorkspace().folders.filter(folder => resources.basename(folder.uri) === searchPathRoot || folder.name === searchPathRoot); if (matchingRoots.length) { return matchingRoots.map(root => { return relativeSearchPathMatch[2] ? - root.uri.with({ path: paths.normalize(paths.join(root.uri.path, relativeSearchPathMatch[2])) }) : + resources.joinPath(root.uri, relativeSearchPathMatch[2]) : root.uri; }); } else { diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 03f9e4057c9..407f714fbf4 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -560,7 +560,7 @@ configurationRegistry.registerConfiguration({ properties: { 'search.exclude': { type: 'object', - description: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), + markdownDescription: nls.localize('exclude', "Configure glob patterns for excluding files and folders in searches. Inherits all glob patterns from the `#files.exclude#` setting. Read more about glob patterns [here](https://code.visualstudio.com/docs/editor/codebasics#_advanced-search-options)."), default: { '**/node_modules': true, '**/bower_components': true }, additionalProperties: { anyOf: [ @@ -585,12 +585,12 @@ configurationRegistry.registerConfiguration({ }, 'search.useRipgrep': { type: 'boolean', - description: nls.localize('useRipgrep', "Controls whether to use ripgrep in text and file search"), + description: nls.localize('useRipgrep', "Controls whether to use ripgrep in text and file search."), default: true }, 'search.useIgnoreFiles': { type: 'boolean', - description: nls.localize('useIgnoreFiles', "Controls whether to use .gitignore and .ignore files when searching for files."), + markdownDescription: nls.localize('useIgnoreFiles', "Controls whether to use `.gitignore` and `.ignore` files when searching for files."), default: true, scope: ConfigurationScope.RESOURCE }, @@ -612,7 +612,7 @@ configurationRegistry.registerConfiguration({ 'search.globalFindClipboard': { type: 'boolean', default: false, - description: nls.localize('search.globalFindClipboard', "Controls if the search view should read or modify the shared find clipboard on macOS"), + description: nls.localize('search.globalFindClipboard', "Controls whether the search view should read or modify the shared find clipboard on macOS."), included: platform.isMacintosh }, 'search.location': { @@ -642,3 +642,14 @@ MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, { }, order: 2 }); + +// Go to menu + +MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, { + group: 'z_go_to', + command: { + id: 'workbench.action.showAllSymbols', + title: nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace...") + }, + order: 3 +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts index c3e961993fc..4ac216e7e23 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -17,6 +17,7 @@ import { IWorkspaceContextService, toWorkspaceFolders, Workspace } from 'vs/plat import { ISearchPathsResult, QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder'; import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; +const DEFAULT_EDITOR_CONFIG = {}; const DEFAULT_USER_CONFIG = { useRipgrep: true, useIgnoreFiles: true }; const DEFAULT_QUERY_PROPS = { useRipgrep: true, disregardIgnoreFiles: false }; @@ -36,6 +37,7 @@ suite('QueryBuilder', () => { mockConfigService = new TestConfigurationService(); mockConfigService.setUserConfiguration('search', DEFAULT_USER_CONFIG); + mockConfigService.setUserConfiguration('editor', DEFAULT_EDITOR_CONFIG); instantiationService.stub(IConfigurationService, mockConfigService); mockContextService = new TestContextService(); diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts index e624664bbe6..699d144f02b 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts @@ -33,7 +33,7 @@ export class Snippet { readonly isFromExtension?: boolean, ) { // - this.prefixLow = prefix.toLowerCase(); + this.prefixLow = prefix ? prefix.toLowerCase() : prefix; } get codeSnippet(): string { @@ -195,7 +195,7 @@ export class SnippetFile { load(): Promise { if (!this._loadPromise) { - this._loadPromise = Promise.resolve(this._fileService.resolveContent(this.location)).then(content => { + this._loadPromise = Promise.resolve(this._fileService.resolveContent(this.location, { encoding: 'utf8' })).then(content => { const data = jsonParse(content.value.toString()); if (typeof data === 'object') { forEach(data, entry => { diff --git a/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts b/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts index 849d454f33d..4632aaf6efb 100644 --- a/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts +++ b/src/vs/workbench/parts/splash/electron-browser/partsSplash.contribution.ts @@ -16,6 +16,7 @@ import { IPartService, Parts, Position } from 'vs/workbench/services/part/common import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { debounceEvent } from 'vs/base/common/event'; import { DEFAULT_EDITOR_MIN_DIMENSIONS } from 'vs/workbench/browser/parts/editor/editor'; +import { ColorIdentifier } from 'vs/platform/theme/common/colorRegistry'; class PartsSplash { @@ -38,13 +39,12 @@ class PartsSplash { } private _savePartsSplash() { - const theme = this._themeService.getTheme(); const colorInfo = { - titleBarBackground: theme.getColor(themes.TITLE_BAR_ACTIVE_BACKGROUND).toString(), - activityBarBackground: theme.getColor(themes.ACTIVITY_BAR_BACKGROUND).toString(), - sideBarBackground: theme.getColor(themes.SIDE_BAR_BACKGROUND).toString(), - statusBarBackground: theme.getColor(themes.STATUS_BAR_BACKGROUND).toString(), - statusBarNoFolderBackground: theme.getColor(themes.STATUS_BAR_NO_FOLDER_BACKGROUND).toString(), + titleBarBackground: this._getThemeColor(themes.TITLE_BAR_ACTIVE_BACKGROUND), + activityBarBackground: this._getThemeColor(themes.ACTIVITY_BAR_BACKGROUND), + sideBarBackground: this._getThemeColor(themes.SIDE_BAR_BACKGROUND), + statusBarBackground: this._getThemeColor(themes.STATUS_BAR_BACKGROUND), + statusBarNoFolderBackground: this._getThemeColor(themes.STATUS_BAR_NO_FOLDER_BACKGROUND), }; const layoutInfo = { sideBarSide: this._partService.getSideBarPosition() === Position.RIGHT ? 'right' : 'left', @@ -57,6 +57,12 @@ class PartsSplash { this._storageService.store('parts-splash-data', JSON.stringify({ id: PartsSplash._splashElementId, colorInfo, layoutInfo }), StorageScope.GLOBAL); } + private _getThemeColor(id: ColorIdentifier): string { + const theme = this._themeService.getTheme(); + const color = theme.getColor(id); + return color ? color.toString() : undefined; + } + private _removePartsSplash(): void { let element = document.getElementById(PartsSplash._splashElementId); if (element) { diff --git a/src/vs/workbench/parts/tasks/common/taskTemplates.ts b/src/vs/workbench/parts/tasks/common/taskTemplates.ts index e19cfd42648..77998b88ac6 100644 --- a/src/vs/workbench/parts/tasks/common/taskTemplates.ts +++ b/src/vs/workbench/parts/tasks/common/taskTemplates.ts @@ -6,9 +6,9 @@ import * as nls from 'vs/nls'; -import { IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -export interface TaskEntry extends IPickOpenEntry { +export interface TaskEntry extends IQuickPickItem { sort?: string; autoDetect: boolean; content: string; diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index a4e284cf727..9f8b91f0d45 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -90,6 +90,7 @@ import { QuickOpenActionContributor } from '../browser/quickOpen'; import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; let tasksCategory = nls.localize('tasksCategory', "Tasks"); @@ -475,6 +476,7 @@ class TaskService implements ITaskService { @IModelService private modelService: IModelService, @IExtensionService private extensionService: IExtensionService, @IQuickOpenService private quickOpenService: IQuickOpenService, + @IQuickInputService private quickInputService: IQuickInputService, @IConfigurationResolverService private configurationResolverService: IConfigurationResolverService, @ITerminalService private terminalService: ITerminalService, @IStorageService private storageService: IStorageService, @@ -1073,7 +1075,7 @@ class TaskService implements ITaskService { }); } - private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): TPromise { + private writeConfiguration(workspaceFolder: IWorkspaceFolder, key: string, value: any): TPromise { if (this.contextService.getWorkbenchState() === WorkbenchState.FOLDER) { return this.configurationService.updateValue(key, value, { resource: workspaceFolder.uri }, ConfigurationTarget.WORKSPACE); } else if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) { @@ -2205,7 +2207,7 @@ class TaskService implements ITaskService { if (stat) { return stat.resource; } - return this.quickOpenService.pick(getTaskTemplates(), { placeHolder: nls.localize('TaskService.template', 'Select a Task Template') }).then((selection) => { + return this.quickInputService.pick(getTaskTemplates(), { placeHolder: nls.localize('TaskService.template', 'Select a Task Template') }).then((selection) => { if (!selection) { return undefined; } diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 245a8ac3a6e..b7a2c8e5915 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -97,8 +97,6 @@ export interface ITerminalConfiguration { windows: { [key: string]: string }; }; showExitAlert: boolean; - experimentalRestore: boolean; - experimentalTextureCachingStrategy: 'static' | 'dynamic'; } export interface ITerminalConfigHelper { diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 2c28f173a59..c1650928b9d 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -6,14 +6,12 @@ import * as errors from 'vs/base/common/errors'; import { Event, Emitter } from 'vs/base/common/event'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; 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 } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; - -const TERMINAL_STATE_STORAGE_KEY = 'terminal.state'; +import { IStorageService } from 'vs/platform/storage/common/storage'; export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -70,8 +68,6 @@ export abstract class TerminalService implements ITerminalService { this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); this.onTabDisposed(tab => this._removeTab(tab)); - lifecycleService.when(LifecyclePhase.Restoring).then(() => this._restoreTabs()); - this._handleContextKeys(); } @@ -94,29 +90,6 @@ export abstract class TerminalService implements ITerminalService { public abstract setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void; public abstract requestExtHostProcess(proxy: ITerminalProcessExtHostProxy, shellLaunchConfig: IShellLaunchConfig, cols: number, rows: number): void; - private _restoreTabs(): void { - if (!this.configHelper.config.experimentalRestore) { - return; - } - - const tabConfigsJson = this._storageService.get(TERMINAL_STATE_STORAGE_KEY, StorageScope.WORKSPACE); - if (!tabConfigsJson) { - return; - } - - const tabConfigs = <{ instances: IShellLaunchConfig[] }[]>JSON.parse(tabConfigsJson); - if (!Array.isArray(tabConfigs)) { - return; - } - - tabConfigs.forEach(tabConfig => { - const instance = this.createTerminal(tabConfig.instances[0]); - for (let i = 1; i < tabConfig.instances.length; i++) { - this.splitInstance(instance, tabConfig.instances[i]); - } - }); - } - private _onWillShutdown(): boolean | TPromise { if (this.terminalInstances.length === 0) { // No terminal instances, don't veto @@ -139,16 +112,6 @@ export abstract class TerminalService implements ITerminalService { } private _onShutdown(): void { - // Store terminal tab layout - if (this.configHelper.config.experimentalRestore) { - const configs = this.terminalTabs.map(tab => { - return { - instances: tab.terminalInstances.map(instance => instance.shellLaunchConfig) - }; - }); - this._storageService.store(TERMINAL_STATE_STORAGE_KEY, JSON.stringify(configs), StorageScope.WORKSPACE); - } - // Dispose of all instances this.terminalInstances.forEach(instance => instance.dispose()); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index 4e288255067..9ef058f2994 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -75,12 +75,12 @@ configurationRegistry.registerConfiguration({ type: 'object', properties: { 'terminal.integrated.shell.linux': { - description: nls.localize('terminal.integrated.shell.linux', "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)."), + markdownDescription: nls.localize('terminal.integrated.shell.linux', "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', default: getTerminalDefaultShellUnixLike() }, 'terminal.integrated.shellArgs.linux': { - description: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + markdownDescription: nls.localize('terminal.integrated.shellArgs.linux', "The command line arguments to use when on the Linux terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', items: { type: 'string' @@ -88,12 +88,12 @@ configurationRegistry.registerConfiguration({ default: [] }, 'terminal.integrated.shell.osx': { - description: nls.localize('terminal.integrated.shell.osx', "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)."), + markdownDescription: nls.localize('terminal.integrated.shell.osx', "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', default: getTerminalDefaultShellUnixLike() }, 'terminal.integrated.shellArgs.osx': { - description: nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + markdownDescription: nls.localize('terminal.integrated.shellArgs.osx', "The command line arguments to use when on the macOS terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', items: { type: 'string' @@ -104,12 +104,12 @@ configurationRegistry.registerConfiguration({ default: ['-l'] }, 'terminal.integrated.shell.windows': { - description: nls.localize('terminal.integrated.shell.windows', "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)."), + markdownDescription: nls.localize('terminal.integrated.shell.windows', "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', default: getTerminalDefaultShellWindows() }, 'terminal.integrated.shellArgs.windows': { - description: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), + markdownDescription: nls.localize('terminal.integrated.shellArgs.windows', "The command line arguments to use when on the Windows terminal. [Read more about configuring the shell](https://code.visualstudio.com/docs/editor/integrated-terminal#_configuration)."), type: 'array', items: { type: 'string' @@ -137,7 +137,7 @@ configurationRegistry.registerConfiguration({ default: true }, 'terminal.integrated.fontFamily': { - description: nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), + markdownDescription: nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to `#editor.fontFamily#`'s value."), type: 'string' }, // TODO: Support font ligatures @@ -189,7 +189,7 @@ configurationRegistry.registerConfiguration({ default: 1000 }, 'terminal.integrated.setLocaleVariables': { - description: nls.localize('terminal.integrated.setLocaleVariables', "Controls whether locale variables are set at startup of the terminal, this defaults to `true` on macOS, `false` on other platforms."), + markdownDescription: nls.localize('terminal.integrated.setLocaleVariables', "Controls whether locale variables are set at startup of the terminal, this defaults to `true` on macOS, `false` on other platforms."), type: 'boolean', default: platform.isMacintosh }, @@ -329,7 +329,7 @@ configurationRegistry.registerConfiguration({ ].sort() }, 'terminal.integrated.env.osx': { - description: nls.localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), + markdownDescription: nls.localize('terminal.integrated.env.osx', "Object with environment variables that will be added to the VS Code process to be used by the terminal on macOS. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -337,7 +337,7 @@ configurationRegistry.registerConfiguration({ default: {} }, 'terminal.integrated.env.linux': { - description: nls.localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), + markdownDescription: nls.localize('terminal.integrated.env.linux', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Linux. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -345,7 +345,7 @@ configurationRegistry.registerConfiguration({ default: {} }, 'terminal.integrated.env.windows': { - description: nls.localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), + markdownDescription: nls.localize('terminal.integrated.env.windows', "Object with environment variables that will be added to the VS Code process to be used by the terminal on Windows. Set to `null` to delete the environment variable."), type: 'object', additionalProperties: { type: ['string', 'null'] @@ -356,19 +356,7 @@ configurationRegistry.registerConfiguration({ description: nls.localize('terminal.integrated.showExitAlert', "Controls whether to show the alert \"The terminal process terminated with exit code\" when exit code is non-zero."), type: 'boolean', default: true - }, - 'terminal.integrated.experimentalRestore': { - description: nls.localize('terminal.integrated.experimentalRestore', "Controls whether to restore terminal sessions for the workspace automatically when launching VS Code. This is an experimental setting; it may be buggy and could change or be removed in the future."), - type: 'boolean', - default: false - }, - // TODO: Default to dynamic and remove setting in 1.27 - 'terminal.integrated.experimentalTextureCachingStrategy': { - description: nls.localize('terminal.integrated.experimentalTextureCachingStrategy', "Controls how the terminal stores glyph textures. `static` is the default and uses a fixed texture to draw the characters from. `dynamic` will draw the characters to the texture as they are needed, this should boost overall performance at the cost of slightly increased draw time the first time a character is drawn. `dynamic` will eventually become the default and this setting will be removed. Changes to this setting will only apply to new terminals."), - type: 'string', - enum: ['static', 'dynamic'], - default: 'dynamic' - }, + } } }); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 42174a7c0ac..0b7ed17b6dc 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -293,7 +293,8 @@ export class TerminalInstance implements ITerminalInstance { rightClickSelectsWord: config.rightClickBehavior === 'selectWord', // TODO: Guess whether to use canvas or dom better rendererType: config.rendererType === 'auto' ? 'canvas' : config.rendererType, - experimentalCharAtlas: config.experimentalTextureCachingStrategy + // TODO: Remove this once the setting is removed upstream + experimentalCharAtlas: 'dynamic' }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 5b865bd3a86..fb61123763f 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -13,7 +13,6 @@ import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; -import { IQuickOpenService, IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { ITerminalInstance, ITerminalService, IShellLaunchConfig, ITerminalConfigHelper, NEVER_SUGGEST_SELECT_WINDOWS_SHELL_STORAGE_KEY, TERMINAL_PANEL_ID, ITerminalProcessExtHostProxy } from 'vs/workbench/parts/terminal/common/terminal'; import { TerminalService as AbstractTerminalService } from 'vs/workbench/parts/terminal/common/terminalService'; import { TerminalConfigHelper } from 'vs/workbench/parts/terminal/electron-browser/terminalConfigHelper'; @@ -29,6 +28,7 @@ import { ipcRenderer as ipc } from 'electron'; import { IOpenFileRequest } from 'vs/platform/windows/common/windows'; import { TerminalInstance } from 'vs/workbench/parts/terminal/electron-browser/terminalInstance'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; +import { IQuickInputService, IQuickPickItem, IPickOptions } from 'vs/platform/quickinput/common/quickInput'; export class TerminalService extends AbstractTerminalService implements ITerminalService { private _configHelper: TerminalConfigHelper; @@ -47,7 +47,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina @ILifecycleService lifecycleService: ILifecycleService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IInstantiationService private readonly _instantiationService: IInstantiationService, - @IQuickOpenService private readonly _quickOpenService: IQuickOpenService, + @IQuickInputService private readonly _quickInputService: IQuickInputService, @INotificationService private readonly _notificationService: INotificationService, @IDialogService private readonly _dialogService: IDialogService, @IExtensionService private readonly _extensionService: IExtensionService @@ -184,10 +184,10 @@ export class TerminalService extends AbstractTerminalService implements ITermina public selectDefaultWindowsShell(): TPromise { return this._detectWindowsShells().then(shells => { - const options: IPickOptions = { + const options: IPickOptions = { placeHolder: nls.localize('terminal.integrated.chooseWindowsShell', "Select your preferred terminal shell, you can change this later in your settings") }; - return this._quickOpenService.pick(shells, options).then(value => { + return this._quickInputService.pick(shells, options).then(value => { if (!value) { return null; } @@ -197,7 +197,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina }); } - private _detectWindowsShells(): TPromise { + private _detectWindowsShells(): TPromise { // 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 @@ -231,7 +231,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina Object.keys(expectedLocations).forEach(key => promises.push(this._validateShellPaths(key, expectedLocations[key]))); return TPromise.join(promises).then(results => { return results.filter(result => !!result).map(result => { - return { + return { label: result[0], description: result[1] }; diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts index c81582ead3b..c3c0cf180a6 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesEditor.ts @@ -158,6 +158,13 @@ export class ReleaseNotesManager { if (!this._releaseNotesCache[version]) { this._releaseNotesCache[version] = this._requestService.request({ url }) .then(asText) + .then(text => { + if (!/^#\s/.test(text)) { // release notes always starts with `#` followed by whitespace + return TPromise.wrapError(new Error('Invalid release notes')); + } + + return TPromise.wrap(text); + }) .then(text => patchKeybindings(text)); } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 941351830b6..04edd26fd77 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -209,9 +209,28 @@ export class Win3264BitContribution implements IWorkbenchContribution { } } +async function isUserSetupInstalled(): Promise { + const rawUserAppId = process.arch === 'x64' ? product.win32x64UserAppId : product.win32UserAppId; + const userAppId = rawUserAppId.replace(/^\{\{/, '{'); + const Registry = await import('winreg'); + const key = new Registry({ + hive: Registry.HKCU, + key: `\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\${userAppId}_is1` + }); + + try { + await new Promise((c, e) => key.get('', (err, result) => err ? e(err) : c(result))); + } catch (err) { + return false; + } + + return true; +} + export class WinUserSetupContribution implements IWorkbenchContribution { private static readonly KEY = 'update/win32-usersetup'; + private static readonly KEY_BOTH = 'update/win32-usersetup-both'; private static readonly STABLE_URL = 'https://vscode-update.azurewebsites.net/latest/win32-x64-user/stable'; private static readonly STABLE_URL_32BIT = 'https://vscode-update.azurewebsites.net/latest/win32-user/stable'; @@ -230,8 +249,34 @@ export class WinUserSetupContribution implements IWorkbenchContribution { @IOpenerService private openerService: IOpenerService, @IUpdateService private updateService: IUpdateService ) { - updateService.onStateChange(this.onUpdateStateChange, this, this.disposables); - this.onUpdateStateChange(this.updateService.state); + const neverShowAgain = new NeverShowAgain(WinUserSetupContribution.KEY_BOTH, this.storageService); + + if (!neverShowAgain.shouldShow()) { + return; + } + + isUserSetupInstalled().then(userSetupIsInstalled => { + if (!userSetupIsInstalled) { + updateService.onStateChange(this.onUpdateStateChange, this, this.disposables); + this.onUpdateStateChange(this.updateService.state); + return; + } + + const handle = this.notificationService.prompt( + severity.Warning, + nls.localize('usersetupsystem', "You are running the system-wide installation of {0}, while having the user-wide distribution installed as well. Make sure you're running the {0} version you expect.", product.nameShort), + [ + { label: nls.localize('ok', "OK"), run: () => null }, + { + label: nls.localize('neveragain', "Don't Show Again"), + isSecondary: true, + run: () => { + neverShowAgain.action.run(handle); + neverShowAgain.action.dispose(); + } + }] + ); + }); } private onUpdateStateChange(state: UpdateState): void { @@ -297,7 +342,7 @@ class CommandAction extends Action { export class UpdateContribution implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; - private static readonly openSettingsId = 'workbench.action.openSettings2'; + private static readonly openSettingsId = 'workbench.action.openSettings'; private static readonly openKeybindingsId = 'workbench.action.openGlobalKeybindings'; private static readonly openUserSnippets = 'workbench.action.openSnippets'; private static readonly selectColorThemeId = 'workbench.action.selectTheme'; diff --git a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts b/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts index 638ea70b328..912f937e607 100644 --- a/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts +++ b/src/vs/workbench/parts/webview/electron-browser/webviewEditorInput.ts @@ -211,7 +211,7 @@ export class WebviewEditorInput extends EditorInput { } } - public resolve(): TPromise { + public resolve(): TPromise { if (this.reviver && !this._revived) { this._revived = true; return this.reviver.reviveWebview(this).then(() => new EditorModel()); diff --git a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts b/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts index 257d791463e..62b0d5e5916 100644 --- a/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts +++ b/src/vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut.ts @@ -16,20 +16,26 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { IExperimentService, ExperimentState } from 'vs/workbench/parts/experiments/node/experimentService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { language, locale } from 'vs/base/common/platform'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; export class TelemetryOptOut implements IWorkbenchContribution { private static TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown'; + private privacyUrl: string; + private optOutUrl: string; constructor( @IStorageService storageService: IStorageService, @IOpenerService openerService: IOpenerService, - @INotificationService notificationService: INotificationService, + @INotificationService private notificationService: INotificationService, @IWindowService windowService: IWindowService, @IWindowsService windowsService: IWindowsService, - @ITelemetryService telemetryService: ITelemetryService, - @IExperimentService experimentService: IExperimentService, - @IConfigurationService configurationService: IConfigurationService + @ITelemetryService private telemetryService: ITelemetryService, + @IExperimentService private experimentService: IExperimentService, + @IConfigurationService private configurationService: IConfigurationService, + @IExtensionGalleryService private galleryService: IExtensionGalleryService ) { if (!product.telemetryOptOutUrl || storageService.get(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN)) { return; @@ -45,42 +51,95 @@ export class TelemetryOptOut implements IWorkbenchContribution { } storageService.store(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true); - const optOutUrl = product.telemetryOptOutUrl; - const privacyUrl = product.privacyStatementUrl || product.telemetryOptOutUrl; + this.optOutUrl = product.telemetryOptOutUrl; + this.privacyUrl = product.privacyStatementUrl || product.telemetryOptOutUrl; if (experimentState && experimentState.state === ExperimentState.Run && telemetryService.isOptedIn) { - notificationService.prompt( - Severity.Info, - localize('telemetryOptOut.optOutOption', "Please help Microsoft improve Visual Studio Code by allowing the collection of usage data. Read our [privacy statement]({0}) for more details.", privacyUrl), - [ - { - label: localize('telemetryOptOut.OptIn', "Yes, glad to help"), - run: () => { } - }, - { - label: localize('telemetryOptOut.OptOut', "No, thanks"), - run: () => { - configurationService.updateValue('telemetry.enableTelemetry', false); - configurationService.updateValue('telemetry.enableCrashReporter', false); - } - }] - ); - experimentService.markAsCompleted(experimentId); + this.runExperiment(experimentId); return; } - const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", privacyUrl, optOutUrl); - const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", privacyUrl, optOutUrl); + const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt out]({1}).", this.privacyUrl, this.optOutUrl); + const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve VS Code by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and learn how to [opt in]({1}).", this.privacyUrl, this.optOutUrl); notificationService.prompt( Severity.Info, telemetryService.isOptedIn ? optOutNotice : optInNotice, [{ label: localize('telemetryOptOut.readMore', "Read More"), - run: () => openerService.open(URI.parse(optOutUrl)) + run: () => openerService.open(URI.parse(this.optOutUrl)) }] ); }) .then(null, onUnexpectedError); } + + private runExperiment(experimentId: string) { + const promptMessageKey = 'telemetryOptOut.optOutOption'; + const yesLabelKey = 'telemetryOptOut.OptIn'; + const noLabelKey = 'telemetryOptOut.OptOut'; + + let promptMessage = localize('telemetryOptOut.optOutOption', "Please help Microsoft improve Visual Studio Code by allowing the collection of usage data. Read our [privacy statement]({0}) for more details.", this.privacyUrl); + let yesLabel = localize('telemetryOptOut.OptIn', "Yes, glad to help"); + let noLabel = localize('telemetryOptOut.OptOut', "No, thanks"); + + let queryPromise = TPromise.as(undefined); + if ((locale !== language && locale !== 'en' && locale.indexOf('en-') === -1)) { + queryPromise = this.galleryService.query({ text: `tag:lp-${locale}` }).then(tagResult => { + if (!tagResult || !tagResult.total) { + return undefined; + } + const extensionToFetchTranslationsFrom = tagResult.firstPage.filter(e => e.publisher === 'MS-CEINTL' && e.name.indexOf('vscode-language-pack') === 0)[0] || tagResult.firstPage[0]; + if (!extensionToFetchTranslationsFrom.assets || !extensionToFetchTranslationsFrom.assets.coreTranslations) { + return undefined; + } + + return this.galleryService.getCoreTranslation(extensionToFetchTranslationsFrom, locale) + .then(translation => { + const translationsFromPack = translation && translation.contents ? translation.contents['vs/workbench/parts/welcome/gettingStarted/electron-browser/telemetryOptOut'] : {}; + if (!!translationsFromPack[promptMessageKey] && !!translationsFromPack[yesLabelKey] && !!translationsFromPack[noLabelKey]) { + promptMessage = translationsFromPack[promptMessageKey].replace('{0}', this.privacyUrl) + ' (Please help Microsoft improve Visual Studio Code by allowing the collection of usage data.)'; + yesLabel = translationsFromPack[yesLabelKey] + ' (Yes)'; + noLabel = translationsFromPack[noLabelKey] + ' (No)'; + } + return undefined; + }); + + }); + } + + const logTelemetry = (optout?: boolean) => { + /* __GDPR__ + "experiments:optout" : { + "optOut": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + } + */ + this.telemetryService.publicLog('experiments:optout', typeof optout === 'boolean' ? { optout } : {}); + }; + + queryPromise.then(() => { + this.notificationService.prompt( + Severity.Info, + promptMessage, + [ + { + label: yesLabel, + run: () => { + logTelemetry(false); + } + }, + { + label: noLabel, + run: () => { + logTelemetry(true); + this.configurationService.updateValue('telemetry.enableTelemetry', false); + this.configurationService.updateValue('telemetry.enableCrashReporter', false); + } + } + ], + logTelemetry + ); + this.experimentService.markAsCompleted(experimentId); + }); + } } diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts index 2e5f8844e43..99341e7cabe 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution.ts @@ -9,7 +9,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } fr import { Registry } from 'vs/platform/registry/common/platform'; import { WelcomePageContribution, WelcomePageAction, WelcomeInputFactory } from 'vs/workbench/parts/welcome/page/electron-browser/welcomePage'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { IEditorInputFactoryRegistry, Extensions as EditorExtensions } from 'vs/workbench/common/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -26,10 +26,10 @@ Registry.as(ConfigurationExtensions.Configuration) 'enumDescriptions': [ localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.none' }, "Start without an editor."), localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.welcomePage' }, "Open the Welcome page (default)."), - localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file."), + localize({ comment: ['This is the description for a setting. Values surrounded by single quotes are not to be translated.'], key: 'workbench.startupEditor.newUntitledFile' }, "Open a new untitled file (only applies when opening an empty workspace)."), ], 'default': 'welcomePage', - 'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none is restored from the previous session. Select 'none' to start without an editor, 'welcomePage' to open the Welcome page (default), 'newUntitledFile' to open a new untitled file (only opening an empty workspace).") + 'description': localize('workbench.startupEditor', "Controls which editor is shown at startup, if none are restored from the previous session.") }, } }); @@ -41,3 +41,12 @@ Registry.as(ActionExtensions.WorkbenchActions) .registerWorkbenchAction(new SyncActionDescriptor(WelcomePageAction, WelcomePageAction.ID, WelcomePageAction.LABEL), 'Help: Welcome', localize('help', "Help")); Registry.as(EditorExtensions.EditorInputFactories).registerEditorInputFactory(WelcomeInputFactory.ID, WelcomeInputFactory); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'workbench.action.showWelcomePage', + title: localize({ key: 'miWelcome', comment: ['&& denotes a mnemonic'] }, "&&Welcome") + }, + order: 1 +}); diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts index 335ba65618e..f0397cfb446 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/walkThrough.contribution.ts @@ -14,7 +14,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as EditorInputExtensions, IEditorInputFactoryRegistry } from 'vs/workbench/common/editor'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -48,3 +48,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughArrowDown); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageUp); KeybindingsRegistry.registerCommandAndKeybindingRule(WalkThroughPageDown); + +MenuRegistry.appendMenuItem(MenuId.MenubarHelpMenu, { + group: '1_welcome', + command: { + id: 'workbench.action.showInteractivePlayground', + title: localize({ key: 'miInteractivePlayground', comment: ['&& denotes a mnemonic'] }, "&&Interactive Playground") + }, + order: 2 +}); \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts index 6b55d1b1930..6197ea71e29 100644 --- a/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts +++ b/src/vs/workbench/services/configuration/common/configurationExtensionPoint.ts @@ -113,7 +113,7 @@ configurationExtPoint.setHandler(extensions => { validateProperties(configuration, extension); - configuration.id = node.id || extension.description.uuid || extension.description.id; + configuration.id = node.id || extension.description.id || extension.description.uuid; configuration.contributedByExtension = true; configuration.title = configuration.title || extension.description.displayName || extension.description.id; configurations.push(configuration); diff --git a/src/vs/workbench/services/configuration/node/configuration.ts b/src/vs/workbench/services/configuration/node/configuration.ts index e09d62ad085..2df1c8ad96e 100644 --- a/src/vs/workbench/services/configuration/node/configuration.ts +++ b/src/vs/workbench/services/configuration/node/configuration.ts @@ -6,6 +6,7 @@ import URI from 'vs/base/common/uri'; import { createHash } from 'crypto'; import * as paths from 'vs/base/common/paths'; +import * as resources from 'vs/base/common/resources'; import { TPromise } from 'vs/base/common/winjs.base'; import { Event, Emitter } from 'vs/base/common/event'; import * as pfs from 'vs/base/node/pfs'; @@ -124,7 +125,7 @@ export class WorkspaceConfiguration extends Disposable { } function isFolderConfigurationFile(resource: URI): boolean { - const name = paths.basename(resource.path); + const name = resources.basename(resource); return [`${FOLDER_SETTINGS_NAME}.json`, `${TASKS_CONFIGURATION_KEY}.json`, `${LAUNCH_CONFIGURATION_KEY}.json`].some(p => p === name);// only workspace config files } @@ -192,7 +193,7 @@ export abstract class AbstractFolderConfiguration extends Disposable implements private parseContents(contents: { resource: URI, value: string }[]): void { for (const content of contents) { - const name = paths.basename(content.resource.path); + const name = resources.basename(content.resource); if (name === `${FOLDER_SETTINGS_NAME}.json`) { this._folderSettingsModelParser.parse(content.value); } else { @@ -215,7 +216,7 @@ export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration { constructor(folder: URI, configFolderRelativePath: string, workbenchState: WorkbenchState) { super(folder, workbenchState); - this.folderConfigurationPath = URI.file(paths.join(this.folder.fsPath, configFolderRelativePath)); + this.folderConfigurationPath = resources.joinPath(folder, configFolderRelativePath); } protected loadFolderConfigurationContents(): TPromise<{ resource: URI, value: string }[]> { @@ -248,7 +249,7 @@ export class NodeBasedFolderConfiguration extends AbstractFolderConfiguration { c({ resource, isDirectory: true, - children: children.map(child => { return { resource: URI.file(paths.join(resource.fsPath, child)) }; }) + children: children.map(child => { return { resource: resources.joinPath(resource, child) }; }) }); } }); @@ -264,7 +265,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura constructor(folder: URI, private configFolderRelativePath: string, workbenchState: WorkbenchState, private fileService: IFileService, from?: AbstractFolderConfiguration) { super(folder, workbenchState, from); - this.folderConfigurationPath = folder.with({ path: paths.join(this.folder.path, configFolderRelativePath) }); + this.folderConfigurationPath = resources.joinPath(folder, configFolderRelativePath); this.reloadConfigurationScheduler = this._register(new RunOnceScheduler(() => this._onDidChange.fire(), 50)); this._register(fileService.onFileChanges(e => this.handleWorkspaceFileEvents(e))); } @@ -295,7 +296,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura for (let i = 0, len = events.length; i < len; i++) { const resource = events[i].resource; - const basename = paths.basename(resource.path); + const basename = resources.basename(resource); const isJson = paths.extname(basename) === '.json'; const isDeletedSettingsFolder = (events[i].type === FileChangeType.DELETED && basename === this.configFolderRelativePath); @@ -337,7 +338,7 @@ export class FileServiceBasedFolderConfiguration extends AbstractFolderConfigura return paths.normalize(relative(this.folderConfigurationPath.fsPath, resource.fsPath)); } } else { - if (paths.isEqualOrParent(resource.path, this.folderConfigurationPath.path, true /* ignorecase */)) { + if (resources.isEqualOrParent(resource, this.folderConfigurationPath, resources.hasToIgnoreCase(resource))) { return paths.normalize(relative(this.folderConfigurationPath.path, resource.path)); } } diff --git a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts index 6df2345bd64..13049eb3358 100644 --- a/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts +++ b/src/vs/workbench/services/crashReporter/electron-browser/crashReporterService.ts @@ -36,7 +36,7 @@ configurationRegistry.registerConfiguration({ 'properties': { 'telemetry.enableCrashReporter': { 'type': 'boolean', - 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to Microsoft.\nThis option requires restart to take effect."), + 'description': nls.localize('telemetry.enableCrashReporting', "Enable crash reports to be sent to a Microsoft online service. \nThis option requires restart to take effect."), 'default': true, 'tags': ['usesOnlineServices'] } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index ab3dda8c90e..16a25f5dbea 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -8,7 +8,6 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import * as objects from 'vs/base/common/objects'; -import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { isWindows, isLinux } from 'vs/base/common/platform'; import { findFreePort } from 'vs/base/node/ports'; @@ -36,6 +35,7 @@ import { IRemoteConsoleLog, log, parse } from 'vs/base/node/console'; import { getScopes } from 'vs/platform/configuration/common/configurationRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class ExtensionHostProcessWorker { @@ -141,7 +141,8 @@ export class ExtensionHostProcessWorker { VERBOSE_LOGGING: true, VSCODE_IPC_HOOK_EXTHOST: pipeName, VSCODE_HANDLES_UNCAUGHT_ERRORS: true, - VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || product.quality !== 'stable' || this._environmentService.verbose) + VSCODE_LOG_STACK: !this._isExtensionDevTestFromCli && (this._isExtensionDevHost || !this._environmentService.isBuilt || product.quality !== 'stable' || this._environmentService.verbose), + VSCODE_LOG_LEVEL: this._environmentService.verbose ? 'trace' : this._environmentService.log }), // We only detach the extension host on windows. Linux and Mac orphan by default // and detach under Linux and Mac create another process group. @@ -170,7 +171,7 @@ export class ExtensionHostProcessWorker { } // Run Extension Host as fork of current process - this._extensionHostProcess = fork(URI.parse(require.toUrl('bootstrap')).fsPath, ['--type=extensionHost'], opts); + this._extensionHostProcess = fork(getPathFromAmdModule(require, 'bootstrap'), ['--type=extensionHost'], opts); // Catch all output coming from the extension host process type Output = { data: string, format: string[] }; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 45c442c325b..a46a116e22e 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -43,18 +43,19 @@ import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { Schemas } from 'vs/base/common/network'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; let _SystemExtensionsRoot: string = null; function getSystemExtensionsRoot(): string { if (!_SystemExtensionsRoot) { - _SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); + _SystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'extensions')); } return _SystemExtensionsRoot; } let _ExtraDevSystemExtensionsRoot: string = null; function getExtraDevSystemExtensionsRoot(): string { if (!_ExtraDevSystemExtensionsRoot) { - _ExtraDevSystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', '.build', 'builtInExtensions')); + _ExtraDevSystemExtensionsRoot = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', '.build', 'builtInExtensions')); } return _ExtraDevSystemExtensionsRoot; } @@ -122,7 +123,7 @@ export class ExtensionHostProcessManager extends Disposable { /** * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object. */ - private readonly _extensionHostProcessProxy: TPromise<{ value: ExtHostExtensionServiceShape; }>; + private _extensionHostProcessProxy: TPromise<{ value: ExtHostExtensionServiceShape; }>; constructor( extensionHostProcessWorker: ExtensionHostProcessWorker, @@ -134,7 +135,6 @@ export class ExtensionHostProcessManager extends Disposable { this._extensionHostProcessFinishedActivateEvents = Object.create(null); this._extensionHostProcessRPCProtocol = null; this._extensionHostProcessCustomers = []; - this._extensionHostProcessProxy = null; this._extensionHostProcessWorker = extensionHostProcessWorker; this.onDidCrash = this._extensionHostProcessWorker.onCrashed; @@ -168,6 +168,7 @@ export class ExtensionHostProcessManager extends Disposable { errors.onUnexpectedError(err); } } + this._extensionHostProcessProxy = null; super.dispose(); } @@ -218,6 +219,11 @@ export class ExtensionHostProcessManager extends Disposable { return NO_OP_VOID_PROMISE; } return this._extensionHostProcessProxy.then((proxy) => { + if (!proxy) { + // this case is already covered above and logged. + // i.e. the extension host could not be started + return NO_OP_VOID_PROMISE; + } return proxy.value.$activateByEvent(activationEvent); }).then(() => { this._extensionHostProcessFinishedActivateEvents[activationEvent] = true; @@ -791,7 +797,7 @@ export class ExtensionService extends Disposable implements IExtensionService { let finalBuiltinExtensions: TPromise = TPromise.wrap(builtinExtensions); if (devMode) { - const builtInExtensionsFilePath = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'build', 'builtInExtensions.json')); + const builtInExtensionsFilePath = path.normalize(path.join(getPathFromAmdModule(require, ''), '..', 'build', 'builtInExtensions.json')); const builtInExtensions = pfs.readFile(builtInExtensionsFilePath, 'utf8') .then(raw => JSON.parse(raw)); diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 3f2e2a1806f..0722e8dfc97 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -4,11 +4,11 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { posix } from 'path'; import { flatten, isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { TernarySearchTree, keys } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import * as resources from 'vs/base/common/resources'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDecodeStreamOptions, toDecodeStream, encodeStream } from 'vs/base/node/encoding'; @@ -42,7 +42,7 @@ function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse const [resource, stat] = tuple; const fileStat: IFileStat = { resource, - name: posix.basename(resource.path), + name: resources.basename(resource), isDirectory: (stat.type & FileType.Directory) !== 0, isSymbolicLink: (stat.type & FileType.SymbolicLink) !== 0, isReadonly: !!(provider.capabilities & FileSystemProviderCapabilities.Readonly), @@ -58,7 +58,7 @@ function toIFileStat(provider: IFileSystemProvider, tuple: [URI, IStat], recurse // resolve children if requested return TPromise.join(entries.map(tuple => { const [name, type] = tuple; - const childResource = resource.with({ path: posix.join(resource.path, name) }); + const childResource = resources.joinPath(resource, name); return toIFileStat(provider, [childResource, new TypeOnlyStat(type)], recurse); })).then(children => { fileStat.children = children; @@ -261,7 +261,7 @@ export class RemoteFileService extends FileService { private _withProvider(resource: URI): TPromise { - if (!posix.isAbsolute(resource.path)) { + if (!resources.isAbsolutePath(resource)) { throw new FileOperationError( localize('invalidPath', "The path of resource '{0}' must be absolute", resource.toString(true)), FileOperationResult.FILE_INVALID_PATH @@ -282,7 +282,7 @@ export class RemoteFileService extends FileService { }); } - existsFile(resource: URI): TPromise { + existsFile(resource: URI): TPromise { if (resource.scheme === Schemas.file) { return super.existsFile(resource); } else { @@ -290,7 +290,7 @@ export class RemoteFileService extends FileService { } } - resolveFile(resource: URI, options?: IResolveFileOptions): TPromise { + resolveFile(resource: URI, options?: IResolveFileOptions): TPromise { if (resource.scheme === Schemas.file) { return super.resolveFile(resource, options); } else { @@ -307,7 +307,7 @@ export class RemoteFileService extends FileService { } } - resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise { + resolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise { // soft-groupBy, keep order, don't rearrange/merge groups let groups: (typeof toResolve)[] = []; @@ -320,7 +320,7 @@ export class RemoteFileService extends FileService { group.push(request); } - const promises: TPromise[] = []; + const promises: TPromise[] = []; for (const group of groups) { if (group[0].resource.scheme === Schemas.file) { promises.push(super.resolveFiles(group)); @@ -331,7 +331,7 @@ export class RemoteFileService extends FileService { return TPromise.join(promises).then(data => flatten(data)); } - private _doResolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise { + private _doResolveFiles(toResolve: { resource: URI; options?: IResolveFileOptions; }[]): TPromise { return this._withProvider(toResolve[0].resource).then(provider => { let result: IResolveFileResult[] = []; let promises = toResolve.map((item, idx) => { @@ -434,12 +434,12 @@ export class RemoteFileService extends FileService { break; // we have hit a directory -> good } catch (e) { // ENOENT - basenames.push(posix.basename(directory.path)); - directory = directory.with({ path: posix.dirname(directory.path) }); + basenames.push(resources.basename(directory)); + directory = resources.dirname(directory); } } for (let i = basenames.length - 1; i >= 0; i--) { - directory = directory.with({ path: posix.join(directory.path, basenames[i]) }); + directory = resources.joinPath(directory, basenames[i]); await provider.mkdir(directory); } } @@ -458,7 +458,7 @@ export class RemoteFileService extends FileService { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return RemoteFileService._mkdirp(provider, resource.with({ path: posix.dirname(resource.path) })).then(() => { + return RemoteFileService._mkdirp(provider, resources.dirname(resource)).then(() => { const encoding = this.encoding.getWriteEncoding(resource); return this._writeFile(provider, resource, new StringSnapshot(content), encoding, { create: true, overwrite: Boolean(options && options.overwrite) }); }); @@ -479,7 +479,7 @@ export class RemoteFileService extends FileService { return super.updateContent(resource, value, options); } else { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return RemoteFileService._mkdirp(provider, resource.with({ path: posix.dirname(resource.path) })).then(() => { + return RemoteFileService._mkdirp(provider, resources.dirname(resource)).then(() => { const snapshot = typeof value === 'string' ? new StringSnapshot(value) : value; return this._writeFile(provider, resource, snapshot, options && options.encoding, { create: true, overwrite: true }); }); @@ -532,12 +532,12 @@ export class RemoteFileService extends FileService { } } - createFolder(resource: URI): TPromise { + createFolder(resource: URI): TPromise { if (resource.scheme === Schemas.file) { return super.createFolder(resource); } else { return this._withProvider(resource).then(RemoteFileService._throwIfFileSystemIsReadonly).then(provider => { - return RemoteFileService._mkdirp(provider, resource.with({ path: posix.dirname(resource.path) })).then(() => { + return RemoteFileService._mkdirp(provider, resources.dirname(resource)).then(() => { return provider.mkdir(resource).then(() => { return this.resolveFile(resource); }); 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 d2791142347..f522fba51c8 100644 --- a/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/nsfw/watcherService.ts @@ -7,7 +7,6 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import uri from 'vs/base/common/uri'; import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/nsfw/watcherIpc'; import { FileChangesEvent, IFilesConfiguration } from 'vs/platform/files/common/files'; @@ -17,6 +16,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { filterEvent } from 'vs/base/common/event'; import { IWatchError } from 'vs/workbench/services/files/node/watcher/nsfw/watcher'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -39,7 +39,7 @@ export class FileWatcher { public startWatching(): () => void { const client = new Client( - uri.parse(require.toUrl('bootstrap')).fsPath, + getPathFromAmdModule(require, 'bootstrap'), { serverName: 'Watcher', args: ['--type=watcherService'], 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 45ca79e02f5..2dc1effa571 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/watcherService.ts @@ -7,7 +7,6 @@ import { getNextTickChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; -import uri from 'vs/base/common/uri'; import { toFileChangesEvent, IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; import { IWatcherChannel, WatcherChannelClient } from 'vs/workbench/services/files/node/watcher/unix/watcherIpc'; import { FileChangesEvent, IFilesConfiguration } from 'vs/platform/files/common/files'; @@ -17,6 +16,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { Schemas } from 'vs/base/common/network'; import { filterEvent } from 'vs/base/common/event'; import { IWatchError } from 'vs/workbench/services/files/node/watcher/unix/watcher'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class FileWatcher { private static readonly MAX_RESTARTS = 5; @@ -42,7 +42,7 @@ export class FileWatcher { const args = ['--type=watcherService']; const client = new Client( - uri.parse(require.toUrl('bootstrap')).fsPath, + getPathFromAmdModule(require, 'bootstrap'), { serverName: 'Watcher', args, @@ -122,4 +122,4 @@ export class FileWatcher { this.isDisposed = true; this.toDispose = dispose(this.toDispose); } -} \ No newline at end of file +} 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 949c3d1b36f..8c18ce5b640 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts @@ -10,9 +10,9 @@ 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 uri from 'vs/base/common/uri'; import { IRawFileChange } from 'vs/workbench/services/files/node/watcher/common'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class OutOfProcessWin32FolderWatcher { @@ -41,7 +41,7 @@ export class OutOfProcessWin32FolderWatcher { args.push('-verbose'); } - this.handle = cp.spawn(uri.parse(require.toUrl('vs/workbench/services/files/node/watcher/win32/CodeHelper.exe')).fsPath, args); + this.handle = cp.spawn(getPathFromAmdModule(require, 'vs/workbench/services/files/node/watcher/win32/CodeHelper.exe'), args); const stdoutLineDecoder = new decoder.LineDecoder(); @@ -116,4 +116,4 @@ export class OutOfProcessWin32FolderWatcher { this.handle = null; } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts index e57b8041f6b..6d834e52b6a 100644 --- a/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/fileService.test.ts @@ -24,6 +24,7 @@ import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/work import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TextModel } from 'vs/editor/common/model/textModel'; import { IEncodingOverride } from 'vs/workbench/services/files/electron-browser/encoding'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; suite('FileService', () => { let service: FileService; @@ -33,7 +34,7 @@ suite('FileService', () => { setup(function () { const id = uuid.generateUuid(); testDir = path.join(parentDir, id); - const sourceDir = require.toUrl('./fixtures/service'); + const sourceDir = getPathFromAmdModule(require, './fixtures/service'); return pfs.copy(sourceDir, testDir).then(() => { service = new FileService(new TestContextService(new Workspace(testDir, testDir, toWorkspaceFolders([{ path: testDir }]))), TestEnvironmentService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), new TestStorageService(), new TestNotificationService(), { disableWatcher: true }); @@ -837,7 +838,7 @@ suite('FileService', () => { // setup const _id = uuid.generateUuid(); const _testDir = path.join(parentDir, _id); - const _sourceDir = require.toUrl('./fixtures/service'); + const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); return pfs.copy(_sourceDir, _testDir).then(() => { const encodingOverride: IEncodingOverride[] = []; @@ -882,7 +883,7 @@ suite('FileService', () => { // setup const _id = uuid.generateUuid(); const _testDir = path.join(parentDir, _id); - const _sourceDir = require.toUrl('./fixtures/service'); + const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); return pfs.copy(_sourceDir, _testDir).then(() => { const encodingOverride: IEncodingOverride[] = []; @@ -927,7 +928,7 @@ suite('FileService', () => { // setup const _id = uuid.generateUuid(); const _testDir = path.join(parentDir, _id); - const _sourceDir = require.toUrl('./fixtures/service'); + const _sourceDir = getPathFromAmdModule(require, './fixtures/service'); const resource = uri.file(path.join(testDir, 'index.html')); const _service = new FileService( diff --git a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts b/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts index 3a9c19ad869..11222a1f411 100644 --- a/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts +++ b/src/vs/workbench/services/files/test/electron-browser/resolver.test.ts @@ -13,9 +13,10 @@ import { StatResolver } from 'vs/workbench/services/files/electron-browser/fileS import uri from 'vs/base/common/uri'; import { isLinux } from 'vs/base/common/platform'; import * as utils from 'vs/workbench/services/files/test/electron-browser/utils'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function create(relativePath: string): StatResolver { - let basePath = require.toUrl('./fixtures/resolver'); + let basePath = getPathFromAmdModule(require, './fixtures/resolver'); let absolutePath = relativePath ? path.join(basePath, relativePath) : basePath; let fsStat = fs.statSync(absolutePath); @@ -23,7 +24,7 @@ function create(relativePath: string): StatResolver { } function toResource(relativePath: string): uri { - let basePath = require.toUrl('./fixtures/resolver'); + let basePath = getPathFromAmdModule(require, './fixtures/resolver'); let absolutePath = relativePath ? path.join(basePath, relativePath) : basePath; return uri.file(absolutePath); diff --git a/src/vs/workbench/services/history/electron-browser/history.ts b/src/vs/workbench/services/history/electron-browser/history.ts index ac8666d41f2..2c0fa3c7118 100644 --- a/src/vs/workbench/services/history/electron-browser/history.ts +++ b/src/vs/workbench/services/history/electron-browser/history.ts @@ -32,6 +32,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ResourceGlobMatcher } from 'vs/workbench/electron-browser/resources'; import { Schemas } from 'vs/base/common/network'; import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor'; +import { IPartService } from 'vs/workbench/services/part/common/partService'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; /** * Stores the selection & view state of an editor and allows to compare it to other selection states. @@ -122,6 +124,9 @@ export class HistoryService extends Disposable implements IHistoryService { private fileInputFactory: IFileInputFactory; + private canNavigateBackContextKey: IContextKey; + private canNavigateForwardContextKey: IContextKey; + constructor( @IEditorService private editorService: EditorServiceImpl, @IEditorGroupsService private editorGroupService: IEditorGroupsService, @@ -132,11 +137,16 @@ export class HistoryService extends Disposable implements IHistoryService { @IFileService private fileService: IFileService, @IWindowsService private windowService: IWindowsService, @IInstantiationService private instantiationService: IInstantiationService, + @IPartService private partService: IPartService, + @IContextKeyService private contextKeyService: IContextKeyService ) { super(); this.activeEditorListeners = []; + this.canNavigateBackContextKey = (new RawContextKey('canNavigateBack', false)).bindTo(this.contextKeyService); + this.canNavigateForwardContextKey = (new RawContextKey('canNavigateForward', false)).bindTo(this.contextKeyService); + this.fileInputFactory = Registry.as(EditorInputExtensions.EditorInputFactories).getFileInputFactory(); this.index = -1; @@ -266,6 +276,8 @@ export class HistoryService extends Disposable implements IHistoryService { private setIndex(value: number): void { this.lastIndex = this.index; this.index = value; + + this.updateContextKeys(); } private doForwardAcrossEditors(): void { @@ -336,6 +348,13 @@ export class HistoryService extends Disposable implements IHistoryService { this.stack.splice(0); this.history = []; this.recentlyClosedFiles = []; + + this.updateContextKeys(); + } + + private updateContextKeys(): void { + this.canNavigateBackContextKey.set(this.stack.length > 0 && this.index > 0); + this.canNavigateForwardContextKey.set(this.stack.length > 0 && this.index < this.stack.length - 1); } private navigate(acrossEditors?: boolean): void { @@ -567,6 +586,9 @@ export class HistoryService extends Disposable implements IHistoryService { if (stackInput instanceof EditorInput) { once(stackInput.onDispose)(() => this.removeFromStack(input)); } + + // Context + this.updateContextKeys(); } private preferResourceInput(input: IEditorInput): IEditorInput | IResourceInput { @@ -593,6 +615,8 @@ export class HistoryService extends Disposable implements IHistoryService { this.stack = this.stack.filter(e => !this.matches(arg1, e.input)); this.index = this.stack.length - 1; // reset index this.lastIndex = -1; + + this.updateContextKeys(); } private removeFromRecentlyClosedFiles(arg1: IEditorInput | IResourceInput | FileChangesEvent): void { @@ -657,8 +681,15 @@ export class HistoryService extends Disposable implements IHistoryService { if (arg2 instanceof EditorInput) { const inputResource = arg2.getResource(); + if (!inputResource) { + return false; + } - return inputResource && this.fileService.canHandleResource(inputResource) && inputResource.toString() === resource.toString(); + if (this.partService.isCreated() && !this.fileService.canHandleResource(inputResource)) { + return false; // make sure to only check this when workbench has started (for https://github.com/Microsoft/vscode/issues/48275) + } + + return inputResource.toString() === resource.toString(); } const resourceInput = arg2 as IResourceInput; diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index c0521529d6e..77a0eb4cd9e 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -540,6 +540,27 @@ export class WorkbenchKeybindingService extends AbstractKeybindingService { let pretty = unboundCommands.sort().join('\n// - '); return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty; } + + mightProducePrintableCharacter(event: IKeyboardEvent): boolean { + if (event.ctrlKey || event.metaKey) { + // ignore ctrl/cmd-combination but not shift/alt-combinatios + return false; + } + // consult the KeyboardMapperFactory to check the given event for + // a printable value. + const mapping = KeyboardMapperFactory.INSTANCE.getRawKeyboardMapping(); + if (!mapping) { + return false; + } + const keyInfo = mapping[event.code]; + if (!keyInfo) { + return false; + } + if (keyInfo.value) { + return true; + } + return false; + } } let schemaId = 'vscode://schemas/keybindings'; @@ -585,7 +606,7 @@ const keyboardConfiguration: IConfigurationNode = { 'type': 'string', 'enum': ['code', 'keyCode'], 'default': 'code', - 'description': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), + 'markdownDescription': nls.localize('dispatch', "Controls the dispatching logic for key presses to use either `code` (recommended) or `keyCode`."), 'included': OS === OperatingSystem.Macintosh || OS === OperatingSystem.Linux }, 'keyboard.touchbar.enabled': { diff --git a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts index 6cf36eab48f..5d5bf78f5af 100644 --- a/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/keyboardMapperTestUtils.ts @@ -12,6 +12,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { readFile, writeFile } from 'vs/base/node/pfs'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { ScanCodeBinding } from 'vs/workbench/services/keybinding/common/scanCode'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface IResolvedKeybinding { label: string; @@ -51,7 +52,7 @@ export function assertResolveUserBinding(mapper: IKeyboardMapper, firstPart: Sim } export function readRawMapping(file: string): TPromise { - return readFile(require.toUrl(`vs/workbench/services/keybinding/test/${file}.js`)).then((buff) => { + return readFile(getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/${file}.js`)).then((buff) => { let contents = buff.toString(); let func = new Function('define', contents); let rawMappings: T = null; @@ -63,7 +64,7 @@ export function readRawMapping(file: string): TPromise { } export function assertMapping(writeFileIfDifferent: boolean, mapper: IKeyboardMapper, file: string): TPromise { - const filePath = require.toUrl(`vs/workbench/services/keybinding/test/${file}`); + const filePath = getPathFromAmdModule(require, `vs/workbench/services/keybinding/test/${file}`); return readFile(filePath).then((buff) => { let expected = buff.toString(); diff --git a/src/vs/workbench/services/preferences/common/preferences.ts b/src/vs/workbench/services/preferences/common/preferences.ts index 45f0caf3b35..79610895b83 100644 --- a/src/vs/workbench/services/preferences/common/preferences.ts +++ b/src/vs/workbench/services/preferences/common/preferences.ts @@ -41,14 +41,17 @@ export interface ISetting { value: any; valueRange: IRange; description: string[]; + descriptionIsMarkdown: boolean; descriptionRanges: IRange[]; overrides?: ISetting[]; overrideOf?: ISetting; + deprecationMessage?: string; // TODO@roblou maybe need new type and new EditorModel for GUI editor instead of ISetting which is used for text settings editor type?: string | string[]; enum?: string[]; enumDescriptions?: string[]; + enumDescriptionsAreMarkdown?: boolean; tags?: string[]; } diff --git a/src/vs/workbench/services/preferences/common/preferencesModels.ts b/src/vs/workbench/services/preferences/common/preferencesModels.ts index 2bb3302a828..c762fddfdd8 100644 --- a/src/vs/workbench/services/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/services/preferences/common/preferencesModels.ts @@ -267,6 +267,7 @@ function parse(model: ITextModel, isSettingsProperty: (currentProperty: string, let settingStartPosition = model.getPositionAt(offset); const setting: ISetting = { description: [], + descriptionIsMarkdown: false, key: name, keyRange: { startLineNumber: settingStartPosition.lineNumber, @@ -551,14 +552,21 @@ export class DefaultSettings extends Disposable { let result: ISetting[] = []; for (let key in settingsObject) { const prop = settingsObject[key]; - if (!prop.deprecationMessage && this.matchesScope(prop)) { + if (this.matchesScope(prop)) { const value = prop.default; - const description = (prop.description || '').split('\n'); + const description = (prop.description || prop.markdownDescription || '').split('\n'); + if (prop.deprecationMessage) { + description.push( + '', + prop.deprecationMessage, + nls.localize('deprecatedSetting.unstable', "This setting should not be used, and will be removed in a future release.")); + } const overrides = OVERRIDE_PROPERTY_PATTERN.test(key) ? this.parseOverrideSettings(prop.default) : []; result.push({ key, value, description, + descriptionIsMarkdown: !prop.description, range: null, keyRange: null, valueRange: null, @@ -566,8 +574,10 @@ export class DefaultSettings extends Disposable { overrides, type: prop.type, enum: prop.enum, - enumDescriptions: prop.enumDescriptions, - tags: prop.tags + enumDescriptions: prop.enumDescriptions || prop.markdownEnumDescriptions, + enumDescriptionsAreMarkdown: !prop.enumDescriptions, + tags: prop.tags, + deprecationMessage: prop.deprecationMessage, }); } } @@ -575,7 +585,17 @@ export class DefaultSettings extends Disposable { } private parseOverrideSettings(overrideSettings: any): ISetting[] { - return Object.keys(overrideSettings).map((key) => ({ key, value: overrideSettings[key], description: [], range: null, keyRange: null, valueRange: null, descriptionRanges: [], overrides: [] })); + return Object.keys(overrideSettings).map((key) => ({ + key, + value: overrideSettings[key], + description: [], + descriptionIsMarkdown: false, + range: null, + keyRange: null, + valueRange: null, + descriptionRanges: [], + overrides: [] + })); } private matchesScope(property: IConfigurationNode): boolean { @@ -899,7 +919,7 @@ class SettingsContentBuilder { if (setting.enumDescriptions && setting.enumDescriptions.some(desc => !!desc)) { setting.enumDescriptions.forEach((desc, i) => { - const displayEnum = escapeInvisibleChars(setting.enum[i]); + const displayEnum = escapeInvisibleChars(String(setting.enum[i])); const line = desc ? `${displayEnum}: ${fixSettingLink(desc)}` : displayEnum; diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 6a01974b0ee..40aba5a08c0 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -96,9 +96,12 @@ export interface ISCMInput { export interface ISCMRepository extends IDisposable { readonly onDidFocus: Event; + readonly selected: boolean; + readonly onDidChangeSelection: Event; readonly provider: ISCMProvider; readonly input: ISCMInput; focus(): void; + setSelected(selected: boolean): void; } export interface ISCMService { @@ -108,6 +111,8 @@ export interface ISCMService { readonly onDidRemoveRepository: Event; readonly repositories: ISCMRepository[]; + readonly selectedRepositories: ISCMRepository[]; + readonly onDidChangeSelectedRepositories: Event; registerSCMProvider(provider: ISCMProvider): ISCMRepository; } diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index d062ae05633..83b3e88e06d 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -10,6 +10,7 @@ import { Event, Emitter } from 'vs/base/common/event'; import { ISCMService, ISCMProvider, ISCMInput, ISCMRepository, IInputValidator } from './scm'; import { ILogService } from 'vs/platform/log/common/log'; import { TPromise } from 'vs/base/common/winjs.base'; +import { equals } from 'vs/base/common/arrays'; class SCMInput implements ISCMInput { @@ -61,6 +62,14 @@ class SCMRepository implements ISCMRepository { private _onDidFocus = new Emitter(); readonly onDidFocus: Event = this._onDidFocus.event; + private _selected = false; + get selected(): boolean { + return this._selected; + } + + private _onDidChangeSelection = new Emitter(); + readonly onDidChangeSelection: Event = this._onDidChangeSelection.event; + readonly input: ISCMInput = new SCMInput(); constructor( @@ -72,6 +81,11 @@ class SCMRepository implements ISCMRepository { this._onDidFocus.fire(); } + setSelected(selected: boolean): void { + this._selected = selected; + this._onDidChangeSelection.fire(selected); + } + dispose(): void { this.disposable.dispose(); this.provider.dispose(); @@ -86,6 +100,12 @@ export class SCMService implements ISCMService { private _repositories: ISCMRepository[] = []; get repositories(): ISCMRepository[] { return [...this._repositories]; } + private _selectedRepositories: ISCMRepository[] = []; + get selectedRepositories(): ISCMRepository[] { return [...this._selectedRepositories]; } + + private _onDidChangeSelectedRepositories = new Emitter(); + readonly onDidChangeSelectedRepositories: Event = this._onDidChangeSelectedRepositories.event; + private _onDidAddProvider = new Emitter(); get onDidAddRepository(): Event { return this._onDidAddProvider.event; } @@ -110,15 +130,35 @@ export class SCMService implements ISCMService { return; } + selectedDisposable.dispose(); this._providerIds.delete(provider.id); this._repositories.splice(index, 1); this._onDidRemoveProvider.fire(repository); + this.onDidChangeSelection(); }); const repository = new SCMRepository(provider, disposable); + const selectedDisposable = repository.onDidChangeSelection(this.onDidChangeSelection, this); + this._repositories.push(repository); this._onDidAddProvider.fire(repository); + // automatically select the first repository + if (this._repositories.length === 1) { + repository.setSelected(true); + } + return repository; } + + private onDidChangeSelection(): void { + const selectedRepositories = this._repositories.filter(r => r.selected); + + if (equals(this._selectedRepositories, selectedRepositories)) { + return; + } + + this._selectedRepositories = this._repositories.filter(r => r.selected); + this._onDidChangeSelectedRepositories.fire(this.selectedRepositories); + } } diff --git a/src/vs/workbench/services/search/node/searchService.ts b/src/vs/workbench/services/search/node/searchService.ts index e1ae90bb784..5d0ce0948d9 100644 --- a/src/vs/workbench/services/search/node/searchService.ts +++ b/src/vs/workbench/services/search/node/searchService.ts @@ -26,6 +26,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; import { IRawSearch, IRawSearchService, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, isSerializedSearchComplete, isSerializedSearchSuccess, ITelemetryEvent } from './search'; import { ISearchChannel, SearchChannelClient } from './searchIpc'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export class SearchService extends Disposable implements ISearchService { public _serviceBrand: any; @@ -125,29 +126,9 @@ export class SearchService extends Disposable implements ISearchService { const schemesInQuery = query.folderQueries.map(fq => fq.folder.scheme); const providerActivations = schemesInQuery.map(scheme => this.extensionService.activateByEvent(`onSearch:${scheme}`)); - const providerPromise = TPromise.join(providerActivations).then(() => { - return TPromise.join(query.folderQueries.map(fq => { - const oneFolderQuery = { - ...query, - ...{ - folderQueries: [fq] - } - }; - - let provider = query.type === QueryType.File ? - this.fileSearchProviders.get(fq.folder.scheme) || this.fileIndexProviders.get(fq.folder.scheme) : - this.textSearchProviders.get(fq.folder.scheme); - - if (!provider && fq.folder.scheme === 'file') { - provider = this.diskSearch; - } - - if (!provider) { - return TPromise.wrapError(new Error('No search provider registered for scheme: ' + fq.folder.scheme)); - } - - return provider.search(oneFolderQuery, onProviderProgress); - })).then(completes => { + const providerPromise = TPromise.join(providerActivations) + .then(() => this.searchWithProviders(query, onProviderProgress)) + .then(completes => { completes = completes.filter(c => !!c); if (!completes.length) { return null; @@ -166,7 +147,6 @@ export class SearchService extends Disposable implements ISearchService { errs = errs.filter(e => !!e); return TPromise.wrapError(errs[0]); }); - }); combinedPromise = providerPromise.then(value => { this.logService.debug(`SearchService#search: ${Date.now() - startTime}ms`); @@ -202,6 +182,45 @@ export class SearchService extends Disposable implements ISearchService { }, () => combinedPromise && combinedPromise.cancel()); } + private searchWithProviders(query: ISearchQuery, onProviderProgress: (progress: ISearchProgressItem) => void) { + const diskSearchQueries: IFolderQuery[] = []; + const searchPs = []; + + query.folderQueries.forEach(fq => { + let provider = query.type === QueryType.File ? + this.fileSearchProviders.get(fq.folder.scheme) || this.fileIndexProviders.get(fq.folder.scheme) : + this.textSearchProviders.get(fq.folder.scheme); + + if (!provider && fq.folder.scheme === 'file') { + diskSearchQueries.push(fq); + } else if (!provider) { + throw new Error('No search provider registered for scheme: ' + fq.folder.scheme); + } else { + const oneFolderQuery = { + ...query, + ...{ + folderQueries: [fq] + } + }; + + searchPs.push(provider.search(oneFolderQuery, onProviderProgress)); + } + }); + + if (diskSearchQueries.length) { + const diskSearchQuery = { + ...query, + ...{ + folderQueries: diskSearchQueries + } + }; + + searchPs.push(this.diskSearch.search(diskSearchQuery, onProviderProgress)); + } + + return TPromise.join(searchPs); + } + private getLocalResults(query: ISearchQuery): ResourceMap { const localResults = new ResourceMap(); @@ -313,7 +332,7 @@ export class DiskSearch implements ISearchResultProvider { } const client = new Client( - uri.parse(require.toUrl('bootstrap')).fsPath, + getPathFromAmdModule(require, 'bootstrap'), opts); const channel = getNextTickChannel(client.getChannel('search')); diff --git a/src/vs/workbench/services/search/node/textSearchWorkerProvider.ts b/src/vs/workbench/services/search/node/textSearchWorkerProvider.ts index 1b7b03ef9a7..14a7e52b6d6 100644 --- a/src/vs/workbench/services/search/node/textSearchWorkerProvider.ts +++ b/src/vs/workbench/services/search/node/textSearchWorkerProvider.ts @@ -7,11 +7,11 @@ import * as os from 'os'; -import uri from 'vs/base/common/uri'; import * as ipc from 'vs/base/parts/ipc/common/ipc'; import { Client } from 'vs/base/parts/ipc/node/ipc.cp'; import { ISearchWorker, ISearchWorkerChannel, SearchWorkerChannelClient } from './worker/searchWorkerIpc'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; export interface ITextSearchWorkerProvider { getWorkers(): ISearchWorker[]; @@ -31,7 +31,7 @@ export class TextSearchWorkerProvider implements ITextSearchWorkerProvider { private createWorker(): void { let client = new Client( - uri.parse(require.toUrl('bootstrap')).fsPath, + getPathFromAmdModule(require, 'bootstrap'), { serverName: 'Search Worker ' + this.workers.length, args: ['--type=searchWorker'], @@ -49,4 +49,4 @@ export class TextSearchWorkerProvider implements ITextSearchWorkerProvider { this.workers.push(channelClient); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/test/node/search.test.ts b/src/vs/workbench/services/search/test/node/search.test.ts index fda8eed27b0..7e8ded6fe5d 100644 --- a/src/vs/workbench/services/search/test/node/search.test.ts +++ b/src/vs/workbench/services/search/test/node/search.test.ts @@ -13,8 +13,9 @@ import * as platform from 'vs/base/common/platform'; import { FileWalker, Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch'; import { IRawFileMatch, IFolderSearch } from 'vs/workbench/services/search/node/search'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; -const TEST_FIXTURES = path.normalize(require.toUrl('./fixtures')); +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); const TEST_ROOT_FOLDER: IFolderSearch = { folder: TEST_FIXTURES }; @@ -23,7 +24,7 @@ const ROOT_FOLDER_QUERY: IFolderSearch[] = [ ]; const ROOT_FOLDER_QUERY_36438: IFolderSearch[] = [ - { folder: path.normalize(require.toUrl('./fixtures2/36438')) } + { folder: path.normalize(getPathFromAmdModule(require, './fixtures2/36438')) } ]; const MULTIROOT_QUERIES: IFolderSearch[] = [ @@ -629,9 +630,9 @@ suite('FileSearchEngine', () => { let engine = new FileSearchEngine({ folderQueries: [], extraFiles: [ - path.normalize(path.join(require.toUrl('./fixtures'), 'site.css')), - path.normalize(path.join(require.toUrl('./fixtures'), 'examples', 'company.js')), - path.normalize(path.join(require.toUrl('./fixtures'), 'index.html')) + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) ], filePattern: '*.js' }); @@ -656,9 +657,9 @@ suite('FileSearchEngine', () => { let engine = new FileSearchEngine({ folderQueries: [], extraFiles: [ - path.normalize(path.join(require.toUrl('./fixtures'), 'site.css')), - path.normalize(path.join(require.toUrl('./fixtures'), 'examples', 'company.js')), - path.normalize(path.join(require.toUrl('./fixtures'), 'index.html')) + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) ], filePattern: '*.*', includePattern: { '**/*.css': true } @@ -684,9 +685,9 @@ suite('FileSearchEngine', () => { let engine = new FileSearchEngine({ folderQueries: [], extraFiles: [ - path.normalize(path.join(require.toUrl('./fixtures'), 'site.css')), - path.normalize(path.join(require.toUrl('./fixtures'), 'examples', 'company.js')), - path.normalize(path.join(require.toUrl('./fixtures'), 'index.html')) + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'site.css')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'examples', 'company.js')), + path.normalize(path.join(getPathFromAmdModule(require, './fixtures'), 'index.html')) ], filePattern: '*.*', excludePattern: { '**/*.css': true } @@ -942,4 +943,4 @@ suite('FileWalker', () => { const lines = stdout.split('\n'); return files.every(file => lines.indexOf(file) >= 0); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/search/test/node/searchService.test.ts b/src/vs/workbench/services/search/test/node/searchService.test.ts index eb205675ab0..23a434569ea 100644 --- a/src/vs/workbench/services/search/test/node/searchService.test.ts +++ b/src/vs/workbench/services/search/test/node/searchService.test.ts @@ -14,12 +14,13 @@ import { SearchService as RawSearchService } from 'vs/workbench/services/search/ import { DiskSearch } from 'vs/workbench/services/search/node/searchService'; import { Emitter, Event } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; const TEST_FOLDER_QUERIES = [ { folder: path.normalize('/some/where') } ]; -const TEST_FIXTURES = path.normalize(require.toUrl('./fixtures')); +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const MULTIROOT_QUERIES: IFolderSearch[] = [ { folder: path.join(TEST_FIXTURES, 'examples') }, { folder: path.join(TEST_FIXTURES, 'more') } @@ -350,4 +351,4 @@ suite('SearchService', () => { }); }); }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts index 93a86a86b12..21484acb566 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -15,12 +15,13 @@ import { ISerializedFileMatch, IRawSearch, IFolderSearch } from 'vs/workbench/se import { Engine as TextSearchEngine } from 'vs/workbench/services/search/node/textSearch'; import { RipgrepEngine } from 'vs/workbench/services/search/node/ripgrepTextSearch'; import { TextSearchWorkerProvider } from 'vs/workbench/services/search/node/textSearchWorkerProvider'; +import { getPathFromAmdModule } from 'vs/base/common/amd'; function countAll(matches: ISerializedFileMatch[]): number { return matches.reduce((acc, m) => acc + m.numMatches, 0); } -const TEST_FIXTURES = path.normalize(require.toUrl('./fixtures')); +const TEST_FIXTURES = path.normalize(getPathFromAmdModule(require, './fixtures')); const EXAMPLES_FIXTURES = path.join(TEST_FIXTURES, 'examples'); const MORE_FIXTURES = path.join(TEST_FIXTURES, 'more'); const TEST_ROOT_FOLDER: IFolderSearch = { folder: TEST_FIXTURES }; diff --git a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts index d4fca9d949e..5a8a60d7238 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts @@ -216,10 +216,10 @@ export class TextMateService implements ITextMateService { loadGrammar: (scopeName: string) => { const location = this._scopeRegistry.getGrammarLocation(scopeName); if (!location) { - this._logService.info(`No grammar found for scope ${scopeName}`); + this._logService.trace(`No grammar found for scope ${scopeName}`); return null; } - return this._fileService.resolveContent(location).then(content => { + return this._fileService.resolveContent(location, { encoding: 'utf8' }).then(content => { return parseRawGrammar(content.value, location.path); }, e => { this._logService.error(`Unable to load and parse grammar for scope ${scopeName} from ${location}`, e); diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index 41e23fc9981..0c3642145b3 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -42,6 +42,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil static DEFAULT_CONTENT_CHANGE_BUFFER_DELAY = CONTENT_CHANGE_EVENT_BUFFER_DELAY; static DEFAULT_ORPHANED_CHANGE_BUFFER_DELAY = 100; + static WHITELIST_JSON = ['package.json', 'package-lock.json', 'tsconfig.json', 'jsconfig.json', 'bower.json', '.eslintrc.json', 'tslint.json', 'composer.json']; + static WHITELIST_WORKSPACE_JSON = ['settings.json', 'extensions.json', 'tasks.json', 'launch.json']; private static saveErrorHandler: ISaveErrorHandler; static setSaveErrorHandler(handler: ISaveErrorHandler): void { TextFileEditorModel.saveErrorHandler = handler; } @@ -350,28 +352,24 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil private loadWithContent(content: IRawTextContent, options?: ILoadOptions, backup?: URI): TPromise { return this.doLoadWithContent(content, backup).then(model => { - // Telemetry: We log the fileGet telemetry event after the model has been loaded to ensure a good mimetype - if (this.isSettingsFile()) { + const settingsType = this.getTypeIfSettings(); + if (settingsType) { /* __GDPR__ - "settingsRead" : {} + "settingsRead" : { + "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + } */ - this.telemetryService.publicLog('settingsRead'); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data + this.telemetryService.publicLog('settingsRead', { settingsType }); // Do not log read to user settings.json and .vscode folder as a fileGet event as it ruins our JSON usage data } else { /* __GDPR__ "fileGet" : { - "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "${include}": [ + "${FileTelemetryData}" + ] } */ - this.telemetryService.publicLog('fileGet', { - mimeType: guessMimeTypes(this.resource.fsPath).join(', '), - ext: path.extname(this.resource.fsPath), - path: this.hashService.createSHA1(this.resource.fsPath), - reason: options && options.reason ? options.reason : LoadReason.OTHER - }); + this.telemetryService.publicLog('fileGet', this.getTelemetryData(options && options.reason ? options.reason : LoadReason.OTHER)); } return model; @@ -717,24 +715,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil this.logService.trace(`doSave(${versionId}) - after updateContent()`, this.resource); // Telemetry - if (this.isSettingsFile()) { + const settingsType = this.getTypeIfSettings(); + if (settingsType) { /* __GDPR__ - "settingsWritten" : {} + "settingsWritten" : { + "settingsType": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + } */ - this.telemetryService.publicLog('settingsWritten'); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data + this.telemetryService.publicLog('settingsWritten', { settingsType }); // Do not log write to user settings.json and .vscode folder as a filePUT event as it ruins our JSON usage data } else { /* __GDPR__ "filePUT" : { - "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true } + "${include}": [ + "${FileTelemetryData}" + ] } */ - this.telemetryService.publicLog('filePUT', { - mimeType: guessMimeTypes(this.resource.fsPath).join(', '), - ext: path.extname(this.resource.fsPath), - reason: options.reason - }); + this.telemetryService.publicLog('filePUT', this.getTelemetryData(options.reason)); } // Update dirty state unless model has changed meanwhile @@ -754,10 +751,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // Emit File Saved Event this._onDidStateChange.fire(StateChange.SAVED); }, error => { - if (!FileOperationError.isFileOperationError(error)) { - // TODO@ben, workaround issue #55051 - this.logService.error(`doSave(${versionId}) - Unexpected error type ${error}`, this.resource); - return; + if (!error) { + error = new Error('Unknown Save Error'); // TODO@remote we should never get null as error (https://github.com/Microsoft/vscode/issues/55051) } this.logService.error(`doSave(${versionId}) - exit - resulted in a save error: ${error.toString()}`, this.resource); @@ -779,20 +774,69 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil })); } - private isSettingsFile(): boolean { + private getTypeIfSettings(): string { if (path.extname(this.resource.fsPath) !== '.json') { - return false; + return ''; } // Check for global settings file if (isEqual(this.resource, URI.file(this.environmentService.appSettingsPath), !isLinux)) { - return true; + return 'global-settings'; + } + + // Check for keybindings file + if (isEqual(this.resource, URI.file(this.environmentService.appKeybindingsPath), !isLinux)) { + return 'keybindings'; + } + + // Check for locale file + if (isEqual(this.resource, URI.file(path.join(this.environmentService.appSettingsHome, 'locale.json')), !isLinux)) { + return 'locale'; + } + + // Check for snippets + if (isEqualOrParent(this.resource, URI.file(path.join(this.environmentService.appSettingsHome, 'snippets')), hasToIgnoreCase(this.resource))) { + return 'snippets'; } // Check for workspace settings file - return this.contextService.getWorkspace().folders.some(folder => { - return isEqualOrParent(this.resource, folder.toResource('.vscode'), hasToIgnoreCase(this.resource)); - }); + const folders = this.contextService.getWorkspace().folders; + for (let i = 0; i < folders.length; i++) { + if (isEqualOrParent(this.resource, folders[i].toResource('.vscode'), hasToIgnoreCase(this.resource))) { + const filename = path.basename(this.resource.fsPath); + if (TextFileEditorModel.WHITELIST_WORKSPACE_JSON.indexOf(filename) > -1) { + return `.vscode/${filename}`; + } + } + } + + return ''; + } + + private getTelemetryData(reason: number): Object { + const ext = path.extname(this.resource.fsPath); + const fileName = path.basename(this.resource.fsPath); + const telemetryData = { + mimeType: guessMimeTypes(this.resource.fsPath).join(', '), + ext, + path: this.hashService.createSHA1(this.resource.fsPath), + reason + }; + + if (ext === '.json' && TextFileEditorModel.WHITELIST_JSON.indexOf(fileName) > -1) { + telemetryData['whitelistedjson'] = fileName; + } + + /* __GDPR__FRAGMENT__ + "FileTelemetryData" : { + "mimeType" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "ext": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "path": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, + "reason": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "whitelistedjson": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + return telemetryData; } private doTouch(versionId: number): TPromise { diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts index 7a52cc4ef45..d9b1331feda 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts @@ -272,7 +272,7 @@ function toCSSSelector(str: string) { function _loadColorTheme(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): TPromise { if (Paths.extname(themeLocation.path) === '.json') { - return fileService.resolveContent(themeLocation).then(content => { + return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); if (errors.length > 0) { @@ -325,7 +325,7 @@ function getPListParser() { } function _loadSyntaxTokens(fileService: IFileService, themeLocation: URI, resultRules: ITokenColorizationRule[], resultColors: IColorMap): TPromise { - return fileService.resolveContent(themeLocation).then(content => { + return fileService.resolveContent(themeLocation, { encoding: 'utf8' }).then(content => { return getPListParser().then(parser => { try { let contentValue = parser.parse(content.value.toString()); diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts index 9fe9957a0ec..1fda23ca011 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts @@ -159,7 +159,7 @@ interface IconThemeDocument extends IconsAssociation { } function _loadIconThemeDocument(fileService: IFileService, location: URI): TPromise { - return fileService.resolveContent(location).then((content) => { + return fileService.resolveContent(location, { encoding: 'utf8' }).then((content) => { let errors: Json.ParseError[] = []; let contentValue = Json.parse(content.value.toString(), errors); if (errors.length > 0 || !contentValue) { diff --git a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts index 74c35400db8..a88b3af5604 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -1011,7 +1011,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }).then(value => { + return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None).then(value => { assert.equal(value.length, 2); let [first, second] = value; assert.equal(first.text, 'testing'); @@ -1032,7 +1032,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }); + return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None); }); }); @@ -1057,7 +1057,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }).then(value => { + return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; assert.equal(first.text, 'testing'); @@ -1074,7 +1074,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }).then(value => { + return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; assert.equal(first.text, 'testing'); @@ -1100,7 +1100,7 @@ suite('ExtHostLanguageFeatures', function () { } })); return rpcProtocol.sync().then(() => { - return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }).then(value => { + return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None).then(value => { assert.equal(value.length, 1); let [first] = value; assert.equal(first.text, 'range2'); @@ -1120,7 +1120,7 @@ suite('ExtHostLanguageFeatures', function () { })); return rpcProtocol.sync().then(() => { - return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }); + return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }, CancellationToken.None); }); }); 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 bd3d303ad19..d1a70c00f48 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -167,7 +167,7 @@ suite('ExtHostSearch', () => { test('no results', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { return TPromise.wrap(null); } }); @@ -185,9 +185,8 @@ suite('ExtHostSearch', () => { ]; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); - return TPromise.wrap(null); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { + return TPromise.wrap(reportedResults); } }); @@ -200,13 +199,12 @@ suite('ExtHostSearch', () => { test('Search canceled', async () => { let cancelRequested = false; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { return new TPromise((resolve, reject) => { token.onCancellationRequested(() => { cancelRequested = true; - progress.report(joinPath(options.folder, 'file1.ts')); - resolve(null); // or reject or nothing? + resolve([joinPath(options.folder, 'file1.ts')]); // or reject or nothing? }); }); } @@ -217,34 +215,9 @@ suite('ExtHostSearch', () => { assert(!results.length); }); - test('provider fail', async () => { - const reportedResults = [ - 'file1.ts', - 'file2.ts', - 'file3.ts', - ]; - - await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults - .map(relativePath => joinPath(options.folder, relativePath)) - .forEach(r => progress.report(r)); - - throw new Error('I broke'); - } - }); - - try { - await runFileSearch(getSimpleQuery()); - assert(false, 'Expected to fail'); - } catch { - // Expected to throw - } - }); - test('provider returns null', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { return null; } }); @@ -259,7 +232,7 @@ suite('ExtHostSearch', () => { test('all provider calls get global include/excludes', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { assert(options.excludes.length === 2 && options.includes.length === 2, 'Missing global include/excludes'); return TPromise.wrap(null); } @@ -288,7 +261,7 @@ suite('ExtHostSearch', () => { test('global/local include/excludes combined', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { if (options.folder.toString() === rootFolderA.toString()) { assert.deepEqual(options.includes.sort(), ['*.ts', 'foo']); assert.deepEqual(options.excludes.sort(), ['*.js', 'bar']); @@ -330,7 +303,7 @@ suite('ExtHostSearch', () => { test('include/excludes resolved correctly', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { assert.deepEqual(options.includes.sort(), ['*.jsx', '*.ts']); assert.deepEqual(options.excludes.sort(), []); @@ -373,11 +346,9 @@ suite('ExtHostSearch', () => { ]; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults - .map(relativePath => joinPath(options.folder, relativePath)) - .forEach(r => progress.report(r)); - return TPromise.wrap(null); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { + return TPromise.wrap(reportedResults + .map(relativePath => joinPath(options.folder, relativePath))); } }); @@ -406,7 +377,7 @@ suite('ExtHostSearch', () => { test('multiroot sibling exclude clause', async () => { await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { let reportedResults: URI[]; if (options.folder.fsPath === rootFolderA.fsPath) { reportedResults = [ @@ -422,8 +393,7 @@ suite('ExtHostSearch', () => { ].map(relativePath => joinPath(rootFolderB, relativePath)); } - reportedResults.forEach(r => progress.report(r)); - return TPromise.wrap(null); + return TPromise.wrap(reportedResults); } }); @@ -468,7 +438,7 @@ suite('ExtHostSearch', () => { ]); }); - test('max results = 1', async () => { + test.skip('max results = 1', async () => { const reportedResults = [ joinPath(rootFolderA, 'file1.ts'), joinPath(rootFolderA, 'file2.ts'), @@ -477,12 +447,10 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults - .forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => wasCanceled = true); - return TPromise.wrap(null); + return TPromise.wrap(reportedResults); } }); @@ -506,7 +474,7 @@ suite('ExtHostSearch', () => { assert(wasCanceled, 'Expected to be canceled when hitting limit'); }); - test('max results = 2', async () => { + test.skip('max results = 2', async () => { const reportedResults = [ joinPath(rootFolderA, 'file1.ts'), joinPath(rootFolderA, 'file2.ts'), @@ -515,11 +483,10 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => wasCanceled = true); - return TPromise.wrap(null); + return TPromise.wrap(reportedResults); } }); @@ -543,7 +510,7 @@ suite('ExtHostSearch', () => { assert(wasCanceled, 'Expected to be canceled when hitting limit'); }); - test('provider returns maxResults exactly', async () => { + test.skip('provider returns maxResults exactly', async () => { const reportedResults = [ joinPath(rootFolderA, 'file1.ts'), joinPath(rootFolderA, 'file2.ts'), @@ -551,11 +518,10 @@ suite('ExtHostSearch', () => { let wasCanceled = false; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => wasCanceled = true); - return TPromise.wrap(null); + return TPromise.wrap(reportedResults); } }); @@ -582,18 +548,17 @@ suite('ExtHostSearch', () => { test('multiroot max results', async () => { let cancels = 0; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { token.onCancellationRequested(() => cancels++); // Provice results async so it has a chance to invoke every provider return new TPromise(r => process.nextTick(r)) .then(() => { - [ + return [ 'file1.ts', 'file2.ts', 'file3.ts', - ].map(relativePath => joinPath(options.folder, relativePath)) - .forEach(r => progress.report(r)); + ].map(relativePath => joinPath(options.folder, relativePath)); }); } }); @@ -628,9 +593,8 @@ suite('ExtHostSearch', () => { ]; await registerTestFileSearchProvider({ - provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, progress: vscode.Progress, token: vscode.CancellationToken): Thenable { - reportedResults.forEach(r => progress.report(r)); - return TPromise.wrap(null); + provideFileSearchResults(query: vscode.FileSearchQuery, options: vscode.FileSearchOptions, token: vscode.CancellationToken): Thenable { + return TPromise.wrap(reportedResults); } }, fancyScheme); diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 0b15100e5bb..f66a8628123 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -7,7 +7,7 @@ import 'vs/workbench/parts/files/electron-browser/files.contribution'; // load our contribution into the test import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import { Promise, TPromise } from 'vs/base/common/winjs.base'; +import { TPromise } from 'vs/base/common/winjs.base'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import * as paths from 'vs/base/common/paths'; import * as resources from 'vs/base/common/resources'; @@ -303,13 +303,13 @@ export class TestExtensionService implements IExtensionService { _serviceBrand: any; onDidRegisterExtensions: Event = Event.None; onDidChangeExtensionsStatus: Event = Event.None; - activateByEvent(activationEvent: string): Promise { return TPromise.as(void 0); } - whenInstalledExtensionsRegistered(): Promise { return TPromise.as(true); } - getExtensions(): Promise { return TPromise.as([]); } - readExtensionPointContributions(extPoint: IExtensionPoint): Promise[]> { return TPromise.as(Object.create(null)); } + activateByEvent(activationEvent: string): TPromise { return TPromise.as(void 0); } + whenInstalledExtensionsRegistered(): TPromise { return TPromise.as(true); } + getExtensions(): TPromise { return TPromise.as([]); } + readExtensionPointContributions(extPoint: IExtensionPoint): TPromise[]> { return TPromise.as(Object.create(null)); } getExtensionsStatus(): { [id: string]: IExtensionsStatus; } { return Object.create(null); } canProfileExtensionHost(): boolean { return false; } - startExtensionHostProfile(): Promise { return TPromise.as(Object.create(null)); } + startExtensionHostProfile(): TPromise { return TPromise.as(Object.create(null)); } restartExtensionHost(): void { } startExtensionHost(): void { } stopExtensionHost(): void { } @@ -370,11 +370,11 @@ export class TestDialogService implements IDialogService { public _serviceBrand: any; - public confirm(confirmation: IConfirmation): Promise { + public confirm(confirmation: IConfirmation): TPromise { return TPromise.as({ confirmed: false }); } - public show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { + public show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): TPromise { return TPromise.as(0); } } @@ -603,7 +603,7 @@ export class TestEditorGroup implements IEditorGroupView { disposed: boolean; editors: ReadonlyArray = []; label: string; - whenRestored: Promise = TPromise.as(void 0); + whenRestored: TPromise = TPromise.as(void 0); element: HTMLElement; minimumWidth: number; maximumWidth: number; @@ -1094,7 +1094,7 @@ export class TestWindowService implements IWindowService { return TPromise.wrap(void 0); } - updateTouchBar(items: ISerializableCommandAction[][]): Promise { + updateTouchBar(items: ISerializableCommandAction[][]): TPromise { return TPromise.as(void 0); } } @@ -1292,27 +1292,31 @@ export class TestWindowsService implements IWindowsService { return TPromise.as(void 0); } - showPreviousWindowTab(): Promise { + newWindowTab(): TPromise { return TPromise.as(void 0); } - showNextWindowTab(): Promise { + showPreviousWindowTab(): TPromise { return TPromise.as(void 0); } - moveWindowTabToNewWindow(): Promise { + showNextWindowTab(): TPromise { return TPromise.as(void 0); } - mergeAllWindowTabs(): Promise { + moveWindowTabToNewWindow(): TPromise { return TPromise.as(void 0); } - toggleWindowTabsBar(): Promise { + mergeAllWindowTabs(): TPromise { return TPromise.as(void 0); } - updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): Promise { + toggleWindowTabsBar(): TPromise { + return TPromise.as(void 0); + } + + updateTouchBar(windowId: number, items: ISerializableCommandAction[][]): TPromise { return TPromise.as(void 0); } @@ -1343,10 +1347,6 @@ export class TestWindowsService implements IWindowsService { return TPromise.as(void 0); } - openAccessibilityOptions(): TPromise { - return TPromise.as(void 0); - } - openAboutDialog(): TPromise { return TPromise.as(void 0); } diff --git a/test/all.js b/test/all.js index 14c6897b197..2222c8995e9 100644 --- a/test/all.js +++ b/test/all.js @@ -49,6 +49,7 @@ function main() { nodeMain: __filename, baseUrl: path.join(path.dirname(__dirname), 'src'), paths: { + 'vs/css': '../test/css.mock', 'vs': `../${ out }/vs`, 'lib': `../${ out }/lib`, 'bootstrap': `../${ out }/bootstrap` diff --git a/test/css.mock.js b/test/css.mock.js new file mode 100644 index 00000000000..1829c6ae48e --- /dev/null +++ b/test/css.mock.js @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +define([], function() { + return { + load: function(name, req, load) { + load({}); + } + }; +}); diff --git a/test/electron/renderer.js b/test/electron/renderer.js index 63729010808..35f970c5c8c 100644 --- a/test/electron/renderer.js +++ b/test/electron/renderer.js @@ -23,6 +23,15 @@ let _tests_glob = '**/test/**/*.test.js'; let loader; let _out; +function uriFromPath(_path) { + var pathName = path.resolve(_path).replace(/\\/g, '/'); + if (pathName.length > 0 && pathName.charAt(0) !== '/') { + pathName = '/' + pathName; + } + + return encodeURI('file://' + pathName); +} + function initLoader(opts) { let outdir = opts.build ? 'out-build' : 'out'; _out = path.join(__dirname, `../../${outdir}`); @@ -33,7 +42,7 @@ function initLoader(opts) { nodeRequire: require, nodeMain: __filename, catchError: true, - baseUrl: path.join(__dirname, '../../src'), + baseUrl: uriFromPath(path.join(__dirname, '../../src')), paths: { 'vs': `../${outdir}/vs`, 'lib': `../${outdir}/lib`, diff --git a/test/smoke/package.json b/test/smoke/package.json index 0ce01ada8b8..c3d79b62111 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": "1.7.7", + "electron": "^2.0.6", "htmlparser2": "^3.9.2", "mkdirp": "^0.5.1", "mocha": "^5.2.0", diff --git a/test/smoke/src/areas/extensions/extensions.ts b/test/smoke/src/areas/extensions/extensions.ts index c904df46862..be72895ae26 100644 --- a/test/smoke/src/areas/extensions/extensions.ts +++ b/test/smoke/src/areas/extensions/extensions.ts @@ -6,7 +6,7 @@ import { Viewlet } from '../workbench/viewlet'; import { Code } from '../../vscode/code'; -const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] input.search-box'; +const SEARCH_BOX = 'div.extensions-viewlet[id="workbench.view.extensions"] .monaco-editor textarea'; export class Extensions extends Viewlet { @@ -27,7 +27,7 @@ export class Extensions extends Viewlet { async searchForExtension(name: string): Promise { await this.code.waitAndClick(SEARCH_BOX); await this.code.waitForActiveElement(SEARCH_BOX); - await this.code.waitForSetValue(SEARCH_BOX, `name:"${name}"`); + await this.code.waitForTypeInEditor(SEARCH_BOX, `name:"${name}"`); } async installExtension(name: string): Promise { diff --git a/test/smoke/src/areas/quickinput/quickinput.ts b/test/smoke/src/areas/quickinput/quickinput.ts index c13b308c0d9..aab4253c970 100644 --- a/test/smoke/src/areas/quickinput/quickinput.ts +++ b/test/smoke/src/areas/quickinput/quickinput.ts @@ -25,4 +25,13 @@ export class QuickInput { private async waitForQuickInputClosed(): Promise { await this.code.waitForElement(QuickInput.QUICK_INPUT, r => !!r && r.attributes.style.indexOf('display: none;') !== -1); } + + async selectQuickInputElement(index: number): Promise { + await this.waitForQuickInputOpened(); + for (let from = 0; from < index; from++) { + await this.code.dispatchKeybinding('down'); + } + await this.code.dispatchKeybinding('enter'); + await this.waitForQuickInputClosed(); + } } diff --git a/test/smoke/src/areas/statusbar/statusbar.test.ts b/test/smoke/src/areas/statusbar/statusbar.test.ts index dfb1d7550d3..c9ba22c290a 100644 --- a/test/smoke/src/areas/statusbar/statusbar.test.ts +++ b/test/smoke/src/areas/statusbar/statusbar.test.ts @@ -38,11 +38,11 @@ export function setup() { await app.workbench.quickopen.waitForQuickOpenOpened(); await app.workbench.quickopen.closeQuickOpen(); await app.workbench.statusbar.clickOn(StatusBarElement.ENCODING_STATUS); - await app.workbench.quickopen.waitForQuickOpenOpened(); - await app.workbench.quickopen.closeQuickOpen(); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.closeQuickInput(); await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); - await app.workbench.quickopen.waitForQuickOpenOpened(); - await app.workbench.quickopen.closeQuickOpen(); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.closeQuickInput(); await app.workbench.statusbar.clickOn(StatusBarElement.LANGUAGE_STATUS); await app.workbench.quickopen.waitForQuickOpenOpened(); await app.workbench.quickopen.closeQuickOpen(); @@ -84,8 +84,8 @@ export function setup() { await app.workbench.quickopen.openFile('app.js'); await app.workbench.statusbar.clickOn(StatusBarElement.EOL_STATUS); - await app.workbench.quickopen.waitForQuickOpenOpened(); - await app.workbench.quickopen.selectQuickOpenElement(1); + await app.workbench.quickinput.waitForQuickInputOpened(); + await app.workbench.quickinput.selectQuickInputElement(1); await app.workbench.statusbar.waitForEOL('CRLF'); }); diff --git a/test/smoke/yarn.lock b/test/smoke/yarn.lock index c628b2089e2..55ab35aceea 100644 --- a/test/smoke/yarn.lock +++ b/test/smoke/yarn.lock @@ -41,9 +41,9 @@ version "8.0.33" resolved "https://registry.yarnpkg.com/@types/node/-/node-8.0.33.tgz#1126e94374014e54478092830704f6ea89df04cd" -"@types/node@^7.0.18": - version "7.0.46" - resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.46.tgz#c3dedd25558c676b3d6303e51799abb9c3f8f314" +"@types/node@^8.0.24": + version "8.10.23" + resolved "https://registry.yarnpkg.com/@types/node/-/node-8.10.23.tgz#e5ccfdafff42af5397c29669b6d7d65f7d629a00" "@types/rimraf@2.0.2": version "2.0.2" @@ -493,11 +493,11 @@ electron-download@^3.0.1: semver "^5.3.0" sumchecker "^1.2.0" -electron@1.7.7: - version "1.7.7" - resolved "https://registry.yarnpkg.com/electron/-/electron-1.7.7.tgz#cfd89ca9eba79d763ac0b0c6dcc583792097b9b6" +electron@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/electron/-/electron-2.0.6.tgz#8e5c1bd2ebc08fa7a6ee906de3753c1ece9d7300" dependencies: - "@types/node" "^7.0.18" + "@types/node" "^8.0.24" electron-download "^3.0.1" extract-zip "^1.0.3" diff --git a/yarn.lock b/yarn.lock index 556d40b5a1f..9cb248b5d3f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -294,10 +294,6 @@ asar@^0.14.0: mksnapshot "^0.3.0" tmp "0.0.28" -asn1@0.1.11: - version "0.1.11" - resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.1.11.tgz#559be18376d08a4ec4dbe80877d27818639b2df7" - asn1@~0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" @@ -306,10 +302,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" -assert-plus@^0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.1.5.tgz#ee74009413002d84cec7219c6ac811812e723160" - assert-plus@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" @@ -336,10 +328,6 @@ async@~0.2.8: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" -async@~0.9.0: - version "0.9.2" - resolved "https://registry.yarnpkg.com/async/-/async-0.9.2.tgz#aea74d5e61c1f899613bf64bda66d4c78f2fd17d" - asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" @@ -367,27 +355,10 @@ aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" -aws-sign@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/aws-sign/-/aws-sign-0.3.0.tgz#3d81ca69b474b1e16518728b51c24ff0bbedc6e9" - aws4@^1.2.1, aws4@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" -azure-storage@^0.3.1: - version "0.3.3" - resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-0.3.3.tgz#5e1920ba75c678cb3f5e52a89136ef36210b58a1" - dependencies: - extend "~1.2.1" - mime "~1.2.4" - node-uuid "~1.4.0" - request "~2.27.0" - underscore "~1.4.4" - validator "~3.1.0" - xml2js "0.2.7" - xmlbuilder "0.4.3" - azure-storage@^1.3.1: version "1.4.0" resolved "https://registry.yarnpkg.com/azure-storage/-/azure-storage-1.4.0.tgz#fb52fa68b3efa6980c33fd7c5cd489b7adc46ed1" @@ -492,12 +463,6 @@ boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" -boom@0.4.x: - version "0.4.2" - resolved "https://registry.yarnpkg.com/boom/-/boom-0.4.2.tgz#7a636e9ded4efcefb19cef4947a3c67dfaee911b" - dependencies: - hoek "0.9.x" - boom@2.x.x: version "2.10.1" resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" @@ -851,12 +816,6 @@ combined-stream@^1.0.5, combined-stream@~1.0.5: dependencies: delayed-stream "~1.0.0" -combined-stream@~0.0.4: - version "0.0.7" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-0.0.7.tgz#0137e657baa5a7541c57ac37ac5fc07d73b4dc1f" - dependencies: - delayed-stream "0.0.5" - commander@*, commander@^2.11.0: version "2.15.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.15.0.tgz#ad2a23a1c3b036e392469b8012cec6b33b4c1322" @@ -928,10 +887,6 @@ convert-source-map@1.X, convert-source-map@^1.1.1: version "1.5.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" -cookie-jar@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/cookie-jar/-/cookie-jar-0.3.0.tgz#bc9a27d4e2b97e186cd57c9e2063cb99fa68cccc" - cookie-signature@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" @@ -973,12 +928,6 @@ crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" -cryptiles@0.2.x: - version "0.2.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-0.2.2.tgz#ed91ff1f17ad13d3748288594f8a48a0d26f325c" - dependencies: - boom "0.4.x" - cryptiles@2.x.x: version "2.0.5" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" @@ -1077,10 +1026,6 @@ cssom@0.3.x, "cssom@>= 0.3.0 < 0.4.0": dependencies: cssom "0.3.x" -ctype@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/ctype/-/ctype-0.5.3.tgz#82c18c2461f74114ef16c135224ad0b9144ca12f" - cuint@^0.2.1: version "0.2.2" resolved "https://registry.yarnpkg.com/cuint/-/cuint-0.2.2.tgz#408086d409550c2631155619e9fa7bcadc3b991b" @@ -1204,10 +1149,6 @@ del@^2.0.2: pinkie-promise "^2.0.0" rimraf "^2.2.8" -delayed-stream@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.5.tgz#d4b1f43a93e8296dfe02694f4680bc37a313c73f" - delayed-stream@0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-0.0.6.tgz#a2646cb7ec3d5d7774614670a7a65de0c173edbc" @@ -1936,22 +1877,10 @@ for-own@^1.0.0: dependencies: for-in "^1.0.1" -forever-agent@~0.5.0: - version "0.5.2" - resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.5.2.tgz#6d0e09c4921f94a27f63d3b49c5feff1ea4c5130" - forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" -form-data@~0.1.0: - version "0.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-0.1.4.tgz#91abd788aba9702b1aabfa8bc01031a2ac9e3b12" - dependencies: - async "~0.9.0" - combined-stream "~0.0.4" - mime "~1.2.11" - form-data@~1.0.0-rc4: version "1.0.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-1.0.1.tgz#ae315db9a4907fa065502304a66d7733475ee37c" @@ -2699,15 +2628,6 @@ has@^1.0.1: dependencies: function-bind "^1.0.2" -hawk@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-1.0.0.tgz#b90bb169807285411da7ffcb8dd2598502d3b52d" - dependencies: - boom "0.4.x" - cryptiles "0.2.x" - hoek "0.9.x" - sntp "0.2.x" - hawk@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" @@ -2726,10 +2646,6 @@ hawk@~6.0.2: hoek "4.x.x" sntp "2.x.x" -hoek@0.9.x: - version "0.9.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-0.9.1.tgz#3d322462badf07716ea7eb85baf88079cddce505" - hoek@2.x.x: version "2.16.3" resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" @@ -2779,14 +2695,6 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-signature@~0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-0.10.1.tgz#4fbdac132559aa8323121e540779c0a012b27e66" - dependencies: - asn1 "0.1.11" - assert-plus "^0.1.5" - ctype "0.5.3" - http-signature@~1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" @@ -3261,7 +3169,7 @@ json-stable-stringify@^1.0.0, json-stable-stringify@^1.0.1: dependencies: jsonify "~0.0.0" -json-stringify-safe@~5.0.0, json-stringify-safe@~5.0.1: +json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" @@ -3778,14 +3686,14 @@ mime-types@^2.1.11, mime-types@^2.1.12, mime-types@~2.1.15, mime-types@~2.1.16, dependencies: mime-db "~1.30.0" -mime@1.2.11, mime@~1.2.11, mime@~1.2.4, mime@~1.2.9: - version "1.2.11" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.2.11.tgz#58203eed86e3a5ef17aed2b7d9ebd47f0a60dd10" - mime@1.4.1, mime@^1.3.4: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" +mime@^1.4.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" + mimic-fn@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" @@ -4093,10 +4001,6 @@ number-is-nan@^1.0.0: version "1.4.3" resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.3.tgz#64348e3b3d80f035b40ac11563d278f8b72db89c" -oauth-sign@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.3.0.tgz#cb540f93bb2b22a7d5941691a288d60e8ea9386e" - oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" @@ -4772,10 +4676,6 @@ qs@6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" -qs@~0.6.0: - version "0.6.6" - resolved "https://registry.yarnpkg.com/qs/-/qs-0.6.6.tgz#6e015098ff51968b8a3c819001d5f2c89bc4b107" - qs@~6.2.0: version "6.2.3" resolved "https://registry.yarnpkg.com/qs/-/qs-6.2.3.tgz#1cfcb25c10a9b2b483053ff39f5dfc9233908cfe" @@ -5103,23 +5003,6 @@ request@2.81.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@~2.27.0: - version "2.27.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.27.0.tgz#dfb1a224dd3a5a9bade4337012503d710e538668" - dependencies: - aws-sign "~0.3.0" - cookie-jar "~0.3.0" - forever-agent "~0.5.0" - form-data "~0.1.0" - hawk "~1.0.0" - http-signature "~0.10.0" - json-stringify-safe "~5.0.0" - mime "~1.2.9" - node-uuid "~1.4.0" - oauth-sign "~0.3.0" - qs "~0.6.0" - tunnel-agent "~0.3.0" - request@~2.74.0: version "2.74.0" resolved "https://registry.yarnpkg.com/request/-/request-2.74.0.tgz#7693ca768bbb0ea5c8ce08c084a45efa05b892ab" @@ -5369,12 +5252,6 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -sntp@0.2.x: - version "0.2.4" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-0.2.4.tgz#fb885f18b0f3aad189f824862536bceeec750900" - dependencies: - hoek "0.9.x" - sntp@1.x.x: version "1.0.9" resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" @@ -5860,10 +5737,6 @@ tunnel-agent@^0.6.0: dependencies: safe-buffer "^5.0.1" -tunnel-agent@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.3.0.tgz#ad681b68f5321ad2827c4cfb1b7d5df2cfe942ee" - tunnel-agent@~0.4.1: version "0.4.3" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" @@ -6056,10 +5929,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validator@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/validator/-/validator-3.1.0.tgz#2ea1ff7e92254d69367f385f015299e5ead8755b" - validator@~3.22.2: version "3.22.2" resolved "https://registry.yarnpkg.com/validator/-/validator-3.22.2.tgz#6f297ae67f7f82acc76d0afdb49f18d9a09c18c0" @@ -6210,9 +6079,9 @@ vscode-chokidar@1.6.2: optionalDependencies: vscode-fsevents "0.3.8" -vscode-debugprotocol@1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.30.0.tgz#ece6d8559733e87bc7a2147b385899777a92af69" +vscode-debugprotocol@1.31.0: + version "1.31.0" + resolved "https://registry.yarnpkg.com/vscode-debugprotocol/-/vscode-debugprotocol-1.31.0.tgz#8467eeabeea65f52da5ac03b03c18e10e8b95eb4" vscode-fsevents@0.3.8: version "0.3.8" @@ -6256,9 +6125,9 @@ vscode-textmate@^4.0.1: dependencies: oniguruma "^7.0.0" -vscode-xterm@3.6.0-beta11: - version "3.6.0-beta11" - resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.6.0-beta11.tgz#d492ae1baf5cf9884f7b49a4fadc0c354248c830" +vscode-xterm@3.7.0-beta3: + version "3.7.0-beta3" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.7.0-beta3.tgz#2306f9650ee2f55637ba1804d10c5ffb9ef944f6" vso-node-api@^6.1.2-preview: version "6.1.2-preview" @@ -6314,6 +6183,10 @@ windows-process-tree@0.2.2: dependencies: nan "^2.6.2" +winreg@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.4.tgz#ba065629b7a925130e15779108cf540990e98d1b" + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"