diff --git a/.github/classifier.yml b/.github/classifier.yml index 7f7e749a8b4..d66324e01fa 100644 --- a/.github/classifier.yml +++ b/.github/classifier.yml @@ -6,7 +6,7 @@ api: [], color-picker: [], css-less-sass: [], - debug: [ weinand ], + debug: [ isidorn ], diff-editor: [], editor: [], editor-1000-limit: [], diff --git a/.github/issue_template.md b/.github/issue_template.md index a7d829e091a..b1a98a132ec 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -1,6 +1,6 @@ - + - + - VSCode Version: - OS Version: @@ -10,4 +10,4 @@ Steps to Reproduce: 2. -Does the issue happen when all extensions are disabled?: Yes/No +Does this issue occur when all extensions are disabled?: Yes/No diff --git a/.travis.yml b/.travis.yml index cdf6766096d..1f28f9d55cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,6 +31,7 @@ addons: - libsecret-1-dev before_install: + - export GITHUB_TOKEN=$PUBLIC_GITHUB_TOKEN - git submodule update --init --recursive - git clone --depth 1 https://github.com/creationix/nvm.git ./.nvm - source ./.nvm/nvm.sh @@ -53,6 +54,7 @@ install: script: - node_modules/.bin/gulp hygiene - node_modules/.bin/gulp electron --silent + - node_modules/.bin/tsc -p ./src/tsconfig.monaco.json --noEmit - node_modules/.bin/gulp compile --silent --max_old_space_size=4096 - node_modules/.bin/gulp optimize-vscode --silent --max_old_space_size=4096 - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/test.sh --coverage --reporter dot; else ./scripts/test.sh --reporter dot; fi diff --git a/.vscode/settings.json b/.vscode/settings.json index d4cdcdbedd8..d3e36aa8ebf 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,6 +10,9 @@ "when": "$(basename).ts" } }, + "files.associations": { + "OSSREADME.json": "jsonc" + }, "search.exclude": { "**/node_modules": true, "**/bower_components": true, @@ -36,4 +39,4 @@ } ], "typescript.tsdk": "node_modules/typescript/lib" -} \ No newline at end of file +} diff --git a/OSSREADME.json b/OSSREADME.json index ccc6ceb8c2e..b24bd919d93 100644 --- a/OSSREADME.json +++ b/OSSREADME.json @@ -1,65 +1,5 @@ // Listing in here platform dependencies that come in at build time - -[{ - "isLicense": true, - "name": "async-each", - "repositoryURL": "https://github.com/paulmillr/async-each", - "license": "MIT", - "licenseDetail": [ - "The MIT License (MIT)", - "", - "Copyright (c) 2016 Paul Miller [(paulmillr.com)](http://paulmillr.com)", - "", - "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." - ], - "isProd": true -}, -{ - "isLicense": true, - "name": "chokidar", - "repositoryURL": "https://github.com/paulmillr/chokidar", - "license": "MIT", - "licenseDetail": [ - "The MIT license.", - "", - "Copyright (c) 2012 - 2016 Paul Miller [paulmillr.com](http://paulmillr.com) & Elan Shanker", - "", - "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." - ], - "isProd": true -}, +[ { "name": "chromium", "version": "58.0.3029.110", @@ -123,40 +63,56 @@ "isProd": true }, { - "isLicense": true, - "name": "ripgrep", - "repositoryURL": "https://github.com/BurntSushi/ripgrep", + "name": "spdlog original", + "version": "0.14.0", + "repositoryURL": "https://github.com/gabime/spdlog", "license": "MIT", + "isProd": true +}, + +// -------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------- +// ONLY LICENSE TEXT AFTER THIS MARKER +// Each license entry should contain precisely the following fields: +// "isLicense": true +// "name": string +// "licenseDetail": string[] +// Furthermore, each license entry should contain a clear reason for existance. + +{ + // Reason: Added here because the repo at https://github.com/paulmillr/async-each + // does not include a LICENSE file that can be machine processed. + "isLicense": true, + "name": "async-each", "licenseDetail": [ "The MIT License (MIT)", "", - "Copyright (c) 2015 Andrew Gallant", + "Copyright (c) 2016 Paul Miller [(paulmillr.com)](http://paulmillr.com)", "", - "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:", + "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 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", + "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." - ], - "isProd": true + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ] }, { + // Reason: The license at https://github.com/aadsm/jschardet/blob/master/LICENSE + // does not include a clear Copyright statement and does not credit authors. "isLicense": true, "name": "jschardet", - "repositoryURL": "https://github.com/aadsm/jschardet", - "license": "LGPL", "licenseDetail": [ "Chardet was originally ported from C++ by Mark Pilgrim. It is now maintained", " by Dan Blanchard and Ian Cordasco, and was formerly maintained by Erik Rose.", @@ -670,12 +626,117 @@ " Ty Coon, President of Vice", "", "That's all there is to it!" - ], - "isProd": true + ] +}, +{ + // Added here because the module `parse5` has a dependency to it. + // The module `parse5` is shipped via the `extension-editing` built-in extension. + // The module `parse5` does not want to remove it https://github.com/inikulin/parse5/issues/225 + "isLicense": true, + "name": "@types/node", + "licenseDetail": [ + "This project is licensed under the MIT license.", + "Copyrights are respective of each contributor listed at the beginning of each definition file.", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] +}, +{ + // We override the license that gets discovered at + // https://github.com/Microsoft/TypeScript/blob/master/LICENSE.txt + // because it does not contain a Copyright statement + "isLicense": true, + "name": "typescript", + "licenseDetail": [ + "Copyright (c) Microsoft Corporation. All rights reserved.", + "", + "Apache License", + "", + "Version 2.0, January 2004", + "", + "http://www.apache.org/licenses/", + "", + "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", + "", + "1. Definitions.", + "", + "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", + "", + "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", + "", + "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", + "", + "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", + "", + "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", + "", + "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", + "", + "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", + "", + "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", + "", + "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", + "", + "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", + "", + "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", + "", + "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", + "", + "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", + "", + "You must give any other recipients of the Work or Derivative Works a copy of this License; and", + "", + "You must cause any modified files to carry prominent notices stating that You changed the files; and", + "", + "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", + "", + "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", + "", + "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", + "", + "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", + "", + "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", + "", + "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", + "", + "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", + "", + "END OF TERMS AND CONDITIONS" + ] }, { "isLicense": true, - "name": "@types/node", + "name": "noice-json-rpc", + "repositoryURL": "https://github.com/nojvek/noice-json-rpc", + "license": "MIT", + "licenseDetail": [ + "Copyright (c) Manoj Patel", + "", + "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." + ] +}, +{ + "isLicense": true, + "name": "@types/source-map", "repositoryURL": "https://www.github.com/DefinitelyTyped/DefinitelyTyped", "license": "MIT", "licenseDetail": [ @@ -687,126 +748,57 @@ "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." - ], - "isProd": true + ] }, { "isLicense": true, - "name": "jsonfile", - "repositoryURL": "https+ssh://git@github.com/jprichardson/node-jsonfile", - "license": "MIT", + "name": "markdown-it-named-headers", "licenseDetail": [ - "(The MIT License)", + "Copyright (c) markdown-it-named-headers authors", "", - "Copyright (c) 2012-2015, JP Richardson ", + "This is free and unencumbered software released into the public domain.", "", - "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:", + "Anyone is free to copy, modify, publish, use, compile, sell, or", + "distribute this software, either in source code form or as a compiled", + "binary, for any purpose, commercial or non-commercial, and by any", + "means.", "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", + "In jurisdictions that recognize copyright laws, the author or authors", + "of this software dedicate any and all copyright interest in the", + "software to the public domain. We make this dedication for the benefit", + "of the public at large and to the detriment of our heirs and", + "successors. We intend this dedication to be an overt act of", + "relinquishment in perpetuity of all present and future rights to this", + "software under copyright law.", "", - "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." - ], - "isProd": true + "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 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.", + "", + "For more information, please refer to " + ] }, { "isLicense": true, - "name": "string_decoder", - "repositoryURL": "https://github.com/rvagg/string_decoder", - "license": "MIT", + "name": "uc.micro", "licenseDetail": [ - "The MIT License (MIT)", + " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE", + " Version 2, December 2004", "", - "Node.js is licensed for use as follows:", + " Copyright (C) 2004 Sam Hocevar ", "", - "\"\"\"", - "Copyright Node.js contributors. All rights reserved.", + " Everyone is permitted to copy and distribute verbatim or modified", + " copies of this license document, and changing it is allowed as long", + " as the name is changed.", "", - "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:", + " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE", + " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION", "", - "The above copyright notice and this permission notice shall be included in", - "all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", - "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", - "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", - "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", - "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING", - "FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS", - "IN THE SOFTWARE.", - "\"\"\"", - "", - "This license applies to parts of Node.js originating from the", - "https://github.com/joyent/node repository:", - "", - "\"\"\"", - "Copyright Joyent, Inc. and other Node contributors. All rights reserved.", - "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.", - "\"\"\"" - ], - "isProd": true -}, -{ - "name": "spdlog original", - "version": "0.14.0", - "repositoryURL": "https://github.com/gabime/spdlog", - "license": "MIT", - "isProd": true -}, -{ - "isLicense": true, - "name": "spdlog", - "version": "0.14.0", - "repositoryURL": "https://github.com/gabime/spdlog", - "license": "MIT", - "licenseDetail": [ - "MIT License", - "", - "Copyright (c) Microsoft Corporation. All rights reserved.", - "", - "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" + " 0. You just DO WHAT THE FUCK YOU WANT TO." ] } ] diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index 00e7f5e3da9..6911462197f 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -1846,4 +1846,4 @@ 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 +END OF vscode-swift NOTICES AND INFORMATION \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index d9471f2a8f8..3ece36f7a3e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,7 @@ install: build_script: - yarn - .\node_modules\.bin\gulp electron + - .\node_modules\.bin\tsc -p .\src\tsconfig.monaco.json --noEmit - npm run compile test_script: diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index a74246fa4d6..45296f50324 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -45,8 +45,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.19.7' }, - { name: 'ms-vscode.node-debug2', version: '1.19.3' } + { name: 'ms-vscode.node-debug', version: '1.20.2' }, + { name: 'ms-vscode.node-debug2', version: '1.20.0' } ]; const excludedExtensions = [ @@ -82,7 +82,7 @@ const vscodeResources = [ 'out-build/vs/workbench/parts/welcome/walkThrough/**/*.md', 'out-build/vs/workbench/services/files/**/*.exe', 'out-build/vs/workbench/services/files/**/*.md', - 'out-build/vs/code/electron-browser/sharedProcess.js', + 'out-build/vs/code/electron-browser/sharedProcess/sharedProcess.js', '!**/test/**' ]; @@ -296,6 +296,7 @@ function packageTask(platform, arch, opts) { .pipe(util.cleanNodeModule('oniguruma', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node', 'src/*.js'])) .pipe(util.cleanNodeModule('windows-mutex', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('native-keymap', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node'])) + .pipe(util.cleanNodeModule('native-is-elevated', ['binding.gyp', 'build/**', 'src/**', 'deps/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('native-watchdog', ['binding.gyp', 'build/**', 'src/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('spdlog', ['binding.gyp', 'build/**', 'deps/**', 'src/**', 'test/**'], ['**/*.node'])) .pipe(util.cleanNodeModule('jschardet', ['dist/**'])) diff --git a/build/lib/compilation.js b/build/lib/compilation.js index 715ceeb4ede..837202d2126 100644 --- a/build/lib/compilation.js +++ b/build/lib/compilation.js @@ -22,6 +22,9 @@ var rootDir = path.join(__dirname, '../../src'); var options = require('../../src/tsconfig.json').compilerOptions; options.verbose = false; options.sourceMap = true; +if (process.env['VSCODE_NO_SOURCEMAP']) { + options.sourceMap = false; +} options.rootDir = rootDir; options.sourceRoot = util.toFileUri(rootDir); function createCompile(build, emitError) { @@ -58,9 +61,13 @@ function compileTask(out, build) { return function () { var compile = createCompile(build, true); var src = es.merge(gulp.src('src/**', { base: 'src' }), gulp.src('node_modules/typescript/lib/lib.d.ts')); + // Do not write .d.ts files to disk, as they are not needed there. + var dtsFilter = util.filter(function (data) { return !/\.d\.ts$/.test(data.path); }); return src .pipe(compile()) + .pipe(dtsFilter) .pipe(gulp.dest(out)) + .pipe(dtsFilter.restore) .pipe(monacodtsTask(out, false)); }; } @@ -70,54 +77,19 @@ function watchTask(out, build) { var compile = createCompile(build); var src = es.merge(gulp.src('src/**', { base: 'src' }), gulp.src('node_modules/typescript/lib/lib.d.ts')); var watchSrc = watch('src/**', { base: 'src' }); + // Do not write .d.ts files to disk, as they are not needed there. + var dtsFilter = util.filter(function (data) { return !/\.d\.ts$/.test(data.path); }); return watchSrc .pipe(util.incremental(compile, src, true)) + .pipe(dtsFilter) .pipe(gulp.dest(out)) + .pipe(dtsFilter.restore) .pipe(monacodtsTask(out, true)); }; } exports.watchTask = watchTask; -function reloadTypeScriptNodeModule() { - var util = require('gulp-util'); - function log(message) { - var rest = []; - for (var _i = 1; _i < arguments.length; _i++) { - rest[_i - 1] = arguments[_i]; - } - util.log.apply(util, [util.colors.cyan('[memory watch dog]'), message].concat(rest)); - } - function heapUsed() { - return (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + ' MB'; - } - return es.through(function (data) { - this.emit('data', data); - }, function () { - log('memory usage after compilation finished: ' + heapUsed()); - // It appears we are running into some variant of - // https://bugs.chromium.org/p/v8/issues/detail?id=2073 - // - // Even though all references are dropped, some - // optimized methods in the TS compiler end up holding references - // to the entire TypeScript language host (>600MB) - // - // The idea is to force v8 to drop references to these - // optimized methods, by "reloading" the typescript node module - log('Reloading typescript node module...'); - var resolvedName = require.resolve('typescript'); - var originalModule = require.cache[resolvedName]; - delete require.cache[resolvedName]; - var newExports = require('typescript'); - require.cache[resolvedName] = originalModule; - for (var prop in newExports) { - if (newExports.hasOwnProperty(prop)) { - originalModule.exports[prop] = newExports[prop]; - } - } - log('typescript node module reloaded.'); - this.emit('end'); - }); -} function monacodtsTask(out, isWatch) { + var basePath = path.resolve(process.cwd(), out); var neededFiles = {}; monacodts.getFilesToWatch(out).forEach(function (filePath) { filePath = path.normalize(filePath); @@ -160,7 +132,7 @@ function monacodtsTask(out, isWatch) { })); } resultStream = es.through(function (data) { - var filePath = path.normalize(data.path); + var filePath = path.normalize(path.resolve(basePath, data.relative)); if (neededFiles[filePath]) { setInputFile(filePath, data.contents.toString()); } diff --git a/build/lib/compilation.ts b/build/lib/compilation.ts index f1ca31f36e8..3d64fe6f885 100644 --- a/build/lib/compilation.ts +++ b/build/lib/compilation.ts @@ -25,6 +25,9 @@ const rootDir = path.join(__dirname, '../../src'); const options = require('../../src/tsconfig.json').compilerOptions; options.verbose = false; options.sourceMap = true; +if (process.env['VSCODE_NO_SOURCEMAP']) { // To be used by developers in a hurry + options.sourceMap = false; +} options.rootDir = rootDir; options.sourceRoot = util.toFileUri(rootDir); @@ -49,7 +52,6 @@ function createCompile(build: boolean, emitError?: boolean): (token?: util.ICanc .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(ts(token)) - // .pipe(build ? reloadTypeScriptNodeModule() : es.through()) .pipe(noDeclarationsFilter) .pipe(build ? nls() : es.through()) .pipe(noDeclarationsFilter.restore) @@ -75,9 +77,14 @@ export function compileTask(out: string, build: boolean): () => NodeJS.ReadWrite gulp.src('node_modules/typescript/lib/lib.d.ts'), ); + // Do not write .d.ts files to disk, as they are not needed there. + const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + return src .pipe(compile()) + .pipe(dtsFilter) .pipe(gulp.dest(out)) + .pipe(dtsFilter.restore) .pipe(monacodtsTask(out, false)); }; } @@ -93,62 +100,22 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt ); const watchSrc = watch('src/**', { base: 'src' }); + // Do not write .d.ts files to disk, as they are not needed there. + const dtsFilter = util.filter(data => !/\.d\.ts$/.test(data.path)); + return watchSrc .pipe(util.incremental(compile, src, true)) + .pipe(dtsFilter) .pipe(gulp.dest(out)) + .pipe(dtsFilter.restore) .pipe(monacodtsTask(out, true)); }; } -function reloadTypeScriptNodeModule(): NodeJS.ReadWriteStream { - var util = require('gulp-util'); - function log(message: any, ...rest: any[]): void { - util.log(util.colors.cyan('[memory watch dog]'), message, ...rest); - } - - function heapUsed(): string { - return (process.memoryUsage().heapUsed / 1024 / 1024).toFixed(2) + ' MB'; - } - - return es.through(function (data) { - this.emit('data', data); - }, function () { - - log('memory usage after compilation finished: ' + heapUsed()); - - // It appears we are running into some variant of - // https://bugs.chromium.org/p/v8/issues/detail?id=2073 - // - // Even though all references are dropped, some - // optimized methods in the TS compiler end up holding references - // to the entire TypeScript language host (>600MB) - // - // The idea is to force v8 to drop references to these - // optimized methods, by "reloading" the typescript node module - - log('Reloading typescript node module...'); - - var resolvedName = require.resolve('typescript'); - - var originalModule = require.cache[resolvedName]; - delete require.cache[resolvedName]; - var newExports = require('typescript'); - require.cache[resolvedName] = originalModule; - - for (var prop in newExports) { - if (newExports.hasOwnProperty(prop)) { - originalModule.exports[prop] = newExports[prop]; - } - } - - log('typescript node module reloaded.'); - - this.emit('end'); - }); -} - function monacodtsTask(out: string, isWatch: boolean): NodeJS.ReadWriteStream { + const basePath = path.resolve(process.cwd(), out); + const neededFiles: { [file: string]: boolean; } = {}; monacodts.getFilesToWatch(out).forEach(function (filePath) { filePath = path.normalize(filePath); @@ -196,7 +163,7 @@ function monacodtsTask(out: string, isWatch: boolean): NodeJS.ReadWriteStream { } resultStream = es.through(function (data) { - const filePath = path.normalize(data.path); + const filePath = path.normalize(path.resolve(basePath, data.relative)); if (neededFiles[filePath]) { setInputFile(filePath, data.contents.toString()); } diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index 0e4c15c7408..93cf72c39c9 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -70,6 +70,10 @@ "name": "vs/workbench/parts/markers", "project": "vscode-workbench" }, + { + "name": "vs/workbench/parts/logs", + "project": "vscode-workbench" + }, { "name": "vs/workbench/parts/nps", "project": "vscode-workbench" @@ -199,4 +203,4 @@ "project": "vscode-workbench" } ] -} +} \ No newline at end of file diff --git a/build/npm/update-grammar.js b/build/npm/update-grammar.js index 9d0cad11dc7..d65112b3456 100644 --- a/build/npm/update-grammar.js +++ b/build/npm/update-grammar.js @@ -14,14 +14,19 @@ var url = require('url'); function getOptions(urlString) { var _url = url.parse(urlString); + var headers = { + 'User-Agent': 'VSCode' + }; + var token = process.env['GITHUB_TOKEN']; + if (token) { + headers['Authorization'] = 'token ' + token + } return { protocol: _url.protocol, host: _url.host, port: _url.port, path: _url.path, - headers: { - 'User-Agent': 'NodeJS' - } + headers: headers }; } @@ -68,8 +73,8 @@ function getCommitSha(repoId, repoPath) { }); } -exports.update = function (repoId, repoPath, dest, modifyGrammar) { - var contentPath = 'https://raw.githubusercontent.com/' + repoId + '/master/' + repoPath; +exports.update = function (repoId, repoPath, dest, modifyGrammar, version = 'master') { + var contentPath = 'https://raw.githubusercontent.com/' + repoId + `/${version}/` + repoPath; console.log('Reading from ' + contentPath); return download(contentPath).then(function (content) { var ext = path.extname(repoPath); diff --git a/build/tfs/darwin/build.sh b/build/tfs/darwin/build.sh index b7d45aad6e0..9c0b7a6f52a 100755 --- a/build/tfs/darwin/build.sh +++ b/build/tfs/darwin/build.sh @@ -19,6 +19,9 @@ step "Install dependencies" \ step "Hygiene" \ npm run gulp -- hygiene +step "Monaco Editor Check" \ + ./node_modules/.bin/tsc -p ./src/tsconfig.monaco.json --noEmit + step "Mix in repository from vscode-distro" \ npm run gulp -- mixin diff --git a/build/tfs/linux/build.sh b/build/tfs/linux/build.sh index 9d9b33de8a0..e23049b5cf4 100755 --- a/build/tfs/linux/build.sh +++ b/build/tfs/linux/build.sh @@ -22,6 +22,9 @@ step "Install dependencies" \ step "Hygiene" \ npm run gulp -- hygiene +step "Monaco Editor Check" \ + ./node_modules/.bin/tsc -p ./src/tsconfig.monaco.json --noEmit + step "Mix in repository from vscode-distro" \ npm run gulp -- mixin diff --git a/build/tfs/win32/1_build.ps1 b/build/tfs/win32/1_build.ps1 index 0dac2d1fcbf..bc6ade13de3 100644 --- a/build/tfs/win32/1_build.ps1 +++ b/build/tfs/win32/1_build.ps1 @@ -24,6 +24,10 @@ step "Hygiene" { exec { & npm run gulp -- hygiene } } +step "Monaco Editor Check" { + exec { & .\node_modules\.bin\tsc -p .\src\tsconfig.monaco.json --noEmit } +} + $env:VSCODE_MIXIN_PASSWORD = $mixinPassword step "Mix in repository from vscode-distro" { exec { & npm run gulp -- mixin } diff --git a/build/tfs/win32/node.ps1 b/build/tfs/win32/node.ps1 index fdfe25ae362..14ccd0008b2 100644 --- a/build/tfs/win32/node.ps1 +++ b/build/tfs/win32/node.ps1 @@ -1,7 +1,7 @@ # install node $env:Path = $env:NVM_HOME + ";" + $env:NVM_SYMLINK + ";" + $env:Path $NodeVersion = "8.9.1" -nvm install $NodeVersion -nvm use $NodeVersion -npm install -g yarn +# nvm install $NodeVersion +# nvm use $NodeVersion +# npm install -g yarn $env:Path = $env:NVM_HOME + "\v" + $NodeVersion + ";" + $env:Path \ No newline at end of file diff --git a/extensions/bat/syntaxes/batchfile.tmLanguage.json b/extensions/bat/syntaxes/batchfile.tmLanguage.json index ce6870deea6..42adcf64854 100644 --- a/extensions/bat/syntaxes/batchfile.tmLanguage.json +++ b/extensions/bat/syntaxes/batchfile.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/mmims/language-batchfile/commit/40b605c75db3967a24b7015f6d3a885360b84e28", + "version": "https://github.com/mmims/language-batchfile/commit/3dd105c31484e5975144478dac1aa91d8f51e528", "scopeName": "source.batchfile", "name": "Batch File", "fileTypes": [ @@ -454,11 +454,15 @@ { "begin": "\\(", "beginCaptures": { - "0": "punctuation.section.group.begin.batchfile" + "0": { + "name": "punctuation.section.group.begin.batchfile" + } }, "end": "\\)", "endCaptures": { - "0": "punctuation.section.group.end.batchfile" + "0": { + "name": "punctuation.section.group.end.batchfile" + } }, "name": "meta.group.batchfile", "patterns": [ diff --git a/extensions/configuration-editing/package.json b/extensions/configuration-editing/package.json index 9659fdcbc96..17394b271f5 100644 --- a/extensions/configuration-editing/package.json +++ b/extensions/configuration-editing/package.json @@ -10,7 +10,8 @@ "Other" ], "activationEvents": [ - "onLanguage:json", "onLanguage:jsonc" + "onLanguage:json", + "onLanguage:jsonc" ], "main": "./out/extension", "scripts": { @@ -18,7 +19,7 @@ "watch": "gulp watch-extension:configuration-editing" }, "dependencies": { - "jsonc-parser": "^0.3.1", + "jsonc-parser": "^1.0.0", "vscode-nls": "^2.0.1" }, "contributes": { @@ -76,4 +77,4 @@ "devDependencies": { "@types/node": "7.0.4" } -} +} \ No newline at end of file diff --git a/extensions/configuration-editing/yarn.lock b/extensions/configuration-editing/yarn.lock index b1e6a363c9d..6e178dd74ea 100644 --- a/extensions/configuration-editing/yarn.lock +++ b/extensions/configuration-editing/yarn.lock @@ -6,12 +6,10 @@ version "7.0.4" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.4.tgz#9aabc135979ded383325749f508894c662948c8b" -jsonc-parser@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-0.3.1.tgz#6ebf5c75224368d4b07ef4c26f9434e657472e95" - dependencies: - vscode-nls "^2.0.2" +jsonc-parser@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" -vscode-nls@^2.0.1, vscode-nls@^2.0.2: +vscode-nls@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" diff --git a/extensions/csharp/syntaxes/csharp.tmLanguage.json b/extensions/csharp/syntaxes/csharp.tmLanguage.json index 9a96c9bbbee..7ce7ce2fc41 100644 --- a/extensions/csharp/syntaxes/csharp.tmLanguage.json +++ b/extensions/csharp/syntaxes/csharp.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/dotnet/csharp-tmLanguage/commit/436456ee5ce44e29cb1752c3b29f493b1de08c42", + "version": "https://github.com/dotnet/csharp-tmLanguage/commit/a334939a6493290f874264712447107bc9545835", "name": "C#", "scopeName": "source.cs", "fileTypes": [ @@ -935,7 +935,7 @@ "name": "punctuation.separator.colon.cs" } }, - "end": "(?=\\{|where|;)", + "end": "(?=\\{|where|;|=>)", "patterns": [ { "name": "keyword.other.class.cs", @@ -2438,18 +2438,12 @@ ] }, "throw-expression": { - "begin": "(?\n (?:\n (?:ref\\s+)? # ref return\n (?:\n (?:(?[_[:alpha:]][_[:alnum:]]*)\\s*\\:\\:\\s*)? # alias-qualification\n (? # identifier + type arguments (if any)\n \\g\\s*\n (?\\s*<(?:[^<>]|\\g)+>\\s*)?\n )\n (?:\\s*\\.\\s*\\g)* | # Are there any more names being dotted into?\n (?\\s*\\((?:[^\\(\\)]|\\g)+\\))\n )\n (?:\\s*\\*\\s*)* # pointer suffix?\n (?:\\s*\\?\\s*)? # nullable suffix?\n (?:\\s*\\[(?:\\s*,\\s*)*\\]\\s*)* # array suffix?\n )\n)\\s+\n(\\g)", + "captures": { + "1": { + "name": "storage.modifier.cs" + }, + "2": { + "patterns": [ + { + "include": "#type" + } + ] + }, + "7": { + "name": "entity.name.variable.parameter.cs" + } + } + }, "argument-list": { "begin": "\\(", "beginCaptures": { diff --git a/extensions/css/client/src/cssMain.ts b/extensions/css/client/src/cssMain.ts index 19f131224ed..5e9f4d2ec7f 100644 --- a/extensions/css/client/src/cssMain.ts +++ b/extensions/css/client/src/cssMain.ts @@ -21,7 +21,7 @@ export function activate(context: ExtensionContext) { // The server is implemented in node let serverModule = context.asAbsolutePath(path.join('server', 'out', 'cssServerMain.js')); // The debug options for the server - let debugOptions = { execArgv: ['--nolazy', '--inspect=6004'] }; + let debugOptions = { execArgv: ['--nolazy', '--inspect=6044'] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index 5dd73152edc..5af88ec2a75 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,7 +8,7 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.2", + "vscode-css-languageservice": "^3.0.3", "vscode-languageserver": "^3.5.0" }, "devDependencies": { diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index 6609e5c54d0..c388bebe832 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -15,6 +15,7 @@ import { DocumentColorRequest, ServerCapabilities as CPServerCapabilities, Color import { getCSSLanguageService, getSCSSLanguageService, getLESSLanguageService, LanguageSettings, LanguageService, Stylesheet } from 'vscode-css-languageservice'; import { getLanguageModelCache } from './languageModelCache'; +import { formatError, runSafe } from './utils/errors'; export interface Settings { css: LanguageSettings; @@ -28,6 +29,10 @@ let connection: IConnection = createConnection(); console.log = connection.console.log.bind(connection.console); console.error = connection.console.error.bind(connection.console); +process.on('unhandledRejection', (e: any) => { + connection.console.error(formatError(`Unhandled exception`, e)); +}); + // Create a simple text document manager. The text document manager // supports full document sync only let documents: TextDocuments = new TextDocuments(); @@ -122,7 +127,7 @@ function updateConfiguration(settings: Settings) { } let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; -const validationDelayMs = 200; +const validationDelayMs = 500; // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. @@ -159,73 +164,95 @@ function validateTextDocument(textDocument: TextDocument): void { let diagnostics = getLanguageService(textDocument).doValidation(textDocument, stylesheet, settings); // Send the computed diagnostics to VSCode. connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + }, e => { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); }); } connection.onCompletion(textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheet)!; /* TODO: remove ! once LS has null annotations */ + return runSafe(() => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).doComplete(document, textDocumentPosition.position, stylesheet)!; /* TODO: remove ! once LS has null annotations */ + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); connection.onHover(textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let styleSheet = stylesheets.get(document); - return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet)!; /* TODO: remove ! once LS has null annotations */ + return runSafe(() => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let styleSheet = stylesheets.get(document); + return getLanguageService(document).doHover(document, textDocumentPosition.position, styleSheet)!; /* TODO: remove ! once LS has null annotations */ + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`); }); connection.onDocumentSymbol(documentSymbolParams => { - let document = documents.get(documentSymbolParams.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentSymbols(document, stylesheet); + return runSafe(() => { + let document = documents.get(documentSymbolParams.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentSymbols(document, stylesheet); + }, [], `Error while computing document symbols for ${documentSymbolParams.textDocument.uri}`); }); connection.onDefinition(documentSymbolParams => { - let document = documents.get(documentSymbolParams.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).findDefinition(document, documentSymbolParams.position, stylesheet); + return runSafe(() => { + let document = documents.get(documentSymbolParams.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).findDefinition(document, documentSymbolParams.position, stylesheet); + }, null, `Error while computing definitions for ${documentSymbolParams.textDocument.uri}`); }); connection.onDocumentHighlight(documentSymbolParams => { - let document = documents.get(documentSymbolParams.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentHighlights(document, documentSymbolParams.position, stylesheet); + return runSafe(() => { + let document = documents.get(documentSymbolParams.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentHighlights(document, documentSymbolParams.position, stylesheet); + }, [], `Error while computing document highlights for ${documentSymbolParams.textDocument.uri}`); }); connection.onReferences(referenceParams => { - let document = documents.get(referenceParams.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet); + return runSafe(() => { + let document = documents.get(referenceParams.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).findReferences(document, referenceParams.position, stylesheet); + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`); }); connection.onCodeAction(codeActionParams => { - let document = documents.get(codeActionParams.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet); + return runSafe(() => { + let document = documents.get(codeActionParams.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).doCodeActions(document, codeActionParams.range, codeActionParams.context, stylesheet); + }, [], `Error while computing code actions for ${codeActionParams.textDocument.uri}`); }); connection.onRequest(DocumentColorRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let stylesheet = stylesheets.get(document); - return getLanguageService(document).findDocumentColors(document, stylesheet); - } - return []; + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let stylesheet = stylesheets.get(document); + return getLanguageService(document).findDocumentColors(document, stylesheet); + } + return []; + }, [], `Error while computing document colors for ${params.textDocument.uri}`); }); connection.onRequest(ColorPresentationRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let stylesheet = stylesheets.get(document); - return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range); - } - return []; + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let stylesheet = stylesheets.get(document); + return getLanguageService(document).getColorPresentations(document, stylesheet, params.color, params.range); + } + return []; + }, [], `Error while computing color presentations for ${params.textDocument.uri}`); }); connection.onRenameRequest(renameParameters => { - let document = documents.get(renameParameters.textDocument.uri); - let stylesheet = stylesheets.get(document); - return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet); + return runSafe(() => { + let document = documents.get(renameParameters.textDocument.uri); + let stylesheet = stylesheets.get(document); + return getLanguageService(document).doRename(document, renameParameters.position, renameParameters.newName, stylesheet); + }, null, `Error while computing renames for ${renameParameters.textDocument.uri}`); }); // Listen on the connection diff --git a/extensions/css/server/src/utils/errors.ts b/extensions/css/server/src/utils/errors.ts new file mode 100644 index 00000000000..d5a0c8e7d05 --- /dev/null +++ b/extensions/css/server/src/utils/errors.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export function formatError(message: string, err: any): string { + if (err instanceof Error) { + let error = err; + return `${message}: ${error.message}\n${error.stack}`; + } else if (typeof err === 'string') { + return `${message}: ${err}`; + } else if (err) { + return `${message}: ${err.toString()}`; + } + return message; +} + +export function runSafe(func: () => Thenable | T, errorVal: T, errorMessage: string): Thenable | T { + try { + let t = func(); + if (t instanceof Promise) { + return t.then(void 0, e => { + console.error(formatError(errorMessage, e)); + return errorVal; + }); + } + return t; + } catch (e) { + console.error(formatError(errorMessage, e)); + return errorVal; + } +} \ No newline at end of file diff --git a/extensions/css/server/tsconfig.json b/extensions/css/server/tsconfig.json index 7f8b647d04b..dc12dad6cf0 100644 --- a/extensions/css/server/tsconfig.json +++ b/extensions/css/server/tsconfig.json @@ -6,8 +6,7 @@ "noUnusedLocals": true, "lib": [ "es5", "es2015.promise" - ], - "strict": true + ] }, "include": [ "src/**/*" diff --git a/extensions/css/server/yarn.lock b/extensions/css/server/yarn.lock index 24a54f0acdf..0420acb7d88 100644 --- a/extensions/css/server/yarn.lock +++ b/extensions/css/server/yarn.lock @@ -6,9 +6,9 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-css-languageservice@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.2.tgz#ae0c43836318455aa290c777556394d6127b8f6c" +vscode-css-languageservice@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.3.tgz#02cc4efa5335f5104e0a2f3b6920faaf59db4f7a" dependencies: vscode-languageserver-types "3.5.0" vscode-nls "^2.0.1" diff --git a/extensions/css/syntaxes/css.tmLanguage.json b/extensions/css/syntaxes/css.tmLanguage.json index 95caf371210..2658a64ff67 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/atom/language-css/commit/ec289867164a34fce48a69776b7f8dc380e3dc37", + "version": "https://github.com/atom/language-css/commit/b781e2152b677d4a38c4501de477948cb6a04f65", "scopeName": "source.css", "name": "CSS", "fileTypes": [ @@ -1058,7 +1058,7 @@ "name": "support.constant.text-direction.css" }, { - "include": "#property-value" + "include": "#property-values" } ] }, diff --git a/extensions/emmet/package.json b/extensions/emmet/package.json index 84196a8b658..263bb63ed63 100644 --- a/extensions/emmet/package.json +++ b/extensions/emmet/package.json @@ -314,12 +314,12 @@ "vscode": "1.0.1" }, "dependencies": { - "@emmetio/html-matcher": "^0.3.1", + "@emmetio/html-matcher": "^0.3.3", "@emmetio/css-parser": "ramya-rao-a/css-parser#vscode", "@emmetio/math-expression": "^0.1.1", - "vscode-emmet-helper": "^1.1.19", - "vscode-languageserver-types": "^3.0.3", + "vscode-emmet-helper": "^1.1.20", + "vscode-languageserver-types": "^3.5.0", "image-size": "^0.5.2", "vscode-nls": "2.0.2" } -} +} \ No newline at end of file diff --git a/extensions/emmet/src/abbreviationActions.ts b/extensions/emmet/src/abbreviationActions.ts index 480699eafe1..b895413c59b 100644 --- a/extensions/emmet/src/abbreviationActions.ts +++ b/extensions/emmet/src/abbreviationActions.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as vscode from 'vscode'; -import { Node, HtmlNode, Rule } from 'EmmetNode'; +import { Node, HtmlNode, Rule, Property } from 'EmmetNode'; import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode } from './util'; const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/; @@ -32,8 +32,14 @@ export function wrapWithAbbreviation(args: any) { const abbreviationPromise = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation' }); const helper = getEmmetHelper(); - return abbreviationPromise.then(abbreviation => { - if (!abbreviation || !abbreviation.trim() || !helper.isAbbreviationValid(syntax, abbreviation)) { return false; } + return abbreviationPromise.then(inputAbbreviation => { + if (!inputAbbreviation || !inputAbbreviation.trim() || !helper.isAbbreviationValid(syntax, inputAbbreviation)) { return false; } + + let extractedResults = helper.extractAbbreviationFromText(inputAbbreviation); + if (!extractedResults) { + return false; + } + let { abbreviation, filter } = extractedResults; let expandAbbrList: ExpandAbbreviationInput[] = []; @@ -48,7 +54,7 @@ export function wrapWithAbbreviation(args: any) { const preceedingWhiteSpace = matches ? matches[1].length : 0; rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + preceedingWhiteSpace, rangeToReplace.end.line, rangeToReplace.end.character); - expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap: ['\n\t$TM_SELECTED_TEXT\n'] }); + expandAbbrList.push({ syntax, abbreviation, rangeToReplace, textToWrap: ['\n\t$TM_SELECTED_TEXT\n'], filter }); }); return expandAbbreviationInRange(editor, expandAbbrList, true); @@ -223,6 +229,23 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen return true; } + // Fix for https://github.com/Microsoft/vscode/issues/34162 + // Other than sass, stylus, we can make use of the terminator tokens to validate position + if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') { + const propertyNode = currentNode; + if (propertyNode.terminatorToken + && propertyNode.separator + && position.isAfterOrEqual(propertyNode.separatorToken.end) + && position.isBeforeOrEqual(propertyNode.terminatorToken.start)) { + return false; + } + if (!propertyNode.terminatorToken + && propertyNode.separator + && position.isAfterOrEqual(propertyNode.separatorToken.end)) { + return false; + } + } + // If current node is a rule or at-rule, then perform additional checks to ensure // emmet suggestions are not provided in the rule selector if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') { @@ -242,7 +265,10 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen if (currentCssNode.parent && (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule') && currentCssNode.selectorToken - && position.line !== currentCssNode.selectorToken.end.line) { + && position.line !== currentCssNode.selectorToken.end.line + && currentCssNode.selectorToken.start.character === abbreviationRange.start.character + && currentCssNode.selectorToken.start.line === abbreviationRange.start.line + ) { return true; } diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index 7d331f575b7..ad611574c05 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -23,6 +23,12 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi const isSyntaxMapped = mappedLanguages[document.languageId] ? true : false; let syntax = getEmmetMode((isSyntaxMapped ? mappedLanguages[document.languageId] : document.languageId), excludedLanguages); + if (!syntax + || emmetConfig['showExpandedAbbreviation'] === 'never' + || ((isSyntaxMapped || syntax === 'jsx') && emmetConfig['showExpandedAbbreviation'] !== 'always')) { + return; + } + const helper = getEmmetHelper(); const extractAbbreviationResults = helper.extractAbbreviation(document, position); if (!extractAbbreviationResults) { @@ -46,12 +52,6 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi } } - if (!syntax - || ((isSyntaxMapped || syntax === 'jsx') - && emmetConfig['showExpandedAbbreviation'] !== 'always')) { - return; - } - let noiseCheckPromise: Thenable = Promise.resolve(); // Fix for https://github.com/Microsoft/vscode/issues/32647 diff --git a/extensions/emmet/src/test/abbreviationAction.test.ts b/extensions/emmet/src/test/abbreviationAction.test.ts index 101da90685c..302fd6f8ce4 100644 --- a/extensions/emmet/src/test/abbreviationAction.test.ts +++ b/extensions/emmet/src/test/abbreviationAction.test.ts @@ -5,44 +5,12 @@ import 'mocha'; import * as assert from 'assert'; -import { Selection, workspace, CompletionList, CancellationTokenSource, Position } from 'vscode'; +import { Selection, workspace, CompletionList, CancellationTokenSource } from 'vscode'; import { withRandomFileEditor, closeAllEditors } from './testUtils'; -import { expandEmmetAbbreviation, wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from '../abbreviationActions'; +import { expandEmmetAbbreviation } from '../abbreviationActions'; import { DefaultCompletionItemProvider } from '../defaultCompletionProvider'; const completionProvider = new DefaultCompletionItemProvider(); -const cssContents = ` -.boo { - margin: 20px 10px; - m10 - background-image: url('tryme.png'); - m10 -} - -.boo .hoo { - margin: 10px; - ind -} -`; - -const scssContents = ` -.boo { - margin: 10px; - p10 - .hoo { - p20 - } -} -@include b(alert) { - - margin: 10px; - p30 - - @include b(alert) { - p40 - } -} -`; const htmlContents = ` @@ -67,53 +35,6 @@ const htmlContents = ` `; -const htmlContentsForWrapTests = ` - -`; - -const wrapBlockElementExpected = ` - -`; - -const wrapInlineElementExpected = ` - -`; - -const wrapSnippetExpected = ` - -`; - -const wrapMultiLineAbbrExpected = ` - -`; - suite('Tests for Expand Abbreviations (HTML)', () => { teardown(() => { // Reset config and close all editors @@ -360,277 +281,6 @@ suite('Tests for Expand Abbreviations (HTML)', () => { }); -suite('Tests for Expand Abbreviations (CSS)', () => { - teardown(closeAllEditors); - - test('Expand abbreviation (CSS)', () => { - return withRandomFileEditor(cssContents, 'css', (editor, doc) => { - editor.selections = [new Selection(3, 1, 3, 4), new Selection(5, 1, 5, 4)]; - return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), cssContents.replace(/m10/g, 'margin: 10px;')); - return Promise.resolve(); - }); - }); - }); - - test('Expand abbreviation in completion list (CSS)', () => { - const abbreviation = 'm10'; - const expandedText = 'margin: 10px;'; - - return withRandomFileEditor(cssContents, 'css', (editor, doc) => { - editor.selection = new Selection(3, 1, 3, 4); - const cancelSrc = new CancellationTokenSource(); - const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); - const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 4), cancelSrc.token); - if (!completionPromise1 || !completionPromise2) { - assert.equal(1, 2, `Problem with expanding m10`); - return Promise.resolve(); - } - - const callBack = (completionList: CompletionList) => { - if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Problem with expanding m10`); - return; - } - const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); - assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); - }; - - return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { - callBack(result1); - callBack(result2); - return Promise.resolve(); - }); - }); - }); - - test('Expand abbreviation (SCSS)', () => { - return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { - editor.selections = [ - new Selection(3, 4, 3, 4), - new Selection(5, 5, 5, 5), - new Selection(11, 4, 11, 4), - new Selection(14, 5, 14, 5) - ]; - return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), scssContents.replace(/p(\d\d)/g, 'padding: $1px;')); - return Promise.resolve(); - }); - }); - }); - - test('Expand abbreviation in completion list (SCSS)', () => { - - return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { - editor.selection = new Selection(3, 4, 3, 4); - const cancelSrc = new CancellationTokenSource(); - const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); - const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 5), cancelSrc.token); - const completionPromise3 = completionProvider.provideCompletionItems(editor.document, new Position(11, 4), cancelSrc.token); - const completionPromise4 = completionProvider.provideCompletionItems(editor.document, new Position(14, 5), cancelSrc.token); - if (!completionPromise1 || !completionPromise2 || !completionPromise3 || !completionPromise4) { - assert.equal(1, 2, `Problem with expanding padding abbreviations`); - return Promise.resolve(); - } - - const callBack = (completionList: CompletionList, abbreviation, expandedText) => { - if (!completionList.items || !completionList.items.length) { - assert.equal(1, 2, `Problem with expanding m10`); - return; - } - const emmetCompletionItem = completionList.items[0]; - assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); - assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); - assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); - }; - - return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { - callBack(result1, 'p10', 'padding: 10px;'); - callBack(result2, 'p20', 'padding: 20px;'); - callBack(result3, 'p30', 'padding: 30px;'); - callBack(result4, 'p40', 'padding: 40px;'); - return Promise.resolve(); - }); - }); - }); - - - test('Invalid locations for abbreviations in scss', () => { - const scssContentsNoExpand = ` -m10 - .boo { - margin: 10px; - .hoo { - background: - } - } - `; - - return withRandomFileEditor(scssContentsNoExpand, 'scss', (editor, doc) => { - editor.selections = [ - new Selection(1, 3, 1, 3), // outside rule - new Selection(5, 15, 5, 15) // in the value part of property value - ]; - return expandEmmetAbbreviation(null).then(() => { - assert.equal(editor.document.getText(), scssContentsNoExpand); - return Promise.resolve(); - }); - }); - }); - - test('Invalid locations for abbreviations in scss in completion list', () => { - const scssContentsNoExpand = ` -m10 - .boo { - margin: 10px; - .hoo { - background: - } - } - `; - - return withRandomFileEditor(scssContentsNoExpand, 'scss', (editor, doc) => { - editor.selection = new Selection(1, 3, 1, 3); // outside rule - const cancelSrc = new CancellationTokenSource(); - let completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); - if (completionPromise) { - assert.equal(1, 2, `m10 gets expanded in invalid location (outside rule)`); - } - - editor.selection = new Selection(5, 15, 5, 15); // in the value part of property value - completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); - if (completionPromise) { - return completionPromise.then((completionList: CompletionList) => { - if (completionList && completionList.items && completionList.items.length > 0) { - assert.equal(1, 2, `m10 gets expanded in invalid location (n the value part of property value)`); - } - return Promise.resolve(); - }); - } - return Promise.resolve(); - }); - }); - -}); - -suite('Tests for Wrap with Abbreviations', () => { - teardown(closeAllEditors); - - const multiCursors = [new Selection(2, 6, 2, 6), new Selection(3, 6, 3, 6)]; - const multiCursorsWithSelection = [new Selection(2, 2, 2, 28), new Selection(3, 2, 3, 33)]; - const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 3, 33)]; - - - test('Wrap with block element using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected); - }); - - test('Wrap with inline element using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected); - }); - - test('Wrap with snippet using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected); - }); - - test('Wrap with multi line abbreviation using multi cursor', () => { - return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected); - }); - - test('Wrap with block element using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected); - }); - - test('Wrap with inline element using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected); - }); - - test('Wrap with snippet using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected); - }); - - test('Wrap with multi line abbreviation using multi cursor selection', () => { - return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected); - }); - - test('Wrap with block element using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected); - }); - - test('Wrap with inline element using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected); - }); - - test('Wrap with snippet using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected); - }); - - test('Wrap with multi line abbreviation using multi cursor full line selection', () => { - return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected); - }); - - test('Wrap individual lines with abbreviation', () => { - const contents = ` - -`; - const wrapIndividualLinesExpected = ` - -`; - return withRandomFileEditor(contents, 'html', (editor, doc) => { - editor.selections = [new Selection(2, 2, 3, 33)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned udnefined.'); - return Promise.resolve(); - } - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); - }); - - test('Wrap individual lines with abbreviation and trim', () => { - const contents = ` - - `; - const wrapIndividualLinesExpected = ` - - `; - return withRandomFileEditor(contents, 'html', (editor, doc) => { - editor.selections = [new Selection(2, 3, 3, 16)]; - const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*|t' }); - if (!promise) { - assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned udnefined.'); - return Promise.resolve(); - } - - return promise.then(() => { - assert.equal(editor.document.getText(), wrapIndividualLinesExpected); - return Promise.resolve(); - }); - }); - }); -}); - suite('Tests for jsx, xml and xsl', () => { teardown(closeAllEditors); @@ -720,18 +370,3 @@ function testHtmlCompletionProvider(selection: Selection, abbreviation: string, }); } -function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string): Thenable { - return withRandomFileEditor(htmlContentsForWrapTests, 'html', (editor, doc) => { - editor.selections = selections; - const promise = wrapWithAbbreviation({ abbreviation }); - if (!promise) { - assert.equal(1, 2, 'Wrap with Abbreviation returned udnefined.'); - return Promise.resolve(); - } - - return promise.then(() => { - assert.equal(editor.document.getText(), expectedContents); - return Promise.resolve(); - }); - }); -} diff --git a/extensions/emmet/src/test/cssAbbreviationAction.test.ts b/extensions/emmet/src/test/cssAbbreviationAction.test.ts new file mode 100644 index 00000000000..00b205c682a --- /dev/null +++ b/extensions/emmet/src/test/cssAbbreviationAction.test.ts @@ -0,0 +1,301 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import { Selection, CompletionList, CancellationTokenSource, Position } from 'vscode'; +import { withRandomFileEditor, closeAllEditors } from './testUtils'; +import { expandEmmetAbbreviation } from '../abbreviationActions'; +import { DefaultCompletionItemProvider } from '../defaultCompletionProvider'; + +const completionProvider = new DefaultCompletionItemProvider(); +const cssContents = ` +.boo { + margin: 20px 10px; + m10 + background-image: url('tryme.png'); + m10 +} + +.boo .hoo { + margin: 10px; + ind +} +`; + +const scssContents = ` +.boo { + margin: 10px; + p10 + .hoo { + p20 + } +} +@include b(alert) { + + margin: 10px; + p30 + + @include b(alert) { + p40 + } +} +.foo { + margin: 10px; + margin: a + .hoo { + color: #000; + } +} +`; + + +suite('Tests for Expand Abbreviations (CSS)', () => { + teardown(closeAllEditors); + + test('Expand abbreviation (CSS)', () => { + return withRandomFileEditor(cssContents, 'css', (editor, doc) => { + editor.selections = [new Selection(3, 1, 3, 4), new Selection(5, 1, 5, 4)]; + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), cssContents.replace(/m10/g, 'margin: 10px;')); + return Promise.resolve(); + }); + }); + }); + + test('Skip when typing property values when there is a property in the next line (CSS)', () => { + const testContent = ` +.foo { + margin: a + margin: 10px; +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(2, 10, 2, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + + test('Skip when typing property values when there is a property in the previous line (CSS)', () => { + const testContent = ` +.foo { + margin: 10px; + margin: a +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(3, 10, 3, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(3, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + + test('Skip when typing property values when it is the only property in the rule (CSS)', () => { + const testContent = ` +.foo { + margin: a +} + `; + + return withRandomFileEditor(testContent, 'css', (editor, doc) => { + editor.selection = new Selection(2, 10, 2, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), testContent); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(2, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); + }); + + test('Expand abbreviation in completion list (CSS)', () => { + const abbreviation = 'm10'; + const expandedText = 'margin: 10px;'; + + return withRandomFileEditor(cssContents, 'css', (editor, doc) => { + editor.selection = new Selection(3, 1, 3, 4); + const cancelSrc = new CancellationTokenSource(); + const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); + const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 4), cancelSrc.token); + if (!completionPromise1 || !completionPromise2) { + assert.equal(1, 2, `Problem with expanding m10`); + return Promise.resolve(); + } + + const callBack = (completionList: CompletionList) => { + if (!completionList.items || !completionList.items.length) { + assert.equal(1, 2, `Problem with expanding m10`); + return; + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + }; + + return Promise.all([completionPromise1, completionPromise2]).then(([result1, result2]) => { + callBack(result1); + callBack(result2); + return Promise.resolve(); + }); + }); + }); + + test('Expand abbreviation (SCSS)', () => { + return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { + editor.selections = [ + new Selection(3, 4, 3, 4), + new Selection(5, 5, 5, 5), + new Selection(11, 4, 11, 4), + new Selection(14, 5, 14, 5) + ]; + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), scssContents.replace(/p(\d\d)/g, 'padding: $1px;')); + return Promise.resolve(); + }); + }); + }); + + test('Expand abbreviation in completion list (SCSS)', () => { + + return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { + editor.selection = new Selection(3, 4, 3, 4); + const cancelSrc = new CancellationTokenSource(); + const completionPromise1 = completionProvider.provideCompletionItems(editor.document, new Position(3, 4), cancelSrc.token); + const completionPromise2 = completionProvider.provideCompletionItems(editor.document, new Position(5, 5), cancelSrc.token); + const completionPromise3 = completionProvider.provideCompletionItems(editor.document, new Position(11, 4), cancelSrc.token); + const completionPromise4 = completionProvider.provideCompletionItems(editor.document, new Position(14, 5), cancelSrc.token); + if (!completionPromise1) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 3 col 4`); + } + if (!completionPromise2) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 5 col 5`); + } + if (!completionPromise3) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 11 col 4`); + } + if (!completionPromise4) { + assert.equal(1, 2, `Problem with expanding padding abbreviations at line 14 col 5`); + } + + if (!completionPromise1 || !completionPromise2 || !completionPromise3 || !completionPromise4) { + return Promise.resolve(); + } + + const callBack = (completionList: CompletionList, abbreviation, expandedText) => { + if (!completionList.items || !completionList.items.length) { + assert.equal(1, 2, `Problem with expanding m10`); + return; + } + const emmetCompletionItem = completionList.items[0]; + assert.equal(emmetCompletionItem.label, expandedText, `Label of completion item doesnt match.`); + assert.equal((emmetCompletionItem.documentation || '').replace(/\|/g, ''), expandedText, `Docs of completion item doesnt match.`); + assert.equal(emmetCompletionItem.filterText, abbreviation, `FilterText of completion item doesnt match.`); + }; + + return Promise.all([completionPromise1, completionPromise2, completionPromise3, completionPromise4]).then(([result1, result2, result3, result4]) => { + callBack(result1, 'p10', 'padding: 10px;'); + callBack(result2, 'p20', 'padding: 20px;'); + callBack(result3, 'p30', 'padding: 30px;'); + callBack(result4, 'p40', 'padding: 40px;'); + return Promise.resolve(); + }); + }); + }); + + + test('Invalid locations for abbreviations in scss', () => { + const scssContentsNoExpand = ` +m10 + .boo { + margin: 10px; + .hoo { + background: + } + } + `; + + return withRandomFileEditor(scssContentsNoExpand, 'scss', (editor, doc) => { + editor.selections = [ + new Selection(1, 3, 1, 3), // outside rule + new Selection(5, 15, 5, 15) // in the value part of property value + ]; + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), scssContentsNoExpand); + return Promise.resolve(); + }); + }); + }); + + test('Invalid locations for abbreviations in scss in completion list', () => { + const scssContentsNoExpand = ` +m10 + .boo { + margin: 10px; + .hoo { + background: + } + } + `; + + return withRandomFileEditor(scssContentsNoExpand, 'scss', (editor, doc) => { + editor.selection = new Selection(1, 3, 1, 3); // outside rule + const cancelSrc = new CancellationTokenSource(); + let completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `m10 gets expanded in invalid location (outside rule)`); + } + + editor.selection = new Selection(5, 15, 5, 15); // in the value part of property value + completionPromise = completionProvider.provideCompletionItems(editor.document, editor.selection.active, cancelSrc.token); + if (completionPromise) { + return completionPromise.then((completionList: CompletionList) => { + if (completionList && completionList.items && completionList.items.length > 0) { + assert.equal(1, 2, `m10 gets expanded in invalid location (n the value part of property value)`); + } + return Promise.resolve(); + }); + } + return Promise.resolve(); + }); + }); + +}); + + test('Skip when typing property values when there is a nested rule in the next line (SCSS)', () => { + return withRandomFileEditor(scssContents, 'scss', (editor, doc) => { + editor.selection = new Selection(19, 10, 19, 10); + return expandEmmetAbbreviation(null).then(() => { + assert.equal(editor.document.getText(), scssContents); + const cancelSrc = new CancellationTokenSource(); + const completionPromise = completionProvider.provideCompletionItems(editor.document, new Position(19, 10), cancelSrc.token); + if (completionPromise) { + assert.equal(1, 2, `Invalid completion at property value`); + } + return Promise.resolve(); + }); + }); +}); + diff --git a/extensions/emmet/src/test/wrapWithAbbreviation.test.ts b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts new file mode 100644 index 00000000000..2739fc496d9 --- /dev/null +++ b/extensions/emmet/src/test/wrapWithAbbreviation.test.ts @@ -0,0 +1,250 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'mocha'; +import * as assert from 'assert'; +import { Selection } from 'vscode'; +import { withRandomFileEditor, closeAllEditors } from './testUtils'; +import { wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from '../abbreviationActions'; + +const htmlContentsForWrapTests = ` + +`; + +const wrapBlockElementExpected = ` + +`; + +const wrapInlineElementExpected = ` + +`; + +const wrapSnippetExpected = ` + +`; + +const wrapMultiLineAbbrExpected = ` + +`; + +suite('Tests for Wrap with Abbreviations', () => { + teardown(closeAllEditors); + + const multiCursors = [new Selection(2, 6, 2, 6), new Selection(3, 6, 3, 6)]; + const multiCursorsWithSelection = [new Selection(2, 2, 2, 28), new Selection(3, 2, 3, 33)]; + const multiCursorsWithFullLineSelection = [new Selection(2, 0, 2, 28), new Selection(3, 0, 3, 33)]; + + + test('Wrap with block element using multi cursor', () => { + return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected); + }); + + test('Wrap with inline element using multi cursor', () => { + return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected); + }); + + test('Wrap with snippet using multi cursor', () => { + return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected); + }); + + test('Wrap with multi line abbreviation using multi cursor', () => { + return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected); + }); + + test('Wrap with block element using multi cursor selection', () => { + return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected); + }); + + test('Wrap with inline element using multi cursor selection', () => { + return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected); + }); + + test('Wrap with snippet using multi cursor selection', () => { + return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected); + }); + + test('Wrap with multi line abbreviation using multi cursor selection', () => { + return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected); + }); + + test('Wrap with block element using multi cursor full line selection', () => { + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected); + }); + + test('Wrap with inline element using multi cursor full line selection', () => { + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected); + }); + + test('Wrap with snippet using multi cursor full line selection', () => { + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected); + }); + + test('Wrap with multi line abbreviation using multi cursor full line selection', () => { + return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected); + }); + + test('Wrap with abbreviation and comment filter', () => { + const contents = ` + + `; + const expectedContents = ` + + `; + + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [new Selection(2, 0, 2, 0)]; + const promise = wrapWithAbbreviation({ abbreviation: 'li.hello|c' }); + if (!promise) { + assert.equal(1, 2, 'Wrap returned udnefined instead of promise.'); + return Promise.resolve(); + } + return promise.then(() => { + assert.equal(editor.document.getText(), expectedContents); + return Promise.resolve(); + }); + }); + }); + + test('Wrap individual lines with abbreviation', () => { + const contents = ` + +`; + const wrapIndividualLinesExpected = ` + +`; + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [new Selection(2, 2, 3, 33)]; + const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*' }); + if (!promise) { + assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned udnefined.'); + return Promise.resolve(); + } + return promise.then(() => { + assert.equal(editor.document.getText(), wrapIndividualLinesExpected); + return Promise.resolve(); + }); + }); + }); + + test('Wrap individual lines with abbreviation with comment filter', () => { + const contents = ` + +`; + const wrapIndividualLinesExpected = ` + +`; + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [new Selection(2, 2, 3, 33)]; + const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello*|c' }); + if (!promise) { + assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned udnefined.'); + return Promise.resolve(); + } + return promise.then(() => { + assert.equal(editor.document.getText(), wrapIndividualLinesExpected); + return Promise.resolve(); + }); + }); + }); + + test('Wrap individual lines with abbreviation and trim', () => { + const contents = ` + + `; + const wrapIndividualLinesExpected = ` + + `; + return withRandomFileEditor(contents, 'html', (editor, doc) => { + editor.selections = [new Selection(2, 3, 3, 16)]; + const promise = wrapIndividualLinesWithAbbreviation({ abbreviation: 'ul>li.hello$*|t' }); + if (!promise) { + assert.equal(1, 2, 'Wrap Individual Lines with Abbreviation returned udnefined.'); + return Promise.resolve(); + } + + return promise.then(() => { + assert.equal(editor.document.getText(), wrapIndividualLinesExpected); + return Promise.resolve(); + }); + }); + }); +}); + + +function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string): Thenable { + return withRandomFileEditor(htmlContentsForWrapTests, 'html', (editor, doc) => { + editor.selections = selections; + const promise = wrapWithAbbreviation({ abbreviation }); + if (!promise) { + assert.equal(1, 2, 'Wrap with Abbreviation returned udnefined.'); + return Promise.resolve(); + } + + return promise.then(() => { + assert.equal(editor.document.getText(), expectedContents); + return Promise.resolve(); + }); + }); +} diff --git a/extensions/emmet/src/typings/EmmetNode.d.ts b/extensions/emmet/src/typings/EmmetNode.d.ts index 476af0ed931..478241f0d8a 100644 --- a/extensions/emmet/src/typings/EmmetNode.d.ts +++ b/extensions/emmet/src/typings/EmmetNode.d.ts @@ -68,9 +68,10 @@ declare module 'EmmetNode' { export interface Property extends CssNode { valueToken: Token - separator: Token + separator: string parent: Rule terminatorToken: Token + separatorToken: Token value: string } diff --git a/extensions/emmet/yarn.lock b/extensions/emmet/yarn.lock index 8d8874a062d..95b87244b85 100644 --- a/extensions/emmet/yarn.lock +++ b/extensions/emmet/yarn.lock @@ -13,9 +13,9 @@ version "0.1.3" resolved "https://registry.yarnpkg.com/@emmetio/extract-abbreviation/-/extract-abbreviation-0.1.3.tgz#dc00bf488ddf86a2a82ca95fb2ccb575bd832f68" -"@emmetio/html-matcher@^0.3.1": - version "0.3.2" - resolved "https://registry.yarnpkg.com/@emmetio/html-matcher/-/html-matcher-0.3.2.tgz#efe0023e97191de1639f01fdcf0a198b588d3624" +"@emmetio/html-matcher@^0.3.3": + version "0.3.3" + resolved "https://registry.yarnpkg.com/@emmetio/html-matcher/-/html-matcher-0.3.3.tgz#0bbdadc0882e185950f03737dc6dbf8f7bd90728" dependencies: "@emmetio/stream-reader" "^2.0.0" "@emmetio/stream-reader-utils" "^0.1.0" @@ -2048,9 +2048,9 @@ vinyl@~2.0.1: remove-trailing-separator "^1.0.1" replace-ext "^1.0.0" -vscode-emmet-helper@^1.1.19: - version "1.1.19" - resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.19.tgz#e5b607076171555c6be3655a8eb11c17f980b2dd" +vscode-emmet-helper@^1.1.20: + version "1.1.20" + resolved "https://registry.yarnpkg.com/vscode-emmet-helper/-/vscode-emmet-helper-1.1.20.tgz#7523dc7f635f74e4becc44d4e519a9db5055a023" dependencies: "@emmetio/extract-abbreviation" "^0.1.1" vscode-languageserver-types "^3.0.3" @@ -2059,6 +2059,10 @@ vscode-languageserver-types@^3.0.3: version "3.3.0" resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.3.0.tgz#8964dc7c2247536fbefd2d6836bf3febac80dd00" +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-nls@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" diff --git a/extensions/extension-editing/package.json b/extensions/extension-editing/package.json index 3dbda782d60..8f4b631803e 100644 --- a/extensions/extension-editing/package.json +++ b/extensions/extension-editing/package.json @@ -20,7 +20,7 @@ "watch": "gulp watch-extension:extension-editing" }, "dependencies": { - "jsonc-parser": "^0.3.1", + "jsonc-parser": "^1.0.0", "markdown-it": "^8.3.1", "parse5": "^3.0.2", "vscode-nls": "^2.0.1" diff --git a/extensions/extension-editing/yarn.lock b/extensions/extension-editing/yarn.lock index b7d1eacebe0..903a9f4e191 100644 --- a/extensions/extension-editing/yarn.lock +++ b/extensions/extension-editing/yarn.lock @@ -20,11 +20,9 @@ entities@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.1.tgz#6e5c2d0a5621b5dadaecef80b90edfb5cd7772f0" -jsonc-parser@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-0.3.1.tgz#6ebf5c75224368d4b07ef4c26f9434e657472e95" - dependencies: - vscode-nls "^2.0.2" +jsonc-parser@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-1.0.0.tgz#ddcc864ae708e60a7a6dd36daea00172fa8d9272" linkify-it@^2.0.0: version "2.0.3" @@ -60,6 +58,6 @@ uc.micro@^1.0.1, uc.micro@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" -vscode-nls@^2.0.1, vscode-nls@^2.0.2: +vscode-nls@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" diff --git a/extensions/git/package.json b/extensions/git/package.json index ecb551df3e9..645361ff5ec 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -68,6 +68,15 @@ "dark": "resources/icons/dark/open-file.svg" } }, + { + "command": "git.openFile2", + "title": "%command.openFile%", + "category": "Git", + "icon": { + "light": "resources/icons/light/open-file-mono.svg", + "dark": "resources/icons/dark/open-file-mono.svg" + } + }, { "command": "git.openHEADFile", "title": "%command.openHEADFile%", @@ -369,6 +378,10 @@ "command": "git.revertChange", "when": "false" }, + { + "command": "git.openFile2", + "when": "false" + }, { "command": "git.unstage", "when": "gitOpenRepositoryCount != 0" @@ -701,11 +714,21 @@ "when": "scmProvider == git && scmResourceGroup == merge", "group": "1_modification" }, + { + "command": "git.openFile", + "when": "scmProvider == git && scmResourceGroup == merge", + "group": "navigation" + }, { "command": "git.stage", "when": "scmProvider == git && scmResourceGroup == merge", "group": "inline" }, + { + "command": "git.openFile2", + "when": "scmProvider == git && scmResourceGroup == merge", + "group": "inline0" + }, { "command": "git.openChange", "when": "scmProvider == git && scmResourceGroup == index", @@ -731,6 +754,11 @@ "when": "scmProvider == git && scmResourceGroup == index", "group": "inline" }, + { + "command": "git.openFile2", + "when": "scmProvider == git && scmResourceGroup == index", + "group": "inline0" + }, { "command": "git.openChange", "when": "scmProvider == git && scmResourceGroup == workingTree", @@ -766,6 +794,11 @@ "when": "scmProvider == git && scmResourceGroup == workingTree", "group": "inline" }, + { + "command": "git.openFile2", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "inline0" + }, { "command": "git.ignore", "when": "scmProvider == git && scmResourceGroup == workingTree", @@ -828,6 +861,11 @@ "default": null, "isExecutable": true }, + "git.autoRepositoryDetection": { + "type": "boolean", + "description": "%config.autoRepositoryDetection%", + "default": true + }, "git.autorefresh": { "type": "boolean", "description": "%config.autorefresh%", @@ -946,6 +984,15 @@ "dark": "#6c6cc4", "highContrast": "#6c6cc4" } + }, + { + "id": "gitDecoration.submoduleResourceForeground", + "description": "%colors.submodule%", + "defaults": { + "light": "#1258a7", + "dark": "#8db9e2", + "highContrast": "#8db9e2" + } } ] }, @@ -954,13 +1001,15 @@ "file-type": "^7.2.0", "iconv-lite": "0.4.19", "vscode-extension-telemetry": "0.0.8", - "vscode-nls": "2.0.2" + "vscode-nls": "2.0.2", + "which": "^1.3.0" }, "devDependencies": { "@types/byline": "4.2.31", "@types/file-type": "^5.2.1", "@types/mocha": "2.2.43", "@types/node": "7.0.43", + "@types/which": "^1.0.28", "mocha": "^3.2.0" } } \ No newline at end of file diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index c72fc11253d..179c430eb4b 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -49,6 +49,7 @@ "command.stashPopLatest": "Pop Latest Stash", "config.enabled": "Whether git is enabled", "config.path": "Path to the git executable", + "config.autoRepositoryDetection": "Whether 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", @@ -67,5 +68,6 @@ "colors.deleted": "Color for deleted resources.", "colors.untracked": "Color for untracked resources.", "colors.ignored": "Color for ignored resources.", - "colors.conflict": "Color for resources with conflicts." + "colors.conflict": "Color for resources with conflicts.", + "colors.submodule": "Color for submodule resources." } \ No newline at end of file diff --git a/extensions/git/resources/icons/dark/open-file-mono.svg b/extensions/git/resources/icons/dark/open-file-mono.svg new file mode 100644 index 00000000000..830727e70b3 --- /dev/null +++ b/extensions/git/resources/icons/dark/open-file-mono.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/git/resources/icons/light/open-file-mono.svg b/extensions/git/resources/icons/light/open-file-mono.svg new file mode 100644 index 00000000000..fa3f245b755 --- /dev/null +++ b/extensions/git/resources/icons/light/open-file-mono.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/extensions/git/resources/icons/light/open-file.svg b/extensions/git/resources/icons/light/open-file.svg index 85a001dccc2..d23a23c6b5f 100644 --- a/extensions/git/resources/icons/light/open-file.svg +++ b/extensions/git/resources/icons/light/open-file.svg @@ -1,3 +1 @@ - -]> \ No newline at end of file + \ No newline at end of file diff --git a/extensions/git/src/autofetch.ts b/extensions/git/src/autofetch.ts index cb8e054e90a..ef309483297 100644 --- a/extensions/git/src/autofetch.ts +++ b/extensions/git/src/autofetch.ts @@ -5,7 +5,7 @@ 'use strict'; -import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget } from 'vscode'; +import { workspace, Disposable, EventEmitter, Memento, window, MessageItem, ConfigurationTarget, commands, Uri } from 'vscode'; import { GitErrorCodes } from './git'; import { Repository, Operation } from './repository'; import { eventToPromise, filterEvent, onceEvent } from './util'; @@ -54,14 +54,20 @@ export class AutoFetcher { } const yes: MessageItem = { title: localize('yes', "Yes") }; + const readMore: MessageItem = { title: localize('read more', "Read More") }; const no: MessageItem = { isCloseAffordance: true, title: localize('no', "No") }; - const askLater: MessageItem = { title: localize('not now', "Not Now") }; - const result = await window.showInformationMessage(localize('suggest auto fetch', "Would you like to enable auto fetching of Git repositories?"), yes, no, askLater); + const askLater: MessageItem = { title: localize('not now', "Ask Me Later") }; + const result = await window.showInformationMessage(localize('suggest auto fetch', "Would you like Code to periodically run `git fetch`?"), yes, readMore, no, askLater); if (result === askLater) { return; } + if (result === readMore) { + commands.executeCommand('vscode.open', Uri.parse('https://go.microsoft.com/fwlink/?linkid=865294')); + return this.onFirstGoodRemoteOperation(); + } + if (result === yes) { const gitConfig = workspace.getConfiguration('git'); gitConfig.update('autofetch', true, ConfigurationTarget.Global); diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index 07e7594aa6b..10f4cd7150e 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -7,12 +7,13 @@ import { Uri, commands, Disposable, window, workspace, QuickPickItem, OutputChannel, Range, WorkspaceEdit, Position, LineChange, SourceControlResourceState, TextDocumentShowOptions, ViewColumn, ProgressLocation, TextEditor, CancellationTokenSource, StatusBarAlignment } from 'vscode'; import { Ref, RefType, Git, GitErrorCodes, Branch } from './git'; -import { Repository, Resource, Status, CommitOptions, ResourceGroupType } from './repository'; +import { Repository, Resource, Status, CommitOptions, ResourceGroupType, RepositoryState } from './repository'; import { Model } from './model'; import { toGitUri, fromGitUri } from './uri'; -import { grep } from './util'; -import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange } from './staging'; +import { grep, eventToPromise, isDescendant } from './util'; +import { applyLineChanges, intersectDiffWithRange, toLineRanges, invertLineChange, getModifiedRange } from './staging'; import * as path from 'path'; +import { lstat, Stats } from 'fs'; import * as os from 'os'; import TelemetryReporter from 'vscode-extension-telemetry'; import * as nls from 'vscode-nls'; @@ -168,8 +169,28 @@ export class CommandCenter { } private async _openResource(resource: Resource, preview?: boolean, preserveFocus?: boolean, preserveSelection?: boolean): Promise { - const left = await this.getLeftResource(resource); - const right = await this.getRightResource(resource); + let stat: Stats | undefined; + + try { + stat = await new Promise((c, e) => lstat(resource.resourceUri.fsPath, (err, stat) => err ? e(err) : c(stat))); + } catch (err) { + // noop + } + + let left: Uri | undefined; + let right: Uri | undefined; + + if (stat && stat.isDirectory()) { + const repository = this.model.getRepositoryForSubmodule(resource.resourceUri); + + if (repository) { + right = toGitUri(resource.resourceUri, resource.resourceGroupType === ResourceGroupType.Index ? 'index' : 'wt', { submoduleOf: repository.root }); + } + } else { + left = await this.getLeftResource(resource); + right = await this.getRightResource(resource); + } + const title = this.getTitle(resource); if (!right) { @@ -330,7 +351,6 @@ export class CommandCenter { const config = workspace.getConfiguration('git'); let value = config.get('defaultCloneDirectory') || os.homedir(); - value = value.replace(/^~/, os.homedir()); const parentPath = await window.showInputBox({ prompt: localize('parent', "Parent Directory"), @@ -358,11 +378,10 @@ export class CommandCenter { statusBarItem.command = cancelCommandId; statusBarItem.show(); - const clonePromise = this.git.clone(url, parentPath, tokenSource.token); + const clonePromise = this.git.clone(url, parentPath.replace(/^~/, os.homedir()), tokenSource.token); try { window.withProgress({ location: ProgressLocation.SourceControl, title: localize('cloning', "Cloning git repository...") }, () => clonePromise); - // window.withProgress({ location: ProgressLocation.Window, title: localize('cloning', "Cloning git repository...") }, () => clonePromise); const repositoryPath = await clonePromise; @@ -484,7 +503,10 @@ export class CommandCenter { } if (resource) { - uris = [...resourceStates.map(r => r.resourceUri), resource.resourceUri]; + const resources = ([resource, ...resourceStates] as Resource[]) + .filter(r => r.type !== Status.DELETED && r.type !== Status.INDEX_DELETED); + + uris = resources.map(r => r.resourceUri); } } @@ -511,6 +533,11 @@ export class CommandCenter { } } + @command('git.openFile2') + async openFile2(arg?: Resource | Uri, ...resourceStates: SourceControlResourceState[]): Promise { + this.openFile(arg, ...resourceStates); + } + @command('git.openHEADFile') async openHEADFile(arg?: Resource | Uri): Promise { let resource: Resource | undefined = undefined; @@ -709,10 +736,7 @@ export class CommandCenter { const modifiedDocument = textEditor.document; const selections = textEditor.selections; const selectedChanges = changes.filter(change => { - const modifiedRange = change.modifiedEndLineNumber === 0 - ? new Range(modifiedDocument.lineAt(change.modifiedStartLineNumber - 1).range.end, modifiedDocument.lineAt(change.modifiedStartLineNumber).range.start) - : new Range(modifiedDocument.lineAt(change.modifiedStartLineNumber - 1).range.start, modifiedDocument.lineAt(change.modifiedEndLineNumber - 1).range.end); - + const modifiedRange = getModifiedRange(modifiedDocument, change); return selections.every(selection => !selection.intersection(modifiedRange)); }); @@ -939,6 +963,25 @@ export class CommandCenter { getCommitMessage: () => Promise, opts?: CommitOptions ): Promise { + const unsavedTextDocuments = workspace.textDocuments + .filter(d => !d.isUntitled && d.isDirty && isDescendant(repository.root, d.uri.fsPath)); + + 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); + 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); + + if (pick === saveAndCommit) { + await Promise.all(unsavedTextDocuments.map(d => d.save())); + await repository.status(); + } else if (pick !== commit) { + return false; // do not commit on cancel + } + } + const config = workspace.getConfiguration('git'); const enableSmartCommit = config.get('enableSmartCommit') === true; const enableCommitSigning = config.get('enableCommitSigning') === true; @@ -963,6 +1006,8 @@ export class CommandCenter { if (!opts) { opts = { all: noStagedChanges }; + } else if (!opts.all && noStagedChanges) { + opts = { ...opts, all: true }; } // enable signing of commits if configurated @@ -996,8 +1041,21 @@ export class CommandCenter { return message; } + let value: string | undefined = undefined; + + if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { + value = (await repository.getCommit(repository.HEAD.commit)).message; + } + + const getPreviousCommitMessage = async () => { + //Only return the previous commit message if it's an amend commit and the repo already has a commit + if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) { + return (await repository.getCommit('HEAD')).message; + } + }; + return await window.showInputBox({ - value: opts && opts.defaultMsg, + value, placeHolder: localize('commit message', "Commit message"), prompt: localize('provide commit message', "Please provide a commit message"), ignoreFocusOut: true @@ -1041,15 +1099,7 @@ export class CommandCenter { @command('git.commitStagedAmend', { repository: true }) async commitStagedAmend(repository: Repository): Promise { - let msg; - if (repository.HEAD) { - if (repository.HEAD.commit) { - let id = repository.HEAD.commit; - let commit = await repository.getCommit(id); - msg = commit.message; - } - } - await this.commitWithAnyInput(repository, { all: false, amend: true, defaultMsg: msg }); + await this.commitWithAnyInput(repository, { all: false, amend: true }); } @command('git.commitAll', { repository: true }) @@ -1267,25 +1317,26 @@ export class CommandCenter { return; } - const picks = remotes.map(r => ({ label: r.name, description: r.url })); + const remotePicks = remotes.map(r => ({ label: r.name, description: r.url })); const placeHolder = localize('pick remote pull repo', "Pick a remote to pull the branch from"); - const pick = await window.showQuickPick(picks, { placeHolder }); + const remotePick = await window.showQuickPick(remotePicks, { placeHolder }); - if (!pick) { + if (!remotePick) { return; } - const branchName = await window.showInputBox({ - placeHolder: localize('branch name', "Branch name"), - prompt: localize('provide branch name', "Please provide a branch name"), - ignoreFocusOut: true - }); + const remoteRefs = repository.refs; + const remoteRefsFiltered = remoteRefs.filter(r => (r.remote === remotePick.label)); + const branchPicks = remoteRefsFiltered.map(r => ({ label: r.name })) as { label: string; description: string }[]; + const branchPick = await window.showQuickPick(branchPicks, { placeHolder }); - if (!branchName) { + if (!branchPick) { return; } - repository.pull(false, pick.label, branchName); + const remoteCharCnt = remotePick.label.length; + + repository.pull(false, remotePick.label, branchPick.label.slice(remoteCharCnt + 1)); } @command('git.pull', { repository: true }) @@ -1321,7 +1372,27 @@ export class CommandCenter { return; } - await repository.push(); + if (!repository.HEAD || !repository.HEAD.name) { + window.showWarningMessage(localize('nobranch', "Please check out a branch to push to a remote.")); + return; + } + + try { + await repository.push(); + } catch (err) { + if (err.gitErrorCode !== GitErrorCodes.NoUpstreamBranch) { + throw err; + } + + const branchName = repository.HEAD.name; + const message = localize('confirm publish branch', "The branch '{0}' has no upstream branch. Would you like to publish this branch?", branchName); + const yes = localize('ok', "OK"); + const pick = await window.showWarningMessage(message, { modal: true }, yes); + + if (pick === yes) { + await this.publish(repository); + } + } } @command('git.pushWithTags', { repository: true }) @@ -1465,7 +1536,10 @@ export class CommandCenter { } private async _stash(repository: Repository, includeUntracked = false): Promise { - if (repository.workingTreeGroup.resourceStates.length === 0) { + const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0; + const noStagedChanges = repository.indexGroup.resourceStates.length === 0; + + if (noUnstagedChanges && noStagedChanges) { window.showInformationMessage(localize('no changes stash', "There are no changes to stash.")); return; } @@ -1641,13 +1715,18 @@ export class CommandCenter { const isSingleResource = arg instanceof Uri; const groups = resources.reduce((result, resource) => { - const repository = this.model.getRepository(resource); + let repository = this.model.getRepository(resource); if (!repository) { console.warn('Could not find git repository for ', resource); return result; } + // Could it be a submodule? + if (resource.fsPath === repository.root) { + repository = this.model.getRepositoryForSubmodule(resource) || repository; + } + const tuple = result.filter(p => p.repository === repository)[0]; if (tuple) { diff --git a/extensions/git/src/contentProvider.ts b/extensions/git/src/contentProvider.ts index 9ab283dcd1e..67b36b3feca 100644 --- a/extensions/git/src/contentProvider.ts +++ b/extensions/git/src/contentProvider.ts @@ -52,7 +52,7 @@ export class GitContentProvider { return; } - this._onDidChange.fire(toGitUri(uri, '', true)); + this._onDidChange.fire(toGitUri(uri, '', { replaceFileExtension: true })); } @debounce(1100) @@ -83,6 +83,18 @@ export class GitContentProvider { } async provideTextDocumentContent(uri: Uri): Promise { + let { path, ref, submoduleOf } = fromGitUri(uri); + + if (submoduleOf) { + const repository = this.model.getRepository(submoduleOf); + + if (!repository) { + return ''; + } + + return await repository.diff(path, { cached: ref === 'index' }); + } + const repository = this.model.getRepository(uri); if (!repository) { @@ -95,8 +107,6 @@ export class GitContentProvider { this.cache[cacheKey] = cacheValue; - let { path, ref } = fromGitUri(uri); - if (ref === '~') { const fileUri = Uri.file(path); const uriString = fileUri.toString(); diff --git a/extensions/git/src/decorationProvider.ts b/extensions/git/src/decorationProvider.ts index fc068efd8c4..a25f6052d69 100644 --- a/extensions/git/src/decorationProvider.ts +++ b/extensions/git/src/decorationProvider.ts @@ -6,35 +6,48 @@ 'use strict'; import { window, workspace, Uri, Disposable, Event, EventEmitter, DecorationData, DecorationProvider, ThemeColor } from 'vscode'; +import * as path from 'path'; import { Repository, GitResourceGroup, Status } from './repository'; import { Model } from './model'; import { debounce } from './decorators'; -import { filterEvent } from './util'; +import { filterEvent, dispose, anyEvent, mapEvent, fireEvent } from './util'; +import { Submodule, GitErrorCodes } from './git'; + +type Callback = { resolve: (status: boolean) => void, reject: (err: any) => void }; class GitIgnoreDecorationProvider implements DecorationProvider { - private readonly _onDidChangeDecorations = new EventEmitter(); - readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; - - private checkIgnoreQueue = new Map void, reject: (err: any) => void }>(); + readonly onDidChangeDecorations: Event; + private queue = new Map; }>(); private disposables: Disposable[] = []; - constructor(private repository: Repository) { - this.disposables.push( - window.registerDecorationProvider(this), - filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore'))(_ => this._onDidChangeDecorations.fire()) - //todo@joh -> events when the ignore status actually changes, not only when the file changes - ); - } + constructor(private model: Model) { + //todo@joh -> events when the ignore status actually changes, not only when the file changes + this.onDidChangeDecorations = fireEvent(anyEvent( + filterEvent(workspace.onDidSaveTextDocument, e => e.fileName.endsWith('.gitignore')), + model.onDidOpenRepository, + model.onDidCloseRepository + )); - dispose(): void { - this.disposables.forEach(d => d.dispose()); - this.checkIgnoreQueue.clear(); + this.disposables.push(window.registerDecorationProvider(this)); } provideDecoration(uri: Uri): Promise { + const repository = this.model.getRepository(uri); + + if (!repository) { + return Promise.resolve(undefined); + } + + let queueItem = this.queue.get(repository.root); + + if (!queueItem) { + queueItem = { repository, queue: new Map() }; + this.queue.set(repository.root, queueItem); + } + return new Promise((resolve, reject) => { - this.checkIgnoreQueue.set(uri.fsPath, { resolve, reject }); + queueItem!.queue.set(uri.fsPath, { resolve, reject }); this.checkIgnoreSoon(); }).then(ignored => { if (ignored) { @@ -48,23 +61,42 @@ class GitIgnoreDecorationProvider implements DecorationProvider { @debounce(500) private checkIgnoreSoon(): void { - const queue = new Map(this.checkIgnoreQueue.entries()); - this.checkIgnoreQueue.clear(); - this.repository.checkIgnore([...queue.keys()]).then(ignoreSet => { - for (const [key, value] of queue.entries()) { - value.resolve(ignoreSet.has(key)); - } - }, err => { - console.error(err); - for (const [, value] of queue.entries()) { - value.reject(err); - } - }); + const queue = new Map(this.queue.entries()); + this.queue.clear(); + + for (const [, item] of queue) { + const paths = [...item.queue.keys()]; + + item.repository.checkIgnore(paths).then(ignoreSet => { + for (const [key, value] of item.queue.entries()) { + value.resolve(ignoreSet.has(key)); + } + }, err => { + if (err.gitErrorCode !== GitErrorCodes.IsInSubmodule) { + console.error(err); + } + + for (const [, value] of item.queue.entries()) { + value.reject(err); + } + }); + } + } + + dispose(): void { + this.disposables.forEach(d => d.dispose()); + this.queue.clear(); } } class GitDecorationProvider implements DecorationProvider { + private static SubmoduleDecorationData: DecorationData = { + title: 'Submodule', + abbreviation: 'S', + color: new ThemeColor('gitDecoration.submoduleResourceForeground') + }; + private readonly _onDidChangeDecorations = new EventEmitter(); readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; @@ -80,6 +112,8 @@ class GitDecorationProvider implements DecorationProvider { private onDidRunGitStatus(): void { let newDecorations = new Map(); + + this.collectSubmoduleDecorationData(newDecorations); this.collectDecorationData(this.repository.indexGroup, newDecorations); this.collectDecorationData(this.repository.workingTreeGroup, newDecorations); this.collectDecorationData(this.repository.mergeGroup, newDecorations); @@ -101,6 +135,12 @@ class GitDecorationProvider implements DecorationProvider { }); } + private collectSubmoduleDecorationData(bucket: Map): void { + for (const submodule of this.repository.submodules) { + bucket.set(Uri.file(path.join(this.repository.root, submodule.path)).toString(), GitDecorationProvider.SubmoduleDecorationData); + } + } + provideDecoration(uri: Uri): DecorationData | undefined { return this.decorations.get(uri.toString()); } @@ -113,17 +153,21 @@ class GitDecorationProvider implements DecorationProvider { export class GitDecorations { - private configListener: Disposable; - private modelListener: Disposable[] = []; + private disposables: Disposable[] = []; + private modelDisposables: Disposable[] = []; private providers = new Map(); constructor(private model: Model) { - this.configListener = workspace.onDidChangeConfiguration(e => e.affectsConfiguration('git.decorations.enabled') && this.update()); + this.disposables.push(new GitIgnoreDecorationProvider(model)); + + const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.decorations.enabled')); + onEnablementChange(this.update, this, this.disposables); this.update(); } private update(): void { const enabled = workspace.getConfiguration('git').get('decorations.enabled'); + if (enabled) { this.enable(); } else { @@ -132,26 +176,25 @@ export class GitDecorations { } private enable(): void { - this.modelListener = []; - this.model.onDidOpenRepository(this.onDidOpenRepository, this, this.modelListener); - this.model.onDidCloseRepository(this.onDidCloseRepository, this, this.modelListener); + this.model.onDidOpenRepository(this.onDidOpenRepository, this, this.modelDisposables); + this.model.onDidCloseRepository(this.onDidCloseRepository, this, this.modelDisposables); this.model.repositories.forEach(this.onDidOpenRepository, this); } private disable(): void { - this.modelListener.forEach(d => d.dispose()); + this.modelDisposables = dispose(this.modelDisposables); this.providers.forEach(value => value.dispose()); this.providers.clear(); } private onDidOpenRepository(repository: Repository): void { const provider = new GitDecorationProvider(repository); - const ignoreProvider = new GitIgnoreDecorationProvider(repository); - this.providers.set(repository, Disposable.from(provider, ignoreProvider)); + this.providers.set(repository, provider); } private onDidCloseRepository(repository: Repository): void { const provider = this.providers.get(repository); + if (provider) { provider.dispose(); this.providers.delete(repository); @@ -159,9 +202,7 @@ export class GitDecorations { } dispose(): void { - this.configListener.dispose(); - this.modelListener.forEach(d => d.dispose()); - this.providers.forEach(value => value.dispose); - this.providers.clear(); + this.disable(); + this.disposables = dispose(this.disposables); } } diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 17e6cd29942..fc6fc35262f 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -9,13 +9,14 @@ import * as fs from 'fs'; import * as path from 'path'; import * as os from 'os'; import * as cp from 'child_process'; +import * as which from 'which'; import { EventEmitter } from 'events'; import iconv = require('iconv-lite'); import * as filetype from 'file-type'; import { assign, uniqBy, groupBy, denodeify, IDisposable, toDisposable, dispose, mkdirp, readBytes, detectUnicodeEncoding, Encoding, onceEvent } from './util'; import { CancellationToken } from 'vscode'; -const readfile = denodeify(fs.readFile); +const readfile = denodeify(fs.readFile); export interface IGit { path: string; @@ -124,10 +125,17 @@ function findSystemGitWin32(base: string, onLookup: (path: string) => void): Pro return findSpecificGit(path.join(base, 'Git', 'cmd', 'git.exe'), onLookup); } +function findGitWin32InPath(onLookup: (path: string) => void): Promise { + const whichPromise = new Promise((c, e) => which('git.exe', (err, path) => err ? e(err) : c(path))); + return whichPromise.then(path => findSpecificGit(path, onLookup)); +} + function findGitWin32(onLookup: (path: string) => void): Promise { return findSystemGitWin32(process.env['ProgramW6432'] as string, onLookup) .then(void 0, () => findSystemGitWin32(process.env['ProgramFiles(x86)'] as string, onLookup)) - .then(void 0, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onLookup)); + .then(void 0, () => findSystemGitWin32(process.env['ProgramFiles'] as string, onLookup)) + .then(void 0, () => findSystemGitWin32(path.join(process.env['LocalAppData'] as string, 'Programs'), onLookup)) + .then(void 0, () => findGitWin32InPath(onLookup)); } export function findGit(hint: string | undefined, onLookup: (path: string) => void): Promise { @@ -317,7 +325,9 @@ export const GitErrorCodes = { BranchAlreadyExists: 'BranchAlreadyExists', NoLocalChanges: 'NoLocalChanges', NoStashFound: 'NoStashFound', - LocalChangesOverwritten: 'LocalChangesOverwritten' + LocalChangesOverwritten: 'LocalChangesOverwritten', + NoUpstreamBranch: 'NoUpstreamBranch', + IsInSubmodule: 'IsInSubmodule' }; function getGitErrorCode(stderr: string): string | undefined { @@ -534,6 +544,72 @@ export class GitStatusParser { } } +export interface Submodule { + name: string; + path: string; + url: string; +} + +export function parseGitmodules(raw: string): Submodule[] { + const regex = /\r?\n/g; + let position = 0; + let match: RegExpExecArray | null = null; + + const result: Submodule[] = []; + let submodule: Partial = {}; + + function parseLine(line: string): void { + const sectionMatch = /^\s*\[submodule "([^"]+)"\]\s*$/.exec(line); + + if (sectionMatch) { + if (submodule.name && submodule.path && submodule.url) { + result.push(submodule as Submodule); + } + + const name = sectionMatch[1]; + + if (name) { + submodule = { name }; + return; + } + } + + if (!submodule) { + return; + } + + const propertyMatch = /^\s*(\w+) = (.*)$/.exec(line); + + if (!propertyMatch) { + return; + } + + const [, key, value] = propertyMatch; + + switch (key) { + case 'path': submodule.path = value; break; + case 'url': submodule.url = value; break; + } + } + + while (match = regex.exec(raw)) { + parseLine(raw.substring(position, match.index)); + position = match.index + match[0].length; + } + + parseLine(raw.substring(position)); + + if (submodule.name && submodule.path && submodule.url) { + result.push(submodule as Submodule); + } + + return result; +} + +export interface DiffOptions { + cached?: boolean; +} + export class Repository { constructor( @@ -672,6 +748,19 @@ export class Repository { } } + async diff(path: string, options: DiffOptions = {}): Promise { + const args = ['diff']; + + if (options.cached) { + args.push('--cached'); + } + + args.push('--', path); + + const result = await this.run(args); + return result.stdout; + } + async add(paths: string[]): Promise { const args = ['add', '-A', '--']; @@ -945,6 +1034,8 @@ export class Repository { err.gitErrorCode = GitErrorCodes.PushRejected; } else if (/Could not read from remote repository/.test(err.stderr || '')) { err.gitErrorCode = GitErrorCodes.RemoteConnectionError; + } else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) { + err.gitErrorCode = GitErrorCodes.NoUpstreamBranch; } throw err; @@ -1059,7 +1150,7 @@ export class Repository { } async getRefs(): Promise { - const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)']); + const result = await this.run(['for-each-ref', '--format', '%(refname) %(objectname)', '--sort', '-committerdate']); const fn = (line: string): Ref | null => { let match: RegExpExecArray | null; @@ -1178,4 +1269,24 @@ export class Repository { return { hash: match[1], message: match[2] }; } + + async updateSubmodules(paths: string[]): Promise { + const args = ['submodule', 'update', '--', ...paths]; + await this.run(args); + } + + async getSubmodules(): Promise { + const gitmodulesPath = path.join(this.root, '.gitmodules'); + + try { + const gitmodulesRaw = await readfile(gitmodulesPath, 'utf8'); + return parseGitmodules(gitmodulesRaw); + } catch (err) { + if (/ENOENT/.test(err.message)) { + return []; + } + + throw err; + } + } } diff --git a/extensions/git/src/model.ts b/extensions/git/src/model.ts index a28d386c38b..954cb774c86 100644 --- a/extensions/git/src/model.ts +++ b/extensions/git/src/model.ts @@ -8,11 +8,12 @@ import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, ConfigurationChangeEvent } from 'vscode'; import { Repository, RepositoryState } from './repository'; import { memoize, sequentialize, debounce } from './decorators'; -import { dispose, anyEvent, filterEvent, IDisposable, isDescendant } from './util'; +import { dispose, anyEvent, filterEvent, IDisposable, isDescendant, find, firstIndex } from './util'; import { Git, GitErrorCodes } from './git'; import * as path from 'path'; import * as fs from 'fs'; import * as nls from 'vscode-nls'; +import { fromGitUri } from './uri'; const localize = nls.loadMessageBundle(); @@ -27,7 +28,7 @@ class RepositoryPick implements QuickPickItem { .join(' '); } - constructor(public readonly repository: Repository) { } + constructor(public readonly repository: Repository, public readonly index: number) { } } export interface ModelChangeEvent { @@ -101,8 +102,11 @@ export class Model { } private onPossibleGitRepositoryChange(uri: Uri): void { - const possibleGitRepositoryPath = uri.fsPath.replace(/\.git.*$/, ''); - this.possibleGitRepositoryPaths.add(possibleGitRepositoryPath); + this.eventuallyScanPossibleGitRepository(uri.fsPath.replace(/\.git.*$/, '')); + } + + private eventuallyScanPossibleGitRepository(path: string) { + this.possibleGitRepositoryPaths.add(path); this.eventuallyScanPossibleGitRepositories(); } @@ -149,6 +153,13 @@ export class Model { } private onDidChangeVisibleTextEditors(editors: TextEditor[]): void { + const config = workspace.getConfiguration('git'); + const enabled = config.get('autoRepositoryDetection') === true; + + if (!enabled) { + return; + } + editors.forEach(editor => { const uri = editor.document.uri; @@ -206,11 +217,20 @@ export class Model { const disappearListener = onDidDisappearRepository(() => dispose()); const changeListener = repository.onDidChangeRepository(uri => this._onDidChangeRepository.fire({ repository, uri })); const originalResourceChangeListener = repository.onDidChangeOriginalResource(uri => this._onDidChangeOriginalResource.fire({ repository, uri })); + const scanSubmodules = () => { + repository.submodules + .map(r => path.join(repository.root, r.path)) + .forEach(p => this.eventuallyScanPossibleGitRepository(p)); + }; + + const statusListener = repository.onDidRunGitStatus(scanSubmodules); + scanSubmodules(); const dispose = () => { disappearListener.dispose(); changeListener.dispose(); originalResourceChangeListener.dispose(); + statusListener.dispose(); repository.dispose(); this.openRepositories = this.openRepositories.filter(e => e !== openRepository); @@ -237,7 +257,16 @@ export class Model { throw new Error(localize('no repositories', "There are no available repositories")); } - const picks = this.openRepositories.map(e => new RepositoryPick(e.repository)); + const picks = this.openRepositories.map((e, index) => new RepositoryPick(e.repository, index)); + const active = window.activeTextEditor; + const repository = active && this.getRepository(active.document.fileName); + const index = firstIndex(picks, pick => pick.repository === repository); + + // Move repository pick containing the active text editor to appear first + if (index > -1) { + picks.unshift(...picks.splice(index, 1)); + } + const placeHolder = localize('pick repo', "Choose a repository"); const pick = await window.showQuickPick(picks, { placeHolder }); @@ -272,14 +301,29 @@ export class Model { } if (hint instanceof Uri) { - const resourcePath = hint.fsPath; + let resourcePath: string; - for (const liveRepository of this.openRepositories) { - const relativePath = path.relative(liveRepository.repository.root, resourcePath); + if (hint.scheme === 'git') { + resourcePath = fromGitUri(hint).path; + } else { + resourcePath = hint.fsPath; + } - if (isDescendant(liveRepository.repository.root, resourcePath)) { - return liveRepository; + outer: + for (const liveRepository of this.openRepositories.sort((a, b) => b.repository.root.length - a.repository.root.length)) { + if (!isDescendant(liveRepository.repository.root, resourcePath)) { + continue; } + + for (const submodule of liveRepository.repository.submodules) { + const submoduleRoot = path.join(liveRepository.repository.root, submodule.path); + + if (isDescendant(submoduleRoot, resourcePath)) { + continue outer; + } + } + + return liveRepository; } return undefined; @@ -300,6 +344,20 @@ export class Model { return undefined; } + getRepositoryForSubmodule(submoduleUri: Uri): Repository | undefined { + for (const repository of this.repositories) { + for (const submodule of repository.submodules) { + const submodulePath = path.join(repository.root, submodule.path); + + if (submodulePath === submoduleUri.fsPath) { + return repository; + } + } + } + + return undefined; + } + dispose(): void { const openRepositories = [...this.openRepositories]; openRepositories.forEach(r => r.dispose()); diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index c400a818d9d..fadf17aa535 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -6,8 +6,8 @@ 'use strict'; import { Uri, Command, EventEmitter, Event, scm, SourceControl, SourceControlInputBox, SourceControlResourceGroup, SourceControlResourceState, SourceControlResourceDecorations, Disposable, ProgressLocation, window, workspace, WorkspaceEdit, ThemeColor, DecorationData, Memento } from 'vscode'; -import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType, GitError } from './git'; -import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant } from './util'; +import { Repository as BaseRepository, Ref, Branch, Remote, Commit, GitErrorCodes, Stash, RefType, GitError, Submodule, DiffOptions } from './git'; +import { anyEvent, filterEvent, eventToPromise, dispose, find, isDescendant, IDisposable, onceEvent, EmptyDisposable, debounceEvent } from './util'; import { memoize, throttle, debounce } from './decorators'; import { toGitUri } from './uri'; import { AutoFetcher } from './autofetch'; @@ -105,7 +105,7 @@ export class Resource implements SourceControlResourceState { } }; - private getIconPath(theme: string): Uri | undefined { + private getIconPath(theme: string): Uri { switch (this.type) { case Status.INDEX_MODIFIED: return Resource.Icons[theme].Modified; case Status.MODIFIED: return Resource.Icons[theme].Modified; @@ -123,7 +123,6 @@ export class Resource implements SourceControlResourceState { case Status.DELETED_BY_US: return Resource.Icons[theme].Conflict; case Status.BOTH_ADDED: return Resource.Icons[theme].Conflict; case Status.BOTH_MODIFIED: return Resource.Icons[theme].Conflict; - default: return void 0; } } @@ -182,7 +181,7 @@ export class Resource implements SourceControlResourceState { return { strikeThrough, faded, tooltip, light, dark, letter, color, source: 'git.resource' /*todo@joh*/ }; } - get letter(): string | undefined { + get letter(): string { switch (this.type) { case Status.INDEX_MODIFIED: case Status.MODIFIED: @@ -207,12 +206,10 @@ export class Resource implements SourceControlResourceState { case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: return 'C'; - default: - return undefined; } } - get color(): ThemeColor | undefined { + get color(): ThemeColor { switch (this.type) { case Status.INDEX_MODIFIED: case Status.MODIFIED: @@ -235,8 +232,6 @@ export class Resource implements SourceControlResourceState { case Status.BOTH_ADDED: case Status.BOTH_MODIFIED: return new ThemeColor('gitDecoration.conflictingResourceForeground'); - default: - return undefined; } } @@ -261,7 +256,7 @@ export class Resource implements SourceControlResourceState { } } - get resourceDecoration(): DecorationData | undefined { + get resourceDecoration(): DecorationData { const title = this.tooltip; const abbreviation = this.letter; const color = this.color; @@ -280,6 +275,7 @@ export class Resource implements SourceControlResourceState { export enum Operation { Status = 'Status', + Diff = 'Diff', Add = 'Add', RevertFiles = 'RevertFiles', Commit = 'Commit', @@ -301,7 +297,8 @@ export enum Operation { Tag = 'Tag', Stash = 'Stash', CheckIgnore = 'CheckIgnore', - LSTree = 'LSTree' + LSTree = 'LSTree', + SubmoduleUpdate = 'SubmoduleUpdate' } function isReadOnly(operation: Operation): boolean { @@ -330,6 +327,7 @@ function shouldShowProgress(operation: Operation): boolean { export interface Operations { isIdle(): boolean; + shouldShowProgress(): boolean; isRunning(operation: Operation): boolean; } @@ -366,6 +364,18 @@ class OperationsImpl implements Operations { return true; } + + shouldShowProgress(): boolean { + const operations = this.operations.keys(); + + for (const operation of operations) { + if (shouldShowProgress(operation)) { + return true; + } + } + + return false; + } } export interface CommitOptions { @@ -373,7 +383,6 @@ export interface CommitOptions { amend?: boolean; signoff?: boolean; signCommit?: boolean; - defaultMsg?: string; } export interface GitResourceGroup extends SourceControlResourceGroup { @@ -385,6 +394,29 @@ export interface OperationResult { error: any; } +class ProgressManager { + + private disposable: IDisposable = EmptyDisposable; + + constructor(private repository: Repository) { + const start = onceEvent(filterEvent(repository.onDidChangeOperations, () => repository.operations.shouldShowProgress())); + const end = onceEvent(filterEvent(debounceEvent(repository.onDidChangeOperations, 300), () => !repository.operations.shouldShowProgress())); + + const setup = () => { + this.disposable = start(() => { + const promise = eventToPromise(end).then(() => setup()); + window.withProgress({ location: ProgressLocation.SourceControl }, () => promise); + }); + }; + + setup(); + } + + dispose(): void { + this.disposable.dispose(); + } +} + export class Repository implements Disposable { private _onDidChangeRepository = new EventEmitter(); @@ -439,6 +471,11 @@ export class Repository implements Disposable { return this._remotes; } + private _submodules: Submodule[] = []; + get submodules(): Submodule[] { + return this._submodules; + } + private _operations = new OperationsImpl(); get operations(): Operations { return this._operations; } @@ -484,6 +521,7 @@ export class Repository implements Disposable { this._sourceControl.inputBox.placeholder = localize('commitMessage', "Message (press {0} to commit)"); this._sourceControl.acceptInputCommand = { command: 'git.commitWithInput', title: localize('commit', "Commit"), arguments: [this._sourceControl] }; this._sourceControl.quickDiffProvider = this; + this._sourceControl.inputBox.lineWarningLength = 72; this.disposables.push(this._sourceControl); this._mergeGroup = this._sourceControl.createResourceGroup('merge', localize('merge changes', "Merge Changes")); @@ -504,6 +542,9 @@ export class Repository implements Disposable { statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables); this._sourceControl.statusBarCommands = statusBar.commands; + const progressManager = new ProgressManager(this); + this.disposables.push(progressManager); + this.updateCommitTemplate(); this.status(); } @@ -513,7 +554,7 @@ export class Repository implements Disposable { return; } - return toGitUri(uri, '', true); + return toGitUri(uri, '', { replaceFileExtension: true }); } private async updateCommitTemplate(): Promise { @@ -524,21 +565,15 @@ export class Repository implements Disposable { } } - // @throttle - // async init(): Promise { - // if (this.state !== State.NotAGitRepository) { - // return; - // } - - // await this.git.init(this.workspaceRoot.fsPath); - // await this.status(); - // } - @throttle async status(): Promise { await this.run(Operation.Status); } + diff(path: string, options: DiffOptions = {}): Promise { + return this.run(Operation.Diff, () => this.repository.diff(path, options)); + } + async add(resources: Uri[]): Promise { await this.run(Operation.Add, () => this.repository.add(resources.map(r => r.fsPath))); } @@ -567,8 +602,18 @@ export class Repository implements Disposable { await this.run(Operation.Clean, async () => { const toClean: string[] = []; const toCheckout: string[] = []; + const submodulesToUpdate: string[] = []; resources.forEach(r => { + const fsPath = r.fsPath; + + for (const submodule of this.submodules) { + if (path.join(this.root, submodule.path) === fsPath) { + submodulesToUpdate.push(fsPath); + return; + } + } + const raw = r.toString(); const scmResource = find(this.workingTreeGroup.resourceStates, sr => sr.resourceUri.toString() === raw); @@ -579,11 +624,11 @@ export class Repository implements Disposable { switch (scmResource.type) { case Status.UNTRACKED: case Status.IGNORED: - toClean.push(r.fsPath); + toClean.push(fsPath); break; default: - toCheckout.push(r.fsPath); + toCheckout.push(fsPath); break; } }); @@ -598,6 +643,10 @@ export class Repository implements Disposable { promises.push(this.repository.checkout('', toCheckout)); } + if (submodulesToUpdate.length > 0) { + promises.push(this.repository.updateSubmodules(submodulesToUpdate)); + } + await Promise.all(promises); }); } @@ -780,7 +829,11 @@ export class Repository implements Disposable { // paths are separated by the null-character resolve(new Set(data.split('\0'))); } else { - reject(new GitError({ stdout: data, stderr, exitCode })); + if (/ is in submodule /.test(stderr)) { + reject(new GitError({ stdout: data, stderr, exitCode, gitErrorCode: GitErrorCodes.IsInSubmodule })); + } else { + reject(new GitError({ stdout: data, stderr, exitCode })); + } } }; @@ -807,37 +860,31 @@ export class Repository implements Disposable { throw new Error('Repository not initialized'); } - const run = async () => { - let error: any = null; + let error: any = null; - this._operations.start(operation); - this._onRunOperation.fire(operation); + this._operations.start(operation); + this._onRunOperation.fire(operation); - try { - const result = await this.retryRun(runOperation); + try { + const result = await this.retryRun(runOperation); - if (!isReadOnly(operation)) { - await this.updateModelState(); - } - - return result; - } catch (err) { - error = err; - - if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { - this.state = RepositoryState.Disposed; - } - - throw err; - } finally { - this._operations.end(operation); - this._onDidRunOperation.fire({ operation, error }); + if (!isReadOnly(operation)) { + await this.updateModelState(); } - }; - return shouldShowProgress(operation) - ? window.withProgress({ location: ProgressLocation.SourceControl }, run) - : run(); + return result; + } catch (err) { + error = err; + + if (err.gitErrorCode === GitErrorCodes.NotAGitRepository) { + this.state = RepositoryState.Disposed; + } + + throw err; + } finally { + this._operations.end(operation); + this._onDidRunOperation.fire({ operation, error }); + } } private async retryRun(runOperation: () => Promise = () => Promise.resolve(null)): Promise { @@ -863,7 +910,7 @@ export class Repository implements Disposable { const { status, didHitLimit } = await this.repository.getStatus(); const config = workspace.getConfiguration('git'); const shouldIgnore = config.get('ignoreLimitWarning') === true; - const useIcons = config.get('decorations.enabled', true); + const useIcons = !config.get('decorations.enabled', true); this.isRepositoryHuge = didHitLimit; @@ -896,11 +943,12 @@ export class Repository implements Disposable { // noop } - const [refs, remotes] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes()]); + const [refs, remotes, submodules] = await Promise.all([this.repository.getRefs(), this.repository.getRemotes(), this.repository.getSubmodules()]); this._HEAD = HEAD; this._refs = refs; this._remotes = remotes; + this._submodules = submodules; const index: Resource[] = []; const workingTree: Resource[] = []; diff --git a/extensions/git/src/staging.ts b/extensions/git/src/staging.ts index 8cfe16e6793..70c7ca71941 100644 --- a/extensions/git/src/staging.ts +++ b/extensions/git/src/staging.ts @@ -72,10 +72,16 @@ export function toLineRanges(selections: Selection[], textDocument: TextDocument return result; } -function getModifiedRange(textDocument: TextDocument, diff: LineChange): Range { - return diff.modifiedEndLineNumber === 0 - ? new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start) - : new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, textDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end); +export function getModifiedRange(textDocument: TextDocument, diff: LineChange): Range { + if (diff.modifiedEndLineNumber === 0) { + if (diff.modifiedStartLineNumber === 0) { + return new Range(textDocument.lineAt(diff.modifiedStartLineNumber).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start); + } else { + return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.end, textDocument.lineAt(diff.modifiedStartLineNumber).range.start); + } + } else { + return new Range(textDocument.lineAt(diff.modifiedStartLineNumber - 1).range.start, textDocument.lineAt(diff.modifiedEndLineNumber - 1).range.end); + } } export function intersectDiffWithRange(textDocument: TextDocument, diff: LineChange, range: Range): LineChange | null { diff --git a/extensions/git/src/test/git.test.ts b/extensions/git/src/test/git.test.ts index d8c1eb405ad..09661eebc9c 100644 --- a/extensions/git/src/test/git.test.ts +++ b/extensions/git/src/test/git.test.ts @@ -6,7 +6,7 @@ 'use strict'; import 'mocha'; -import { GitStatusParser } from '../git'; +import { GitStatusParser, parseGitmodules } from '../git'; import * as assert from 'assert'; suite('git', () => { @@ -135,4 +135,44 @@ suite('git', () => { ]); }); }); + + suite('parseGitmodules', () => { + test('empty', () => { + assert.deepEqual(parseGitmodules(''), []); + }); + + test('sample', () => { + const sample = `[submodule "deps/spdlog"] + path = deps/spdlog + url = https://github.com/gabime/spdlog.git +`; + + assert.deepEqual(parseGitmodules(sample), [ + { name: 'deps/spdlog', path: 'deps/spdlog', url: 'https://github.com/gabime/spdlog.git' } + ]); + }); + + test('big', () => { + const sample = `[submodule "deps/spdlog"] + path = deps/spdlog + url = https://github.com/gabime/spdlog.git +[submodule "deps/spdlog2"] + path = deps/spdlog2 + url = https://github.com/gabime/spdlog.git +[submodule "deps/spdlog3"] + path = deps/spdlog3 + url = https://github.com/gabime/spdlog.git +[submodule "deps/spdlog4"] + path = deps/spdlog4 + url = https://github.com/gabime/spdlog4.git +`; + + assert.deepEqual(parseGitmodules(sample), [ + { name: 'deps/spdlog', path: 'deps/spdlog', url: 'https://github.com/gabime/spdlog.git' }, + { name: 'deps/spdlog2', path: 'deps/spdlog2', url: 'https://github.com/gabime/spdlog.git' }, + { name: 'deps/spdlog3', path: 'deps/spdlog3', url: 'https://github.com/gabime/spdlog.git' }, + { name: 'deps/spdlog4', path: 'deps/spdlog4', url: 'https://github.com/gabime/spdlog4.git' } + ]); + }); + }); }); \ No newline at end of file diff --git a/extensions/git/src/uri.ts b/extensions/git/src/uri.ts index 0a56c9a5a8e..70b1c647b0f 100644 --- a/extensions/git/src/uri.ts +++ b/extensions/git/src/uri.ts @@ -7,20 +7,45 @@ import { Uri } from 'vscode'; -export function fromGitUri(uri: Uri): { path: string; ref: string; } { +export interface GitUriParams { + path: string; + ref: string; + submoduleOf?: string; +} + +export function fromGitUri(uri: Uri): GitUriParams { return JSON.parse(uri.query); } +export interface GitUriOptions { + replaceFileExtension?: boolean; + submoduleOf?: string; +} + // As a mitigation for extensions like ESLint showing warnings and errors // for git URIs, let's change the file extension of these uris to .git, // when `replaceFileExtension` is true. -export function toGitUri(uri: Uri, ref: string, replaceFileExtension = false): Uri { +export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Uri { + const params: GitUriParams = { + path: uri.fsPath, + ref + }; + + if (options.submoduleOf) { + params.submoduleOf = options.submoduleOf; + } + + let path = uri.path; + + if (options.replaceFileExtension) { + path = `${path}.git`; + } else if (options.submoduleOf) { + path = `${path}.diff`; + } + return uri.with({ scheme: 'git', - path: replaceFileExtension ? `${uri.path}.git` : uri.path, - query: JSON.stringify({ - path: uri.fsPath, - ref - }) + path, + query: JSON.stringify(params) }); } \ No newline at end of file diff --git a/extensions/git/src/util.ts b/extensions/git/src/util.ts index 6831fea9428..c050594c106 100644 --- a/extensions/git/src/util.ts +++ b/extensions/git/src/util.ts @@ -34,6 +34,10 @@ export function combinedDisposable(disposables: IDisposable[]): IDisposable { export const EmptyDisposable = toDisposable(() => null); +export function fireEvent(event: Event): Event { + return (listener, thisArgs = null, disposables?) => event(_ => listener.call(thisArgs), null, disposables); +} + export function mapEvent(event: Event, map: (i: I) => O): Event { return (listener, thisArgs = null, disposables?) => event(i => listener.call(thisArgs, map(i)), null, disposables); } @@ -69,6 +73,16 @@ export function onceEvent(event: Event): Event { }; } +export function debounceEvent(event: Event, delay: number): Event { + return (listener, thisArgs = null, disposables?) => { + let timer: NodeJS.Timer; + return event(e => { + clearTimeout(timer); + timer = setTimeout(() => listener.call(thisArgs, e), delay); + }, null, disposables); + }; +} + export function eventToPromise(event: Event): Promise { return new Promise(c => onceEvent(event)(c)); } @@ -116,6 +130,10 @@ export function groupBy(arr: T[], fn: (el: T) => string): { [key: string]: T[ }, Object.create(null)); } +export function denodeify(fn: Function): (a: A, b: B, c: C) => Promise; +export function denodeify(fn: Function): (a: A, b: B) => Promise; +export function denodeify(fn: Function): (a: A) => Promise; +export function denodeify(fn: Function): (...args: any[]) => Promise; export function denodeify(fn: Function): (...args: any[]) => Promise { return (...args) => new Promise((c, e) => fn(...args, (err: any, r: any) => err ? e(err) : c(r))); } @@ -177,6 +195,16 @@ export function uniqueFilter(keyFn: (t: T) => string): (t: T) => boolean { }; } +export function firstIndex(array: T[], fn: (t: T) => boolean): number { + for (let i = 0; i < array.length; i++) { + if (fn(array[i])) { + return i; + } + } + + return -1; +} + export function find(array: T[], fn: (t: T) => boolean): T | undefined { let result: T | undefined = undefined; diff --git a/extensions/git/yarn.lock b/extensions/git/yarn.lock index 9f5376164a6..a9d4c679d30 100644 --- a/extensions/git/yarn.lock +++ b/extensions/git/yarn.lock @@ -26,6 +26,10 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" +"@types/which@^1.0.28": + version "1.0.28" + resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6" + applicationinsights@0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.18.0.tgz#162ebb48a383408bc4de44db32b417307f45bbc1" @@ -123,6 +127,10 @@ inherits@2: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + json3@3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" @@ -238,6 +246,12 @@ vscode-nls@2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" +which@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/which/-/which-1.3.0.tgz#ff04bdfc010ee547d780bec38e1ac1c2777d253a" + dependencies: + isexe "^2.0.0" + winreg@1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/winreg/-/winreg-1.2.3.tgz#93ad116b2696da87d58f7265a8fcea5254a965d5" diff --git a/extensions/gitsyntax/.vscodeignore b/extensions/gitsyntax/.vscodeignore index 77ab386fc7d..30c5d19d6c3 100644 --- a/extensions/gitsyntax/.vscodeignore +++ b/extensions/gitsyntax/.vscodeignore @@ -1 +1,2 @@ -test/** \ No newline at end of file +test/** +build/** \ No newline at end of file diff --git a/extensions/gitsyntax/OSSREADME.json b/extensions/gitsyntax/OSSREADME.json index 0e3ddd52faf..347ae77f6e5 100644 --- a/extensions/gitsyntax/OSSREADME.json +++ b/extensions/gitsyntax/OSSREADME.json @@ -1,29 +1,42 @@ // ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS: -[{ - "name": "textmate/git.tmbundle", - "version": "0.0.0", - "license": "MIT", - "repositoryURL": "https://github.com/textmate/git.tmbundle", - "licenseDetail": [ - "Copyright (c) 2008 Tim Harper", - "", - "Permission is hereby granted, free of charge, to any person obtaining", - "a copy of this software and associated documentation files (the\"", - "Software\"), to deal in the Software without restriction, including", - "without limitation the rights to use, copy, modify, merge, publish,", - "distribute, sublicense, and/or sell copies of the Software, and to", - "permit persons to whom the Software is furnished to do so, subject to", - "the following conditions:", - "", - "The above copyright notice and this permission notice shall be", - "included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", - "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", - "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND", - "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE", - "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION", - "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION", - "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] -}] \ No newline at end of file +[ + { + "name": "textmate/git.tmbundle", + "version": "0.0.0", + "license": "MIT", + "repositoryURL": "https://github.com/textmate/git.tmbundle", + "licenseDetail": [ + "Copyright (c) 2008 Tim Harper", + "", + "Permission is hereby granted, free of charge, to any person obtaining", + "a copy of this software and associated documentation files (the\"", + "Software\"), to deal in the Software without restriction, including", + "without limitation the rights to use, copy, modify, merge, publish,", + "distribute, sublicense, and/or sell copies of the Software, and to", + "permit persons to whom the Software is furnished to do so, subject to", + "the following conditions:", + "", + "The above copyright notice and this permission notice shall be", + "included in all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,", + "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF", + "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND", + "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE", + "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION", + "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION", + "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." + ] + }, + { + "name": "textmate/diff.tmbundle", + "version": "0.0.0", + "repositoryURL": "https://github.com/textmate/diff.tmbundle", + "licenseDetail": [ + "Permission to copy, use, modify, sell and distribute this", + "software is granted. This software is provided \"as is\" without", + "express or implied warranty, and with no claim as to its", + "suitability for any purpose." + ] + } +] \ No newline at end of file diff --git a/extensions/gitsyntax/build/update-grammars.js b/extensions/gitsyntax/build/update-grammars.js new file mode 100644 index 00000000000..268157d43f8 --- /dev/null +++ b/extensions/gitsyntax/build/update-grammars.js @@ -0,0 +1,16 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var updateGrammar = require('../../../build/npm/update-grammar'); + +updateGrammar.update('textmate/git.tmbundle', 'Syntaxes/Git%20Commit%20Message.tmLanguage', './syntaxes/git-commit.tmLanguage.json'); +updateGrammar.update('textmate/git.tmbundle', 'Syntaxes/Git%20Rebase%20Message.tmLanguage', './syntaxes/git-rebase.tmLanguage.json'); +updateGrammar.update('textmate/diff.tmbundle', 'Syntaxes/Diff.plist', './syntaxes/diff.tmLanguage.json'); + + + + + diff --git a/extensions/gitsyntax/diff.language-configuration.json b/extensions/gitsyntax/diff.language-configuration.json new file mode 100644 index 00000000000..b61fbe63d34 --- /dev/null +++ b/extensions/gitsyntax/diff.language-configuration.json @@ -0,0 +1,11 @@ +{ + "comments": { + "lineComment": "#", + "blockComment": [ "#", " " ] + }, + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ] +} \ No newline at end of file diff --git a/extensions/gitsyntax/package.json b/extensions/gitsyntax/package.json index 0040fe55473..306713af50b 100644 --- a/extensions/gitsyntax/package.json +++ b/extensions/gitsyntax/package.json @@ -11,7 +11,7 @@ "Other" ], "scripts": { - "update-grammar": "node ../../build/npm/update-grammar.js textmate/git.tmbundle Syntaxes/Git%20Commit%20Message.tmLanguage ./syntaxes/git-commit.tmLanguage.json Syntaxes/Git%20Rebase%20Message.tmLanguage ./syntaxes/git-rebase.tmLanguage.json" + "update-grammar": "node ./build/update-grammars.js" }, "contributes": { "languages": [ @@ -37,6 +37,19 @@ "git-rebase-todo" ], "configuration": "./git-rebase.language-configuration.json" + }, + { + "id": "diff", + "aliases": [ + "Diff", + "diff" + ], + "extensions": [ + ".patch", + ".diff", + ".rej" + ], + "configuration": "./diff.language-configuration.json" } ], "grammars": [ @@ -49,12 +62,19 @@ "language": "git-rebase", "scopeName": "text.git-rebase", "path": "./syntaxes/git-rebase.tmLanguage.json" + }, + { + "language": "diff", + "scopeName": "source.diff", + "path": "./syntaxes/diff.tmLanguage.json" } ], "configurationDefaults": { - "[git-commit]": { - "editor.rulers": [72] - } - } + "[git-commit]": { + "editor.rulers": [ + 72 + ] + } + } } } \ No newline at end of file diff --git a/extensions/gitsyntax/syntaxes/diff.tmLanguage.json b/extensions/gitsyntax/syntaxes/diff.tmLanguage.json new file mode 100644 index 00000000000..e0d60de1081 --- /dev/null +++ b/extensions/gitsyntax/syntaxes/diff.tmLanguage.json @@ -0,0 +1,168 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/textmate/diff.tmbundle/blob/master/Syntaxes/Diff.plist", + "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/textmate/diff.tmbundle/commit/0593bb775eab1824af97ef2172fd38822abd97d7", + "fileTypes": [ + "patch", + "diff", + "rej" + ], + "firstLineMatch": "(?x)^\n\t\t(===\\ modified\\ file\n\t\t|==== \\s* // .+ \\s - \\s .+ \\s+ ====\n\t\t|Index:\\ \n\t\t|---\\ [^%\\n]\n\t\t|\\*\\*\\*.*\\d{4}\\s*$\n\t\t|\\d+(,\\d+)* (a|d|c) \\d+(,\\d+)* $\n\t\t|diff\\ --git\\ \n\t\t|commit\\ [0-9a-f]{40}$\n\t\t)", + "keyEquivalent": "^~D", + "name": "Diff", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.definition.separator.diff" + } + }, + "match": "^((\\*{15})|(={67})|(-{3}))$\\n?", + "name": "meta.separator.diff" + }, + { + "match": "^\\d+(,\\d+)*(a|d|c)\\d+(,\\d+)*$\\n?", + "name": "meta.diff.range.normal" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.range.diff" + }, + "2": { + "name": "meta.toc-list.line-number.diff" + }, + "3": { + "name": "punctuation.definition.range.diff" + } + }, + "match": "^(@@)\\s*(.+?)\\s*(@@)($\\n?)?", + "name": "meta.diff.range.unified" + }, + { + "captures": { + "3": { + "name": "punctuation.definition.range.diff" + }, + "4": { + "name": "punctuation.definition.range.diff" + }, + "6": { + "name": "punctuation.definition.range.diff" + }, + "7": { + "name": "punctuation.definition.range.diff" + } + }, + "match": "^(((\\-{3}) .+ (\\-{4}))|((\\*{3}) .+ (\\*{4})))$\\n?", + "name": "meta.diff.range.context" + }, + { + "match": "^diff --git a/.*$\\n?", + "name": "meta.diff.header.git" + }, + { + "match": "^diff (-|\\S+\\s+\\S+).*$\\n?", + "name": "meta.diff.header.command" + }, + { + "captures": { + "4": { + "name": "punctuation.definition.from-file.diff" + }, + "6": { + "name": "punctuation.definition.from-file.diff" + }, + "7": { + "name": "punctuation.definition.from-file.diff" + } + }, + "match": "(^(((-{3}) .+)|((\\*{3}) .+))$\\n?|^(={4}) .+(?= - ))", + "name": "meta.diff.header.from-file" + }, + { + "captures": { + "2": { + "name": "punctuation.definition.to-file.diff" + }, + "3": { + "name": "punctuation.definition.to-file.diff" + }, + "4": { + "name": "punctuation.definition.to-file.diff" + } + }, + "match": "(^(\\+{3}) .+$\\n?| (-) .* (={4})$\\n?)", + "name": "meta.diff.header.to-file" + }, + { + "captures": { + "3": { + "name": "punctuation.definition.inserted.diff" + }, + "6": { + "name": "punctuation.definition.inserted.diff" + } + }, + "match": "^(((>)( .*)?)|((\\+).*))$\\n?", + "name": "markup.inserted.diff" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.changed.diff" + } + }, + "match": "^(!).*$\\n?", + "name": "markup.changed.diff" + }, + { + "captures": { + "3": { + "name": "punctuation.definition.deleted.diff" + }, + "6": { + "name": "punctuation.definition.deleted.diff" + } + }, + "match": "^(((<)( .*)?)|((-).*))$\\n?", + "name": "markup.deleted.diff" + }, + { + "begin": "^(#)", + "captures": { + "1": { + "name": "punctuation.definition.comment.diff" + } + }, + "comment": "Git produces unified diffs with embedded comments\"", + "end": "\\n", + "name": "comment.line.number-sign.diff" + }, + { + "match": "^index [0-9a-f]{7,40}\\.\\.[0-9a-f]{7,40}.*$\\n?", + "name": "meta.diff.index.git" + }, + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.diff" + }, + "2": { + "name": "meta.toc-list.file-name.diff" + } + }, + "match": "^Index(:) (.+)$\\n?", + "name": "meta.diff.index" + }, + { + "match": "^Only in .*: .*$\\n?", + "name": "meta.diff.only-in" + } + ], + "scopeName": "source.diff", + "uuid": "7E848FF4-708E-11D9-97B4-0011242E4184" +} \ No newline at end of file diff --git a/extensions/gitsyntax/test/colorize-fixtures/example.diff b/extensions/gitsyntax/test/colorize-fixtures/example.diff new file mode 100644 index 00000000000..256b719212f --- /dev/null +++ b/extensions/gitsyntax/test/colorize-fixtures/example.diff @@ -0,0 +1,7 @@ +diff --git a/helloworld.txt b/helloworld.txt +index e4f37c4..557db03 100644 +--- a/helloworld.txt ++++ b/helloworld.txt +@@ -1 +1 @@ +-Hello world ++Hello World \ No newline at end of file diff --git a/extensions/gitsyntax/test/colorize-results/example_diff.json b/extensions/gitsyntax/test/colorize-results/example_diff.json new file mode 100644 index 00000000000..07ec9f737b5 --- /dev/null +++ b/extensions/gitsyntax/test/colorize-results/example_diff.json @@ -0,0 +1,167 @@ +[ + { + "c": "diff --git a/helloworld.txt b/helloworld.txt", + "t": "source.diff meta.diff.header.git", + "r": { + "dark_plus": "meta.diff.header: #569CD6", + "light_plus": "meta.diff.header: #000080", + "dark_vs": "meta.diff.header: #569CD6", + "light_vs": "meta.diff.header: #000080", + "hc_black": "meta.diff.header: #000080" + } + }, + { + "c": "index e4f37c4..557db03 100644", + "t": "source.diff meta.diff.index.git", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "---", + "t": "source.diff meta.diff.header.from-file punctuation.definition.from-file.diff", + "r": { + "dark_plus": "meta.diff.header: #569CD6", + "light_plus": "meta.diff.header: #000080", + "dark_vs": "meta.diff.header: #569CD6", + "light_vs": "meta.diff.header: #000080", + "hc_black": "meta.diff.header: #000080" + } + }, + { + "c": " a/helloworld.txt", + "t": "source.diff meta.diff.header.from-file", + "r": { + "dark_plus": "meta.diff.header: #569CD6", + "light_plus": "meta.diff.header: #000080", + "dark_vs": "meta.diff.header: #569CD6", + "light_vs": "meta.diff.header: #000080", + "hc_black": "meta.diff.header: #000080" + } + }, + { + "c": "+++", + "t": "source.diff meta.diff.header.to-file punctuation.definition.to-file.diff", + "r": { + "dark_plus": "meta.diff.header: #569CD6", + "light_plus": "meta.diff.header: #000080", + "dark_vs": "meta.diff.header: #569CD6", + "light_vs": "meta.diff.header: #000080", + "hc_black": "meta.diff.header: #000080" + } + }, + { + "c": " b/helloworld.txt", + "t": "source.diff meta.diff.header.to-file", + "r": { + "dark_plus": "meta.diff.header: #569CD6", + "light_plus": "meta.diff.header: #000080", + "dark_vs": "meta.diff.header: #569CD6", + "light_vs": "meta.diff.header: #000080", + "hc_black": "meta.diff.header: #000080" + } + }, + { + "c": "@@", + "t": "source.diff meta.diff.range.unified punctuation.definition.range.diff", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.diff meta.diff.range.unified", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-1 +1", + "t": "source.diff meta.diff.range.unified meta.toc-list.line-number.diff", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": " ", + "t": "source.diff meta.diff.range.unified", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "@@", + "t": "source.diff meta.diff.range.unified punctuation.definition.range.diff", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "-", + "t": "source.diff markup.deleted.diff punctuation.definition.deleted.diff", + "r": { + "dark_plus": "markup.deleted: #CE9178", + "light_plus": "markup.deleted: #A31515", + "dark_vs": "markup.deleted: #CE9178", + "light_vs": "markup.deleted: #A31515", + "hc_black": "markup.deleted: #CE9178" + } + }, + { + "c": "Hello world", + "t": "source.diff markup.deleted.diff", + "r": { + "dark_plus": "markup.deleted: #CE9178", + "light_plus": "markup.deleted: #A31515", + "dark_vs": "markup.deleted: #CE9178", + "light_vs": "markup.deleted: #A31515", + "hc_black": "markup.deleted: #CE9178" + } + }, + { + "c": "+", + "t": "source.diff markup.inserted.diff punctuation.definition.inserted.diff", + "r": { + "dark_plus": "markup.inserted: #B5CEA8", + "light_plus": "markup.inserted: #09885A", + "dark_vs": "markup.inserted: #B5CEA8", + "light_vs": "markup.inserted: #09885A", + "hc_black": "markup.inserted: #B5CEA8" + } + }, + { + "c": "Hello World", + "t": "source.diff markup.inserted.diff", + "r": { + "dark_plus": "markup.inserted: #B5CEA8", + "light_plus": "markup.inserted: #09885A", + "dark_vs": "markup.inserted: #B5CEA8", + "light_vs": "markup.inserted: #09885A", + "hc_black": "markup.inserted: #B5CEA8" + } + } +] \ No newline at end of file diff --git a/extensions/go/syntaxes/go.json b/extensions/go/syntaxes/go.json index 4a62b05ef8c..f4e17874a84 100644 --- a/extensions/go/syntaxes/go.json +++ b/extensions/go/syntaxes/go.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-go/commit/f7c6ca60bfd9d11252560b21e9378e5f82438ce3", + "version": "https://github.com/atom/language-go/commit/b6fd68f74efa109679e31fe6f4a41ac105262d0e", "scopeName": "source.go", "name": "Go", "comment": "Go language", @@ -491,7 +491,7 @@ "name": "keyword.operator.increment.go" }, { - "match": "(==|!=|<=|>=|<[^<]|>[^>])", + "match": "(==|!=|<=|>=|<(?!<)|>(?!>))", "name": "keyword.operator.comparison.go" }, { diff --git a/extensions/html/client/src/htmlMain.ts b/extensions/html/client/src/htmlMain.ts index a30fe9ea712..f5885b3654f 100644 --- a/extensions/html/client/src/htmlMain.ts +++ b/extensions/html/client/src/htmlMain.ts @@ -39,7 +39,7 @@ export function activate(context: ExtensionContext) { // The server is implemented in node let serverModule = context.asAbsolutePath(path.join('server', 'out', 'htmlServerMain.js')); // The debug options for the server - let debugOptions = { execArgv: ['--nolazy', '--inspect=6004'] }; + let debugOptions = { execArgv: ['--nolazy', '--inspect=6045'] }; // If the extension is launch in debug mode the debug server options are use // Otherwise the run options are used diff --git a/extensions/html/server/package.json b/extensions/html/server/package.json index 9056f257be6..65cd75b02e9 100644 --- a/extensions/html/server/package.json +++ b/extensions/html/server/package.json @@ -8,8 +8,8 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^3.0.2", - "vscode-html-languageservice": "^2.0.13", + "vscode-css-languageservice": "^3.0.3", + "vscode-html-languageservice": "^2.0.14", "vscode-languageserver": "^3.5.0", "vscode-nls": "^2.0.2", "vscode-uri": "^1.0.1" diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 2593ab4b147..85383529fd4 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -5,7 +5,6 @@ 'use strict'; import { createConnection, IConnection, TextDocuments, InitializeParams, InitializeResult, RequestType, DocumentRangeFormattingRequest, Disposable, DocumentSelector, TextDocumentPositionParams, ServerCapabilities, Position } from 'vscode-languageserver'; -import { DocumentContext } from 'vscode-html-languageservice'; import { TextDocument, Diagnostic, DocumentLink, SymbolInformation } from 'vscode-languageserver-types'; import { getLanguageModes, LanguageModes, Settings } from './modes/languageModes'; @@ -15,13 +14,12 @@ import { DidChangeWorkspaceFoldersNotification, WorkspaceFolder } from 'vscode-l import { format } from './modes/formatting'; import { pushAll } from './utils/arrays'; -import { endsWith, startsWith } from './utils/strings'; - -import * as url from 'url'; -import * as path from 'path'; +import { getDocumentContext } from './utils/documentContext'; import uri from 'vscode-uri'; +import { formatError, runSafe } from './utils/errors'; import * as nls from 'vscode-nls'; + nls.config(process.env['VSCODE_NLS_CONFIG']); namespace TagCloseRequest { @@ -34,6 +32,10 @@ let connection: IConnection = createConnection(); console.log = connection.console.log.bind(connection.console); console.error = connection.console.error.bind(connection.console); +process.on('unhandledRejection', (e: any) => { + connection.console.error(formatError(`Unhandled exception`, e)); +}); + // Create a simple text document manager. The text document manager // supports full document sync only let documents: TextDocuments = new TextDocuments(); @@ -41,7 +43,6 @@ let documents: TextDocuments = new TextDocuments(); // for open, change and close text document events documents.listen(connection); -let workspacePath: string | undefined | null; let workspaceFolders: WorkspaceFolder[] | undefined; var languageModes: LanguageModes; @@ -77,8 +78,13 @@ function getDocumentSettings(textDocument: TextDocument, needsDocumentSettings: connection.onInitialize((params: InitializeParams): InitializeResult => { let initializationOptions = params.initializationOptions; - workspacePath = params.rootPath; workspaceFolders = (params).workspaceFolders; + if (!Array.isArray(workspaceFolders)) { + workspaceFolders = []; + if (params.rootPath) { + workspaceFolders.push({ name: '', uri: uri.file(params.rootPath).toString() }); + } + } languageModes = getLanguageModes(initializationOptions ? initializationOptions.embeddedLanguages : { css: true, javascript: true }); documents.onDidClose(e => { @@ -168,7 +174,7 @@ connection.onDidChangeConfiguration((change) => { }); let pendingValidationRequests: { [uri: string]: NodeJS.Timer } = {}; -const validationDelayMs = 200; +const validationDelayMs = 500; // The content of a text document has changed. This event is emitted // when the text document first opened or when its content has changed. @@ -207,191 +213,195 @@ function isValidationEnabled(languageId: string, settings: Settings = globalSett } async function validateTextDocument(textDocument: TextDocument) { - let diagnostics: Diagnostic[] = []; - if (textDocument.languageId === 'html') { - let modes = languageModes.getAllModesInDocument(textDocument); - let settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation)); - modes.forEach(mode => { - if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) { - pushAll(diagnostics, mode.doValidation(textDocument, settings)); - } - }); + try { + let diagnostics: Diagnostic[] = []; + if (textDocument.languageId === 'html') { + let modes = languageModes.getAllModesInDocument(textDocument); + let settings = await getDocumentSettings(textDocument, () => modes.some(m => !!m.doValidation)); + modes.forEach(mode => { + if (mode.doValidation && isValidationEnabled(mode.getId(), settings)) { + pushAll(diagnostics, mode.doValidation(textDocument, settings)); + } + }); + } + connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); + } catch (e) { + connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e)); } - connection.sendDiagnostics({ uri: textDocument.uri, diagnostics }); } connection.onCompletion(async textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doComplete) { - let doComplete = mode.doComplete; - if (mode.getId() !== 'html') { - connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + return runSafe(async () => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (mode && mode.doComplete) { + let doComplete = mode.doComplete; + if (mode.getId() !== 'html') { + connection.telemetry.logEvent({ key: 'html.embbedded.complete', value: { languageId: mode.getId() } }); + } + let settings = await getDocumentSettings(document, () => doComplete.length > 2); + return doComplete(document, textDocumentPosition.position, settings); } - let settings = await getDocumentSettings(document, () => doComplete.length > 2); - return doComplete(document, textDocumentPosition.position, settings); - } - return { isIncomplete: true, items: [] }; + return { isIncomplete: true, items: [] }; + }, null, `Error while computing completions for ${textDocumentPosition.textDocument.uri}`); }); connection.onCompletionResolve(item => { - let data = item.data; - if (data && data.languageId && data.uri) { - let mode = languageModes.getMode(data.languageId); - let document = documents.get(data.uri); - if (mode && mode.doResolve && document) { - return mode.doResolve(document, item); + return runSafe(() => { + let data = item.data; + if (data && data.languageId && data.uri) { + let mode = languageModes.getMode(data.languageId); + let document = documents.get(data.uri); + if (mode && mode.doResolve && document) { + return mode.doResolve(document, item); + } } - } - return item; + return item; + }, null, `Error while resolving completion proposal`); }); connection.onHover(textDocumentPosition => { - let document = documents.get(textDocumentPosition.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); - if (mode && mode.doHover) { - return mode.doHover(document, textDocumentPosition.position); - } - return null; + return runSafe(() => { + let document = documents.get(textDocumentPosition.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position); + if (mode && mode.doHover) { + return mode.doHover(document, textDocumentPosition.position); + } + return null; + }, null, `Error while computing hover for ${textDocumentPosition.textDocument.uri}`); }); connection.onDocumentHighlight(documentHighlightParams => { - let document = documents.get(documentHighlightParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); - if (mode && mode.findDocumentHighlight) { - return mode.findDocumentHighlight(document, documentHighlightParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(documentHighlightParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, documentHighlightParams.position); + if (mode && mode.findDocumentHighlight) { + return mode.findDocumentHighlight(document, documentHighlightParams.position); + } + return []; + }, [], `Error while computing document highlights for ${documentHighlightParams.textDocument.uri}`); }); connection.onDefinition(definitionParams => { - let document = documents.get(definitionParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, definitionParams.position); - if (mode && mode.findDefinition) { - return mode.findDefinition(document, definitionParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(definitionParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, definitionParams.position); + if (mode && mode.findDefinition) { + return mode.findDefinition(document, definitionParams.position); + } + return []; + }, null, `Error while computing definitions for ${definitionParams.textDocument.uri}`); }); connection.onReferences(referenceParams => { - let document = documents.get(referenceParams.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, referenceParams.position); - if (mode && mode.findReferences) { - return mode.findReferences(document, referenceParams.position); - } - return []; + return runSafe(() => { + let document = documents.get(referenceParams.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, referenceParams.position); + if (mode && mode.findReferences) { + return mode.findReferences(document, referenceParams.position); + } + return []; + }, [], `Error while computing references for ${referenceParams.textDocument.uri}`); }); connection.onSignatureHelp(signatureHelpParms => { - let document = documents.get(signatureHelpParms.textDocument.uri); - let mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); - if (mode && mode.doSignatureHelp) { - return mode.doSignatureHelp(document, signatureHelpParms.position); - } - return null; + return runSafe(() => { + let document = documents.get(signatureHelpParms.textDocument.uri); + let mode = languageModes.getModeAtPosition(document, signatureHelpParms.position); + if (mode && mode.doSignatureHelp) { + return mode.doSignatureHelp(document, signatureHelpParms.position); + } + return null; + }, null, `Error while computing signature help for ${signatureHelpParms.textDocument.uri}`); }); connection.onDocumentRangeFormatting(async formatParams => { - let document = documents.get(formatParams.textDocument.uri); - let settings = await getDocumentSettings(document, () => true); - if (!settings) { - settings = globalSettings; - } - let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; - let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; + return runSafe(async () => { + let document = documents.get(formatParams.textDocument.uri); + let settings = await getDocumentSettings(document, () => true); + if (!settings) { + settings = globalSettings; + } + let unformattedTags: string = settings && settings.html && settings.html.format && settings.html.format.unformatted || ''; + let enabledModes = { css: !unformattedTags.match(/\bstyle\b/), javascript: !unformattedTags.match(/\bscript\b/) }; - return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); + return format(languageModes, document, formatParams.range, formatParams.options, settings, enabledModes); + }, [], `Error while formatting range for ${formatParams.textDocument.uri}`); }); connection.onDocumentLinks(documentLinkParam => { - let document = documents.get(documentLinkParam.textDocument.uri); - let documentContext: DocumentContext = { - resolveReference: (ref, base) => { - if (base) { - ref = url.resolve(base, ref); - } - if (ref[0] === '/') { - let root = getRootFolder(document.uri); - if (root) { - return uri.file(path.join(root, ref)).toString(); + return runSafe(() => { + let document = documents.get(documentLinkParam.textDocument.uri); + let links: DocumentLink[] = []; + if (document) { + let documentContext = getDocumentContext(document.uri, workspaceFolders); + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentLinks) { + pushAll(links, m.findDocumentLinks(document, documentContext)); } - } - return url.resolve(document.uri, ref); - }, - - }; - let links: DocumentLink[] = []; - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentLinks) { - pushAll(links, m.findDocumentLinks(document, documentContext)); + }); } - }); - return links; + return links; + }, [], `Error while document links for ${documentLinkParam.textDocument.uri}`); }); -function getRootFolder(docUri: string): string | undefined | null { - if (workspaceFolders) { - for (let folder of workspaceFolders) { - let folderURI = folder.uri; - if (!endsWith(folderURI, '/')) { - folderURI = folderURI + '/'; - } - if (startsWith(docUri, folderURI)) { - return folderURI; - } - } - return void 0; - } - return workspacePath; -} + connection.onDocumentSymbol(documentSymbolParms => { - let document = documents.get(documentSymbolParms.textDocument.uri); - let symbols: SymbolInformation[] = []; - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentSymbols) { - pushAll(symbols, m.findDocumentSymbols(document)); - } - }); - return symbols; + return runSafe(() => { + let document = documents.get(documentSymbolParms.textDocument.uri); + let symbols: SymbolInformation[] = []; + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentSymbols) { + pushAll(symbols, m.findDocumentSymbols(document)); + } + }); + return symbols; + }, [], `Error while computing document symbols for ${documentSymbolParms.textDocument.uri}`); }); connection.onRequest(DocumentColorRequest.type, params => { - let infos: ColorInformation[] = []; - let document = documents.get(params.textDocument.uri); - if (document) { - languageModes.getAllModesInDocument(document).forEach(m => { - if (m.findDocumentColors) { - pushAll(infos, m.findDocumentColors(document)); - } - }); - } - return infos; + return runSafe(() => { + let infos: ColorInformation[] = []; + let document = documents.get(params.textDocument.uri); + if (document) { + languageModes.getAllModesInDocument(document).forEach(m => { + if (m.findDocumentColors) { + pushAll(infos, m.findDocumentColors(document)); + } + }); + } + return infos; + }, [], `Error while computing document colors for ${params.textDocument.uri}`); }); connection.onRequest(ColorPresentationRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let mode = languageModes.getModeAtPosition(document, params.range.start); - if (mode && mode.getColorPresentations) { - return mode.getColorPresentations(document, params.color, params.range); + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let mode = languageModes.getModeAtPosition(document, params.range.start); + if (mode && mode.getColorPresentations) { + return mode.getColorPresentations(document, params.color, params.range); + } } - } - return []; + return []; + }, [], `Error while computing color presentations for ${params.textDocument.uri}`); }); connection.onRequest(TagCloseRequest.type, params => { - let document = documents.get(params.textDocument.uri); - if (document) { - let pos = params.position; - if (pos.character > 0) { - let mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); - if (mode && mode.doAutoClose) { - return mode.doAutoClose(document, pos); + return runSafe(() => { + let document = documents.get(params.textDocument.uri); + if (document) { + let pos = params.position; + if (pos.character > 0) { + let mode = languageModes.getModeAtPosition(document, Position.create(pos.line, pos.character - 1)); + if (mode && mode.doAutoClose) { + return mode.doAutoClose(document, pos); + } } } - } - return null; + return null; + }, null, `Error while computing tag close actions for ${params.textDocument.uri}`); }); diff --git a/extensions/html/server/src/test/documentContext.test.ts b/extensions/html/server/src/test/documentContext.test.ts new file mode 100644 index 00000000000..4d9dba13fbc --- /dev/null +++ b/extensions/html/server/src/test/documentContext.test.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import { getDocumentContext } from '../utils/documentContext'; + +suite('Document Context', () => { + + test('Context', function (): any { + const docURI = 'file:///users/test/folder/test.html'; + const rootFolders = [{ name: '', uri: 'file:///users/test/' }]; + + let context = getDocumentContext(docURI, rootFolders); + assert.equal(context.resolveReference('/', docURI), 'file:///users/test/'); + assert.equal(context.resolveReference('/message.html', docURI), 'file:///users/test/message.html'); + assert.equal(context.resolveReference('message.html', docURI), 'file:///users/test/folder/message.html'); + assert.equal(context.resolveReference('message.html', 'file:///users/test/'), 'file:///users/test/message.html'); + }); +}); \ No newline at end of file diff --git a/extensions/html/server/src/utils/documentContext.ts b/extensions/html/server/src/utils/documentContext.ts new file mode 100644 index 00000000000..fb56e1d882c --- /dev/null +++ b/extensions/html/server/src/utils/documentContext.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import { DocumentContext } from 'vscode-html-languageservice'; +import { endsWith, startsWith } from '../utils/strings'; +import * as url from 'url'; +import { WorkspaceFolder } from 'vscode-languageserver-protocol/lib/protocol.workspaceFolders.proposed'; + +export function getDocumentContext(documentUri: string, workspaceFolders: WorkspaceFolder[]): DocumentContext { + function getRootFolder(): string | undefined { + for (let folder of workspaceFolders) { + let folderURI = folder.uri; + if (!endsWith(folderURI, '/')) { + folderURI = folderURI + '/'; + } + if (startsWith(documentUri, folderURI)) { + return folderURI; + } + } + return void 0; + } + + return { + resolveReference: (ref, base = documentUri) => { + if (ref[0] === '/') { // resolve absolute path against the current workspace folder + if (startsWith(base, 'file://')) { + let folderUri = getRootFolder(); + if (folderUri) { + return folderUri + ref.substr(1); + } + } + } + return url.resolve(base, ref); + }, + }; +} + diff --git a/extensions/html/server/src/utils/errors.ts b/extensions/html/server/src/utils/errors.ts new file mode 100644 index 00000000000..d5a0c8e7d05 --- /dev/null +++ b/extensions/html/server/src/utils/errors.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export function formatError(message: string, err: any): string { + if (err instanceof Error) { + let error = err; + return `${message}: ${error.message}\n${error.stack}`; + } else if (typeof err === 'string') { + return `${message}: ${err}`; + } else if (err) { + return `${message}: ${err.toString()}`; + } + return message; +} + +export function runSafe(func: () => Thenable | T, errorVal: T, errorMessage: string): Thenable | T { + try { + let t = func(); + if (t instanceof Promise) { + return t.then(void 0, e => { + console.error(formatError(errorMessage, e)); + return errorVal; + }); + } + return t; + } catch (e) { + console.error(formatError(errorMessage, e)); + return errorVal; + } +} \ No newline at end of file diff --git a/extensions/html/server/yarn.lock b/extensions/html/server/yarn.lock index f2c569af414..d676d5f4456 100644 --- a/extensions/html/server/yarn.lock +++ b/extensions/html/server/yarn.lock @@ -10,16 +10,16 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -vscode-css-languageservice@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.2.tgz#ae0c43836318455aa290c777556394d6127b8f6c" +vscode-css-languageservice@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-3.0.3.tgz#02cc4efa5335f5104e0a2f3b6920faaf59db4f7a" dependencies: vscode-languageserver-types "3.5.0" vscode-nls "^2.0.1" -vscode-html-languageservice@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.0.13.tgz#09c4437cffb0800b865d71552f4dfd8240c796d8" +vscode-html-languageservice@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/vscode-html-languageservice/-/vscode-html-languageservice-2.0.14.tgz#15491c11bb7e196f6fd03b6c6c768901935862eb" dependencies: vscode-languageserver-types "3.5.0" vscode-nls "^2.0.2" diff --git a/extensions/javascript/package.json b/extensions/javascript/package.json index 15da42cf0a9..8a6e0ed6708 100644 --- a/extensions/javascript/package.json +++ b/extensions/javascript/package.json @@ -11,8 +11,8 @@ ], "main": "./out/javascriptMain", "dependencies": { - "jsonc-parser": "^0.3.1", - "request-light": "^0.2.0", + "jsonc-parser": "^1.0.0", + "request-light": "^0.2.2", "vscode-nls": "^2.0.2" }, "scripts": { @@ -101,27 +101,27 @@ "jsonValidation": [ { "fileMatch": "package.json", - "url": "http://json.schemastore.org/package" + "url": "https://schemastore.azurewebsites.net/schemas/json/package.json" }, { "fileMatch": "bower.json", - "url": "http://json.schemastore.org/bower" + "url": "https://schemastore.azurewebsites.net/schemas/json/bower.json" }, { "fileMatch": ".bowerrc", - "url": "http://json.schemastore.org/bowerrc" + "url": "https://schemastore.azurewebsites.net/schemas/json/bowerrc.json" }, { "fileMatch": ".babelrc", - "url": "http://json.schemastore.org/babelrc" + "url": "https://schemastore.azurewebsites.net/schemas/json/babelrc.json" }, { "fileMatch": ".babelrc.json", - "url": "http://json.schemastore.org/babelrc" + "url": "https://schemastore.azurewebsites.net/schemas/json/babelrc.json" }, { "fileMatch": "jsconfig.json", - "url": "http://json.schemastore.org/jsconfig" + "url": "https://schemastore.azurewebsites.net/schemas/json/jsconfig.json" }, { "fileMatch": "jsconfig.json", @@ -129,7 +129,7 @@ }, { "fileMatch": "jsconfig.*.json", - "url": "http://json.schemastore.org/jsconfig" + "url": "https://schemastore.azurewebsites.net/schemas/json/jsconfig.json" }, { "fileMatch": "jsconfig.*.json", diff --git a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json index 9adeabe27aa..29765c0ba34 100644 --- a/extensions/javascript/syntaxes/JavaScript.tmLanguage.json +++ b/extensions/javascript/syntaxes/JavaScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/06831946d0a23cb56be0c1ead7cab10be01306cd", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/8b44958a27860957872cc6f628d843a71686af63", "name": "JavaScript (with React support)", "scopeName": "source.js", "fileTypes": [ @@ -110,39 +110,52 @@ }, { "name": "keyword.control.trycatch.js", - "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js" @@ -532,7 +546,7 @@ } }, { - "match": "(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)", + "match": "(?:(?)", "captures": { "1": { "name": "storage.modifier.async.js" @@ -1002,7 +1016,7 @@ }, { "name": "meta.arrow.js", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js" @@ -1046,7 +1060,7 @@ }, "indexer-declaration": { "name": "meta.indexer.declaration.js", - "begin": "(?:(?\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.js", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2061,7 +2079,7 @@ }, { "name": "keyword.operator.expression.import.js", - "match": "(?![\\.\\$])\\bimport(?=\\s*[\\(]\\s*[\\\"\\'\\`])" + "match": "(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", + "match": "(?x) (?:(\\.)|(\\?\\.(?!\\s*[[:digit:]]))) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.js" }, "2": { - "name": "support.constant.dom.js" + "name": "punctuation.accessor.optional.js" }, "3": { + "name": "support.constant.dom.js" + }, + "4": { "name": "support.variable.property.dom.js" } } }, { "name": "support.class.node.js", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js" }, "2": { + "name": "punctuation.accessor.optional.js" + }, + "3": { "name": "entity.name.function.js" } } }, { - "match": "(\\.)\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.js" }, "2": { + "name": "punctuation.accessor.optional.js" + }, + "3": { "name": "variable.other.constant.property.js" } } }, { - "match": "(\\.)\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js" }, "2": { + "name": "punctuation.accessor.optional.js" + }, + "3": { "name": "variable.other.property.js" } } @@ -2618,24 +2673,27 @@ "patterns": [ { "name": "support.class.js", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\.\\s*prototype\\b(?!\\$))" + "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(\\.)\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js" }, "2": { - "name": "variable.other.constant.object.property.js" + "name": "punctuation.accessor.optional.js" }, "3": { + "name": "variable.other.constant.object.property.js" + }, + "4": { "name": "variable.other.object.property.js" } } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.js" @@ -2784,7 +2842,7 @@ }, { "name": "storage.modifier.js", - "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.js.jsx" @@ -532,7 +546,7 @@ } }, { - "match": "(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)", + "match": "(?:(?)", "captures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -1002,7 +1016,7 @@ }, { "name": "meta.arrow.js.jsx", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.js.jsx" @@ -1046,7 +1060,7 @@ }, "indexer-declaration": { "name": "meta.indexer.declaration.js.jsx", - "begin": "(?:(?\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.js.jsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2061,7 +2079,7 @@ }, { "name": "keyword.operator.expression.import.js.jsx", - "match": "(?![\\.\\$])\\bimport(?=\\s*[\\(]\\s*[\\\"\\'\\`])" + "match": "(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", + "match": "(?x) (?:(\\.)|(\\?\\.(?!\\s*[[:digit:]]))) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.js.jsx" }, "2": { - "name": "support.constant.dom.js.jsx" + "name": "punctuation.accessor.optional.js.jsx" }, "3": { + "name": "support.constant.dom.js.jsx" + }, + "4": { "name": "support.variable.property.dom.js.jsx" } } }, { "name": "support.class.node.js.jsx", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.js.jsx" }, "2": { + "name": "punctuation.accessor.optional.js.jsx" + }, + "3": { "name": "entity.name.function.js.jsx" } } }, { - "match": "(\\.)\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.js.jsx" }, "2": { + "name": "punctuation.accessor.optional.js.jsx" + }, + "3": { "name": "variable.other.constant.property.js.jsx" } } }, { - "match": "(\\.)\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js.jsx" }, "2": { + "name": "punctuation.accessor.optional.js.jsx" + }, + "3": { "name": "variable.other.property.js.jsx" } } @@ -2618,24 +2673,27 @@ "patterns": [ { "name": "support.class.js.jsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\.\\s*prototype\\b(?!\\$))" + "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(\\.)\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.js.jsx" }, "2": { - "name": "variable.other.constant.object.property.js.jsx" + "name": "punctuation.accessor.optional.js.jsx" }, "3": { + "name": "variable.other.constant.object.property.js.jsx" + }, + "4": { "name": "variable.other.object.property.js.jsx" } } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.js.jsx" @@ -2784,7 +2842,7 @@ }, { "name": "storage.modifier.js.jsx", - "match": "(? => { return connection.sendRequest(VSCodeContentRequest.type, uri).then(responseText => { return responseText; }, error => { - return error.message; + return Promise.reject(error.message); }); } if (uri.indexOf('//schema.management.azure.com/') !== -1) { @@ -232,7 +232,7 @@ documents.onDidClose(event => { }); let pendingValidationRequests: { [uri: string]: NodeJS.Timer; } = {}; -const validationDelayMs = 200; +const validationDelayMs = 500; function cleanPendingValidation(textDocument: TextDocument): void { let request = pendingValidationRequests[textDocument.uri]; diff --git a/extensions/json/server/yarn.lock b/extensions/json/server/yarn.lock index b7fd490aa8a..c526bf67451 100644 --- a/extensions/json/server/yarn.lock +++ b/extensions/json/server/yarn.lock @@ -6,9 +6,11 @@ version "7.0.43" resolved "https://registry.yarnpkg.com/@types/node/-/node-7.0.43.tgz#a187e08495a075f200ca946079c914e1a5fe962c" -agent-base@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-1.0.2.tgz#6890d3fb217004b62b70f8928e0fae5f8952a706" +agent-base@4, agent-base@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.2.tgz#80fa6cde440f4dcf9af2617cf246099b5d99f0c8" + dependencies: + es6-promisify "^5.0.0" debug@2: version "2.6.9" @@ -16,25 +18,35 @@ debug@2: dependencies: ms "2.0.0" -extend@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - -http-proxy-agent@^0.2.6: - version "0.2.7" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-0.2.7.tgz#e17fda65f0902d952ce7921e62c7ff8862655a5e" +debug@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: - agent-base "~1.0.1" - debug "2" - extend "3" + ms "2.0.0" -https-proxy-agent@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-0.3.6.tgz#713fa38e5d353f50eb14a342febe29033ed1619b" +es6-promise@^4.0.3: + version "4.1.1" + resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" + +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" dependencies: - agent-base "~1.0.1" + es6-promise "^4.0.3" + +http-proxy-agent@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.0.0.tgz#46482a2f0523a4d6082551709f469cb3e4a85ff4" + dependencies: + agent-base "4" debug "2" - extend "3" + +https-proxy-agent@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.1.1.tgz#a7ce4382a1ba8266ee848578778122d491260fd9" + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" jsonc-parser@^1.0.0: version "1.0.0" @@ -44,12 +56,12 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -request-light@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.1.tgz#986f5a82893e9d1ca6a896ebe6f46c51c6b4557f" +request-light@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.2.2.tgz#53e48af32ad1514e45221ea5ece5ce782720f712" dependencies: - http-proxy-agent "^0.2.6" - https-proxy-agent "^0.3.5" + http-proxy-agent "2.0.0" + https-proxy-agent "2.1.1" vscode-nls "^2.0.2" vscode-json-languageservice@^3.0.4: diff --git a/extensions/log/OSSREADME.json b/extensions/log/OSSREADME.json new file mode 100644 index 00000000000..048224ec072 --- /dev/null +++ b/extensions/log/OSSREADME.json @@ -0,0 +1,9 @@ +// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS: +[ + { + "name": "vscode-logfile-highlighter", + "version": "1.2.0", + "license": "MIT", + "repositoryURL": "https://github.com/emilast/vscode-logfile-highlighter" + } +] \ No newline at end of file diff --git a/extensions/log/package.json b/extensions/log/package.json new file mode 100644 index 00000000000..04d399d172d --- /dev/null +++ b/extensions/log/package.json @@ -0,0 +1,37 @@ +{ + "name": "log", + "version": "0.1.0", + "publisher": "vscode", + "engines": { + "vscode": "*" + }, + "scripts": { + "update-grammar": "node ../../build/npm/update-grammar.js emilast/vscode-logfile-highlighter syntaxes/log.tmLanguage ./syntaxes/log.tmLanguage.json" + }, + "contributes": { + "languages": [ + { + "id": "log", + "extensions": [ + ".log", + "*.log.?" + ], + "aliases": [ + "log" + ] + } + ], + "grammars": [ + { + "language": "Log", + "scopeName": "text.log", + "path": "./syntaxes/log.tmLanguage.json" + }, + { + "language": "log", + "scopeName": "text.log", + "path": "./syntaxes/log.tmLanguage.json" + } + ] + } +} \ No newline at end of file diff --git a/extensions/log/syntaxes/log.tmLanguage.json b/extensions/log/syntaxes/log.tmLanguage.json new file mode 100644 index 00000000000..ec06fad00fd --- /dev/null +++ b/extensions/log/syntaxes/log.tmLanguage.json @@ -0,0 +1,93 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/emilast/vscode-logfile-highlighter/blob/master/syntaxes/log.tmLanguage", + "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/emilast/vscode-logfile-highlighter/commit/3f3ea0a69165bc95f62bb487fda73e925ae229a6", + "scopeName": "text.log", + "fileTypes": [ + "log" + ], + "name": "Log file", + "patterns": [ + { + "match": "\\b(DEBUG)\\b|(?i)\\b(debug)\\:", + "name": "markup.changed log.debug" + }, + { + "match": "(?i)\\[(debug|dbug|dbg|de|d)\\]", + "name": "markup.changed log.debug" + }, + { + "match": "\\b(HINT|INFO|INFORMATION)\\b|(?i)\\b(info|information)\\:", + "name": "markup.inserted log.info" + }, + { + "match": "(?i)\\[(information|info|inf|in|i)\\]", + "name": "markup.inserted log.info" + }, + { + "match": "\\b(WARNING|WARN)\\b|(?i)\\b(warning)\\:", + "name": "markup.deleted log.warning" + }, + { + "match": "(?i)\\[(warning|warn|wrn|wn|w)\\]", + "name": "markup.deleted log.warning" + }, + { + "match": "\\b(ERROR|FAILURE|FAIL)\\b|(?i)\\b(error)\\:", + "name": "string.regexp, strong log.error" + }, + { + "match": "(?i)\\[(error|eror|err|er|e|fatal|fatl|ftl|fa|f)\\]", + "name": "string.regexp, strong log.error" + }, + { + "match": "\\b\\d{4}-\\d{2}-\\d{2}(T|\\b)", + "name": "comment log.date" + }, + { + "match": "\\b\\d{2}[^\\w\\s]\\d{2}[^\\w\\s]\\d{4}\\b", + "name": "comment log.date" + }, + { + "match": "\\d{2}:\\d{2}(:\\d{2}([.,]\\d{3,6})?)?(Z| ?[+-]\\d{2}:\\d{2})?\\b", + "name": "comment log.date" + }, + { + "match": "[0-9a-fA-F]{8}[-]?([0-9a-fA-F]{4}[-]?){3}[0-9a-fA-F]{12}", + "name": "constant.language log.constant" + }, + { + "match": "\\b([0-9]+|true|false|null)\\b", + "name": "constant.language log.constant" + }, + { + "match": "\"[^\"]*\"", + "name": "string log.string" + }, + { + "match": "'[^']*'", + "name": "string log.string" + }, + { + "match": "\\b([a-zA-Z.]*Exception)\\b", + "name": "string.regexp, emphasis log.type" + }, + { + "begin": "^[\\t ]*at", + "end": "$", + "name": "string.key, emphasis log.exception" + }, + { + "match": "\\b(http|https|ftp|file)://\\S+\\b/?", + "name": "constant.language log.constant" + }, + { + "match": "\\b([\\w]+\\.)+(\\w)+\\b", + "name": "constant.language log.constant" + } + ], + "uuid": "E81BB6AB-CAC7-4C27-9A79-4137A4693EBD" +} \ No newline at end of file diff --git a/extensions/log/test/colorize-fixtures/test.log b/extensions/log/test/colorize-fixtures/test.log new file mode 100644 index 00000000000..59c594fdd18 --- /dev/null +++ b/extensions/log/test/colorize-fixtures/test.log @@ -0,0 +1,9 @@ +[2017-12-21 12:47:29.584] [renderer1] [trace] CommandService#executeCommand workbench.view.explorer +[2017-12-21 12:47:29.614] [renderer1] [trace] CommandService#executeCommand setContext +[2017-12-21 12:47:29.632] [renderer1] [trace] SCMService#registerSCMProvider +[2017-12-21 12:47:29.636] [renderer1] [trace] CommandService#executeCommand setContext +[2017-12-21 12:47:32.164] [renderer1] [trace] CommandService#executeCommand workbench.action.showCommands +[2017-12-21 12:47:33.122] [renderer1] [trace] CommandService#executeCommand workbench.action.openLogFile +[2017-12-21 12:47:34.249] [renderer1] [trace] CommandService#executeCommand workbench.action.openLogViewer +[2017-12-21 12:47:48.078] [renderer1] [trace] CommandService#executeCommand workbench.action.showCommands +[2017-12-21 12:47:49.294] [renderer1] [trace] CommandService#executeCommand workbench.action.reloadWindow \ No newline at end of file diff --git a/extensions/log/test/colorize-results/test_log.json b/extensions/log/test/colorize-results/test_log.json new file mode 100644 index 00000000000..0b295cad127 --- /dev/null +++ b/extensions/log/test/colorize-results/test_log.json @@ -0,0 +1,563 @@ +[ + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:29.584", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.view.explorer", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:29.614", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand setContext", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:29.632", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] SCMService#registerSCMProvider", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:29.636", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand setContext", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:32.164", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.action.showCommands", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:33.122", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.action.openLogFile", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:34.249", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.action.openLogViewer", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:48.078", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.action.showCommands", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + }, + { + "c": "[", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "2017-12-21", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": " ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "12:47:49.294", + "t": "text.log comment log.date", + "r": { + "dark_plus": "comment: #608B4E", + "light_plus": "comment: #008000", + "dark_vs": "comment: #608B4E", + "light_vs": "comment: #008000", + "hc_black": "comment: #7CA668" + } + }, + { + "c": "] [renderer1] [trace] CommandService#executeCommand ", + "t": "text.log", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF" + } + }, + { + "c": "workbench.action.reloadWindow", + "t": "text.log constant.language log.constant", + "r": { + "dark_plus": "constant.language: #569CD6", + "light_plus": "constant.language: #0000FF", + "dark_vs": "constant.language: #569CD6", + "light_vs": "constant.language: #0000FF", + "hc_black": "constant.language: #569CD6" + } + } +] \ No newline at end of file diff --git a/extensions/markdown/OSSREADME.json b/extensions/markdown/OSSREADME.json index 79c18ea56c0..33c460e00c0 100644 --- a/extensions/markdown/OSSREADME.json +++ b/extensions/markdown/OSSREADME.json @@ -15,38 +15,6 @@ "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." ] }, -{ - "isLicense": true, - "name": "markdown-it-named-headers", - "licenseDetail": [ - "Copyright (c) markdown-it-named-headers authors", - "", - "This is free and unencumbered software released into the public domain.", - "", - "Anyone is free to copy, modify, publish, use, compile, sell, or", - "distribute this software, either in source code form or as a compiled", - "binary, for any purpose, commercial or non-commercial, and by any", - "means.", - "", - "In jurisdictions that recognize copyright laws, the author or authors", - "of this software dedicate any and all copyright interest in the", - "software to the public domain. We make this dedication for the benefit", - "of the public at large and to the detriment of our heirs and", - "successors. We intend this dedication to be an overt act of", - "relinquishment in perpetuity of all present and future rights to this", - "software under copyright law.", - "", - "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 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.", - "", - "For more information, please refer to " - ] -}, { "name": "textmate/markdown.tmbundle", "version": "0.0.0", @@ -67,23 +35,4 @@ "to the base-name name of the original file, and an extension of txt, html, or similar. For example", "\"tidy\" is accompanied by \"tidy-license.txt\"." ] -}, -{ - "isLicense": true, - "name": "uc.micro", - "licenseDetail": [ - " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE", - " Version 2, December 2004", - "", - " Copyright (C) 2004 Sam Hocevar ", - "", - " Everyone is permitted to copy and distribute verbatim or modified", - " copies of this license document, and changing it is allowed as long", - " as the name is changed.", - "", - " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE", - " TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION", - "", - " 0. You just DO WHAT THE FUCK YOU WANT TO." - ] }] diff --git a/extensions/markdown/language-configuration.json b/extensions/markdown/language-configuration.json index 6fa9139b76f..23a25ede9cc 100644 --- a/extensions/markdown/language-configuration.json +++ b/extensions/markdown/language-configuration.json @@ -39,6 +39,10 @@ ["`", "`"] ], "folding": { - "offSide": true + "offSide": true, + "markers": { + "start": "^\\s*", + "end": "^\\s*" + } } -} \ No newline at end of file +} diff --git a/extensions/markdown/media/main.js b/extensions/markdown/media/main.js index ddf21afbbb9..1d9bf7a3608 100644 --- a/extensions/markdown/media/main.js +++ b/extensions/markdown/media/main.js @@ -2,7 +2,7 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - +// @ts-check 'use strict'; (function () { @@ -29,24 +29,58 @@ }; } + /** + * @param {string} command + * @param {any[]} args + */ + function postMessage(command, args) { + window.parent.postMessage({ + command: 'did-click-link', + data: `command:${command}?${encodeURIComponent(JSON.stringify(args))}` + }, 'file://'); + } + + /** + * @typedef {{ element: Element, line: number }} CodeLineElement + */ + + /** + * @return {CodeLineElement[]} + */ + const getCodeLineElements = (() => { + /** @type {CodeLineElement[]} */ + let elements; + return () => { + if (!elements) { + elements = Array.prototype.map.call( + document.getElementsByClassName('code-line'), + element => { + const line = +element.getAttribute('data-line'); + return { element, line } + }) + .filter(x => !isNaN(x.line)); + } + return elements; + }; + })() + /** * Find the html elements that map to a specific target line in the editor. * * If an exact match, returns a single element. If the line is between elements, * returns the element prior to and the element after the given line. + * + * @param {number} targetLine + * + * @returns {{ previous: CodeLineElement, next?: CodeLineElement }} */ function getElementsForSourceLine(targetLine) { - const lines = document.getElementsByClassName('code-line'); - let previous = lines[0] && +lines[0].getAttribute('data-line') ? { line: +lines[0].getAttribute('data-line'), element: lines[0] } : null; - for (const element of lines) { - const lineNumber = +element.getAttribute('data-line'); - if (isNaN(lineNumber)) { - continue; - } - const entry = { line: lineNumber, element: element }; - if (lineNumber === targetLine) { + const lines = getCodeLineElements(); + let previous = lines[0] || null; + for (const entry of lines) { + if (entry.line === targetLine) { return { previous: entry, next: null }; - } else if (lineNumber > targetLine) { + } else if (entry.line > targetLine) { return { previous, next: entry }; } previous = entry; @@ -56,28 +90,37 @@ /** * Find the html elements that are at a specific pixel offset on the page. + * + * @returns {{ previous: CodeLineElement, next?: CodeLineElement }} */ function getLineElementsAtPageOffset(offset) { - const lines = document.getElementsByClassName('code-line'); + const lines = getCodeLineElements() + const position = offset - window.scrollY; - let previous = null; - for (const element of lines) { - const line = +element.getAttribute('data-line'); - if (isNaN(line)) { - continue; + + let lo = -1; + let hi = lines.length - 1; + while (lo + 1 < hi) { + const mid = Math.floor((lo + hi) / 2); + const bounds = lines[mid].element.getBoundingClientRect(); + if (bounds.top + bounds.height >= position) { + hi = mid; + } else { + lo = mid; } - const bounds = element.getBoundingClientRect(); - const entry = { element, line }; - if (position < bounds.top) { - if (previous && previous.fractional < 1) { - previous.line += previous.fractional; - return { previous }; - } - return { previous, next: entry }; - } - entry.fractional = (position - bounds.top) / (bounds.height); - previous = entry; } + + const hiElement = lines[hi]; + if (hi >= 1 && hiElement.element.getBoundingClientRect().top > position) { + const loElement = lines[lo]; + const bounds = loElement.element.getBoundingClientRect(); + const previous = { element: loElement.element, line: loElement.line + (position - bounds.top) / (bounds.height) }; + const next = { element: hiElement.element, line: hiElement.line, fractional: 0 }; + return { previous, next }; + } + + const bounds = hiElement.element.getBoundingClientRect(); + const previous = { element: hiElement.element, line: hiElement.line + (position - bounds.top) / (bounds.height) }; return { previous }; } @@ -87,6 +130,8 @@ /** * Attempt to reveal the element for a source line in the editor. + * + * @param {number} line */ function scrollToRevealSourceLine(line) { const { previous, next } = getElementsForSourceLine(line); @@ -196,14 +241,37 @@ const offset = event.pageY; const line = getEditorLineNumberForPageOffset(offset); if (!isNaN(line)) { - const args = [settings.source, line]; - window.parent.postMessage({ - command: "did-click-link", - data: `command:_markdown.didClick?${encodeURIComponent(JSON.stringify(args))}` - }, "file://"); + postMessage('_markdown.didClick', [settings.source, line]); } }); + document.addEventListener('click', event => { + if (!event) { + return; + } + + const baseElement = document.getElementsByTagName('base')[0]; + + /** @type {*} */ + let node = event.target; + while (node) { + if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { + if (node.getAttribute('href').startsWith('#')) { + break; + } + if (node.href.startsWith('file://')) { + const [path, fragment] = node.href.replace(/^file:\/\//i, '').split('#'); + postMessage('_markdown.openDocumentLink', { path, fragment }); + event.preventDefault(); + event.stopPropagation(); + break; + } + break; + } + node = node.parentNode; + } + }, true); + if (settings.scrollEditorWithPreview) { window.addEventListener('scroll', throttle(() => { if (scrollDisabled) { @@ -211,11 +279,7 @@ } else { const line = getEditorLineNumberForPageOffset(window.scrollY); if (!isNaN(line)) { - const args = [settings.source, line]; - window.parent.postMessage({ - command: 'did-click-link', - data: `command:_markdown.revealLine?${encodeURIComponent(JSON.stringify(args))}` - }, 'file://'); + postMessage('_markdown.revealLine', [settings.source, line]); } } }, 50)); diff --git a/extensions/markdown/package.json b/extensions/markdown/package.json index f26564acd10..417e2456a6a 100644 --- a/extensions/markdown/package.json +++ b/extensions/markdown/package.json @@ -311,8 +311,8 @@ "highlight.js": "9.5.0", "markdown-it": "^8.4.0", "markdown-it-named-headers": "0.0.4", - "vscode-extension-telemetry": "0.0.8", - "vscode-nls": "2.0.2" + "vscode-extension-telemetry": "^0.0.8", + "vscode-nls": "^2.0.2" }, "devDependencies": { "@types/highlight.js": "9.1.10", @@ -321,4 +321,4 @@ "gulp-rename": "^1.2.2", "gulp-replace": "^0.5.4" } -} \ No newline at end of file +} diff --git a/extensions/markdown/src/commands.ts b/extensions/markdown/src/commands.ts index 0051c2fa0f8..b0b029f1b24 100644 --- a/extensions/markdown/src/commands.ts +++ b/extensions/markdown/src/commands.ts @@ -140,7 +140,7 @@ export class RefreshPreviewCommand implements Command { } else { // update all generated md documents for (const document of vscode.workspace.textDocuments) { - if (document.uri.scheme === 'markdown') { + if (document.uri.scheme === MDDocumentContentProvider.scheme) { this.contentProvider.update(document.uri); } } @@ -264,6 +264,16 @@ export class OpenDocumentLinkCommand implements Command { new vscode.Range(line, 0, line, 0), vscode.TextEditorRevealType.AtTop); } + + const lineNumberFragment = args.fragment.match(/^L(\d+)$/); + if (lineNumberFragment) { + const line = +lineNumberFragment[1] - 1; + if (!isNaN(line)) { + return editor.revealRange( + new vscode.Range(line, 0, line, 0), + vscode.TextEditorRevealType.AtTop); + } + } } }; diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index 77487fbec18..44694ae41a7 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -28,7 +28,7 @@ export function activate(context: vscode.ExtensionContext) { const selector = 'markdown'; const contentProvider = new MDDocumentContentProvider(engine, context, cspArbiter, logger); - context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(selector, contentProvider)); + context.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(MDDocumentContentProvider.scheme, contentProvider)); loadMarkdownExtensions(contentProvider, engine); diff --git a/extensions/markdown/src/features/previewContentProvider.ts b/extensions/markdown/src/features/previewContentProvider.ts index 39dfa953213..27f1aa753fb 100644 --- a/extensions/markdown/src/features/previewContentProvider.ts +++ b/extensions/markdown/src/features/previewContentProvider.ts @@ -22,16 +22,16 @@ const previewStrings = { export function isMarkdownFile(document: vscode.TextDocument) { return document.languageId === 'markdown' - && document.uri.scheme !== 'markdown'; // prevent processing of own documents + && document.uri.scheme !== MDDocumentContentProvider.scheme; // prevent processing of own documents } export function getMarkdownUri(uri: vscode.Uri) { - if (uri.scheme === 'markdown') { + if (uri.scheme === MDDocumentContentProvider.scheme) { return uri; } return uri.with({ - scheme: 'markdown', + scheme: MDDocumentContentProvider.scheme, path: uri.path + '.rendered', query: uri.toString() }); @@ -139,6 +139,8 @@ class PreviewConfigManager { } export class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { + public static readonly scheme = 'markdown'; + private _onDidChange = new vscode.EventEmitter(); private _waiting: boolean = false; private previewConfigurations = new PreviewConfigManager(); @@ -279,7 +281,7 @@ export class MDDocumentContentProvider implements vscode.TextDocumentContentProv public updateConfiguration() { // update all generated md documents for (const document of vscode.workspace.textDocuments) { - if (document.uri.scheme === 'markdown') { + if (document.uri.scheme === MDDocumentContentProvider.scheme) { const sourceUri = vscode.Uri.parse(document.uri.query); if (this.previewConfigurations.shouldUpdateConfiguration(sourceUri)) { this.update(document.uri); diff --git a/extensions/markdown/src/markdownEngine.ts b/extensions/markdown/src/markdownEngine.ts index 4ed96316277..a4be37ed984 100644 --- a/extensions/markdown/src/markdownEngine.ts +++ b/extensions/markdown/src/markdownEngine.ts @@ -137,8 +137,9 @@ export class MarkdownEngine { md.normalizeLink = (link: string) => { try { let uri = vscode.Uri.parse(link); - if (!uri.scheme && uri.path && !uri.fragment) { + if (!uri.scheme && uri.path) { // Assume it must be a file + const fragment = uri.fragment; if (uri.path[0] === '/') { const root = vscode.workspace.getWorkspaceFolder(this.currentDocument); if (root) { @@ -147,6 +148,10 @@ export class MarkdownEngine { } else { uri = vscode.Uri.file(path.join(path.dirname(this.currentDocument.path), uri.path)); } + + if (fragment) { + uri = uri.with({ fragment }); + } return normalizeLink(uri.toString(true)); } } catch (e) { diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage b/extensions/markdown/syntaxes/markdown.tmLanguage index 1c1edd48ad9..26a48c15d3e 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage +++ b/extensions/markdown/syntaxes/markdown.tmLanguage @@ -522,11 +522,11 @@ include - #list_paragraph + #block include - #block + #list_paragraph while @@ -549,11 +549,11 @@ include - #list_paragraph + #block include - #block + #list_paragraph while diff --git a/extensions/markdown/syntaxes/markdown.tmLanguage.base b/extensions/markdown/syntaxes/markdown.tmLanguage.base index 91e71b45a4e..ca8b920a22f 100644 --- a/extensions/markdown/syntaxes/markdown.tmLanguage.base +++ b/extensions/markdown/syntaxes/markdown.tmLanguage.base @@ -347,11 +347,11 @@ include - #list_paragraph + #block include - #block + #list_paragraph while @@ -374,11 +374,11 @@ include - #list_paragraph + #block include - #block + #list_paragraph while diff --git a/extensions/markdown/yarn.lock b/extensions/markdown/yarn.lock index 2b58fcb5d2f..4baed92f526 100644 --- a/extensions/markdown/yarn.lock +++ b/extensions/markdown/yarn.lock @@ -155,14 +155,14 @@ util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -vscode-extension-telemetry@0.0.8: +vscode-extension-telemetry@^0.0.8: version "0.0.8" resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.8.tgz#2261bff986b6690a6f1f746a45ac5bd1f85d29e0" dependencies: applicationinsights "0.18.0" winreg "1.2.3" -vscode-nls@2.0.2: +vscode-nls@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da" diff --git a/extensions/ms-vscode.node-debug/OSSREADME.json b/extensions/ms-vscode.node-debug/OSSREADME.json deleted file mode 100644 index 215c33cbe0f..00000000000 --- a/extensions/ms-vscode.node-debug/OSSREADME.json +++ /dev/null @@ -1,16 +0,0 @@ -[{ - "isLicense": true, - "name": "@types/source-map", - "repositoryURL": "https://www.github.com/DefinitelyTyped/DefinitelyTyped", - "license": "MIT", - "licenseDetail": [ - "This project is licensed under the MIT license.", - "Copyrights are respective of each contributor listed at the beginning of each definition file.", - "", - "Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:", - "", - "The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.", - "", - "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE." - ] -}] diff --git a/extensions/ms-vscode.node-debug/package-lock.json b/extensions/ms-vscode.node-debug/package-lock.json index 82260da1aa1..526e856f483 100644 --- a/extensions/ms-vscode.node-debug/package-lock.json +++ b/extensions/ms-vscode.node-debug/package-lock.json @@ -1,6 +1,6 @@ { "name": "node-debug", - "version": "1.19.2", + "version": "1.19.7", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -42,21 +42,21 @@ } }, "@types/mocha": { - "version": "2.2.42", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.42.tgz", - "integrity": "sha512-b6gVDoxEbAQGwbV7gSzeFw/hy3/eEAokztktdzl4bHvGgb9K5zW4mVQDlVYch2w31m8t/J7L2iqhQvz3r5edCQ==", + "version": "2.2.44", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-2.2.44.tgz", + "integrity": "sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw==", "dev": true }, "@types/node": { - "version": "6.0.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.52.tgz", - "integrity": "sha1-GsOpm0IyD55GNILyWvTCNZRzqqY=", + "version": "7.0.43", + "resolved": "https://registry.npmjs.org/@types/node/-/node-7.0.43.tgz", + "integrity": "sha512-7scYwwfHNppXvH/9JzakbVxk0o0QUILVk1Lv64GRaxwPuGpnF1QBiwdvhDpLcymb8BpomQL3KYoWKq3wUdDMhQ==", "dev": true }, "@types/source-map": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.5.1.tgz", - "integrity": "sha512-/GVAjL1Y8puvZab63n8tsuBiYwZt1bApMdx58/msQ9ID5T05ov+wm/ZV1DvYC/DKKEygpTJViqQvkh5Rhrl4CA==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.5.2.tgz", + "integrity": "sha512-++w4WmMbk3dS3UeHGzAG+xJOSz5Xqtjys/TBkqG3qp3SeWE7Wwezqe5eB7B51cxUyh4PW7bwVotpsLdBK0D8cw==", "dev": true }, "abbrev": { @@ -4755,9 +4755,9 @@ } }, "mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.5.0.tgz", + "integrity": "sha512-v/jMDoK/qKptnTuC3YUNbIj8uUYvTCIHzVu9BHldKSWja48wusAtfjlcBlqnFrqClu3yf69ScDxBPrIyFnF51g==", "dev": true }, "mime-db": { @@ -5149,13 +5149,30 @@ "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", "dev": true }, + "parse-semver": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/parse-semver/-/parse-semver-1.1.1.tgz", + "integrity": "sha1-mkr9bfBj3Egm+T+6SpnPIj9mbLg=", + "dev": true, + "requires": { + "semver": "5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", + "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", + "dev": true + } + } + }, "parse5": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", "dev": true, "requires": { - "@types/node": "6.0.52" + "@types/node": "7.0.43" } }, "path-dirname": { @@ -6671,9 +6688,9 @@ } }, "vsce": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.32.0.tgz", - "integrity": "sha1-EN+pIyGCwg6r5r8xJdMzpLIG/j0=", + "version": "1.33.2", + "resolved": "https://registry.npmjs.org/vsce/-/vsce-1.33.2.tgz", + "integrity": "sha1-NkX2mq+YTiL3TqSdNfON0Y1m/18=", "dev": true, "requires": { "cheerio": "1.0.0-rc.2", @@ -6682,9 +6699,10 @@ "glob": "7.1.2", "lodash": "4.17.4", "markdown-it": "8.4.0", - "mime": "1.4.1", + "mime": "1.5.0", "minimatch": "3.0.4", "osenv": "0.1.4", + "parse-semver": "1.1.1", "read": "1.0.7", "semver": "5.4.1", "tmp": "0.0.29", @@ -6709,9 +6727,9 @@ } }, "vscode": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.6.tgz", - "integrity": "sha1-Ru0a+iwbnWifY5TI8WvR1xkPdfs=", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/vscode/-/vscode-1.1.8.tgz", + "integrity": "sha512-kT6sIA1AEKR5M+us2fXk5dxwV9SR/IEdLHNmVW4/dl1wNBHoEvgIo1qMQwHNxPVTQmw70KTGZ9UVeVb8FbpNFA==", "dev": true, "requires": { "glob": "7.1.2", @@ -6739,34 +6757,26 @@ } }, "vscode-debugadapter": { - "version": "1.25.0-pre.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.25.0-pre.0.tgz", - "integrity": "sha1-0pDsVH5h5Pvss2P/9ojSAyMZQmQ=", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.25.0.tgz", + "integrity": "sha512-tsOtNNKKTbnQanARdkFfUxI8qKVKba+QHOKWC1reDDeeyvzoNKkLMGkL/xsiKn5vQDeaP3zFBcLY8Ysak9GrvQ==", "requires": { - "vscode-debugprotocol": "1.25.0-pre.0" - }, - "dependencies": { - "vscode-debugprotocol": { - "version": "1.25.0-pre.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.25.0-pre.0.tgz", - "integrity": "sha1-rYPnvZWxmseV31D6Di/pA0YqcrY=" - } + "vscode-debugprotocol": "1.25.0" } }, "vscode-debugadapter-testsupport": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.24.0.tgz", - "integrity": "sha1-rDZ1scU/wW+1JMvSt+znEhtiXng=", + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/vscode-debugadapter-testsupport/-/vscode-debugadapter-testsupport-1.25.0.tgz", + "integrity": "sha512-6E2N7CoH7B0KEDvI9mFVFt4H+dRFDhtj3PmLVjNojfZ1VZZS2yfhE0XO0E5Axdhef3zTpUU6WZoeOOMVFGZGIg==", "dev": true, "requires": { - "vscode-debugprotocol": "1.24.0" + "vscode-debugprotocol": "1.25.0" } }, "vscode-debugprotocol": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.24.0.tgz", - "integrity": "sha1-28EOjX2VsQJyehmvPw/O9+JSsI4=", - "dev": true + "version": "1.25.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.25.0.tgz", + "integrity": "sha512-e1EUy/5npqa0NlAwRCUu8A9LnVRf6tkwiPQcCLyUFCC9o2GxcAqH5Va4mqXDoxQ58ar3zODivKQeRb3z1KH7WA==" }, "vscode-nls": { "version": "2.0.2", @@ -6774,9 +6784,9 @@ "integrity": "sha1-gIUiOAhEuK0VNJmvXDsDkhrqAto=" }, "vscode-nls-dev": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/vscode-nls-dev/-/vscode-nls-dev-2.1.5.tgz", - "integrity": "sha1-GfqjsYp/MCIBA5pMlnu9IvoShE0=", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/vscode-nls-dev/-/vscode-nls-dev-2.1.6.tgz", + "integrity": "sha512-1IylC/ekENYqz1vEItfrzrMXS8LW9aZQnNTU6BfdwT0Jddzed+l+nvU8amgVKFFmC1/GoiMFk5wtC20zWBbEbw==", "dev": true, "requires": { "clone": "1.0.3", diff --git a/extensions/ms-vscode.node-debug2/OSSREADME.json b/extensions/ms-vscode.node-debug2/OSSREADME.json deleted file mode 100644 index 83ffc0884d2..00000000000 --- a/extensions/ms-vscode.node-debug2/OSSREADME.json +++ /dev/null @@ -1,23 +0,0 @@ -[{ - "isLicense": true, - "name": "noice-json-rpc", - "repositoryURL": "https://github.com/nojvek/noice-json-rpc", - "license": "MIT", - "licenseDetail": [ - "Copyright (c) Manoj Patel", - "", - "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." - ] -}] diff --git a/extensions/package.json b/extensions/package.json index 339d071066a..aa1e4b4fa52 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": "2.6.2" + "typescript": "2.7.0-insiders.20171214" }, "scripts": { "postinstall": "node ./postinstall" diff --git a/extensions/php/build/update-grammar.js b/extensions/php/build/update-grammar.js index ac1f9887b28..55cd1ae9c41 100644 --- a/extensions/php/build/update-grammar.js +++ b/extensions/php/build/update-grammar.js @@ -20,5 +20,6 @@ function adaptInjectionScope(grammar) { injections[newInjectionKey] = injection; } -updateGrammar.update('atom/language-php', 'grammars/php.cson', './syntaxes/php.tmLanguage.json', adaptInjectionScope); +updateGrammar.update('atom/language-php', 'grammars/php.cson', './syntaxes/php.tmLanguage.json', undefined); +updateGrammar.update('atom/language-php', 'grammars/html.cson', './syntaxes/html.tmLanguage.json', adaptInjectionScope); diff --git a/extensions/php/package.json b/extensions/php/package.json index 6954ae5c661..fb5758ff8bf 100644 --- a/extensions/php/package.json +++ b/extensions/php/package.json @@ -35,10 +35,15 @@ } ], "grammars": [ + { + "language": "php", + "scopeName": "source.php", + "path": "./syntaxes/php.tmLanguage.json" + }, { "language": "php", "scopeName": "text.html.php", - "path": "./syntaxes/php.tmLanguage.json", + "path": "./syntaxes/html.tmLanguage.json", "embeddedLanguages": { "text.html": "html", "source.php": "php", diff --git a/extensions/php/syntaxes/html.tmLanguage.json b/extensions/php/syntaxes/html.tmLanguage.json new file mode 100644 index 00000000000..747d39b1d0d --- /dev/null +++ b/extensions/php/syntaxes/html.tmLanguage.json @@ -0,0 +1,106 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/atom/language-php/blob/master/grammars/html.cson", + "If you want to provide a fix or improvement, please create a pull request against the original repository.", + "Once accepted there, we are happy to receive an update request." + ], + "version": "https://github.com/atom/language-php/commit/29c140e1531e0b5e842e5bfd4377f879d8b79cd4", + "scopeName": "text.html.php", + "name": "PHP", + "fileTypes": [ + "aw", + "ctp", + "inc", + "install", + "module", + "php", + "php_cs", + "php3", + "php4", + "php5", + "phpt", + "phtml", + "profile" + ], + "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n php\\d?\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n php\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n (?:php|phtml)\n (?=\\s|:|$)\n)\n|\n# Regular opening PHP tags\n^\\s*<\\?(?i:php|=|\\s|$)", + "foldingStartMarker": "(/\\*|\\{\\s*$|<<)", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.php" + } + }, + "end": "(\\?)>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.php" + }, + "1": { + "name": "source.php" + } + }, + "name": "meta.embedded.block.php", + "contentName": "source.php", + "patterns": [ + { + "include": "source.php" + } + ] + }, + { + "begin": "<\\?(?i:php|=)?", + "beginCaptures": { + "0": { + "name": "punctuation.section.embedded.begin.php" + } + }, + "end": "(\\?)>", + "endCaptures": { + "0": { + "name": "punctuation.section.embedded.end.php" + }, + "1": { + "name": "source.php" + } + }, + "name": "meta.embedded.line.php", + "contentName": "source.php", + "patterns": [ + { + "include": "source.php" + } + ] + } + ] + } + } +} \ No newline at end of file diff --git a/extensions/php/syntaxes/php.tmLanguage.json b/extensions/php/syntaxes/php.tmLanguage.json index aa0446b8af1..9cacdd3c23c 100644 --- a/extensions/php/syntaxes/php.tmLanguage.json +++ b/extensions/php/syntaxes/php.tmLanguage.json @@ -4,111 +4,806 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-php/commit/71231bfb975ac56d9c13c5b4cda21c081ebbc6ee", - "scopeName": "text.html.php", - "name": "PHP", - "fileTypes": [ - "aw", - "ctp", - "inc", - "install", - "module", - "php", - "php_cs", - "php3", - "php4", - "php5", - "phpt", - "phtml", - "profile", - "theme" - ], - "firstLineMatch": "(?x)\n# Hashbang\n^\\#!.*(?:\\s|\\/)\n php\\d?\n(?:$|\\s)\n|\n# Modeline\n(?i:\n # Emacs\n -\\*-(?:\\s*(?=[^:;\\s]+\\s*-\\*-)|(?:.*?[;\\s]|(?<=-\\*-))mode\\s*:\\s*)\n php\n (?=[\\s;]|(?]?\\d+|m)?|\\sex)(?=:(?=\\s*set?\\s[^\\n:]+:)|:(?!\\s*set?\\s))(?:(?:\\s|\\s*:\\s*)\\w*(?:\\s*=(?:[^\\n\\\\\\s]|\\\\.)*)?)*[\\s:](?:filetype|ft|syntax)\\s*=\n (?:php|phtml)\n (?=\\s|:|$)\n)", - "foldingStartMarker": "(/\\*|\\{\\s*$|<<)", - "beginCaptures": { - "0": { - "name": "punctuation.section.embedded.begin.php" - } - }, - "contentName": "source.php", - "end": "(\\?)>", - "endCaptures": { - "0": { - "name": "punctuation.section.embedded.end.php" - }, - "1": { - "name": "source.php" - } - }, - "name": "meta.embedded.block.php", + "version": "https://github.com/atom/language-php/commit/11b2057dbf32355019621ee3769f5c8ef577f524", + "scopeName": "source.php", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?i)^\\s*(interface)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)\\s*(extends)?\\s*", + "beginCaptures": { + "1": { + "name": "storage.type.interface.php" + }, + "2": { + "name": "entity.name.type.interface.php" + }, + "3": { + "name": "storage.modifier.extends.php" + } + }, + "end": "(?i)((?:[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*\\s*,\\s*)*)([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?\\s*(?:(?={)|$)", + "endCaptures": { + "1": { "patterns": [ { - "include": "#language" + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", + "name": "entity.other.inherited-class.php" + }, + { + "match": ",", + "name": "punctuation.separator.classes.php" + } + ] + }, + "2": { + "name": "entity.other.inherited-class.php" + } + }, + "name": "meta.interface.php", + "patterns": [ + { + "include": "#namespace" + } + ] + }, + { + "begin": "(?i)^\\s*(trait)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.type.trait.php" + }, + "2": { + "name": "entity.name.type.trait.php" + } + }, + "end": "(?={)", + "name": "meta.trait.php", + "patterns": [ + { + "include": "#comments" + } + ] + }, + { + "match": "(?i)(?:^|(?<=<\\?php))\\s*(namespace)\\s+([a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+)(?=\\s*;)", + "name": "meta.namespace.php", + "captures": { + "1": { + "name": "keyword.other.namespace.php" + }, + "2": { + "name": "entity.name.type.namespace.php", + "patterns": [ + { + "match": "\\\\", + "name": "punctuation.separator.inheritance.php" + } + ] + } + } + }, + { + "begin": "(?i)(?:^|(?<=<\\?php))\\s*(namespace)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.other.namespace.php" + } + }, + "end": "(?<=})|(?=\\?>)", + "name": "meta.namespace.php", + "patterns": [ + { + "include": "#comments" + }, + { + "match": "(?i)[a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+", + "name": "entity.name.type.namespace.php", + "captures": { + "0": { + "patterns": [ + { + "match": "\\\\", + "name": "punctuation.separator.inheritance.php" + } + ] + } + } + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.namespace.begin.bracket.curly.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.namespace.end.bracket.curly.php" + } + }, + "patterns": [ + { + "include": "$self" } ] }, { - "begin": "<\\?(?i:php|=)?", + "match": "[^\\s]+", + "name": "invalid.illegal.identifier.php" + } + ] + }, + { + "match": "\\s+(?=use\\b)" + }, + { + "begin": "(?i)\\buse\\b", + "beginCaptures": { + "0": { + "name": "keyword.other.use.php" + } + }, + "end": "(?<=})|(?=;)", + "name": "meta.use.php", + "patterns": [ + { + "match": "\\b(const|function)\\b", + "name": "storage.type.${1:/downcase}.php" + }, + { + "begin": "{", "beginCaptures": { "0": { - "name": "punctuation.section.embedded.begin.php" + "name": "punctuation.definition.use.begin.bracket.curly.php" } }, - "end": ">", + "end": "}", "endCaptures": { "0": { - "name": "punctuation.section.embedded.end.php" + "name": "punctuation.definition.use.end.bracket.curly.php" } }, - "name": "meta.embedded.line.php", "patterns": [ { - "captures": { - "1": { - "name": "source.php" - }, - "2": { - "name": "punctuation.section.embedded.end.php" - }, - "3": { - "name": "source.php" - } - }, - "match": "\\G(\\s*)((\\?))(?=>)", - "name": "meta.special.empty-tag.php" + "include": "#scope-resolution" }, { - "begin": "\\G", - "contentName": "source.php", - "end": "(\\?)(?=>)", - "endCaptures": { - "0": { - "name": "punctuation.section.embedded.end.php" - }, + "match": "(?xi)\n\\b(as)\n\\s+(final|abstract|public|private|protected|static)\n\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)", + "captures": { "1": { - "name": "source.php" + "name": "keyword.other.use-as.php" + }, + "2": { + "name": "storage.modifier.php" + }, + "3": { + "name": "entity.other.alias.php" + } + } + }, + { + "match": "(?xi)\n\\b(as)\n\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)", + "captures": { + "1": { + "name": "keyword.other.use-as.php" + }, + "2": { + "patterns": [ + { + "match": "^(?:final|abstract|public|private|protected|static)$", + "name": "storage.modifier.php" + }, + { + "match": ".+", + "name": "entity.other.alias.php" + } + ] + } + } + }, + { + "match": "(?i)\\b(insteadof)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)", + "captures": { + "1": { + "name": "keyword.other.use-insteadof.php" + }, + "2": { + "name": "support.class.php" + } + } + }, + { + "match": ";", + "name": "punctuation.terminator.expression.php" + }, + { + "include": "#use-inner" + } + ] + }, + { + "include": "#use-inner" + } + ] + }, + { + "begin": "(?i)^\\s*(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)", + "beginCaptures": { + "1": { + "name": "storage.modifier.${1:/downcase}.php" + }, + "2": { + "name": "storage.type.class.php" + }, + "3": { + "name": "entity.name.type.class.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.class.end.bracket.curly.php" + } + }, + "name": "meta.class.php", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?i)(extends)\\s+", + "beginCaptures": { + "1": { + "name": "storage.modifier.extends.php" + } + }, + "contentName": "meta.other.inherited-class.php", + "end": "(?i)(?=[^a-z0-9_\\x{7f}-\\x{7fffffff}\\\\])", + "patterns": [ + { + "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*\\\\)", + "end": "(?i)([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{7fffffff}\\\\])", + "endCaptures": { + "1": { + "name": "entity.other.inherited-class.php" } }, "patterns": [ { - "include": "#language" + "include": "#namespace" + } + ] + }, + { + "include": "#class-builtin" + }, + { + "include": "#namespace" + }, + { + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", + "name": "entity.other.inherited-class.php" + } + ] + }, + { + "begin": "(?i)(implements)\\s+", + "beginCaptures": { + "1": { + "name": "storage.modifier.implements.php" + } + }, + "end": "(?i)(?=[;{])", + "patterns": [ + { + "include": "#comments" + }, + { + "begin": "(?i)(?=[a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+)", + "contentName": "meta.other.inherited-class.php", + "end": "(?i)(?:\\s*(?:,|(?=[^a-z0-9_\\x{7f}-\\x{7fffffff}\\\\\\s]))\\s*)", + "patterns": [ + { + "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*\\\\)", + "end": "(?i)([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{7fffffff}\\\\])", + "endCaptures": { + "1": { + "name": "entity.other.inherited-class.php" + } + }, + "patterns": [ + { + "include": "#namespace" + } + ] + }, + { + "include": "#class-builtin" + }, + { + "include": "#namespace" + }, + { + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", + "name": "entity.other.inherited-class.php" } ] } ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.class.begin.bracket.curly.php" + } + }, + "end": "(?=}|\\?>)", + "contentName": "meta.class.body.php", + "patterns": [ + { + "include": "$self" + } + ] } ] - } - }, - "patterns": [ + }, { - "include": "text.html.basic" + "include": "#switch_statement" + }, + { + "match": "(?x)\n\\s*\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", + "captures": { + "1": { + "name": "keyword.control.${1:/downcase}.php" + } + } + }, + { + "begin": "(?i)\\b((?:require|include)(?:_once)?)\\s+", + "beginCaptures": { + "1": { + "name": "keyword.control.import.include.php" + } + }, + "end": "(?=\\s|;|$|\\?>)", + "name": "meta.include.php", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "\\b(catch)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.control.exception.catch.php" + }, + "2": { + "name": "punctuation.definition.parameters.begin.bracket.round.php" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.php" + } + }, + "name": "meta.catch.php", + "patterns": [ + { + "include": "#namespace" + }, + { + "match": "(?xi)\n([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Exception class\n((?:\\s*\\|\\s*[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)*) # Optional additional exception classes\n\\s*\n((\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable", + "captures": { + "1": { + "name": "support.class.exception.php" + }, + "2": { + "patterns": [ + { + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", + "name": "support.class.exception.php" + }, + { + "match": "\\|", + "name": "punctuation.separator.delimiter.php" + } + ] + }, + "3": { + "name": "variable.other.php" + }, + "4": { + "name": "punctuation.definition.variable.php" + } + } + } + ] + }, + { + "match": "\\b(catch|try|throw|exception|finally)\\b", + "name": "keyword.control.exception.php" + }, + { + "begin": "(?i)\\b(function)\\s*(?=\\()", + "beginCaptures": { + "1": { + "name": "storage.type.function.php" + } + }, + "end": "(?={)", + "name": "meta.function.closure.php", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.parameters.begin.bracket.round.php" + } + }, + "contentName": "meta.function.parameters.php", + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.php" + } + }, + "patterns": [ + { + "include": "#function-parameters" + } + ] + }, + { + "begin": "(?i)(use)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "keyword.other.function.use.php" + }, + "2": { + "name": "punctuation.definition.parameters.begin.bracket.round.php" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.parameters.end.bracket.round.php" + } + }, + "patterns": [ + { + "captures": { + "1": { + "name": "variable.other.php" + }, + "2": { + "name": "storage.modifier.reference.php" + }, + "3": { + "name": "punctuation.definition.variable.php" + } + }, + "match": "(?i)((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)\\s*(?=,|\\))", + "name": "meta.function.closure.use.php" + } + ] + } + ] + }, + { + "begin": "(?x)\n((?:(?:final|abstract|public|private|protected|static)\\s+)*)\n(function)\\s+\n(?i:\n (__(?:call|construct|debugInfo|destruct|get|set|isset|unset|toString|\n clone|set_state|sleep|wakeup|autoload|invoke|callStatic))\n |([a-zA-Z_\\x{7f}-\\x{7fffffff}][a-zA-Z0-9_\\x{7f}-\\x{7fffffff}]*)\n)\n\\s*(\\()", + "beginCaptures": { + "1": { + "patterns": [ + { + "match": "final|abstract|public|private|protected|static", + "name": "storage.modifier.php" + } + ] + }, + "2": { + "name": "storage.type.function.php" + }, + "3": { + "name": "support.function.magic.php" + }, + "4": { + "name": "entity.name.function.php" + }, + "5": { + "name": "punctuation.definition.parameters.begin.bracket.round.php" + } + }, + "contentName": "meta.function.parameters.php", + "end": "(\\))(?:\\s*(:)\\s*([a-zA-Z_\\x{7f}-\\x{7fffffff}][a-zA-Z0-9_\\x{7f}-\\x{7fffffff}]*))?", + "endCaptures": { + "1": { + "name": "punctuation.definition.parameters.end.bracket.round.php" + }, + "2": { + "name": "keyword.operator.return-value.php" + }, + "3": { + "name": "storage.type.php" + } + }, + "name": "meta.function.php", + "patterns": [ + { + "include": "#function-parameters" + } + ] + }, + { + "include": "#invoke-call" + }, + { + "include": "#scope-resolution" + }, + { + "include": "#variables" + }, + { + "include": "#strings" + }, + { + "captures": { + "1": { + "name": "support.function.construct.php" + }, + "2": { + "name": "punctuation.definition.array.begin.bracket.round.php" + }, + "3": { + "name": "punctuation.definition.array.end.bracket.round.php" + } + }, + "match": "(array)(\\()(\\))", + "name": "meta.array.empty.php" + }, + { + "begin": "(array)\\s*(\\()", + "beginCaptures": { + "1": { + "name": "support.function.construct.php" + }, + "2": { + "name": "punctuation.definition.array.begin.bracket.round.php" + } + }, + "end": "\\)|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.array.end.bracket.round.php" + } + }, + "name": "meta.array.php", + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "match": "(?i)(\\()\\s*(array|real|double|float|int(?:eger)?|bool(?:ean)?|string|object|binary|unset)\\s*(\\))", + "captures": { + "1": { + "name": "punctuation.definition.storage-type.begin.bracket.round.php" + }, + "2": { + "name": "storage.type.php" + }, + "3": { + "name": "punctuation.definition.storage-type.end.bracket.round.php" + } + } + }, + { + "match": "(?i)\\b(array|real|double|float|int(eger)?|bool(ean)?|string|class|var|function|interface|trait|parent|self|object)\\b", + "name": "storage.type.php" + }, + { + "match": "(?i)\\b(global|abstract|const|extends|implements|final|private|protected|public|static)\\b", + "name": "storage.modifier.php" + }, + { + "include": "#object" + }, + { + "match": ";", + "name": "punctuation.terminator.expression.php" + }, + { + "match": ":", + "name": "punctuation.terminator.statement.php" + }, + { + "include": "#heredoc" + }, + { + "include": "#numbers" + }, + { + "match": "(?i)\\bclone\\b", + "name": "keyword.other.clone.php" + }, + { + "match": "\\.=?", + "name": "keyword.operator.string.php" + }, + { + "match": "=>", + "name": "keyword.operator.key.php" + }, + { + "captures": { + "1": { + "name": "keyword.operator.assignment.php" + }, + "2": { + "name": "storage.modifier.reference.php" + }, + "3": { + "name": "storage.modifier.reference.php" + } + }, + "match": "(?i)(\\=)(&)|(&)(?=[$a-z_])" + }, + { + "match": "@", + "name": "keyword.operator.error-control.php" + }, + { + "match": "===|==|!==|!=|<>", + "name": "keyword.operator.comparison.php" + }, + { + "match": "=|\\+=|\\-=|\\*=|/=|%=|&=|\\|=|\\^=|<<=|>>=", + "name": "keyword.operator.assignment.php" + }, + { + "match": "<=>|<=|>=|<|>", + "name": "keyword.operator.comparison.php" + }, + { + "match": "\\-\\-|\\+\\+", + "name": "keyword.operator.increment-decrement.php" + }, + { + "match": "\\-|\\+|\\*|/|%", + "name": "keyword.operator.arithmetic.php" + }, + { + "match": "(?i)(!|&&|\\|\\|)|\\b(and|or|xor|as)\\b", + "name": "keyword.operator.logical.php" + }, + { + "include": "#function-call" + }, + { + "match": "<<|>>|~|\\^|&|\\|", + "name": "keyword.operator.bitwise.php" + }, + { + "begin": "(?i)\\b(instanceof)\\s+(?=[\\\\$a-z_])", + "beginCaptures": { + "1": { + "name": "keyword.operator.type.php" + } + }, + "end": "(?=[^\\\\$a-z0-9_\\x{7f}-\\x{7fffffff}])", + "patterns": [ + { + "include": "#class-name" + }, + { + "include": "#variable-name" + } + ] + }, + { + "include": "#instantiation" + }, + { + "captures": { + "1": { + "name": "keyword.control.goto.php" + }, + "2": { + "name": "support.other.php" + } + }, + "match": "(?i)(goto)\\s+([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)" + }, + { + "captures": { + "1": { + "name": "entity.name.goto-label.php" + } + }, + "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)\\s*:(?!:)" + }, + { + "include": "#string-backtick" + }, + { + "include": "#ternary_shorthand" + }, + { + "include": "#null_coalescing" + }, + { + "include": "#ternary_expression" + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.curly.php" + } + }, + "end": "}|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.curly.php" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "\\[", + "beginCaptures": { + "0": { + "name": "punctuation.section.array.begin.php" + } + }, + "end": "\\]|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.section.array.end.php" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.begin.bracket.round.php" + } + }, + "end": "\\)|(?=\\?>)", + "endCaptures": { + "0": { + "name": "punctuation.definition.end.bracket.round.php" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + { + "include": "#constants" + }, + { + "match": ",", + "name": "punctuation.separator.delimiter.php" } ], "repository": { @@ -279,7 +974,7 @@ } }, { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", "name": "constant.other.php" } ] @@ -294,7 +989,7 @@ "name": "punctuation.separator.delimiter.php" }, { - "begin": "(?xi)\n(array) # Typehint\n\\s+((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable name with possible reference\n\\s*(=)\\s*(array)\\s*(\\() # Default value", + "begin": "(?xi)\n(array) # Typehint\n\\s+((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name with possible reference\n\\s*(=)\\s*(array)\\s*(\\() # Default value", "beginCaptures": { "1": { "name": "storage.type.php" @@ -339,7 +1034,7 @@ ] }, { - "match": "(?xi)\n(array|callable) # Typehint\n\\s+((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable name with possible reference\n(?: # Optional default value\n \\s*(=)\\s*\n (?:\n (null)\n |\n (\\[)((?>[^\\[\\]]+|\\[\\g<8>\\])*)(\\])\n |((?:\\S*?\\(\\))|(?:\\S*?))\n )\n)?\n\\s*(?=,|\\)|/[/*]|\\#|$) # A closing parentheses (end of argument list) or a comma or a comment", + "match": "(?xi)\n(array|callable) # Typehint\n\\s+((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name with possible reference\n(?: # Optional default value\n \\s*(=)\\s*\n (?:\n (null)\n |\n (\\[)((?>[^\\[\\]]+|\\[\\g<8>\\])*)(\\])\n |((?:\\S*?\\(\\))|(?:\\S*?))\n )\n)?\n\\s*(?=,|\\)|/[/*]|\\#|$) # A closing parentheses (end of argument list) or a comma or a comment", "name": "meta.function.parameter.array.php", "captures": { "1": { @@ -379,13 +1074,13 @@ } }, { - "begin": "(?xi)\n(\\\\?(?:[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*\\\\)*) # Optional namespace\n([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Typehinted class name\n\\s+((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable name with possible reference", + "begin": "(?xi)\n(\\\\?(?:[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*\\\\)*) # Optional namespace\n([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Typehinted class name\n\\s+((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name with possible reference", "beginCaptures": { "1": { "name": "support.other.namespace.php", "patterns": [ { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", "name": "storage.type.php" }, { @@ -423,7 +1118,7 @@ "end": "(?=,|\\)|/[/*]|\\#)", "patterns": [ { - "include": "#language" + "include": "$self" } ] } @@ -444,11 +1139,11 @@ "name": "punctuation.definition.variable.php" } }, - "match": "(?xi)\n((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable name with possible reference\n\\s*(?=,|\\)|/[/*]|\\#|$) # A closing parentheses (end of argument list) or a comma or a comment", + "match": "(?xi)\n((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name with possible reference\n\\s*(?=,|\\)|/[/*]|\\#|$) # A closing parentheses (end of argument list) or a comma or a comment", "name": "meta.function.parameter.no-default.php" }, { - "begin": "(?xi)\n((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable name with possible reference\n\\s*(=)\\s*\n(?:(\\[)((?>[^\\[\\]]+|\\[\\g<6>\\])*)(\\]))? # Optional default type", + "begin": "(?xi)\n((&)?\\s*(\\.\\.\\.)?(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable name with possible reference\n\\s*(=)\\s*\n(?:(\\[)((?>[^\\[\\]]+|\\[\\g<6>\\])*)(\\]))? # Optional default type", "beginCaptures": { "1": { "name": "variable.other.php" @@ -492,7 +1187,7 @@ "function-call": { "patterns": [ { - "begin": "(?xi)\n(\n \\\\?\\b # Optional root namespace\n [a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]* # First namespace\n (?:\\\\[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)+ # Additional namespaces\n)\\s*(\\()", + "begin": "(?xi)\n(\n \\\\?(?)", - "name": "meta.namespace.php", - "patterns": [ - { - "include": "#comments" - }, - { - "match": "(?i)[a-z0-9_\\x{7f}-\\x{ff}\\\\]+", - "name": "entity.name.type.namespace.php", - "captures": { - "0": { - "patterns": [ - { - "match": "\\\\", - "name": "punctuation.separator.inheritance.php" - } - ] - } - } - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.namespace.begin.bracket.curly.php" - } - }, - "end": "}|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.definition.namespace.end.bracket.curly.php" - } - }, - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "match": "[^\\s]+", - "name": "invalid.illegal.identifier.php" - } - ] - }, - { - "match": "\\s+(?=use\\b)" - }, - { - "begin": "(?i)\\buse\\b", - "beginCaptures": { - "0": { - "name": "keyword.other.use.php" - } - }, - "end": "(?<=})|(?=;)", - "name": "meta.use.php", - "patterns": [ - { - "match": "\\b(const|function)\\b", - "name": "storage.type.${1:/downcase}.php" - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.use.begin.bracket.curly.php" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.definition.use.end.bracket.curly.php" - } - }, - "patterns": [ - { - "include": "#scope-resolution" - }, - { - "match": "(?xi)\n\\b(as)\n\\s+(final|abstract|public|private|protected|static)\n\\s+([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\n\\b", - "captures": { - "1": { - "name": "keyword.other.use-as.php" - }, - "2": { - "name": "storage.modifier.php" - }, - "3": { - "name": "entity.other.alias.php" - } - } - }, - { - "match": "(?xi)\n\\b(as)\n\\s+([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\n\\b", - "captures": { - "1": { - "name": "keyword.other.use-as.php" - }, - "2": { - "patterns": [ - { - "match": "^(?:final|abstract|public|private|protected|static)$", - "name": "storage.modifier.php" - }, - { - "match": ".+", - "name": "entity.other.alias.php" - } - ] - } - } - }, - { - "match": "(?i)\\b(insteadof)\\s+([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)", - "captures": { - "1": { - "name": "keyword.other.use-insteadof.php" - }, - "2": { - "name": "support.class.php" - } - } - }, - { - "match": ";", - "name": "punctuation.terminator.expression.php" - }, - { - "include": "#use-inner" - } - ] - }, - { - "include": "#use-inner" - } - ] - }, - { - "begin": "(?i)^\\s*(?:(abstract|final)\\s+)?(class)\\s+([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)", - "beginCaptures": { - "1": { - "name": "storage.modifier.${1:/downcase}.php" - }, - "2": { - "name": "storage.type.class.php" - }, - "3": { - "name": "entity.name.type.class.php" - } - }, - "end": "}|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.definition.class.end.bracket.curly.php" - } - }, - "name": "meta.class.php", - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(?i)(extends)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.extends.php" - } - }, - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?=[^a-z0-9_\\x{7f}-\\x{ff}\\\\])", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{ff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", - "name": "entity.other.inherited-class.php" - } - ] - }, - { - "begin": "(?i)(implements)\\s+", - "beginCaptures": { - "1": { - "name": "storage.modifier.implements.php" - } - }, - "end": "(?i)(?=[;{])", - "patterns": [ - { - "include": "#comments" - }, - { - "begin": "(?i)(?=[a-z0-9_\\x{7f}-\\x{ff}\\\\]+)", - "contentName": "meta.other.inherited-class.php", - "end": "(?i)(?:\\s*(?:,|(?=[^a-z0-9_\\x{7f}-\\x{ff}\\\\\\s]))\\s*)", - "patterns": [ - { - "begin": "(?i)(?=\\\\?[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*\\\\)", - "end": "(?i)([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?(?=[^a-z0-9_\\x{7f}-\\x{ff}\\\\])", - "endCaptures": { - "1": { - "name": "entity.other.inherited-class.php" - } - }, - "patterns": [ - { - "include": "#namespace" - } - ] - }, - { - "include": "#class-builtin" - }, - { - "include": "#namespace" - }, - { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", - "name": "entity.other.inherited-class.php" - } - ] - } - ] - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.class.begin.bracket.curly.php" - } - }, - "end": "(?=}|\\?>)", - "contentName": "meta.class.body.php", - "patterns": [ - { - "include": "#language" - } - ] - } - ] - }, - { - "include": "#switch_statement" - }, - { - "match": "(?x)\n\\s*\n\\b(\n break|case|continue|declare|default|die|do|\n else(if)?|end(declare|for(each)?|if|switch|while)|exit|\n for(each)?|if|return|switch|use|while|yield\n)\\b", - "captures": { - "1": { - "name": "keyword.control.${1:/downcase}.php" - } - } - }, - { - "begin": "(?i)\\b((?:require|include)(?:_once)?)\\s+", - "beginCaptures": { - "1": { - "name": "keyword.control.import.include.php" - } - }, - "end": "(?=\\s|;|$|\\?>)", - "name": "meta.include.php", - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "begin": "\\b(catch)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "keyword.control.exception.catch.php" - }, - "2": { - "name": "punctuation.definition.parameters.begin.bracket.round.php" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.php" - } - }, - "name": "meta.catch.php", - "patterns": [ - { - "include": "#namespace" - }, - { - "match": "(?xi)\n([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Exception class\n((?:\\s*\\|\\s*[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)*) # Optional additional exception classes\n\\s*\n((\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable", - "captures": { - "1": { - "name": "support.class.exception.php" - }, - "2": { - "patterns": [ - { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", - "name": "support.class.exception.php" - }, - { - "match": "\\|", - "name": "punctuation.separator.delimiter.php" - } - ] - }, - "3": { - "name": "variable.other.php" - }, - "4": { - "name": "punctuation.definition.variable.php" - } - } - } - ] - }, - { - "match": "\\b(catch|try|throw|exception|finally)\\b", - "name": "keyword.control.exception.php" - }, - { - "begin": "(?i)\\b(function)\\s*(?=\\()", - "beginCaptures": { - "1": { - "name": "storage.type.function.php" - } - }, - "end": "(?={)", - "name": "meta.function.closure.php", - "patterns": [ - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.parameters.begin.bracket.round.php" - } - }, - "contentName": "meta.function.parameters.php", - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.php" - } - }, - "patterns": [ - { - "include": "#function-parameters" - } - ] - }, - { - "begin": "(?i)(use)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "keyword.other.function.use.php" - }, - "2": { - "name": "punctuation.definition.parameters.begin.bracket.round.php" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.definition.parameters.end.bracket.round.php" - } - }, - "patterns": [ - { - "captures": { - "1": { - "name": "variable.other.php" - }, - "2": { - "name": "storage.modifier.reference.php" - }, - "3": { - "name": "punctuation.definition.variable.php" - } - }, - "match": "(?i)((&)?\\s*(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\\s*(?=,|\\))", - "name": "meta.function.closure.use.php" - } - ] - } - ] - }, - { - "begin": "(?x)\n((?:(?:final|abstract|public|private|protected|static)\\s+)*)\n(function)\\s+\n(?i:\n (__(?:call|construct|debugInfo|destruct|get|set|isset|unset|toString|\n clone|set_state|sleep|wakeup|autoload|invoke|callStatic))\n |([a-zA-Z_\\x{7f}-\\x{ff}][a-zA-Z0-9_\\x{7f}-\\x{ff}]*)\n)\n\\s*(\\()", - "beginCaptures": { - "1": { - "patterns": [ - { - "match": "final|abstract|public|private|protected|static", - "name": "storage.modifier.php" - } - ] - }, - "2": { - "name": "storage.type.function.php" - }, - "3": { - "name": "support.function.magic.php" - }, - "4": { - "name": "entity.name.function.php" - }, - "5": { - "name": "punctuation.definition.parameters.begin.bracket.round.php" - } - }, - "contentName": "meta.function.parameters.php", - "end": "(\\))(?:\\s*(:)\\s*([a-zA-Z_\\x{7f}-\\x{ff}][a-zA-Z0-9_\\x{7f}-\\x{ff}]*))?", - "endCaptures": { - "1": { - "name": "punctuation.definition.parameters.end.bracket.round.php" - }, - "2": { - "name": "keyword.operator.return-value.php" - }, - "3": { - "name": "storage.type.php" - } - }, - "name": "meta.function.php", - "patterns": [ - { - "include": "#function-parameters" - } - ] - }, - { - "include": "#invoke-call" - }, - { - "include": "#scope-resolution" - }, - { - "include": "#variables" - }, - { - "include": "#strings" - }, - { - "captures": { - "1": { - "name": "support.function.construct.php" - }, - "2": { - "name": "punctuation.definition.array.begin.bracket.round.php" - }, - "3": { - "name": "punctuation.definition.array.end.bracket.round.php" - } - }, - "match": "(array)(\\()(\\))", - "name": "meta.array.empty.php" - }, - { - "begin": "(array)(\\()", - "beginCaptures": { - "1": { - "name": "support.function.construct.php" - }, - "2": { - "name": "punctuation.definition.array.begin.bracket.round.php" - } - }, - "end": "\\)|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.definition.array.end.bracket.round.php" - } - }, - "name": "meta.array.php", - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "match": "(?i)(\\()\\s*(array|real|double|float|int(?:eger)?|bool(?:ean)?|string|object|binary|unset)\\s*(\\))", - "captures": { - "1": { - "name": "punctuation.definition.storage-type.begin.bracket.round.php" - }, - "2": { - "name": "storage.type.php" - }, - "3": { - "name": "punctuation.definition.storage-type.end.bracket.round.php" - } - } - }, - { - "match": "(?i)\\b(array|real|double|float|int(eger)?|bool(ean)?|string|class|var|function|interface|trait|parent|self|object)\\b", - "name": "storage.type.php" - }, - { - "match": "(?i)\\b(global|abstract|const|extends|implements|final|private|protected|public|static)\\b", - "name": "storage.modifier.php" - }, - { - "include": "#object" - }, - { - "match": ";", - "name": "punctuation.terminator.expression.php" - }, - { - "match": ":", - "name": "punctuation.terminator.statement.php" - }, - { - "include": "#heredoc" - }, - { - "include": "#numbers" - }, - { - "match": "(?i)\\bclone\\b", - "name": "keyword.other.clone.php" - }, - { - "match": "\\.=?", - "name": "keyword.operator.string.php" - }, - { - "match": "=>", - "name": "keyword.operator.key.php" - }, - { - "captures": { - "1": { - "name": "keyword.operator.assignment.php" - }, - "2": { - "name": "storage.modifier.reference.php" - }, - "3": { - "name": "storage.modifier.reference.php" - } - }, - "match": "(?i)(\\=)(&)|(&)(?=[$a-z_])" - }, - { - "match": "@", - "name": "keyword.operator.error-control.php" - }, - { - "match": "===|==|!==|!=|<>", - "name": "keyword.operator.comparison.php" - }, - { - "match": "=|\\+=|\\-=|\\*=|/=|%=|&=|\\|=|\\^=|<<=|>>=", - "name": "keyword.operator.assignment.php" - }, - { - "match": "<=>|<=|>=|<|>", - "name": "keyword.operator.comparison.php" - }, - { - "match": "\\-\\-|\\+\\+", - "name": "keyword.operator.increment-decrement.php" - }, - { - "match": "\\-|\\+|\\*|/|%", - "name": "keyword.operator.arithmetic.php" - }, - { - "match": "(?i)(!|&&|\\|\\|)|\\b(and|or|xor|as)\\b", - "name": "keyword.operator.logical.php" - }, - { - "include": "#function-call" - }, - { - "match": "<<|>>|~|\\^|&|\\|", - "name": "keyword.operator.bitwise.php" - }, - { - "begin": "(?i)\\b(instanceof)\\s+(?=[\\\\$a-z_])", - "beginCaptures": { - "1": { - "name": "keyword.operator.type.php" - } - }, - "end": "(?=[^\\\\$a-z0-9_\\x{7f}-\\x{ff}])", - "patterns": [ - { - "include": "#class-name" - }, - { - "include": "#variable-name" - } - ] - }, - { - "include": "#instantiation" - }, - { - "captures": { - "1": { - "name": "keyword.control.goto.php" - }, - "2": { - "name": "support.other.php" - } - }, - "match": "(?i)(goto)\\s+([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)" - }, - { - "captures": { - "1": { - "name": "entity.name.goto-label.php" - } - }, - "match": "(?i)^\\s*([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\\s*:(?!:)" - }, - { - "include": "#string-backtick" - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.definition.begin.bracket.curly.php" - } - }, - "end": "}|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.curly.php" - } - }, - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "begin": "\\[", - "beginCaptures": { - "0": { - "name": "punctuation.section.array.begin.php" - } - }, - "end": "\\]|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.section.array.end.php" - } - }, - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "begin": "\\(", - "beginCaptures": { - "0": { - "name": "punctuation.definition.begin.bracket.round.php" - } - }, - "end": "\\)|(?=\\?>)", - "endCaptures": { - "0": { - "name": "punctuation.definition.end.bracket.round.php" - } - }, - "patterns": [ - { - "include": "#language" - } - ] - }, - { - "include": "#constants" - }, - { - "match": ",", - "name": "punctuation.separator.delimiter.php" - } - ] - }, "namespace": { - "begin": "(?i)(?:(namespace)|[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?(\\\\)", + "begin": "(?i)(?:(namespace)|[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?(\\\\)", "beginCaptures": { "1": { "name": "variable.language.namespace.php" @@ -2098,7 +2011,7 @@ "name": "punctuation.separator.inheritance.php" } }, - "end": "(?i)(?![a-z0-9_\\x{7f}-\\x{ff}]*\\\\)", + "end": "(?i)(?![a-z0-9_\\x{7f}-\\x{7fffffff}]*\\\\)", "name": "support.other.namespace.php", "patterns": [ { @@ -2159,12 +2072,12 @@ }, "patterns": [ { - "include": "#language" + "include": "$self" } ] }, { - "begin": "(?i)(->)([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\\s*(\\()", + "begin": "(?i)(->)([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)\\s*(\\()", "beginCaptures": { "1": { "name": "keyword.operator.class.php" @@ -2185,7 +2098,7 @@ "name": "meta.method-call.php", "patterns": [ { - "include": "#language" + "include": "$self" } ] }, @@ -2201,7 +2114,7 @@ "name": "punctuation.definition.variable.php" } }, - "match": "(?i)(->)((\\$+)?[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?" + "match": "(?i)(->)((\\$+)?[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?" } ] }, @@ -2258,8 +2171,8 @@ "include": "#instantiation" }, { - "begin": "(?xi)\n(?=[a-z0-9_\\x{7f}-\\x{ff}\\\\]+(::)\n ([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?\n)", - "end": "(?i)(::)([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)?", + "begin": "(?xi)\n(?=[a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]+(::)\n ([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?\n)", + "end": "(?i)(::)([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)?", "endCaptures": { "1": { "name": "keyword.operator.class.php" @@ -2311,7 +2224,7 @@ "match": "(@xlink)\\s+(.+)\\s*$" }, { - "begin": "(@(?:global|param|property(-(read|write))?|return|throws|var))\\s+(?=[A-Za-z_\\x{7f}-\\x{ff}\\\\]|\\()", + "begin": "(@(?:global|param|property(-(read|write))?|return|throws|var))\\s+(?=[A-Za-z_\\x{7f}-\\x{7fffffff}\\\\]|\\()", "beginCaptures": { "1": { "name": "keyword.other.phpdoc.php" @@ -2347,7 +2260,7 @@ ] }, "php_doc_types": { - "match": "(?i)[a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*(\\|[a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)*", + "match": "(?i)[a-z_\\x{7f}-\\x{7fffffff}\\\\][a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]*(\\|[a-z_\\x{7f}-\\x{7fffffff}\\\\][a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]*)*", "captures": { "0": { "patterns": [ @@ -2399,7 +2312,7 @@ ] }, "php_doc_types_array_single": { - "match": "(?i)([a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)(\\[\\])", + "match": "(?i)([a-z_\\x{7f}-\\x{7fffffff}\\\\][a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]*)(\\[\\])", "captures": { "1": { "patterns": [ @@ -2433,7 +2346,7 @@ "name": "constant.character.escape.regex.php" }, { - "include": "#interpolation" + "include": "#interpolation_double_quoted" }, { "captures": { @@ -2458,7 +2371,7 @@ "name": "string.regexp.character-class.php", "patterns": [ { - "include": "#interpolation" + "include": "#interpolation_double_quoted" } ] }, @@ -2517,7 +2430,7 @@ "scope-resolution": { "patterns": [ { - "match": "(?i)([a-z_\\x{7f}-\\x{ff}\\\\][a-z0-9_\\x{7f}-\\x{ff}\\\\]*)(?=\\s*::)", + "match": "(?i)([a-z_\\x{7f}-\\x{7fffffff}\\\\][a-z0-9_\\x{7f}-\\x{7fffffff}\\\\]*)(?=\\s*::)", "captures": { "1": { "patterns": [ @@ -2536,7 +2449,7 @@ } }, { - "begin": "(?i)(::)\\s*([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)\\s*(\\()", + "begin": "(?i)(::)\\s*([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)\\s*(\\()", "beginCaptures": { "1": { "name": "keyword.operator.class.php" @@ -2557,7 +2470,7 @@ "name": "meta.method-call.static.php", "patterns": [ { - "include": "#language" + "include": "$self" } ] }, @@ -2573,7 +2486,7 @@ } }, { - "match": "(?xi)\n(::)\\s*\n(?:\n ((\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Variable\n |\n ([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*) # Constant\n)?", + "match": "(?xi)\n(::)\\s*\n(?:\n ((\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Variable\n |\n ([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*) # Constant\n)?", "captures": { "1": { "name": "keyword.operator.class.php" @@ -2647,7 +2560,7 @@ "name": "string.quoted.single.sql", "patterns": [ { - "include": "#interpolation" + "include": "#interpolation_double_quoted" } ] }, @@ -2657,12 +2570,12 @@ "name": "string.quoted.other.backtick.sql", "patterns": [ { - "include": "#interpolation" + "include": "#interpolation_double_quoted" } ] }, { - "include": "#interpolation" + "include": "#interpolation_double_quoted" }, { "include": "source.sql" @@ -2736,7 +2649,7 @@ "name": "string.interpolated.php", "patterns": [ { - "match": "\\\\.", + "match": "\\\\`", "name": "constant.character.escape.php" }, { @@ -2760,7 +2673,7 @@ "name": "string.quoted.double.php", "patterns": [ { - "include": "#interpolation" + "include": "#interpolation_double_quoted" } ] }, @@ -3270,7 +3183,7 @@ }, "patterns": [ { - "include": "#language" + "include": "$self" } ] }, @@ -3284,7 +3197,7 @@ "end": "(?=}|\\?>)", "patterns": [ { - "include": "#language" + "include": "$self" } ] } @@ -3304,7 +3217,7 @@ "name": "keyword.other.use-as.php" } }, - "end": "(?i)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*", + "end": "(?i)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", "endCaptures": { "0": { "name": "entity.other.alias.php" @@ -3323,13 +3236,13 @@ "var_basic": { "patterns": [ { + "match": "(?i)(\\$+)[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*", + "name": "variable.other.php", "captures": { "1": { "name": "punctuation.definition.variable.php" } - }, - "match": "(?i)(\\$+)[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*\\b", - "name": "variable.other.php" + } } ] }, @@ -3401,7 +3314,7 @@ "name": "punctuation.section.array.end.php" } }, - "match": "(?xi)\n((\\$)(?[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*))\n(?:\n (->)(\\g)\n |\n (\\[)(?:(\\d+)|((\\$)\\g)|([a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*))(\\])\n)?" + "match": "(?xi)\n((\\$)(?[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*))\n(?:\n (->)(\\g)\n |\n (\\[)(?:(\\d+)|((\\$)\\g)|([a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*))(\\])\n)?" }, { "captures": { @@ -3415,7 +3328,7 @@ "name": "punctuation.definition.variable.php" } }, - "match": "(?i)((\\${)(?[a-z_\\x{7f}-\\x{ff}][a-z0-9_\\x{7f}-\\x{ff}]*)(}))" + "match": "(?i)((\\${)(?[a-z_\\x{7f}-\\x{7fffffff}][a-z0-9_\\x{7f}-\\x{7fffffff}]*)(}))" } ] }, @@ -3448,11 +3361,38 @@ }, "patterns": [ { - "include": "#language" + "include": "$self" } ] } ] + }, + "ternary_shorthand": { + "match": "\\?:", + "name": "keyword.operator.ternary.php" + }, + "ternary_expression": { + "begin": "\\?", + "beginCaptures": { + "0": { + "name": "keyword.operator.ternary.php" + } + }, + "end": ":", + "endCaptures": { + "0": { + "name": "keyword.operator.ternary.php" + } + }, + "patterns": [ + { + "include": "$self" + } + ] + }, + "null_coalescing": { + "match": "\\?\\?", + "name": "keyword.operator.null-coalescing.php" } } } \ No newline at end of file diff --git a/extensions/ruby/language-configuration.json b/extensions/ruby/language-configuration.json index fc4125f0691..47c434deffa 100644 --- a/extensions/ruby/language-configuration.json +++ b/extensions/ruby/language-configuration.json @@ -23,7 +23,7 @@ ["'", "'"] ], "indentationRules": { - "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while)|(.*\\sdo\\b))\\b[^\\{;]*$", - "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif)\\b)" + "increaseIndentPattern": "^\\s*((begin|class|(private|protected)\\s+def|def|else|elsif|ensure|for|if|module|rescue|unless|until|when|while|case)|([^#]*\\sdo\\b)|([^#]*=\\s*(case|if|unless)))\\b([^#\\{;]|(\"|'|\/).*\\4)*(#.*)?$", + "decreaseIndentPattern": "^\\s*([}\\]]([,)]?\\s*(#|$)|\\.[a-zA-Z_]\\w*\\b)|(end|rescue|ensure|else|elsif|when)\\b)" } } diff --git a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json index 6aa3daeefe6..874d7d16a00 100644 --- a/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json +++ b/extensions/shellscript/syntaxes/shell-unix-bash.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-shellscript/commit/f2cec59e541e3e10153a8e3e5e681baf139c81a3", + "version": "https://github.com/atom/language-shellscript/commit/5206d247e33bb4e7ebeb320f10814ecd4132f9eb", "scopeName": "source.shell", "name": "Shell Script", "fileTypes": [ @@ -753,7 +753,7 @@ ] } }, - "match": "(<<<)\\s*(([^\\s\\\\]|\\\\.)+)", + "match": "(<<<)\\s*(([^\\s)\\\\]|\\\\.)+)", "name": "meta.herestring.shell" } ] diff --git a/extensions/theme-defaults/themes/light_defaults.json b/extensions/theme-defaults/themes/light_defaults.json index ad85f382140..0e0ac26ff9d 100644 --- a/extensions/theme-defaults/themes/light_defaults.json +++ b/extensions/theme-defaults/themes/light_defaults.json @@ -9,6 +9,7 @@ "editor.selectionHighlightBackground": "#ADD6FF4D", "editorSuggestWidget.background": "#F3F3F3", "activityBarBadge.background": "#007ACC", - "sideBarTitle.foreground": "#6F6F6F" + "sideBarTitle.foreground": "#6F6F6F", + "list.hoverBackground": "#e0e0e0" } } \ No newline at end of file diff --git a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json index 3b64b90309f..cb50d3813e6 100644 --- a/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json +++ b/extensions/theme-monokai-dimmed/themes/dimmed-monokai-color-theme.json @@ -5,7 +5,7 @@ "list.activeSelectionBackground": "#707070", "list.focusBackground": "#707070", "list.inactiveSelectionBackground": "#4e4e4e", - "list.hoverBackground": "#707070", + "list.hoverBackground": "#444444", "list.highlightForeground": "#e58520", "button.background": "#565656", "editor.background": "#1e1e1e", @@ -210,14 +210,6 @@ "foreground": "#6089B4" } }, - { - "name": "Language Constant", - "scope": "constant.language", - "settings": { - "fontStyle": "", - "foreground": "#FF0080" - } - }, { "name": "Meta Brace", "scope": "punctuation.section.embedded -(source string source punctuation.section.embedded), meta.brace.erb.html", diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 0e0bcae413e..d34cdb01cdb 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -486,6 +486,7 @@ "pickerGroup.border": "#749351", "list.activeSelectionForeground": "#6c6c6c", "list.focusBackground": "#CADEB9", + "list.hoverBackground": "#e0e0e0", "list.activeSelectionBackground": "#c4d9b1", "list.inactiveSelectionBackground": "#d3dbcd", "list.highlightForeground": "#9769dc", diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff index b823e159a1d..476c5a9946b 100644 Binary files a/extensions/theme-seti/icons/seti.woff and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json index 72b34229231..1226c0dac6a 100644 --- a/extensions/theme-seti/icons/vs-seti-icon-theme.json +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -117,850 +117,919 @@ "fontCharacter": "\\E00F", "fontColor": "#6d8086" }, - "_code-climate_light": { + "_clojure_light": { "fontCharacter": "\\E010", + "fontColor": "#498ba7" + }, + "_clojure": { + "fontCharacter": "\\E010", + "fontColor": "#519aba" + }, + "_code-climate_light": { + "fontCharacter": "\\E011", "fontColor": "#7fae42" }, "_code-climate": { - "fontCharacter": "\\E010", + "fontCharacter": "\\E011", "fontColor": "#8dc149" }, "_coffee_light": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E012", "fontColor": "#b7b73b" }, "_coffee": { - "fontCharacter": "\\E011", + "fontCharacter": "\\E012", "fontColor": "#cbcb41" }, "_coffee_erb": { - "fontCharacter": "\\E012" + "fontCharacter": "\\E013" }, "_coldfusion_light": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E014", "fontColor": "#498ba7" }, "_coldfusion": { - "fontCharacter": "\\E013", + "fontCharacter": "\\E014", "fontColor": "#519aba" }, "_config_light": { - "fontCharacter": "\\E014", + "fontCharacter": "\\E015", "fontColor": "#627379" }, "_config": { - "fontCharacter": "\\E014", + "fontCharacter": "\\E015", "fontColor": "#6d8086" }, "_cpp_light": { - "fontCharacter": "\\E015", + "fontCharacter": "\\E016", "fontColor": "#9068b0" }, "_cpp": { - "fontCharacter": "\\E015", + "fontCharacter": "\\E016", "fontColor": "#a074c4" }, + "_crystal_light": { + "fontCharacter": "\\E017", + "fontColor": "#bfc2c1" + }, + "_crystal": { + "fontCharacter": "\\E017", + "fontColor": "#d4d7d6" + }, + "_crystal_embedded_light": { + "fontCharacter": "\\E018", + "fontColor": "#bfc2c1" + }, + "_crystal_embedded": { + "fontCharacter": "\\E018", + "fontColor": "#d4d7d6" + }, "_css_light": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E019", "fontColor": "#498ba7" }, "_css": { - "fontCharacter": "\\E016", + "fontCharacter": "\\E019", "fontColor": "#519aba" }, "_csv_light": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E01A", "fontColor": "#7fae42" }, "_csv": { - "fontCharacter": "\\E017", + "fontCharacter": "\\E01A", "fontColor": "#8dc149" }, "_d_light": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E01B", "fontColor": "#b8383d" }, "_d": { - "fontCharacter": "\\E018", + "fontCharacter": "\\E01B", "fontColor": "#cc3e44" }, "_db_light": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E01C", "fontColor": "#dd4b78" }, "_db": { - "fontCharacter": "\\E019", + "fontCharacter": "\\E01C", "fontColor": "#f55385" }, "_default_light": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E01D", "fontColor": "#bfc2c1" }, "_default": { - "fontCharacter": "\\E01A", + "fontCharacter": "\\E01D", "fontColor": "#d4d7d6" }, "_deprecation-cop": { - "fontCharacter": "\\E01B" + "fontCharacter": "\\E01E" }, "_docker_light": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01F", "fontColor": "#dd4b78" }, "_docker": { - "fontCharacter": "\\E01C", + "fontCharacter": "\\E01F", "fontColor": "#f55385" }, "_editorconfig": { - "fontCharacter": "\\E01D" + "fontCharacter": "\\E020" }, "_ejs_light": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E021", "fontColor": "#b7b73b" }, "_ejs": { - "fontCharacter": "\\E01E", + "fontCharacter": "\\E021", "fontColor": "#cbcb41" }, "_elixir_light": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E022", "fontColor": "#9068b0" }, "_elixir": { - "fontCharacter": "\\E01F", + "fontCharacter": "\\E022", "fontColor": "#a074c4" }, "_elixir_script_light": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E023", "fontColor": "#9068b0" }, "_elixir_script": { - "fontCharacter": "\\E020", + "fontCharacter": "\\E023", "fontColor": "#a074c4" }, "_elm_light": { - "fontCharacter": "\\E021", + "fontCharacter": "\\E024", "fontColor": "#498ba7" }, "_elm": { - "fontCharacter": "\\E021", + "fontCharacter": "\\E024", "fontColor": "#519aba" }, "_error": { - "fontCharacter": "\\E022" + "fontCharacter": "\\E025" }, "_eslint_light": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E026", "fontColor": "#455155" }, "_eslint": { - "fontCharacter": "\\E023", + "fontCharacter": "\\E026", "fontColor": "#4d5a5e" }, + "_ethereum_light": { + "fontCharacter": "\\E027", + "fontColor": "#498ba7" + }, + "_ethereum": { + "fontCharacter": "\\E027", + "fontColor": "#519aba" + }, "_f-sharp_light": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E028", "fontColor": "#498ba7" }, "_f-sharp": { - "fontCharacter": "\\E024", + "fontCharacter": "\\E028", "fontColor": "#519aba" }, "_favicon_light": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E029", "fontColor": "#b7b73b" }, "_favicon": { - "fontCharacter": "\\E025", + "fontCharacter": "\\E029", "fontColor": "#cbcb41" }, "_firebase_light": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E02A", "fontColor": "#cc6d2e" }, "_firebase": { - "fontCharacter": "\\E026", + "fontCharacter": "\\E02A", "fontColor": "#e37933" }, "_firefox_light": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E02B", "fontColor": "#cc6d2e" }, "_firefox": { - "fontCharacter": "\\E027", + "fontCharacter": "\\E02B", "fontColor": "#e37933" }, "_folder": { - "fontCharacter": "\\E028" + "fontCharacter": "\\E02C" }, "_font_light": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02D", "fontColor": "#b8383d" }, "_font": { - "fontCharacter": "\\E029", + "fontCharacter": "\\E02D", "fontColor": "#cc3e44" }, "_git_light": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02E", "fontColor": "#3b4b52" }, "_git": { - "fontCharacter": "\\E02A", + "fontCharacter": "\\E02E", "fontColor": "#41535b" }, "_git_folder": { - "fontCharacter": "\\E02B" + "fontCharacter": "\\E02F" }, "_git_ignore": { - "fontCharacter": "\\E02C" + "fontCharacter": "\\E030" }, "_github": { - "fontCharacter": "\\E02D" + "fontCharacter": "\\E031" }, "_go_light": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E032", "fontColor": "#498ba7" }, "_go": { - "fontCharacter": "\\E02E", + "fontCharacter": "\\E032", "fontColor": "#519aba" }, "_go2_light": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E033", "fontColor": "#498ba7" }, "_go2": { - "fontCharacter": "\\E02F", + "fontCharacter": "\\E033", "fontColor": "#519aba" }, "_gradle_light": { - "fontCharacter": "\\E030", + "fontCharacter": "\\E034", "fontColor": "#7fae42" }, "_gradle": { - "fontCharacter": "\\E030", + "fontCharacter": "\\E034", "fontColor": "#8dc149" }, "_grails_light": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E035", "fontColor": "#7fae42" }, "_grails": { - "fontCharacter": "\\E031", + "fontCharacter": "\\E035", "fontColor": "#8dc149" }, "_grunt_light": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E036", "fontColor": "#cc6d2e" }, "_grunt": { - "fontCharacter": "\\E032", + "fontCharacter": "\\E036", "fontColor": "#e37933" }, "_gulp_light": { - "fontCharacter": "\\E033", + "fontCharacter": "\\E037", "fontColor": "#b8383d" }, "_gulp": { - "fontCharacter": "\\E033", + "fontCharacter": "\\E037", "fontColor": "#cc3e44" }, "_hacklang": { - "fontCharacter": "\\E034" + "fontCharacter": "\\E038" }, "_haml_light": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E039", "fontColor": "#b8383d" }, "_haml": { - "fontCharacter": "\\E035", + "fontCharacter": "\\E039", "fontColor": "#cc3e44" }, "_haskell_light": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E03A", "fontColor": "#9068b0" }, "_haskell": { - "fontCharacter": "\\E036", + "fontCharacter": "\\E03A", + "fontColor": "#a074c4" + }, + "_haxe_light": { + "fontCharacter": "\\E03B", + "fontColor": "#9068b0" + }, + "_haxe": { + "fontCharacter": "\\E03B", "fontColor": "#a074c4" }, "_heroku_light": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E03C", "fontColor": "#9068b0" }, "_heroku": { - "fontCharacter": "\\E037", + "fontCharacter": "\\E03C", "fontColor": "#a074c4" }, "_hex_light": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E03D", "fontColor": "#b8383d" }, "_hex": { - "fontCharacter": "\\E038", + "fontCharacter": "\\E03D", "fontColor": "#cc3e44" }, "_html_light": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E03E", "fontColor": "#cc6d2e" }, "_html": { - "fontCharacter": "\\E039", + "fontCharacter": "\\E03E", "fontColor": "#e37933" }, + "_html_erb_light": { + "fontCharacter": "\\E03F", + "fontColor": "#b8383d" + }, "_html_erb": { - "fontCharacter": "\\E03A" + "fontCharacter": "\\E03F", + "fontColor": "#cc3e44" }, "_ignored_light": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E040", "fontColor": "#3b4b52" }, "_ignored": { - "fontCharacter": "\\E03B", + "fontCharacter": "\\E040", "fontColor": "#41535b" }, "_illustrator_light": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E041", "fontColor": "#b7b73b" }, "_illustrator": { - "fontCharacter": "\\E03C", + "fontCharacter": "\\E041", "fontColor": "#cbcb41" }, "_image_light": { - "fontCharacter": "\\E03D", + "fontCharacter": "\\E042", "fontColor": "#9068b0" }, "_image": { - "fontCharacter": "\\E03D", + "fontCharacter": "\\E042", "fontColor": "#a074c4" }, "_info_light": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E043", "fontColor": "#498ba7" }, "_info": { - "fontCharacter": "\\E03E", + "fontCharacter": "\\E043", "fontColor": "#519aba" }, "_ionic_light": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E044", "fontColor": "#498ba7" }, "_ionic": { - "fontCharacter": "\\E03F", + "fontCharacter": "\\E044", "fontColor": "#519aba" }, "_jade_light": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E045", "fontColor": "#b8383d" }, "_jade": { - "fontCharacter": "\\E040", + "fontCharacter": "\\E045", "fontColor": "#cc3e44" }, "_java_light": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E046", "fontColor": "#b8383d" }, "_java": { - "fontCharacter": "\\E041", + "fontCharacter": "\\E046", "fontColor": "#cc3e44" }, "_javascript_light": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E047", "fontColor": "#498ba7" }, "_javascript": { - "fontCharacter": "\\E042", + "fontCharacter": "\\E047", "fontColor": "#519aba" }, "_jenkins_light": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E048", "fontColor": "#b8383d" }, "_jenkins": { - "fontCharacter": "\\E043", + "fontCharacter": "\\E048", "fontColor": "#cc3e44" }, "_jinja_light": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E049", "fontColor": "#b8383d" }, "_jinja": { - "fontCharacter": "\\E044", + "fontCharacter": "\\E049", "fontColor": "#cc3e44" }, "_js_erb": { - "fontCharacter": "\\E045" + "fontCharacter": "\\E04A" }, "_json_light": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E04B", "fontColor": "#b7b73b" }, "_json": { - "fontCharacter": "\\E046", + "fontCharacter": "\\E04B", "fontColor": "#cbcb41" }, "_julia_light": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E04C", "fontColor": "#9068b0" }, "_julia": { - "fontCharacter": "\\E047", + "fontCharacter": "\\E04C", "fontColor": "#a074c4" }, "_karma_light": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04D", "fontColor": "#7fae42" }, "_karma": { - "fontCharacter": "\\E048", + "fontCharacter": "\\E04D", "fontColor": "#8dc149" }, "_less_light": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04E", "fontColor": "#498ba7" }, "_less": { - "fontCharacter": "\\E049", + "fontCharacter": "\\E04E", "fontColor": "#519aba" }, "_license_light": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04F", "fontColor": "#b8383d" }, "_license": { - "fontCharacter": "\\E04A", + "fontCharacter": "\\E04F", "fontColor": "#cc3e44" }, "_liquid_light": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E050", "fontColor": "#7fae42" }, "_liquid": { - "fontCharacter": "\\E04B", + "fontCharacter": "\\E050", "fontColor": "#8dc149" }, "_livescript_light": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E051", "fontColor": "#498ba7" }, "_livescript": { - "fontCharacter": "\\E04C", + "fontCharacter": "\\E051", "fontColor": "#519aba" }, "_lock_light": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E052", "fontColor": "#7fae42" }, "_lock": { - "fontCharacter": "\\E04D", + "fontCharacter": "\\E052", "fontColor": "#8dc149" }, "_lua_light": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E053", "fontColor": "#498ba7" }, "_lua": { - "fontCharacter": "\\E04E", + "fontCharacter": "\\E053", "fontColor": "#519aba" }, "_makefile_light": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E054", "fontColor": "#498ba7" }, "_makefile": { - "fontCharacter": "\\E04F", + "fontCharacter": "\\E054", "fontColor": "#519aba" }, "_markdown_light": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E055", "fontColor": "#498ba7" }, "_markdown": { - "fontCharacter": "\\E050", + "fontCharacter": "\\E055", "fontColor": "#519aba" }, "_maven_light": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E056", "fontColor": "#b8383d" }, "_maven": { - "fontCharacter": "\\E051", + "fontCharacter": "\\E056", "fontColor": "#cc3e44" }, "_mdo_light": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E057", "fontColor": "#b8383d" }, "_mdo": { - "fontCharacter": "\\E052", + "fontCharacter": "\\E057", "fontColor": "#cc3e44" }, "_mustache_light": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E058", "fontColor": "#cc6d2e" }, "_mustache": { - "fontCharacter": "\\E053", + "fontCharacter": "\\E058", "fontColor": "#e37933" }, "_new-file": { - "fontCharacter": "\\E054" + "fontCharacter": "\\E059" }, "_npm_light": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E05A", "fontColor": "#b8383d" }, "_npm": { - "fontCharacter": "\\E055", + "fontCharacter": "\\E05A", "fontColor": "#cc3e44" }, "_npm_ignored_light": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E05B", "fontColor": "#3b4b52" }, "_npm_ignored": { - "fontCharacter": "\\E056", + "fontCharacter": "\\E05B", "fontColor": "#41535b" }, "_nunjucks_light": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E05C", "fontColor": "#7fae42" }, "_nunjucks": { - "fontCharacter": "\\E057", + "fontCharacter": "\\E05C", "fontColor": "#8dc149" }, "_ocaml_light": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E05D", "fontColor": "#cc6d2e" }, "_ocaml": { - "fontCharacter": "\\E058", + "fontCharacter": "\\E05D", + "fontColor": "#e37933" + }, + "_odata_light": { + "fontCharacter": "\\E05E", + "fontColor": "#cc6d2e" + }, + "_odata": { + "fontCharacter": "\\E05E", "fontColor": "#e37933" }, "_pdf_light": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05F", "fontColor": "#b8383d" }, "_pdf": { - "fontCharacter": "\\E059", + "fontCharacter": "\\E05F", "fontColor": "#cc3e44" }, "_perl_light": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E060", "fontColor": "#498ba7" }, "_perl": { - "fontCharacter": "\\E05A", + "fontCharacter": "\\E060", "fontColor": "#519aba" }, "_photoshop_light": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E061", "fontColor": "#498ba7" }, "_photoshop": { - "fontCharacter": "\\E05B", + "fontCharacter": "\\E061", "fontColor": "#519aba" }, "_php_light": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E062", "fontColor": "#9068b0" }, "_php": { - "fontCharacter": "\\E05C", + "fontCharacter": "\\E062", "fontColor": "#a074c4" }, "_powershell_light": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E063", "fontColor": "#498ba7" }, "_powershell": { - "fontCharacter": "\\E05D", + "fontCharacter": "\\E063", "fontColor": "#519aba" }, "_project": { - "fontCharacter": "\\E05E" + "fontCharacter": "\\E064" }, "_pug_light": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E065", "fontColor": "#b8383d" }, "_pug": { - "fontCharacter": "\\E05F", + "fontCharacter": "\\E065", "fontColor": "#cc3e44" }, "_puppet_light": { - "fontCharacter": "\\E060", + "fontCharacter": "\\E066", "fontColor": "#b7b73b" }, "_puppet": { - "fontCharacter": "\\E060", + "fontCharacter": "\\E066", "fontColor": "#cbcb41" }, "_python_light": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E067", "fontColor": "#498ba7" }, "_python": { - "fontCharacter": "\\E061", + "fontCharacter": "\\E067", "fontColor": "#519aba" }, "_rails": { - "fontCharacter": "\\E062" + "fontCharacter": "\\E068" }, "_react_light": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E069", "fontColor": "#498ba7" }, "_react": { - "fontCharacter": "\\E063", + "fontCharacter": "\\E069", "fontColor": "#519aba" }, "_rollup_light": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E06A", "fontColor": "#b8383d" }, "_rollup": { - "fontCharacter": "\\E064", + "fontCharacter": "\\E06A", "fontColor": "#cc3e44" }, "_ruby_light": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E06B", "fontColor": "#b8383d" }, "_ruby": { - "fontCharacter": "\\E065", + "fontCharacter": "\\E06B", "fontColor": "#cc3e44" }, "_rust_light": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E06C", "fontColor": "#627379" }, "_rust": { - "fontCharacter": "\\E066", + "fontCharacter": "\\E06C", "fontColor": "#6d8086" }, "_salesforce_light": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E06D", "fontColor": "#498ba7" }, "_salesforce": { - "fontCharacter": "\\E067", + "fontCharacter": "\\E06D", "fontColor": "#519aba" }, "_sass_light": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E06E", "fontColor": "#dd4b78" }, "_sass": { - "fontCharacter": "\\E068", + "fontCharacter": "\\E06E", "fontColor": "#f55385" }, "_sbt_light": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06F", "fontColor": "#498ba7" }, "_sbt": { - "fontCharacter": "\\E069", + "fontCharacter": "\\E06F", "fontColor": "#519aba" }, "_scala_light": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E070", "fontColor": "#b8383d" }, "_scala": { - "fontCharacter": "\\E06A", + "fontCharacter": "\\E070", "fontColor": "#cc3e44" }, "_search": { - "fontCharacter": "\\E06B" + "fontCharacter": "\\E071" }, "_settings": { - "fontCharacter": "\\E06C" + "fontCharacter": "\\E072" }, "_shell_light": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E073", "fontColor": "#455155" }, "_shell": { - "fontCharacter": "\\E06D", + "fontCharacter": "\\E073", "fontColor": "#4d5a5e" }, "_slim_light": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E074", "fontColor": "#cc6d2e" }, "_slim": { - "fontCharacter": "\\E06E", + "fontCharacter": "\\E074", "fontColor": "#e37933" }, "_smarty_light": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E075", "fontColor": "#b7b73b" }, "_smarty": { - "fontCharacter": "\\E06F", + "fontCharacter": "\\E075", "fontColor": "#cbcb41" }, "_spring_light": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E076", "fontColor": "#7fae42" }, "_spring": { - "fontCharacter": "\\E070", + "fontCharacter": "\\E076", "fontColor": "#8dc149" }, "_stylus_light": { - "fontCharacter": "\\E071", + "fontCharacter": "\\E077", "fontColor": "#7fae42" }, "_stylus": { - "fontCharacter": "\\E071", + "fontCharacter": "\\E077", "fontColor": "#8dc149" }, "_sublime_light": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E078", "fontColor": "#cc6d2e" }, "_sublime": { - "fontCharacter": "\\E072", + "fontCharacter": "\\E078", "fontColor": "#e37933" }, "_svg_light": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E079", "fontColor": "#9068b0" }, "_svg": { - "fontCharacter": "\\E073", + "fontCharacter": "\\E079", "fontColor": "#a074c4" }, "_swift_light": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E07A", "fontColor": "#cc6d2e" }, "_swift": { - "fontCharacter": "\\E074", + "fontCharacter": "\\E07A", "fontColor": "#e37933" }, "_terraform_light": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E07B", "fontColor": "#9068b0" }, "_terraform": { - "fontCharacter": "\\E075", + "fontCharacter": "\\E07B", "fontColor": "#a074c4" }, "_tex_light": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E07C", "fontColor": "#bfc2c1" }, "_tex": { - "fontCharacter": "\\E076", + "fontCharacter": "\\E07C", "fontColor": "#d4d7d6" }, "_time-cop": { - "fontCharacter": "\\E077" + "fontCharacter": "\\E07D" }, "_todo": { - "fontCharacter": "\\E078" + "fontCharacter": "\\E07E" }, "_twig_light": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E07F", "fontColor": "#7fae42" }, "_twig": { - "fontCharacter": "\\E079", + "fontCharacter": "\\E07F", "fontColor": "#8dc149" }, "_typescript_light": { - "fontCharacter": "\\E07A", - "fontColor": "#498ba7" + "fontCharacter": "\\E080", + "fontColor": "#b7b73b" }, "_typescript": { - "fontCharacter": "\\E07A", - "fontColor": "#519aba" + "fontCharacter": "\\E080", + "fontColor": "#cbcb41" }, "_vala_light": { - "fontCharacter": "\\E07B", + "fontCharacter": "\\E081", "fontColor": "#627379" }, "_vala": { - "fontCharacter": "\\E07B", + "fontCharacter": "\\E081", "fontColor": "#6d8086" }, "_video_light": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E082", "fontColor": "#dd4b78" }, "_video": { - "fontCharacter": "\\E07C", + "fontCharacter": "\\E082", "fontColor": "#f55385" }, "_vue_light": { - "fontCharacter": "\\E07D", + "fontCharacter": "\\E083", "fontColor": "#7fae42" }, "_vue": { - "fontCharacter": "\\E07D", + "fontCharacter": "\\E083", "fontColor": "#8dc149" }, + "_webpack_light": { + "fontCharacter": "\\E084", + "fontColor": "#498ba7" + }, + "_webpack": { + "fontCharacter": "\\E084", + "fontColor": "#519aba" + }, + "_wgt_light": { + "fontCharacter": "\\E085", + "fontColor": "#498ba7" + }, + "_wgt": { + "fontCharacter": "\\E085", + "fontColor": "#519aba" + }, "_windows_light": { - "fontCharacter": "\\E07E", + "fontCharacter": "\\E086", "fontColor": "#498ba7" }, "_windows": { - "fontCharacter": "\\E07E", + "fontCharacter": "\\E086", "fontColor": "#519aba" }, "_word_light": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E087", "fontColor": "#498ba7" }, "_word": { - "fontCharacter": "\\E07F", + "fontCharacter": "\\E087", "fontColor": "#519aba" }, "_xls_light": { - "fontCharacter": "\\E080", + "fontCharacter": "\\E088", "fontColor": "#7fae42" }, "_xls": { - "fontCharacter": "\\E080", + "fontCharacter": "\\E088", "fontColor": "#8dc149" }, "_xml_light": { - "fontCharacter": "\\E081", + "fontCharacter": "\\E089", "fontColor": "#cc6d2e" }, "_xml": { - "fontCharacter": "\\E081", + "fontCharacter": "\\E089", "fontColor": "#e37933" }, "_yarn_light": { - "fontCharacter": "\\E082", + "fontCharacter": "\\E08A", "fontColor": "#498ba7" }, "_yarn": { - "fontCharacter": "\\E082", + "fontCharacter": "\\E08A", "fontColor": "#519aba" }, "_yml_light": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#9068b0" }, "_yml": { - "fontCharacter": "\\E083", + "fontCharacter": "\\E08B", "fontColor": "#a074c4" }, "_zip_light": { - "fontCharacter": "\\E084", + "fontCharacter": "\\E08C", "fontColor": "#627379" }, "_zip": { - "fontCharacter": "\\E084", + "fontCharacter": "\\E08C", "fontColor": "#6d8086" } }, @@ -976,6 +1045,9 @@ "config": "_config", "cfg": "_config", "conf": "_config", + "cr": "_crystal", + "ecr": "_crystal_embedded", + "slang": "_crystal_embedded", "cson": "_json", "css.map": "_css", "sss": "_css", @@ -1002,9 +1074,12 @@ "gradle": "_gradle", "gsp": "_grails", "haml": "_haml", - "hjs": "_mustache", "hs": "_haskell", "lhs": "_haskell", + "hx": "_haxe", + "hxs": "_haxe", + "hxp": "_haxe", + "hxml": "_haxe", "class": "_java", "classpath": "_java", "js.map": "_javascript", @@ -1032,22 +1107,26 @@ "mli": "_ocaml", "cmx": "_ocaml", "cmxa": "_ocaml", + "odata": "_odata", "php.inc": "_php", "pug": "_pug", "pp": "_puppet", "epp": "_puppet", "cjsx": "_react", - "erb.html": "_ruby", - "html.erb": "_ruby", + "erb": "_html_erb", + "erb.html": "_html_erb", + "html.erb": "_html_erb", "sass": "_sass", "springbeans": "_spring", "slim": "_slim", "smarty.tpl": "_smarty", "sbt": "_sbt", "scala": "_scala", + "sol": "_ethereum", "styl": "_stylus", "tf": "_terraform", "tf.json": "_terraform", + "tfvars": "_terraform", "tex": "_tex", "sty": "_tex", "dtx": "_tex", @@ -1055,11 +1134,13 @@ "txt": "_default", "toml": "_config", "twig": "_twig", + "spec.ts": "_typescript", "vala": "_vala", "vapi": "_vala", "vue": "_vue", "jar": "_zip", "zip": "_zip", + "wgt": "_wgt", "ai": "_illustrator", "psd": "_photoshop", "pdf": "_pdf", @@ -1074,7 +1155,6 @@ "pxm": "_image", "svg": "_svg", "svgx": "_image", - "webp": "_image", "sublime-project": "_sublime", "sublime-workspace": "_sublime", "component": "_salesforce", @@ -1128,6 +1208,7 @@ "bower.json": "_bower", "docker-healthcheck": "_docker", "docker-compose.yml": "_docker", + "docker-compose.yaml": "_docker", "firebase.json": "_firebase", "geckodriver": "_firefox", "gruntfile.js": "_grunt", @@ -1140,6 +1221,8 @@ "sass-lint.yml": "_sass", "yarn.clean": "_yarn", "yarn.lock": "_yarn", + "webpack.config.js": "_webpack", + "webpack.config.build.js": "_webpack", "license": "_license", "licence": "_license", "copying": "_license", @@ -1154,6 +1237,7 @@ }, "languageIds": { "bat": "_windows", + "clojure": "_clojure", "coffeescript": "_coffee", "c": "_c", "cpp": "_cpp", @@ -1204,6 +1288,9 @@ "config": "_config_light", "cfg": "_config_light", "conf": "_config_light", + "cr": "_crystal_light", + "ecr": "_crystal_embedded_light", + "slang": "_crystal_embedded_light", "cson": "_json_light", "css.map": "_css_light", "sss": "_css_light", @@ -1230,9 +1317,12 @@ "gradle": "_gradle_light", "gsp": "_grails_light", "haml": "_haml_light", - "hjs": "_mustache_light", "hs": "_haskell_light", "lhs": "_haskell_light", + "hx": "_haxe_light", + "hxs": "_haxe_light", + "hxp": "_haxe_light", + "hxml": "_haxe_light", "class": "_java_light", "classpath": "_java_light", "js.map": "_javascript_light", @@ -1260,22 +1350,26 @@ "mli": "_ocaml_light", "cmx": "_ocaml_light", "cmxa": "_ocaml_light", + "odata": "_odata_light", "php.inc": "_php_light", "pug": "_pug_light", "pp": "_puppet_light", "epp": "_puppet_light", "cjsx": "_react_light", - "erb.html": "_ruby_light", - "html.erb": "_ruby_light", + "erb": "_html_erb_light", + "erb.html": "_html_erb_light", + "html.erb": "_html_erb_light", "sass": "_sass_light", "springbeans": "_spring_light", "slim": "_slim_light", "smarty.tpl": "_smarty_light", "sbt": "_sbt_light", "scala": "_scala_light", + "sol": "_ethereum_light", "styl": "_stylus_light", "tf": "_terraform_light", "tf.json": "_terraform_light", + "tfvars": "_terraform_light", "tex": "_tex_light", "sty": "_tex_light", "dtx": "_tex_light", @@ -1283,11 +1377,13 @@ "txt": "_default_light", "toml": "_config_light", "twig": "_twig_light", + "spec.ts": "_typescript_light", "vala": "_vala_light", "vapi": "_vala_light", "vue": "_vue_light", "jar": "_zip_light", "zip": "_zip_light", + "wgt": "_wgt_light", "ai": "_illustrator_light", "psd": "_photoshop_light", "pdf": "_pdf_light", @@ -1342,6 +1438,7 @@ }, "languageIds": { "bat": "_windows_light", + "clojure": "_clojure_light", "coffeescript": "_coffee_light", "c": "_c_light", "cpp": "_cpp_light", @@ -1394,6 +1491,7 @@ "bower.json": "_bower_light", "docker-healthcheck": "_docker_light", "docker-compose.yml": "_docker_light", + "docker-compose.yaml": "_docker_light", "firebase.json": "_firebase_light", "geckodriver": "_firefox_light", "gruntfile.js": "_grunt_light", @@ -1406,6 +1504,8 @@ "sass-lint.yml": "_sass_light", "yarn.clean": "_yarn_light", "yarn.lock": "_yarn_light", + "webpack.config.js": "_webpack_light", + "webpack.config.build.js": "_webpack_light", "license": "_license_light", "licence": "_license_light", "copying": "_license_light", @@ -1418,5 +1518,5 @@ "npm-debug.log": "_npm_ignored_light" } }, - "version": "https://github.com/jesseweed/seti-ui/commit/c44b29c7a5b2f189fccfd58ceea02b117678caa9" + "version": "https://github.com/jesseweed/seti-ui/commit/188dda34a56b9555c7d363771264c24f4693983d" } \ No newline at end of file diff --git a/extensions/typescript/OSSREADME.json b/extensions/typescript/OSSREADME.json index d9220b9f35b..160da003cd8 100644 --- a/extensions/typescript/OSSREADME.json +++ b/extensions/typescript/OSSREADME.json @@ -4,70 +4,4 @@ "license": "MIT", "repositoryURL": "https://github.com/Microsoft/TypeScript-TmLanguage", "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." -}, -{ - // We override the license that gets discovered at - // https://github.com/Microsoft/TypeScript/blob/master/LICENSE.txt - // because it does not contain a Copyright statement - "isLicense": true, - "name": "typescript", - "licenseDetail": [ - "Copyright (c) Microsoft Corporation. All rights reserved.", - "", - "Apache License", - "", - "Version 2.0, January 2004", - "", - "http://www.apache.org/licenses/", - "", - "TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION", - "", - "1. Definitions.", - "", - "\"License\" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.", - "", - "\"Licensor\" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.", - "", - "\"Legal Entity\" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, \"control\" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.", - "", - "\"You\" (or \"Your\") shall mean an individual or Legal Entity exercising permissions granted by this License.", - "", - "\"Source\" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.", - "", - "\"Object\" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.", - "", - "\"Work\" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).", - "", - "\"Derivative Works\" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.", - "", - "\"Contribution\" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, \"submitted\" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as \"Not a Contribution.\"", - "", - "\"Contributor\" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.", - "", - "2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.", - "", - "3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.", - "", - "4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:", - "", - "You must give any other recipients of the Work or Derivative Works a copy of this License; and", - "", - "You must cause any modified files to carry prominent notices stating that You changed the files; and", - "", - "You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and", - "", - "If the Work includes a \"NOTICE\" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.", - "", - "5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.", - "", - "6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.", - "", - "7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.", - "", - "8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.", - "", - "9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.", - "", - "END OF TERMS AND CONDITIONS" - ] }] diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index 1997b63f7f7..6e182cef3d1 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -13,8 +13,8 @@ }, "dependencies": { "semver": "4.3.6", - "vscode-extension-telemetry": "0.0.8", - "vscode-nls": "2.0.1" + "vscode-extension-telemetry": "^0.0.8", + "vscode-nls": "^2.0.2" }, "devDependencies": { "@types/node": "8.0.33", @@ -548,7 +548,7 @@ "jsonValidation": [ { "fileMatch": "tsconfig.json", - "url": "http://json.schemastore.org/tsconfig" + "url": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json" }, { "fileMatch": "tsconfig.json", @@ -556,7 +556,7 @@ }, { "fileMatch": "tsconfig.*.json", - "url": "http://json.schemastore.org/tsconfig" + "url": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json" }, { "fileMatch": "tsconfig.*.json", @@ -564,7 +564,7 @@ }, { "fileMatch": "typings.json", - "url": "http://json.schemastore.org/typings" + "url": "https://schemastore.azurewebsites.net/schemas/json/typings.json" } ], "taskDefinitions": [ diff --git a/extensions/typescript/src/extension.ts b/extensions/typescript/src/extension.ts index a6e1673b2cc..f54c8d99eda 100644 --- a/extensions/typescript/src/extension.ts +++ b/extensions/typescript/src/extension.ts @@ -17,6 +17,7 @@ import { standardLanguageDescriptions } from './utils/languageDescription'; import ManagedFileContextManager from './utils/managedFileContext'; import { lazy, Lazy } from './utils/lazy'; import * as fileSchemes from './utils/fileSchemes'; +import LogDirectoryProvider from './utils/logDirectoryProvider'; export function activate( context: vscode.ExtensionContext @@ -61,7 +62,13 @@ function createLazyClientHost( commandManager: CommandManager ): Lazy { return lazy(() => { - const clientHost = new TypeScriptServiceClientHost(standardLanguageDescriptions, context.workspaceState, plugins, commandManager); + const logDirectoryProvider = new LogDirectoryProvider(context); + const clientHost = new TypeScriptServiceClientHost( + standardLanguageDescriptions, + context.workspaceState, + plugins, + commandManager, + logDirectoryProvider); context.subscriptions.push(clientHost); const host = clientHost; clientHost.serviceClient.onReady().then(() => { diff --git a/extensions/typescript/src/features/baseCodeLensProvider.ts b/extensions/typescript/src/features/baseCodeLensProvider.ts index 896cb9b3d06..fb43d148838 100644 --- a/extensions/typescript/src/features/baseCodeLensProvider.ts +++ b/extensions/typescript/src/features/baseCodeLensProvider.ts @@ -19,12 +19,29 @@ export class ReferencesCodeLens extends CodeLens { } } +export class CachedNavTreeResponse { + response?: Promise; + version: number = -1; + document: string = ''; + + matches(document: TextDocument): boolean { + return this.version === document.version && this.document === document.uri.toString(); + } + + update(document: TextDocument, response: Promise) { + this.response = response; + this.version = document.version; + this.document = document.uri.toString(); + } +} + export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider { private enabled: boolean = true; private onDidChangeCodeLensesEmitter = new EventEmitter(); public constructor( - protected client: ITypeScriptServiceClient + protected client: ITypeScriptServiceClient, + private cachedResponse: CachedNavTreeResponse ) { } public get onDidChangeCodeLenses(): Event { @@ -48,10 +65,15 @@ export abstract class TypeScriptBaseCodeLensProvider implements CodeLensProvider return []; } try { - const response = await this.client.execute('navtree', { file: filepath }, token); + if (!this.cachedResponse.matches(document)) { + this.cachedResponse.update(document, this.client.execute('navtree', { file: filepath }, token)); + } + + const response = await this.cachedResponse.response; if (!response) { return []; } + const tree = response.body; const referenceableSpans: Range[] = []; if (tree && tree.childItems) { diff --git a/extensions/typescript/src/features/completionItemProvider.ts b/extensions/typescript/src/features/completionItemProvider.ts index f3800d5cd10..775fcb40518 100644 --- a/extensions/typescript/src/features/completionItemProvider.ts +++ b/extensions/typescript/src/features/completionItemProvider.ts @@ -3,13 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, TextEdit, Range, SnippetString, workspace, ProviderResult, CompletionContext, Uri, MarkdownString, window, QuickPickItem } from 'vscode'; +import { CompletionItem, TextDocument, Position, CompletionItemKind, CompletionItemProvider, CancellationToken, Range, SnippetString, workspace, CompletionContext, Uri, MarkdownString, window, QuickPickItem } from 'vscode'; import { ITypeScriptServiceClient } from '../typescriptService'; import TypingsStatus from '../utils/typingsStatus'; +import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { CompletionEntry, CompletionsRequestArgs, CompletionDetailsRequestArgs, CompletionEntryDetails, CodeAction } from '../protocol'; import * as Previewer from '../utils/previewer'; import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert'; @@ -18,37 +18,53 @@ import { applyCodeAction } from '../utils/codeAction'; import * as languageModeIds from '../utils/languageModeIds'; import { CommandManager, Command } from '../utils/commandManager'; -let localize = nls.loadMessageBundle(); +const localize = nls.loadMessageBundle(); class MyCompletionItem extends CompletionItem { public readonly source: string | undefined; + public readonly useCodeSnippet: boolean; + constructor( public readonly position: Position, public readonly document: TextDocument, - entry: CompletionEntry, + entry: Proto.CompletionEntry, enableDotCompletions: boolean, - public readonly useCodeSnippetsOnMethodSuggest: boolean + useCodeSnippetsOnMethodSuggest: boolean ) { super(entry.name); this.source = entry.source; - this.sortText = entry.sortText; + + if (entry.isRecommended) { + // Make sure isRecommended property always comes first + // https://github.com/Microsoft/vscode/issues/40325 + this.sortText = '\0' + entry.sortText; + } else if (entry.source) { + // De-prioritze auto-imports + // https://github.com/Microsoft/vscode/issues/40311 + this.sortText = '\uffff' + entry.sortText; + } else { + this.sortText = entry.sortText; + } + this.kind = MyCompletionItem.convertKind(entry.kind); this.position = position; this.commitCharacters = MyCompletionItem.getCommitCharacters(enableDotCompletions, !useCodeSnippetsOnMethodSuggest, entry.kind); + this.useCodeSnippet = useCodeSnippetsOnMethodSuggest && (this.kind === CompletionItemKind.Function || this.kind === CompletionItemKind.Method); if (entry.replacementSpan) { - let span: protocol.TextSpan = entry.replacementSpan; - // The indexing for the range returned by the server uses 1-based indexing. - // We convert to 0-based indexing. - this.textEdit = TextEdit.replace(tsTextSpanToVsRange(span), entry.name); - } else { + this.range = tsTextSpanToVsRange(entry.replacementSpan); + } + } + + public resolve(): void { + if (!this.range) { // Try getting longer, prefix based range for completions that span words - const wordRange = document.getWordRangeAtPosition(position); - const text = document.getText(new Range(position.line, Math.max(0, position.character - entry.name.length), position.line, position.character)).toLowerCase(); - const entryName = entry.name.toLowerCase(); + const wordRange = this.document.getWordRangeAtPosition(this.position); + const text = this.document.getText(new Range(this.position.line, Math.max(0, this.position.character - this.label.length), this.position.line, this.position.character)).toLowerCase(); + const entryName = this.label.toLowerCase(); for (let i = entryName.length; i >= 0; --i) { - if (text.endsWith(entryName.substr(0, i)) && (!wordRange || wordRange.start.character > position.character - i)) { - this.range = new Range(position.line, Math.max(0, position.character - i), position.line, position.character); + if (text.endsWith(entryName.substr(0, i)) && (!wordRange || wordRange.start.character > this.position.character - i)) { + this.range = new Range(this.position.line, Math.max(0, this.position.character - i), this.position.line, this.position.character); break; } } @@ -98,7 +114,11 @@ class MyCompletionItem extends CompletionItem { return CompletionItemKind.Property; } - private static getCommitCharacters(enableDotCompletions: boolean, enableCallCompletions: boolean, kind: string): string[] | undefined { + private static getCommitCharacters( + enableDotCompletions: boolean, + enableCallCompletions: boolean, + kind: string + ): string[] | undefined { switch (kind) { case PConst.Kind.memberGetAccessor: case PConst.Kind.memberSetAccessor: @@ -134,7 +154,7 @@ class ApplyCompletionCodeActionCommand implements Command { private readonly client: ITypeScriptServiceClient ) { } - public async execute(_file: string, codeActions: CodeAction[]): Promise { + public async execute(_file: string, codeActions: Proto.CodeAction[]): Promise { if (codeActions.length === 0) { return true; } @@ -181,7 +201,6 @@ namespace Configuration { export const nameSuggestions = 'nameSuggestions'; export const quickSuggestionsForPaths = 'quickSuggestionsForPaths'; export const autoImportSuggestions = 'autoImportSuggestions.enabled'; - } export default class TypeScriptCompletionItemProvider implements CompletionItemProvider { @@ -250,7 +269,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP } try { - const args: CompletionsRequestArgs = { + const args: Proto.CompletionsRequestArgs = { ...vsPositionToTsFileLocation(file, position), includeExternalModuleExports: config.autoImportSuggestions }; @@ -305,77 +324,86 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP } } - public resolveCompletionItem(item: CompletionItem, token: CancellationToken): ProviderResult { + public async resolveCompletionItem( + item: CompletionItem, + token: CancellationToken + ): Promise { if (!(item instanceof MyCompletionItem)) { - return null; + return undefined; } const filepath = this.client.normalizePath(item.document.uri); if (!filepath) { - return null; + return undefined; } - const args: CompletionDetailsRequestArgs = { + + item.resolve(); + + const args: Proto.CompletionDetailsRequestArgs = { ...vsPositionToTsFileLocation(filepath, item.position), entryNames: [ item.source ? { name: item.label, source: item.source } : item.label ] }; - return this.client.execute('completionEntryDetails', args, token).then((response) => { - const details = response.body; - if (!details || !details.length || !details[0]) { - return item; - } - const detail = details[0]; - item.detail = Previewer.plain(detail.displayParts); - const documentation = new MarkdownString(); - if (detail.source) { - let importPath = `'${Previewer.plain(detail.source)}'`; - if (this.client.apiVersion.has260Features() && !this.client.apiVersion.has262Features()) { - // Try to resolve the real import name that will be added - if (detail.codeActions && detail.codeActions[0]) { - const action = detail.codeActions[0]; - if (action.changes[0] && action.changes[0].textChanges[0]) { - const textChange = action.changes[0].textChanges[0]; - const matchedImport = textChange.newText.match(/(['"])(.+?)\1/); - if (matchedImport) { - importPath = matchedImport[0]; - item.detail += ` — from ${matchedImport[0]}`; - } + let response: Proto.CompletionDetailsResponse; + try { + response = await this.client.execute('completionEntryDetails', args, token); + } catch { + return item; + } + + const details = response.body; + if (!details || !details.length || !details[0]) { + return item; + } + const detail = details[0]; + item.detail = Previewer.plain(detail.displayParts); + const documentation = new MarkdownString(); + if (detail.source) { + let importPath = `'${Previewer.plain(detail.source)}'`; + + if (this.client.apiVersion.has260Features() && !this.client.apiVersion.has262Features()) { + // Try to resolve the real import name that will be added + if (detail.codeActions && detail.codeActions[0]) { + const action = detail.codeActions[0]; + if (action.changes[0] && action.changes[0].textChanges[0]) { + const textChange = action.changes[0].textChanges[0]; + const matchedImport = textChange.newText.match(/(['"])(.+?)\1/); + if (matchedImport) { + importPath = matchedImport[0]; + item.detail += ` — from ${matchedImport[0]}`; } } - documentation.appendMarkdown(localize('autoImportLabel', 'Auto import from {0}', importPath)); - } else { - const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath); - item.detail = `${autoImportLabel}\n${item.detail}`; } - documentation.appendMarkdown('\n\n'); + documentation.appendMarkdown(localize('autoImportLabel', 'Auto import from {0}', importPath)); + } else { + const autoImportLabel = localize('autoImportLabel', 'Auto import from {0}', importPath); + item.detail = `${autoImportLabel}\n${item.detail}`; } + documentation.appendMarkdown('\n\n'); + } - Previewer.addmarkdownDocumentation(documentation, detail.documentation, detail.tags); - item.documentation = documentation; + Previewer.addMarkdownDocumentation(documentation, detail.documentation, detail.tags); + item.documentation = documentation; - if (detail.codeActions && detail.codeActions.length) { - item.command = { - title: '', - command: ApplyCompletionCodeActionCommand.ID, - arguments: [filepath, detail.codeActions] - }; + if (detail.codeActions && detail.codeActions.length) { + item.command = { + title: '', + command: ApplyCompletionCodeActionCommand.ID, + arguments: [filepath, detail.codeActions] + }; + } + + if (detail && item.useCodeSnippet) { + const shouldCompleteFunction = await this.isValidFunctionCompletionContext(filepath, item.position); + if (shouldCompleteFunction) { + item.insertText = this.snippetForFunctionCall(detail); } - - if (detail && item.useCodeSnippetsOnMethodSuggest && (item.kind === CompletionItemKind.Function || item.kind === CompletionItemKind.Method)) { - return this.isValidFunctionCompletionContext(filepath, item.position).then(shouldCompleteFunction => { - if (shouldCompleteFunction) { - item.insertText = this.snippetForFunctionCall(detail); - } - return item; - }); - } - return item; - }, () => { - return item; - }); + } + + return item; } private async isValidFunctionCompletionContext(filepath: string, position: Position): Promise { @@ -398,9 +426,14 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP } } - private snippetForFunctionCall(detail: CompletionEntryDetails): SnippetString { - const suggestionArgumentNames: string[] = []; - let hasOptionalParemeters = false; + private snippetForFunctionCall(detail: Proto.CompletionEntryDetails): SnippetString { + let hasOptionalParameters = false; + let hasAddedParameters = false; + + const snippet = new SnippetString(); + snippet.appendText(detail.name); + snippet.appendText('('); + let parenCount = 0; let i = 0; for (; i < detail.displayParts.length; ++i) { @@ -411,9 +444,13 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP // Skip optional parameters const nameIsFollowedByOptionalIndicator = next && next.text === '?'; if (!nameIsFollowedByOptionalIndicator) { - suggestionArgumentNames.push(`\${${i + 1}:${part.text}}`); + if (hasAddedParameters) { + snippet.appendText(', '); + } + hasAddedParameters = true; + snippet.appendPlaceholder(part.text); } - hasOptionalParemeters = hasOptionalParemeters || nameIsFollowedByOptionalIndicator; + hasOptionalParameters = hasOptionalParameters || nameIsFollowedByOptionalIndicator; } else if (part.kind === 'punctuation') { if (part.text === '(') { ++parenCount; @@ -421,13 +458,17 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP --parenCount; } else if (part.text === '...' && parenCount === 1) { // Found rest parmeter. Do not fill in any further arguments - hasOptionalParemeters = true; + hasOptionalParameters = true; break; } } } - const codeSnippet = `${detail.name}(${suggestionArgumentNames.join(', ')}${hasOptionalParemeters ? '${' + i + '}' : ''})$0`; - return new SnippetString(codeSnippet); + if (hasOptionalParameters) { + snippet.appendTabstop(); + } + snippet.appendText(')'); + snippet.appendTabstop(0); + return snippet; } private getConfiguration(resource: Uri): Configuration { diff --git a/extensions/typescript/src/features/definitionProviderBase.ts b/extensions/typescript/src/features/definitionProviderBase.ts index 47be5791b6b..a931905bb4d 100644 --- a/extensions/typescript/src/features/definitionProviderBase.ts +++ b/extensions/typescript/src/features/definitionProviderBase.ts @@ -11,7 +11,8 @@ import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/conver export default class TypeScriptDefinitionProviderBase { constructor( - private client: ITypeScriptServiceClient) { } + private client: ITypeScriptServiceClient + ) { } protected async getSymbolLocations( definitionType: 'definition' | 'implementation' | 'typeDefinition', diff --git a/extensions/typescript/src/features/diagnostics.ts b/extensions/typescript/src/features/diagnostics.ts index 9bbc03e035b..526ed6f8ce4 100644 --- a/extensions/typescript/src/features/diagnostics.ts +++ b/extensions/typescript/src/features/diagnostics.ts @@ -6,10 +6,35 @@ import { Diagnostic, DiagnosticCollection, languages, Uri } from 'vscode'; import { ITypeScriptServiceClient } from '../typescriptService'; +class DiagnosticSet { + private _map: ObjectMap = Object.create(null); + + public set( + file: Uri, + diagnostics: Diagnostic[] + ) { + this._map[this.key(file)] = diagnostics; + } + + public get( + file: Uri + ): Diagnostic[] { + return this._map[this.key(file)] || []; + } + + public clear(): void { + this._map = Object.create(null); + } + + private key(file: Uri): string { + return file.toString(true); + } +} + export default class DiagnosticsManager { - private syntaxDiagnostics: ObjectMap; - private semanticDiagnostics: ObjectMap; + private readonly syntaxDiagnostics: DiagnosticSet; + private readonly semanticDiagnostics: DiagnosticSet; private readonly currentDiagnostics: DiagnosticCollection; private _validate: boolean = true; @@ -17,8 +42,8 @@ export default class DiagnosticsManager { language: string, private readonly client: ITypeScriptServiceClient ) { - this.syntaxDiagnostics = Object.create(null); - this.semanticDiagnostics = Object.create(null); + this.syntaxDiagnostics = new DiagnosticSet(); + this.semanticDiagnostics = new DiagnosticSet(); this.currentDiagnostics = languages.createDiagnosticCollection(language); } @@ -28,8 +53,8 @@ export default class DiagnosticsManager { public reInitialize(): void { this.currentDiagnostics.clear(); - this.syntaxDiagnostics = Object.create(null); - this.semanticDiagnostics = Object.create(null); + this.syntaxDiagnostics.clear(); + this.semanticDiagnostics.clear(); } public set validate(value: boolean) { @@ -43,12 +68,12 @@ export default class DiagnosticsManager { } public syntaxDiagnosticsReceived(file: Uri, syntaxDiagnostics: Diagnostic[]): void { - this.syntaxDiagnostics[this.key(file)] = syntaxDiagnostics; + this.syntaxDiagnostics.set(file, syntaxDiagnostics); this.updateCurrentDiagnostics(file); } public semanticDiagnosticsReceived(file: Uri, semanticDiagnostics: Diagnostic[]): void { - this.semanticDiagnostics[this.key(file)] = semanticDiagnostics; + this.semanticDiagnostics.set(file, semanticDiagnostics); this.updateCurrentDiagnostics(file); } @@ -60,17 +85,13 @@ export default class DiagnosticsManager { this.currentDiagnostics.delete(this.client.asUrl(file)); } - private key(file: Uri): string { - return file.toString(true); - } - private updateCurrentDiagnostics(file: Uri) { if (!this._validate) { return; } - const semanticDiagnostics = this.semanticDiagnostics[this.key(file)] || []; - const syntaxDiagnostics = this.syntaxDiagnostics[this.key(file)] || []; + const semanticDiagnostics = this.semanticDiagnostics.get(file); + const syntaxDiagnostics = this.syntaxDiagnostics.get(file); this.currentDiagnostics.set(file, semanticDiagnostics.concat(syntaxDiagnostics)); } } \ No newline at end of file diff --git a/extensions/typescript/src/features/directiveCommentCompletionProvider.ts b/extensions/typescript/src/features/directiveCommentCompletionProvider.ts index 2253a2acb27..60ae5cf14c0 100644 --- a/extensions/typescript/src/features/directiveCommentCompletionProvider.ts +++ b/extensions/typescript/src/features/directiveCommentCompletionProvider.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Position, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, ProviderResult, Range } from 'vscode'; +import { Position, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, Range } from 'vscode'; import { ITypeScriptServiceClient } from '../typescriptService'; @@ -39,7 +39,11 @@ export default class DirectiveCommentCompletionProvider implements CompletionIte private client: ITypeScriptServiceClient, ) { } - public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult { + public provideCompletionItems( + document: TextDocument, + position: Position, + _token: CancellationToken + ): CompletionItem[] { if (!this.client.apiVersion.has230Features()) { return []; } @@ -63,7 +67,10 @@ export default class DirectiveCommentCompletionProvider implements CompletionIte return []; } - public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) { + public resolveCompletionItem( + item: CompletionItem, + _token: CancellationToken + ) { return item; } } \ No newline at end of file diff --git a/extensions/typescript/src/features/documentHighlightProvider.ts b/extensions/typescript/src/features/documentHighlightProvider.ts index 7c62fcd1fc8..d155f78f187 100644 --- a/extensions/typescript/src/features/documentHighlightProvider.ts +++ b/extensions/typescript/src/features/documentHighlightProvider.ts @@ -8,10 +8,12 @@ import { DocumentHighlightProvider, DocumentHighlight, DocumentHighlightKind, Te import { ITypeScriptServiceClient } from '../typescriptService'; import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert'; +const stringDelimiters = ['"', '\'', '`']; export default class TypeScriptDocumentHighlightProvider implements DocumentHighlightProvider { public constructor( - private client: ITypeScriptServiceClient) { } + private client: ITypeScriptServiceClient + ) { } public async provideDocumentHighlights( resource: TextDocument, @@ -34,7 +36,6 @@ export default class TypeScriptDocumentHighlightProvider implements DocumentHigh if (this.client.apiVersion.has213Features() && firstOccurrence.start.offset > 1) { // Check to see if contents around first occurrence are string delimiters const contents = resource.getText(new Range(firstOccurrence.start.line - 1, firstOccurrence.start.offset - 1 - 1, firstOccurrence.end.line - 1, firstOccurrence.end.offset - 1 + 1)); - const stringDelimiters = ['"', '\'', '`']; if (contents && contents.length > 2 && stringDelimiters.indexOf(contents[0]) >= 0 && contents[0] === contents[contents.length - 1]) { return []; } diff --git a/extensions/typescript/src/features/hoverProvider.ts b/extensions/typescript/src/features/hoverProvider.ts index 23e62e3f8c2..0a3a99c8ef3 100644 --- a/extensions/typescript/src/features/hoverProvider.ts +++ b/extensions/typescript/src/features/hoverProvider.ts @@ -13,9 +13,14 @@ import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/conver export default class TypeScriptHoverProvider implements HoverProvider { public constructor( - private client: ITypeScriptServiceClient) { } + private client: ITypeScriptServiceClient + ) { } - public async provideHover(document: TextDocument, position: Position, token: CancellationToken): Promise { + public async provideHover( + document: TextDocument, + position: Position, + token: CancellationToken + ): Promise { const filepath = this.client.normalizePath(document.uri); if (!filepath) { return undefined; @@ -35,7 +40,9 @@ export default class TypeScriptHoverProvider implements HoverProvider { return undefined; } - private static getContents(data: Proto.QuickInfoResponseBody) { + private static getContents( + data: Proto.QuickInfoResponseBody + ) { const parts = []; if (data.displayString) { diff --git a/extensions/typescript/src/features/implementationsCodeLensProvider.ts b/extensions/typescript/src/features/implementationsCodeLensProvider.ts index b9d4c74f422..1d44d15ed58 100644 --- a/extensions/typescript/src/features/implementationsCodeLensProvider.ts +++ b/extensions/typescript/src/features/implementationsCodeLensProvider.ts @@ -7,7 +7,7 @@ import { CodeLens, CancellationToken, TextDocument, Range, Location, workspace } import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens } from './baseCodeLensProvider'; +import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens, CachedNavTreeResponse } from './baseCodeLensProvider'; import { ITypeScriptServiceClient } from '../typescriptService'; import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert'; @@ -17,9 +17,10 @@ const localize = nls.loadMessageBundle(); export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { public constructor( client: ITypeScriptServiceClient, - private readonly language: string + private readonly language: string, + cachedResponse: CachedNavTreeResponse ) { - super(client); + super(client, cachedResponse); } public updateConfiguration(): void { diff --git a/extensions/typescript/src/features/jsDocCompletionProvider.ts b/extensions/typescript/src/features/jsDocCompletionProvider.ts index 4d520563f22..ab515a9a074 100644 --- a/extensions/typescript/src/features/jsDocCompletionProvider.ts +++ b/extensions/typescript/src/features/jsDocCompletionProvider.ts @@ -182,7 +182,7 @@ class TryCompleteJsDocCommand implements Command { template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)\s*$/gm, (_param, type, post) => { let out = '* @param '; if (type === ' {any}' || type === ' {*}') { - out += `{*\$\{${snippetIndex++}\}} `; + out += `{\$\{${snippetIndex++}:*\}} `; } else if (type) { out += type + ' '; } diff --git a/extensions/typescript/src/features/quickFixProvider.ts b/extensions/typescript/src/features/quickFixProvider.ts index 9293818f1b0..cbbc910e483 100644 --- a/extensions/typescript/src/features/quickFixProvider.ts +++ b/extensions/typescript/src/features/quickFixProvider.ts @@ -42,17 +42,7 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv commandManager.register(new ApplyCodeActionCommand(client)); } - public provideCodeActions( - _document: vscode.TextDocument, - _range: vscode.Range, - _context: vscode.CodeActionContext, - _token: vscode.CancellationToken - ) { - // Uses provideCodeActions2 instead - return []; - } - - public async provideCodeActions2( + public async provideCodeActions( document: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, @@ -103,16 +93,15 @@ export default class TypeScriptQuickFixProvider implements vscode.CodeActionProv .filter(code => supportedActions[code])); } - private getCommandForAction(action: Proto.CodeAction): vscode.CodeAction { - return { - title: action.description, - edits: getEditForCodeAction(this.client, action), - command: action.commands ? { + private getCommandForAction(tsAction: Proto.CodeAction): vscode.CodeAction { + const codeAction = new vscode.CodeAction(tsAction.description, getEditForCodeAction(this.client, tsAction)); + if (tsAction.commands) { + codeAction.command = { command: ApplyCodeActionCommand.ID, - arguments: [action], - title: action.description - } : undefined, - diagnostics: [] - }; + arguments: [tsAction], + title: tsAction.description + }; + } + return codeAction; } -} \ No newline at end of file +} diff --git a/extensions/typescript/src/features/refactorProvider.ts b/extensions/typescript/src/features/refactorProvider.ts index e2a6da69e7c..14dca20a4b5 100644 --- a/extensions/typescript/src/features/refactorProvider.ts +++ b/extensions/typescript/src/features/refactorProvider.ts @@ -19,7 +19,7 @@ class ApplyRefactoringCommand implements Command { constructor( private readonly client: ITypeScriptServiceClient, - private formattingOptionsManager: FormattingOptionsManager + private readonly formattingOptionsManager: FormattingOptionsManager ) { } public async execute( @@ -36,7 +36,6 @@ class ApplyRefactoringCommand implements Command { refactor, action }; - const response = await this.client.execute('getEditsForRefactor', args); if (!response || !response.body || !response.body.edits.length) { return false; @@ -106,12 +105,7 @@ export default class TypeScriptRefactorProvider implements vscode.CodeActionProv commandManager.register(new SelectRefactorCommand(doRefactoringCommand)); } - public async provideCodeActions() { - // Uses provideCodeActions2 instead - return []; - } - - public async provideCodeActions2( + public async provideCodeActions( document: vscode.TextDocument, _range: vscode.Range, _context: vscode.CodeActionContext, diff --git a/extensions/typescript/src/features/referencesCodeLensProvider.ts b/extensions/typescript/src/features/referencesCodeLensProvider.ts index d643c9c34b8..098a3469f3b 100644 --- a/extensions/typescript/src/features/referencesCodeLensProvider.ts +++ b/extensions/typescript/src/features/referencesCodeLensProvider.ts @@ -7,7 +7,7 @@ import { CodeLens, CancellationToken, TextDocument, Range, Location, workspace } import * as Proto from '../protocol'; import * as PConst from '../protocol.const'; -import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens } from './baseCodeLensProvider'; +import { TypeScriptBaseCodeLensProvider, ReferencesCodeLens, CachedNavTreeResponse } from './baseCodeLensProvider'; import { ITypeScriptServiceClient } from '../typescriptService'; import { tsTextSpanToVsRange, vsPositionToTsFileLocation } from '../utils/convert'; @@ -17,9 +17,10 @@ const localize = nls.loadMessageBundle(); export default class TypeScriptReferencesCodeLensProvider extends TypeScriptBaseCodeLensProvider { public constructor( client: ITypeScriptServiceClient, - private readonly language: string + private readonly language: string, + cachedResponse: CachedNavTreeResponse ) { - super(client); + super(client, cachedResponse); } public updateConfiguration(): void { diff --git a/extensions/typescript/src/features/workspaceSymbolProvider.ts b/extensions/typescript/src/features/workspaceSymbolProvider.ts index b344f5498ce..fb16385887f 100644 --- a/extensions/typescript/src/features/workspaceSymbolProvider.ts +++ b/extensions/typescript/src/features/workspaceSymbolProvider.ts @@ -62,22 +62,28 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo searchValue: search }; const response = await this.client.execute('navto', args, token); + if (!response.body) { + return []; + } + const result: SymbolInformation[] = []; - const data = response.body; - if (data) { - for (const item of data) { - if (!item.containerName && item.kind === 'alias') { - continue; - } - const range = tsTextSpanToVsRange(item); - let label = item.name; - if (item.kind === 'method' || item.kind === 'function') { - label += '()'; - } - result.push(new SymbolInformation(label, getSymbolKind(item), item.containerName || '', - new Location(this.client.asUrl(item.file), range))); + for (const item of response.body) { + if (!item.containerName && item.kind === 'alias') { + continue; } + const range = tsTextSpanToVsRange(item); + const label = TypeScriptWorkspaceSymbolProvider.getLabel(item); + result.push(new SymbolInformation(label, getSymbolKind(item), item.containerName || '', + new Location(this.client.asUrl(item.file), range))); } return result; } + + private static getLabel(item: Proto.NavtoItem) { + let label = item.name; + if (item.kind === 'method' || item.kind === 'function') { + label += '()'; + } + return label; + } } \ No newline at end of file diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 82dd3b9326d..bef75a6cb38 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -37,6 +37,8 @@ import { CommandManager } from './utils/commandManager'; import DiagnosticsManager from './features/diagnostics'; import { LanguageDescription } from './utils/languageDescription'; import * as fileSchemes from './utils/fileSchemes'; +import { CachedNavTreeResponse } from './features/baseCodeLensProvider'; +import LogDirectoryProvider from './utils/logDirectoryProvider'; const validateSetting = 'validate.enable'; @@ -148,12 +150,14 @@ class LanguageProvider { this.disposables.push(languages.registerCodeActionsProvider(selector, new (await import('./features/refactorProvider')).default(client, this.formattingOptionsManager, commandManager))); this.registerVersionDependentProviders(); - const referenceCodeLensProvider = new (await import('./features/referencesCodeLensProvider')).default(client, this.description.id); + const cachedResponse = new CachedNavTreeResponse(); + + const referenceCodeLensProvider = new (await import('./features/referencesCodeLensProvider')).default(client, this.description.id, cachedResponse); referenceCodeLensProvider.updateConfiguration(); this.toUpdateOnConfigurationChanged.push(referenceCodeLensProvider); this.disposables.push(languages.registerCodeLensProvider(selector, referenceCodeLensProvider)); - const implementationCodeLensProvider = new (await import('./features/implementationsCodeLensProvider')).default(client, this.description.id); + const implementationCodeLensProvider = new (await import('./features/implementationsCodeLensProvider')).default(client, this.description.id, cachedResponse); implementationCodeLensProvider.updateConfiguration(); this.toUpdateOnConfigurationChanged.push(implementationCodeLensProvider); this.disposables.push(languages.registerCodeLensProvider(selector, implementationCodeLensProvider)); @@ -280,7 +284,8 @@ export class TypeScriptServiceClientHost implements ITypeScriptServiceClientHost descriptions: LanguageDescription[], workspaceState: Memento, plugins: TypeScriptServerPlugin[], - private readonly commandManager: CommandManager + private readonly commandManager: CommandManager, + logDirectoryProvider: LogDirectoryProvider ) { const handleProjectCreateOrDelete = () => { this.client.execute('reloadProjects', null, false); @@ -297,7 +302,7 @@ export class TypeScriptServiceClientHost implements ITypeScriptServiceClientHost configFileWatcher.onDidDelete(handleProjectCreateOrDelete, this, this.disposables); configFileWatcher.onDidChange(handleProjectChange, this, this.disposables); - this.client = new TypeScriptServiceClient(this, workspaceState, version => this.versionStatus.onDidChangeTypeScriptVersion(version), plugins); + this.client = new TypeScriptServiceClient(this, workspaceState, version => this.versionStatus.onDidChangeTypeScriptVersion(version), plugins, logDirectoryProvider); this.disposables.push(this.client); this.versionStatus = new VersionStatus(resource => this.client.normalizePath(resource)); diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index 89964180240..83d840c4138 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -6,7 +6,6 @@ import * as cp from 'child_process'; import * as path from 'path'; import * as fs from 'fs'; -import * as os from 'os'; import * as electron from './utils/electron'; import { Reader } from './utils/wireProtocol'; @@ -27,6 +26,8 @@ import { TypeScriptServiceConfiguration, TsServerLogLevel } from './utils/config import { TypeScriptVersionProvider, TypeScriptVersion } from './utils/versionProvider'; import { TypeScriptVersionPicker } from './utils/versionPicker'; import * as fileSchemes from './utils/fileSchemes'; +import { inferredProjectConfig } from './utils/tsconfig'; +import LogDirectoryProvider from './utils/logDirectoryProvider'; const localize = nls.loadMessageBundle(); @@ -113,7 +114,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private pathSeparator: string; - private _onReady: { promise: Promise; resolve: () => void; reject: () => void; }; + private _onReady?: { promise: Promise; resolve: () => void; reject: () => void; }; private _configuration: TypeScriptServiceConfiguration; private versionProvider: TypeScriptVersionProvider; private versionPicker: TypeScriptVersionPicker; @@ -154,7 +155,8 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient private readonly host: ITypeScriptServiceClientHost, private readonly workspaceState: Memento, private readonly onDidChangeTypeScriptVersion: (version: TypeScriptVersion) => void, - public readonly plugins: TypeScriptServerPlugin[] + public readonly plugins: TypeScriptServerPlugin[], + private readonly logDirectoryProvider: LogDirectoryProvider ) { this.pathSeparator = path.sep; this.lastStart = Date.now(); @@ -162,7 +164,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient var p = new Promise((resolve, reject) => { this._onReady = { promise: p, resolve, reject }; }); - this._onReady.promise = p; + this._onReady!.promise = p; this.servicePromise = null; this.lastError = null; @@ -267,7 +269,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } public onReady(): Promise { - return this._onReady.promise; + return this._onReady!.promise; } private info(message: string, data?: any): void { @@ -300,10 +302,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient return Promise.reject(new Error('Could not create TS service')); } - public startService(resendModels: boolean = false): Thenable { + public startService(resendModels: boolean = false): Promise { let currentVersion = this.versionPicker.currentVersion; - return this.servicePromise = new Promise((resolve, reject) => { + return this.servicePromise = new Promise(async (resolve, reject) => { this.info(`Using tsserver from: ${currentVersion.path}`); if (!fs.existsSync(currentVersion.tsServerPath)) { window.showWarningMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. Falling back to bundled TypeScript version.', currentVersion.path)); @@ -320,21 +322,19 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this.lastError = null; try { - const options: electron.IForkOptions = { + const tsServerForkArgs = await this.getTsServerArgs(currentVersion); + const tsServerForkOptions: electron.IForkOptions = { execArgv: [] // [`--debug-brk=5859`] }; - - electron.fork(currentVersion.tsServerPath, this.getTsServerArgs(currentVersion), options, this.logger, (err: any, childProcess: cp.ChildProcess | null) => { + electron.fork(currentVersion.tsServerPath, tsServerForkArgs, tsServerForkOptions, this.logger, (err: any, childProcess: cp.ChildProcess | null) => { if (err || !childProcess) { this.lastError = err; this.error('Starting TSServer failed with error.', err); window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err)); /* __GDPR__ - "error" : { - "message": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" } - } + "error" : {} */ - this.logTelemetry('error', { message: err.message }); + this.logTelemetry('error'); this.resetClientVersion(); return; } @@ -380,7 +380,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient (msg) => { this.dispatchMessage(msg); }, error => { this.error('ReaderError', error); }); - this._onReady.resolve(); + this._onReady!.resolve(); resolve(childProcess); this._onTsServerStarted.fire(); @@ -443,7 +443,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } try { - await commands.executeCommand('_workbench.action.files.revealInOS', Uri.parse(this.tsServerLogFile)); + await commands.executeCommand('revealFileInOS', Uri.parse(this.tsServerLogFile)); return true; } catch { window.showWarningMessage(localize( @@ -476,20 +476,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } private getCompilerOptionsForInferredProjects(configuration: TypeScriptServiceConfiguration): Proto.ExternalProjectCompilerOptions { - const compilerOptions: Proto.ExternalProjectCompilerOptions = { - module: 'CommonJS' as Proto.ModuleKind, - target: 'ES6' as Proto.ScriptTarget, + return { + ...inferredProjectConfig(configuration), + allowJs: true, allowSyntheticDefaultImports: true, allowNonTsExtensions: true, - allowJs: true, - jsx: 'Preserve' as Proto.JsxEmit }; - - if (this.apiVersion.has230Features()) { - compilerOptions.checkJs = configuration.checkJs; - compilerOptions.experimentalDecorators = configuration.experimentalDecorators; - } - return compilerOptions; } private serviceExited(restart: boolean): void { @@ -835,7 +827,9 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient this.logTelemetry(telemetryData.telemetryEventName, properties); } - private getTsServerArgs(currentVersion: TypeScriptVersion): string[] { + private async getTsServerArgs( + currentVersion: TypeScriptVersion + ): Promise { const args: string[] = []; if (this.apiVersion.has206Features()) { @@ -861,11 +855,12 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient if (this.apiVersion.has222Features()) { if (this._configuration.tsServerLogLevel !== TsServerLogLevel.Off) { - try { - const logDir = fs.mkdtempSync(path.join(os.tmpdir(), `vscode-tsserver-log-`)); + const logDir = await this.logDirectoryProvider.getNewLogDirectory(); + if (logDir) { this.tsServerLogFile = path.join(logDir, `tsserver.log`); this.info(`TSServer log file: ${this.tsServerLogFile}`); - } catch (e) { + } else { + this.tsServerLogFile = null; this.error('Could not create TSServer log directory'); } diff --git a/extensions/typescript/src/utils/languageConfigurations.ts b/extensions/typescript/src/utils/languageConfigurations.ts index ed002e9d1a8..f1553448882 100644 --- a/extensions/typescript/src/utils/languageConfigurations.ts +++ b/extensions/typescript/src/utils/languageConfigurations.ts @@ -30,16 +30,16 @@ export const jsTsLanguageConfiguration = { action: { indentAction: IndentAction.None, appendText: ' * ' } }, { // e.g. * ...| - beforeText: /^(\t|(\ \ ))*\ \*(\ ([^\*]|\*(?!\/))*)?$/, + beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/, action: { indentAction: IndentAction.None, appendText: '* ' } }, { // e.g. */| - beforeText: /^(\t|(\ \ ))*\ \*\/\s*$/, + beforeText: /^(\t|[ ])*[ ]\*\/\s*$/, action: { indentAction: IndentAction.None, removeText: 1 } }, { // e.g. *-----*/| - beforeText: /^(\t|(\ \ ))*\ \*[^/]*\*\/\s*$/, + beforeText: /^(\t|[ ])*[ ]\*[^/]*\*\/\s*$/, action: { indentAction: IndentAction.None, removeText: 1 } } ] diff --git a/extensions/typescript/src/utils/logDirectoryProvider.ts b/extensions/typescript/src/utils/logDirectoryProvider.ts new file mode 100644 index 00000000000..30633031ec8 --- /dev/null +++ b/extensions/typescript/src/utils/logDirectoryProvider.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as fs from 'fs'; +import * as path from 'path'; +import * as vscode from 'vscode'; + +export default class LogDirectoryProvider { + public constructor( + private readonly context: vscode.ExtensionContext + ) { } + + public async getNewLogDirectory(): Promise { + const root = await this.context.logger.logDirectory; + try { + return fs.mkdtempSync(path.join(root, `tsserver-log-`)); + } catch (e) { + return undefined; + } + } +} \ No newline at end of file diff --git a/extensions/typescript/src/utils/previewer.ts b/extensions/typescript/src/utils/previewer.ts index 4625a18a0a2..3e2e691cb30 100644 --- a/extensions/typescript/src/utils/previewer.ts +++ b/extensions/typescript/src/utils/previewer.ts @@ -49,11 +49,11 @@ export function markdownDocumentation( tags: Proto.JSDocTagInfo[] ): MarkdownString { const out = new MarkdownString(); - addmarkdownDocumentation(out, documentation, tags); + addMarkdownDocumentation(out, documentation, tags); return out; } -export function addmarkdownDocumentation( +export function addMarkdownDocumentation( out: MarkdownString, documentation: Proto.SymbolDisplayPart[], tags: Proto.JSDocTagInfo[] diff --git a/extensions/typescript/src/utils/tsconfig.ts b/extensions/typescript/src/utils/tsconfig.ts index fd65741cf5b..91293a8cc11 100644 --- a/extensions/typescript/src/utils/tsconfig.ts +++ b/extensions/typescript/src/utils/tsconfig.ts @@ -5,27 +5,39 @@ import * as vscode from 'vscode'; import * as path from 'path'; +import * as Proto from '../protocol'; + import { TypeScriptServiceConfiguration } from './configuration'; export function isImplicitProjectConfigFile(configFileName: string) { return configFileName.indexOf('/dev/null/') === 0; } -function getEmptyConfig( - isTypeScriptProject: boolean, +export function inferredProjectConfig( + config: TypeScriptServiceConfiguration +): Proto.ExternalProjectCompilerOptions { + const base: Proto.ExternalProjectCompilerOptions = { + module: 'commonjs' as Proto.ModuleKind, + target: 'es2016' as Proto.ScriptTarget, + jsx: 'preserve' as Proto.JsxEmit + }; + + if (config.checkJs) { + base.checkJs = true; + } + + if (config.experimentalDecorators) { + base.experimentalDecorators = true; + } + + return base; +} + +function inferredProjectConfigSnippet( config: TypeScriptServiceConfiguration ) { - const compilerOptions = [ - '"target": "ES6"', - '"module": "commonjs"', - '"jsx": "preserve"', - ]; - if (!isTypeScriptProject && config.checkJs) { - compilerOptions.push('"checkJs": true'); - } - if (!isTypeScriptProject && config.experimentalDecorators) { - compilerOptions.push('"experimentalDecorators": true'); - } + const baseConfig = inferredProjectConfig(config); + const compilerOptions = Object.keys(baseConfig).map(key => `"${key}": ${JSON.stringify(baseConfig[key])}`); return new vscode.SnippetString(`{ "compilerOptions": { ${compilerOptions.join(',\n\t\t')}$0 @@ -51,7 +63,7 @@ export async function openOrCreateConfigFile( const doc = await vscode.workspace.openTextDocument(configFile.with({ scheme: 'untitled' })); const editor = await vscode.window.showTextDocument(doc, col); if (editor.document.getText().length === 0) { - await editor.insertSnippet(getEmptyConfig(isTypeScriptProject, config)); + await editor.insertSnippet(inferredProjectConfigSnippet(config)); } return editor; } diff --git a/extensions/typescript/syntaxes/TypeScript.tmLanguage.json b/extensions/typescript/syntaxes/TypeScript.tmLanguage.json index e37e885a6b5..cc81558cfc2 100644 --- a/extensions/typescript/syntaxes/TypeScript.tmLanguage.json +++ b/extensions/typescript/syntaxes/TypeScript.tmLanguage.json @@ -4,7 +4,7 @@ "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/06831946d0a23cb56be0c1ead7cab10be01306cd", + "version": "https://github.com/Microsoft/TypeScript-TmLanguage/commit/8b44958a27860957872cc6f628d843a71686af63", "name": "TypeScript", "scopeName": "source.ts", "fileTypes": [ @@ -107,39 +107,52 @@ }, { "name": "keyword.control.trycatch.ts", - "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.ts" @@ -526,7 +540,7 @@ } }, { - "match": "(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)", + "match": "(?:(?)", "captures": { "1": { "name": "storage.modifier.async.ts" @@ -996,7 +1010,7 @@ }, { "name": "meta.arrow.ts", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.ts" @@ -1040,7 +1054,7 @@ }, "indexer-declaration": { "name": "meta.indexer.declaration.ts", - "begin": "(?:(?\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.ts", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2055,7 +2073,7 @@ }, { "name": "keyword.operator.expression.import.ts", - "match": "(?![\\.\\$])\\bimport(?=\\s*[\\(]\\s*[\\\"\\'\\`])" + "match": "(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", + "match": "(?x) (?:(\\.)|(\\?\\.(?!\\s*[[:digit:]]))) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.ts" }, "2": { - "name": "support.constant.dom.ts" + "name": "punctuation.accessor.optional.ts" }, "3": { + "name": "support.constant.dom.ts" + }, + "4": { "name": "support.variable.property.dom.ts" } } }, { "name": "support.class.node.ts", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.ts" }, "2": { + "name": "punctuation.accessor.optional.ts" + }, + "3": { "name": "entity.name.function.ts" } } }, { - "match": "(\\.)\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.ts" }, "2": { + "name": "punctuation.accessor.optional.ts" + }, + "3": { "name": "variable.other.constant.property.ts" } } }, { - "match": "(\\.)\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.ts" }, "2": { + "name": "punctuation.accessor.optional.ts" + }, + "3": { "name": "variable.other.property.ts" } } @@ -2649,24 +2704,27 @@ "patterns": [ { "name": "support.class.ts", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\.\\s*prototype\\b(?!\\$))" + "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(\\.)\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.ts" }, "2": { - "name": "variable.other.constant.object.property.ts" + "name": "punctuation.accessor.optional.ts" }, "3": { + "name": "variable.other.constant.object.property.ts" + }, + "4": { "name": "variable.other.object.property.ts" } } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.ts" @@ -2815,7 +2873,7 @@ }, { "name": "storage.modifier.ts", - "match": "(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", + "match": "(?x)(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n)) |\n# typeannotation is fn type: < | () | (... | (param: | (param, | (param? | (param= | (param) =>\n(:\\s*(\n (<) |\n ([(]\\s*(\n ([)]) |\n (\\.\\.\\.) |\n ([_$[:alnum:]]+\\s*(\n ([:,?=])|\n ([)]\\s*=>)\n ))\n ))\n)))", "captures": { "1": { "name": "storage.modifier.tsx" @@ -529,7 +543,7 @@ } }, { - "match": "(?:\\s*\\b(public|private|protected|readonly)\\s+)?(\\.\\.\\.)?\\s*(?)", + "match": "(?:(?)", "captures": { "1": { "name": "storage.modifier.async.tsx" @@ -999,7 +1013,7 @@ }, { "name": "meta.arrow.tsx", - "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", + "begin": "(?x) (?:\n (? is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n )\n)", "beginCaptures": { "1": { "name": "storage.modifier.async.tsx" @@ -1043,7 +1057,7 @@ }, "indexer-declaration": { "name": "meta.indexer.declaration.tsx", - "begin": "(?:(?\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", - "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "end": "(?<=\\))(?!(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*)\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "name": "meta.function-call.tsx", - "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\.\\s*)*|(\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", - "end": "(?=\\s*(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", + "begin": "(?=(([_$[:alpha:]][_$[:alnum:]]*\\s*\\??\\.\\s*)*|(\\??\\.\\s*)?)([_$[:alpha:]][_$[:alnum:]]*))", + "end": "(?=\\s*(\\?\\.\\s*)?(<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\))|\\<\\s*(([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))|(\\'[^\\']*\\')|(\\\"[^\\\"]*\\\")|(\\`[^\\`]*\\`))([^<>\\(]|(\\([^\\(\\)]*\\)))*\\>)*>\\s*)?\\()", "patterns": [ { "include": "#literal" @@ -2058,7 +2076,7 @@ }, { "name": "keyword.operator.expression.import.tsx", - "match": "(?![\\.\\$])\\bimport(?=\\s*[\\(]\\s*[\\\"\\'\\`])" + "match": "(?:(?]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", + "match": "(?x) (?:(\\.)|(\\?\\.(?!\\s*[[:digit:]]))) \\s* (?:\n (ATTRIBUTE_NODE|CDATA_SECTION_NODE|COMMENT_NODE|DOCUMENT_FRAGMENT_NODE|DOCUMENT_NODE|DOCUMENT_TYPE_NODE\n |DOMSTRING_SIZE_ERR|ELEMENT_NODE|ENTITY_NODE|ENTITY_REFERENCE_NODE|HIERARCHY_REQUEST_ERR|INDEX_SIZE_ERR\n |INUSE_ATTRIBUTE_ERR|INVALID_CHARACTER_ERR|NO_DATA_ALLOWED_ERR|NO_MODIFICATION_ALLOWED_ERR|NOT_FOUND_ERR\n |NOT_SUPPORTED_ERR|NOTATION_NODE|PROCESSING_INSTRUCTION_NODE|TEXT_NODE|WRONG_DOCUMENT_ERR)\n |\n (_content|[xyz]|abbr|above|accept|acceptCharset|accessKey|action|align|[av]Link(?:color)?|all|alt|anchors|appCodeName\n |appCore|applets|appMinorVersion|appName|appVersion|archive|areas|arguments|attributes|availHeight|availLeft|availTop\n |availWidth|axis|background|backgroundColor|backgroundImage|below|bgColor|body|border|borderBottomWidth|borderColor\n |borderLeftWidth|borderRightWidth|borderStyle|borderTopWidth|borderWidth|bottom|bufferDepth|callee|caller|caption\n |cellPadding|cells|cellSpacing|ch|characterSet|charset|checked|childNodes|chOff|cite|classes|className|clear\n |clientInformation|clip|clipBoardData|closed|code|codeBase|codeType|color|colorDepth|cols|colSpan|compact|complete\n |components|content|controllers|cookie|cookieEnabled|cords|cpuClass|crypto|current|data|dateTime|declare|defaultCharset\n |defaultChecked|defaultSelected|defaultStatus|defaultValue|defaultView|defer|description|dialogArguments|dialogHeight\n |dialogLeft|dialogTop|dialogWidth|dir|directories|disabled|display|docmain|doctype|documentElement|elements|embeds\n |enabledPlugin|encoding|enctype|entities|event|expando|external|face|fgColor|filename|firstChild|fontFamily|fontSize\n |fontWeight|form|formName|forms|frame|frameBorder|frameElement|frames|hasFocus|hash|headers|height|history|host\n |hostname|href|hreflang|hspace|htmlFor|httpEquiv|id|ids|ignoreCase|images|implementation|index|innerHeight|innerWidth\n |input|isMap|label|lang|language|lastChild|lastIndex|lastMatch|lastModified|lastParen|layer[sXY]|left|leftContext\n |lineHeight|link|linkColor|links|listStyleType|localName|location|locationbar|longDesc|lowsrc|lowSrc|marginBottom\n |marginHeight|marginLeft|marginRight|marginTop|marginWidth|maxLength|media|menubar|method|mimeTypes|multiline|multiple\n |name|nameProp|namespaces|namespaceURI|next|nextSibling|nodeName|nodeType|nodeValue|noHref|noResize|noShade|notationName\n |notations|noWrap|object|offscreenBuffering|onLine|onreadystatechange|opener|opsProfile|options|oscpu|outerHeight\n |outerWidth|ownerDocument|paddingBottom|paddingLeft|paddingRight|paddingTop|page[XY]|page[XY]Offset|parent|parentLayer\n |parentNode|parentWindow|pathname|personalbar|pixelDepth|pkcs11|platform|plugins|port|prefix|previous|previousDibling\n |product|productSub|profile|profileend|prompt|prompter|protocol|publicId|readOnly|readyState|referrer|rel|responseText\n |responseXML|rev|right|rightContext|rowIndex|rows|rowSpan|rules|scheme|scope|screen[XY]|screenLeft|screenTop|scripts\n |scrollbars|scrolling|sectionRowIndex|security|securityPolicy|selected|selectedIndex|selection|self|shape|siblingAbove\n |siblingBelow|size|source|specified|standby|start|status|statusbar|statusText|style|styleSheets|suffixes|summary\n |systemId|systemLanguage|tagName|tags|target|tBodies|text|textAlign|textDecoration|textIndent|textTransform|tFoot|tHead\n |title|toolbar|top|type|undefined|uniqueID|updateInterval|URL|URLUnencoded|useMap|userAgent|userLanguage|userProfile\n |vAlign|value|valueType|vendor|vendorSub|version|visibility|vspace|whiteSpace|width|X[MS]LDocument|zIndex))\\b(?!\\$|\\s*(<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)?\\()", "captures": { "1": { "name": "punctuation.accessor.tsx" }, "2": { - "name": "support.constant.dom.tsx" + "name": "punctuation.accessor.optional.tsx" }, "3": { + "name": "support.constant.dom.tsx" + }, + "4": { "name": "support.variable.property.dom.tsx" } } }, { "name": "support.class.node.tsx", - "match": "(?x)(?)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", + "match": "(?x)(?:(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*)?([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*=\\s*(\n ((async\\s+)?(\n (function\\s*[(<*]) |\n (function\\s+) |\n ([_$[:alpha:]][_$[:alnum:]]*\\s*=>)\n )) |\n ((async\\s*)?(\n # sure shot arrow functions even if => is on new line\n(\n [(]\\s*\n (\n ([)]\\s*:) | # ():\n ((\\.\\.\\.\\s*)?[_$[:alpha:]][_$[:alnum:]]*\\s*:) # [(]param: | [(]...param:\n )\n) |\n(\n [<]\\s*[_$[:alpha:]][_$[:alnum:]]*\\s+extends\\s*[^=>] # < typeparam extends\n) |\n# arrow function possible to detect only with => on same line\n(\n (<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<]|\\<\\s*([_$[:alpha:]]|(\\{[^\\{\\}]*\\})|(\\([^\\(\\)]*\\))|(\\[[^\\[\\]]*\\]))([^=<>]|=[^<])*\\>)*>\\s*)? # typeparameters\n \\(\\s*([_$[:alpha:]\\{\\[]([^()]|\\((\\s*[^()]*)?\\))*)?\\) # parameteres\n (\\s*:\\s*([^<>\\(\\)]|\\<[^<>]+\\>|\\([^\\(\\)]+\\))+)? # return type\n \\s*=> # arrow operator\n)\n ))\n))", "captures": { "1": { "name": "punctuation.accessor.tsx" }, "2": { + "name": "punctuation.accessor.optional.tsx" + }, + "3": { "name": "entity.name.function.tsx" } } }, { - "match": "(\\.)\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([[:upper:]][_$[:digit:][:upper:]]*)(?![_$[:alnum:]])", "captures": { "1": { "name": "punctuation.accessor.tsx" }, "2": { + "name": "punctuation.accessor.optional.tsx" + }, + "3": { "name": "variable.other.constant.property.tsx" } } }, { - "match": "(\\.)\\s*([_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*([_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.tsx" }, "2": { + "name": "punctuation.accessor.optional.tsx" + }, + "3": { "name": "variable.other.property.tsx" } } @@ -2615,24 +2670,27 @@ "patterns": [ { "name": "support.class.tsx", - "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\.\\s*prototype\\b(?!\\$))" + "match": "([_$[:alpha:]][_$[:alnum:]]*)(?=\\s*\\??\\.\\s*prototype\\b(?!\\$))" }, { - "match": "(?x)(\\.)\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:(\\.)|(\\?\\.(?!\\s*[[:digit:]])))\\s*(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "punctuation.accessor.tsx" }, "2": { - "name": "variable.other.constant.object.property.tsx" + "name": "punctuation.accessor.optional.tsx" }, "3": { + "name": "variable.other.constant.object.property.tsx" + }, + "4": { "name": "variable.other.object.property.tsx" } } }, { - "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", + "match": "(?x)(?:\n ([[:upper:]][_$[:digit:][:upper:]]*) |\n ([_$[:alpha:]][_$[:alnum:]]*)\n)(?=\\s*\\??\\.\\s*[_$[:alpha:]][_$[:alnum:]]*)", "captures": { "1": { "name": "variable.other.constant.object.tsx" @@ -2781,7 +2839,7 @@ }, { "name": "storage.modifier.tsx", - "match": "(?=2 一起工作最佳" } \ No newline at end of file diff --git a/i18n/chs/extensions/git/package.i18n.json b/i18n/chs/extensions/git/package.i18n.json index 3f973cac673..cb8443b7aec 100644 --- a/i18n/chs/extensions/git/package.i18n.json +++ b/i18n/chs/extensions/git/package.i18n.json @@ -46,7 +46,7 @@ "command.sync": "同步", "command.syncRebase": "同步(变基)", "command.publish": "发布分支", - "command.showOutput": "显示 GIT 输出", + "command.showOutput": "显示 Git 输出", "command.ignore": "将文件添加到 .gitignore", "command.stashIncludeUntracked": "储藏(包含未跟踪)", "command.stash": "储藏", @@ -57,7 +57,7 @@ "config.autorefresh": "是否启用自动刷新", "config.autofetch": "是否启用自动拉取", "config.enableLongCommitWarning": "是否针对长段提交消息进行警告", - "config.confirmSync": "同步 Git 存储库前进行确认", + "config.confirmSync": "同步 GIT 存储库前请先进行确认", "config.countBadge": "控制 Git 徽章计数器。“all”计算所有更改。“tracked”只计算跟踪的更改。“off”关闭此功能。", "config.checkoutType": "控制运行“签出到...”命令时列出的分支的类型。\"all\" 显示所有 refs,\"local\" 只显示本地分支,\"tags\" 只显示标记,\"remote\" 只显示远程分支。", "config.ignoreLegacyWarning": "忽略旧版 Git 警告", diff --git a/i18n/chs/src/vs/code/node/cliProcessMain.i18n.json b/i18n/chs/src/vs/code/node/cliProcessMain.i18n.json index 39c71863011..0b63356dd40 100644 --- a/i18n/chs/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/chs/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "未安装扩展“{0}”。", "useId": "确保使用完整扩展 ID,包括发布服务器,如: {0}", "successVsixInstall": "已成功安装扩展“{0}”!", + "cancelVsixInstall": "已取消安装扩展“{0}”。", "alreadyInstalled": "已安装扩展“{0}”。", "foundExtension": "在商店中找到了“{0}”。", "installing": "正在安装...", diff --git a/i18n/chs/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/chs/src/vs/editor/common/config/commonEditorConfig.i18n.json index 3aa5c1a68d4..9c145d8c4d9 100644 --- a/i18n/chs/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/chs/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "限制最小映射的宽度,尽量多地呈现特定数量的列", "find.seedSearchStringFromSelection": "控制是否将编辑器的选中内容作为搜索词填入到查找组件", "find.autoFindInSelection": "控制当编辑器中选中多个字符或多行文字时是否开启“在选定内容中查找”选项 ", + "find.globalFindClipboard": "控制查找小组件是否应在 macOS 上读取或修改在应用间共享的查找剪贴板", "wordWrap.off": "永不换行。", "wordWrap.on": "将在视区宽度处换行。", "wordWrap.wordWrapColumn": "将在 \"editor.wordWrapColumn\" 处换行。", @@ -77,7 +78,7 @@ "renderControlCharacters": "控制编辑器是否应呈现控制字符", "renderIndentGuides": "控制编辑器是否应呈现缩进参考线", "renderLineHighlight": "控制编辑器应如何呈现当前行突出显示,可能为“无”、“装订线”、“线”和“全部”。", - "codeLens": "控制编辑器是否显示代码滤镜", + "codeLens": "控制编辑器是否显示 CodeLens", "folding": "控制编辑器是否启用代码折叠功能", "showFoldingControls": "控制是否自动隐藏导航线上的折叠控件。", "matchBrackets": "当选择其中一项时,将突出显示匹配的括号。", diff --git a/i18n/chs/src/vs/editor/common/view/editorColorRegistry.i18n.json b/i18n/chs/src/vs/editor/common/view/editorColorRegistry.i18n.json index ad84081b84e..168dd2fb958 100644 --- a/i18n/chs/src/vs/editor/common/view/editorColorRegistry.i18n.json +++ b/i18n/chs/src/vs/editor/common/view/editorColorRegistry.i18n.json @@ -6,7 +6,7 @@ { "lineHighlight": "光标所在行高亮内容的背景颜色。", "lineHighlightBorderBox": "光标所在行四周边框的背景颜色。", - "rangeHighlight": "突出显示范围的背景颜色,例如 \"Quick Open\" 和“查找”功能。", + "rangeHighlight": "高亮范围的背景色,例如由 \"Quick Open\" 和“查找”功能高亮的范围。", "caret": "编辑器光标颜色。", "editorCursorBackground": "编辑器光标的背景色。可以自定义块型光标覆盖字符的颜色。", "editorWhitespaces": "编辑器中空白字符的颜色。", @@ -15,7 +15,7 @@ "editorRuler": "编辑器标尺的颜色。", "editorCodeLensForeground": "编辑器 CodeLens 的前景色", "editorBracketMatchBackground": "匹配括号的背景色", - "editorBracketMatchBorder": "匹配括号外框颜色", + "editorBracketMatchBorder": "匹配括号外框的颜色", "editorOverviewRulerBorder": "概览标尺边框的颜色。", "editorGutter": "编辑器导航线的背景色。导航线包括边缘符号和行号。", "errorForeground": "编辑器中错误波浪线的前景色。", diff --git a/i18n/chs/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/chs/src/vs/editor/contrib/folding/folding.i18n.json index 90d90c3c8ff..6ad3874f3bb 100644 --- a/i18n/chs/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/chs/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "折叠", "foldRecursivelyAction.label": "以递归方式折叠", "foldAllBlockComments.label": "折叠所有块注释", + "foldAllMarkerRegions.label": "折叠所有区域", + "unfoldAllMarkerRegions.label": "展开所有区域", "foldAllAction.label": "全部折叠", "unfoldAllAction.label": "全部展开", "foldLevelAction.label": "折叠级别 {0}" diff --git a/i18n/chs/src/vs/platform/actions/electron-browser/menusExtensionPoint.i18n.json b/i18n/chs/src/vs/platform/actions/electron-browser/menusExtensionPoint.i18n.json index 21c4685e274..d14243882a7 100644 --- a/i18n/chs/src/vs/platform/actions/electron-browser/menusExtensionPoint.i18n.json +++ b/i18n/chs/src/vs/platform/actions/electron-browser/menusExtensionPoint.i18n.json @@ -8,8 +8,8 @@ "requirestring": "属性“{0}”是必要属性,其类型必须是“string”", "optstring": "属性“{0}”可以被省略,否则其类型必须为“string”", "vscode.extension.contributes.menuItem.command": "要执行的命令的标识符。该命令必须在 \"commands\" 部分中声明", - "vscode.extension.contributes.menuItem.alt": "要执行的替代命令的标识符。该命令必须在“命令”部分中声明", - "vscode.extension.contributes.menuItem.when": "为显示此项必须为 \"ture\" 的条件", + "vscode.extension.contributes.menuItem.alt": "要执行的替代命令的标识符。该命令必须在 ”commands\" 部分中声明", + "vscode.extension.contributes.menuItem.when": "为显示此项必须为 \"true\" 的条件", "vscode.extension.contributes.menuItem.group": "此命令所属的组", "vscode.extension.contributes.menus": "向编辑器提供菜单项", "menus.commandPalette": "命令面板", @@ -26,9 +26,9 @@ "view.viewTitle": "提供的视图的标题菜单", "view.itemContext": "提供的视图中的项目的上下文菜单", "nonempty": "应为非空值。", - "opticon": "可以省略属性“图标”或者它必须是一个字符串或类似“{dark, light}”的文本", - "requireStringOrObject": "属性“{0}”为必需且其类型必须为“字符串”或“对象”", - "requirestrings": "属性“{0}”和“{1}`”为必需且其类型必须为“字符串”", + "opticon": "属性 \"icon\" 可以省略,否则其必须为字符串或是类似 \"{dark, light}\" 的文本", + "requireStringOrObject": "属性“{0}”是必要属性,其类型必须是 \"string\" 或 \"object\"", + "requirestrings": "属性“{0}”和“{1}”是必要属性,其类型必须是 \"string\"", "vscode.extension.contributes.commandType.command": "要执行的命令的标识符", "vscode.extension.contributes.commandType.title": "在 UI 中依据其表示命令的标题", "vscode.extension.contributes.commandType.category": "(可选)类别字符串按命令在 UI 中分组", @@ -36,7 +36,7 @@ "vscode.extension.contributes.commandType.icon.light": "使用浅色主题时的图标路径", "vscode.extension.contributes.commandType.icon.dark": "使用深色主题时的图标路径", "vscode.extension.contributes.commands": "对命令面板提供命令。", - "dup": "命令“{0}”多次出现在“命令”部分。", + "dup": "命令“{0}”在 \"commands\" 部分重复出现。", "menuId.invalid": "“{0}”为无效菜单标识符", "missing.command": "菜单项引用未在“命令”部分进行定义的命令“{0}”。", "missing.altCommand": "菜单项引用了未在 \"commands\" 部分定义的替代命令“{0}”。", diff --git a/i18n/chs/src/vs/platform/environment/node/argv.i18n.json b/i18n/chs/src/vs/platform/environment/node/argv.i18n.json index 77d5d77fd7b..9a1faa5d983 100644 --- a/i18n/chs/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/chs/src/vs/platform/environment/node/argv.i18n.json @@ -7,7 +7,7 @@ "gotoValidation": "\"--goto\" 模式中的参数格式应为 \"FILE(:LINE(:CHARACTER))\"。", "diff": "将两个文件相互比较。", "add": "将文件夹添加到最后一个活动窗口。", - "goto": "打开路径下的文件并定位到特定行和特定字符。", + "goto": "打开路径下的文件并定位到特定行和特定列。", "locale": "要使用的区域设置(例如 en-US 或 zh-TW)。", "newWindow": "强制创建一个新的 Code 实例。", "performance": "通过启用 \"Developer: Startup Performance\" 命令开始。", @@ -16,6 +16,7 @@ "inspect-brk-extensions": "允许在扩展主机在启动后暂停时进行扩展的调试与分析。检查开发人员工具可获取连接 URI。", "reuseWindow": "在上一活动窗口中强制打开文件或文件夹。", "userDataDir": "指定存放用户数据的目录。此目录在以 root 身份运行时十分有用。", + "log": "使用的日志级别。默认值为 \"info\"。允许的值为 \"critical\" (关键)、\"error\" (错误)、\"warn\" (警告)、\"info\" (信息)、\"debug\" (调试)、\"trace\" (跟踪) 和 \"off\" (关闭)。", "verbose": "打印详细输出(隐含 --wait 参数)。", "wait": "等文件关闭后再返回。", "extensionHomePath": "设置扩展的根路径。", diff --git a/i18n/chs/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/chs/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index 66040db95cc..992b3f197f4 100644 --- a/i18n/chs/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/chs/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -4,15 +4,15 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "vscode.extension.engines.vscode": "对于 VS Code 扩展程序,指定该扩展程序与之兼容的 VS Code 版本。不能为 *. 例如: ^0.10.5 表示最低兼容 VS Code 版本 0.10.5。", + "vscode.extension.engines.vscode": "对于 VS Code 扩展,指定与其兼容的 VS Code 版本。不能为 *。 例如: ^0.10.5 表示最低兼容 VS Code 版本 0.10.5。", "vscode.extension.publisher": "VS Code 扩展的发布者。", "vscode.extension.displayName": "VS Code 库中使用的扩展的显示名称。", "vscode.extension.categories": "VS Code 库用于对扩展进行分类的类别。", - "vscode.extension.galleryBanner": "在 VS Code 商店中使用的横幅。", + "vscode.extension.galleryBanner": "VS Code 商城使用的横幅。", "vscode.extension.galleryBanner.color": "VS Code 商城页标题上的横幅颜色。", "vscode.extension.galleryBanner.theme": "横幅文字的颜色主题。", "vscode.extension.contributes": "由此包表示的 VS Code 扩展的所有贡献。", - "vscode.extension.preview": "在商店中将扩展标记为“预览版”", + "vscode.extension.preview": "在 Marketplace 中设置扩展,将其标记为“预览”。", "vscode.extension.activationEvents": "VS Code 扩展的激活事件。", "vscode.extension.activationEvents.onLanguage": "在打开被解析为指定语言的文件时发出的激活事件。", "vscode.extension.activationEvents.onCommand": "在调用指定命令时发出的激活事件。", @@ -22,7 +22,7 @@ "vscode.extension.activationEvents.workspaceContains": "在打开至少包含一个匹配指定 glob 模式的文件的文件夹时发出的激活事件。", "vscode.extension.activationEvents.onView": "在指定视图被展开时发出的激活事件。", "vscode.extension.activationEvents.star": "在 VS Code 启动时发出的激活事件。为确保良好的最终用户体验,请仅在其他激活事件组合不适用于你的情况时,才在扩展中使用此事件。", - "vscode.extension.badges": "在商店扩展页面侧边栏中显示的徽章的数组。", + "vscode.extension.badges": "在 Marketplace 的扩展页边栏中显示的徽章数组。", "vscode.extension.badges.url": "徽章图像 URL。", "vscode.extension.badges.href": "徽章链接。", "vscode.extension.badges.description": "徽章说明。", diff --git a/i18n/chs/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/chs/src/vs/workbench/electron-browser/actions.i18n.json index 8b65741ef0b..24ec2fcb7bd 100644 --- a/i18n/chs/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/chs/src/vs/workbench/electron-browser/actions.i18n.json @@ -67,5 +67,6 @@ "warn": "警告", "err": "错误", "critical": "关键", - "off": "关闭" + "off": "关闭", + "selectLogLevel": "选择日志级别" } \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/chs/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 0623903445b..361a7a420b1 100644 --- a/i18n/chs/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "已添加断点,行 {0}, 文件 {1}", "breakpointRemoved": "已删除断点,行 {0},文件 {1}", "compoundMustHaveConfigurations": "复合项必须拥有 \"configurations\" 属性集,才能启动多个配置。", + "configMissing": "\"launch.json\" 中缺少配置“{0}”。", + "launchJsonDoesNotExist": "\"launch.json\" 不存在。", "debugRequestNotSupported": "所选调试配置的属性“{0}”的值“{1}”不受支持。", "debugRequesMissing": "所选的调试配置缺少属性“{0}”。", "debugTypeNotSupported": "配置的类型“{0}”不受支持。", diff --git a/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json b/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json index 3f110c4b49d..a463252daaf 100644 --- a/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json @@ -6,7 +6,7 @@ { "name": "扩展名", "extension id": "扩展标识符", - "preview": "预览", + "preview": "预览版", "publisher": "发布服务器名称", "install count": "安装计数", "rating": "评级", diff --git a/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index e4ad4894419..a6c445fa00d 100644 --- a/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "ratedByUsers": "评价来自 {0} 位用户" + "ratedByUsers": "评价来自 {0} 位用户", + "ratedBySingleUser": "评价来自 1 位用户" } \ No newline at end of file diff --git a/i18n/cht/extensions/css/client/out/cssMain.i18n.json b/i18n/cht/extensions/css/client/out/cssMain.i18n.json index 114a1822e0a..a7dadfd1a6a 100644 --- a/i18n/cht/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/cht/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "CSS 語言伺服器" + "cssserver.name": "CSS 語言伺服器", + "folding.start": "摺疊區域開始", + "folding.end": "摺疊區域結尾" } \ No newline at end of file diff --git a/i18n/cht/extensions/emmet/package.i18n.json b/i18n/cht/extensions/emmet/package.i18n.json index 7d6aff82b16..efdcb2b4198 100644 --- a/i18n/cht/extensions/emmet/package.i18n.json +++ b/i18n/cht/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "使用 BEM 篩選時用於類別的修飾詞分隔符號", "emmetPreferencesFilterCommentBefore": "套用註解篩選時必須置於相符元素前的註解定義。", "emmetPreferencesFilterCommentAfter": "套用註解篩選時必須置於相符元素後的註解定義。 ", - "emmetPreferencesFilterCommentTrigger": "必須採用縮寫以套用註解篩選的屬性名稱逗點分隔清單" + "emmetPreferencesFilterCommentTrigger": "必須採用縮寫以套用註解篩選的屬性名稱逗點分隔清單", + "emmetPreferencesFormatNoIndentTags": "陣列的標籤名稱不應向內縮排", + "emmetPreferencesFormatForceIndentTags": "陣列的標籤名稱應總是向內縮排", + "emmetPreferencesAllowCompactBoolean": "若為 true,則生成布林屬性的嚴謹表示法" } \ No newline at end of file diff --git a/i18n/cht/extensions/git/out/autofetch.i18n.json b/i18n/cht/extensions/git/out/autofetch.i18n.json index bd190303079..78cd65caa1f 100644 --- a/i18n/cht/extensions/git/out/autofetch.i18n.json +++ b/i18n/cht/extensions/git/out/autofetch.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "yes": "是", - "no": "否" + "no": "否", + "not now": "不是現在", + "suggest auto fetch": "是否啟用 Git 儲存庫的自動擷取?" } \ No newline at end of file diff --git a/i18n/cht/extensions/git/out/commands.i18n.json b/i18n/cht/extensions/git/out/commands.i18n.json index 24b380b69e3..1ace18f1610 100644 --- a/i18n/cht/extensions/git/out/commands.i18n.json +++ b/i18n/cht/extensions/git/out/commands.i18n.json @@ -9,10 +9,12 @@ "create branch": "$(plus) 建立新的分支", "repourl": "儲存庫 URL", "parent": "父目錄", + "cancel": "$ (同步 ~ 旋轉) 複製儲存庫.. 按一下以取消", "cancel tooltip": "取消複製", "cloning": "正在複製 Git 儲存庫...", "openrepo": "開啟儲存庫", "proposeopen": "要開啟複製的儲存庫嗎?", + "init": "選擇工作區資料夾以初始化 git 儲存庫", "init repo": "初始化儲存庫", "create repo": "初始化儲存庫", "are you sure": "這會建立一個 Git 儲存庫在 '{0}'。確定要繼續嗎?", @@ -58,6 +60,7 @@ "provide tag name": "請提供標籤名稱", "tag message": "訊息", "provide tag message": "請提供訊息以標註標籤", + "no remotes to fetch": "您的儲存庫未設定要擷取的遠端來源。", "no remotes to pull": "您的儲存庫未設定要提取的遠端來源。", "pick remote pull repo": "挑選要將分支提取出的遠端", "no remotes to push": "您的儲存庫未設定要推送的遠端目標。", @@ -74,6 +77,7 @@ "no stashes": "沒有要隱藏可供還原。", "pick stash to pop": "請挑選要快顯的隱藏", "clean repo": "請先清除您的本地儲存庫工作區再簽出。", + "cant push": "無法將參考推送到遠端。請先嘗試執行 '提取' 以整合您的變更。", "git error details": "Git: {0}", "git error": "Git 錯誤", "open git log": "開啟 Git 記錄" diff --git a/i18n/cht/extensions/git/out/main.i18n.json b/i18n/cht/extensions/git/out/main.i18n.json index 1705b21123c..b9e759fe018 100644 --- a/i18n/cht/extensions/git/out/main.i18n.json +++ b/i18n/cht/extensions/git/out/main.i18n.json @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "looking": "尋找 git : {0}", "using git": "正在使用來自 {1} 的 git {0}", + "downloadgit": "下載 Git", "neverShowAgain": "不要再顯示", + "notfound": "找不到 Git。安裝它或使用 \"git. path\" 設置。", "updateGit": "更新 Git", "git20": "您似乎已安裝 Git {0}。Code 搭配 Git >= 2 的執行效果最佳" } \ No newline at end of file diff --git a/i18n/cht/extensions/git/package.i18n.json b/i18n/cht/extensions/git/package.i18n.json index c21ef005686..5bb3d198bb4 100644 --- a/i18n/cht/extensions/git/package.i18n.json +++ b/i18n/cht/extensions/git/package.i18n.json @@ -36,6 +36,7 @@ "command.renameBranch": "重新命名分支...", "command.merge": "合併分支...", "command.createTag": "建立標籤", + "command.fetch": "擷取", "command.pull": "提取", "command.pullRebase": "提取 (重訂基底)", "command.pullFrom": "從...提取", @@ -60,6 +61,7 @@ "config.countBadge": "控制 git 徽章計數器。[全部] 會計算所有變更。[已追蹤] 只會計算追蹤的變更。[關閉] 會將其關閉。", "config.checkoutType": "控制在執行 [簽出至...] 時,會列出那些類型的分支。[全部] 會顯示所有參考,[本機] 只會顯示本機分支,[標記] 只會顯示標記,[遠端] 只會顯示遠端分支。", "config.ignoreLegacyWarning": "略過舊的 Git 警告", + "config.ignoreMissingGitWarning": "忽略遺漏 Git 時的警告", "config.ignoreLimitWarning": "當儲存庫中有過多變更時,略過警告。", "config.defaultCloneDirectory": "複製 Git 儲存庫的預設位置", "config.enableSmartCommit": "無暫存變更時提交所有變更。", diff --git a/i18n/cht/extensions/html/client/out/htmlMain.i18n.json b/i18n/cht/extensions/html/client/out/htmlMain.i18n.json index 7a77e714d97..d6e7c1e3a63 100644 --- a/i18n/cht/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/cht/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "HTML 語言伺服器" + "htmlserver.name": "HTML 語言伺服器", + "folding.start": "摺疊區域開始", + "folding.end": "摺疊區域結束" } \ No newline at end of file diff --git a/i18n/cht/extensions/markdown/out/security.i18n.json b/i18n/cht/extensions/markdown/out/security.i18n.json index 870789578bf..4aaab7a1cbd 100644 --- a/i18n/cht/extensions/markdown/out/security.i18n.json +++ b/i18n/cht/extensions/markdown/out/security.i18n.json @@ -11,5 +11,8 @@ "disable.title": "停用", "disable.description": "允許所有內容與指令碼執行。不建議", "moreInfo.title": "詳細資訊", + "enableSecurityWarning.title": "在此工作區中啟用預覽安全警告", + "disableSecurityWarning.title": "禁用此工作區中的預覽安全警告", + "toggleSecurityWarning.description": "不影響內容安全級別", "preview.showPreviewSecuritySelector.title": "選擇此工作區 Markdown 預覽的安全性設定" } \ No newline at end of file diff --git a/i18n/cht/extensions/merge-conflict/package.i18n.json b/i18n/cht/extensions/merge-conflict/package.i18n.json index 21711a0ff32..7189e30af1b 100644 --- a/i18n/cht/extensions/merge-conflict/package.i18n.json +++ b/i18n/cht/extensions/merge-conflict/package.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "command.category": "合併衝突", + "command.accept.all-current": "接受所有當前項目", "command.accept.all-incoming": "接受所有來源", "command.accept.all-both": "接受兩者所有項目", "command.accept.current": "接受當前項目", diff --git a/i18n/cht/extensions/typescript/out/features/completionItemProvider.i18n.json b/i18n/cht/extensions/typescript/out/features/completionItemProvider.i18n.json index f75533e302e..c42d9c7db98 100644 --- a/i18n/cht/extensions/typescript/out/features/completionItemProvider.i18n.json +++ b/i18n/cht/extensions/typescript/out/features/completionItemProvider.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "selectCodeAction": "選擇要套用的程式碼動作", "acquiringTypingsLabel": "正在擷取 typings...", "acquiringTypingsDetail": "正在為 IntelliSense 擷取 typings 定義。", "autoImportLabel": "自動從 {0} 匯入" diff --git a/i18n/cht/src/vs/code/electron-main/main.i18n.json b/i18n/cht/src/vs/code/electron-main/main.i18n.json index 0311ac8ba64..e36aac75610 100644 --- a/i18n/cht/src/vs/code/electron-main/main.i18n.json +++ b/i18n/cht/src/vs/code/electron-main/main.i18n.json @@ -6,6 +6,7 @@ { "secondInstanceNoResponse": "另一個 {0} 執行個體正在執行,但沒有回應", "secondInstanceNoResponseDetail": "請關閉其他所有執行個體,然後再試一次。", + "secondInstanceAdmin": "{0} 的第二個實例已作為管理員運行。", "secondInstanceAdminDetail": "請關閉其他執行個體,然後再試一次。", "close": "關閉(&&C)" } \ No newline at end of file diff --git a/i18n/cht/src/vs/code/electron-main/menus.i18n.json b/i18n/cht/src/vs/code/electron-main/menus.i18n.json index 4aad58bdfe6..a9c5252b3c1 100644 --- a/i18n/cht/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/cht/src/vs/code/electron-main/menus.i18n.json @@ -22,10 +22,12 @@ "miQuit": "結束 {0}", "miNewFile": "新增檔案(&&N)", "miOpen": "開啟(&&O)...", + "miOpenWorkspace": "開啟工作區 (&&k)...", "miOpenFolder": "開啟資料夾(&&F)...", "miOpenFile": "開啟檔案(&&O)...", "miOpenRecent": "開啟最近的檔案(&&R)", "miSaveWorkspaceAs": "另存工作區為...", + "miAddFolderToWorkspace": "新增資料夾至工作區 (&&A)...", "miSave": "儲存(&&S)", "miSaveAs": "另存新檔(&&A)...", "miSaveAll": "全部儲存(&&L)", @@ -155,6 +157,7 @@ "mMergeAllWindows": "合併所有視窗", "miToggleDevTools": "切換開發人員工具(&&T)", "miAccessibilityOptions": "協助工具選項(&&O)", + "miReportIssue": "回報問題 (&&I)", "miWelcome": "歡迎使用(&&W)", "miInteractivePlayground": "Interactive Playground(&&I)", "miDocumentation": "文件(&&D)", @@ -181,6 +184,7 @@ "miDownloadingUpdate": "正在下載更新...", "miInstallingUpdate": "正在安裝更新...", "miCheckForUpdates": "查看是否有更新", + "aboutDetail": "版本 {0} \n認可 {1} \n日期 {2} \nShell {3} \n轉譯器 {4} \n節點 {5} \n架構 {6}", "okButton": "確定", "copy": "複製(&&C)" } \ No newline at end of file diff --git a/i18n/cht/src/vs/code/node/cliProcessMain.i18n.json b/i18n/cht/src/vs/code/node/cliProcessMain.i18n.json index 02ee16e48c2..df13867eaad 100644 --- a/i18n/cht/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/cht/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "未安裝擴充功能 '{0}'。", "useId": "請確定您使用完整擴充功能識別碼 (包括發行者),例如: {0}", "successVsixInstall": "已成功安裝延伸模組 '{0}'!", + "cancelVsixInstall": "已取消安裝擴充功能 \"{0}\"。", "alreadyInstalled": "已安裝過擴充功能 '{0}'。", "foundExtension": "在市集中找到 '{0}'。", "installing": "正在安裝...", diff --git a/i18n/cht/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/cht/src/vs/editor/common/config/commonEditorConfig.i18n.json index dd7847676ef..71be40322db 100644 --- a/i18n/cht/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/cht/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "限制迷你地圖的寬度,以呈現最多的資料行", "find.seedSearchStringFromSelection": "控制編譯器選取範圍是否預設為尋找工具的搜尋字串", "find.autoFindInSelection": "控制編譯器內選取多字元或多行內文是否開啟選取範圍尋找功能", + "find.globalFindClipboard": "控制尋找小工具是否在 macOS 上讀取或修改共用尋找剪貼簿  ", "wordWrap.off": "一律不換行。", "wordWrap.on": "依檢視區寬度換行。", "wordWrap.wordWrapColumn": "於 'editor.wordWrapColumn' 換行。", diff --git a/i18n/cht/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/cht/src/vs/editor/contrib/folding/folding.i18n.json index 0a6abe06965..5ca5c126d3a 100644 --- a/i18n/cht/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/cht/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "摺疊", "foldRecursivelyAction.label": "以遞迴方式摺疊", "foldAllBlockComments.label": "摺疊全部區塊註解", + "foldAllMarkerRegions.label": "折疊所有區域", + "unfoldAllMarkerRegions.label": "展開所有區域", "foldAllAction.label": "全部摺疊", "unfoldAllAction.label": "全部展開", "foldLevelAction.label": "摺疊層級 {0}" diff --git a/i18n/cht/src/vs/platform/environment/node/argv.i18n.json b/i18n/cht/src/vs/platform/environment/node/argv.i18n.json index 09f8c0cfeaf..0874564b4c9 100644 --- a/i18n/cht/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/cht/src/vs/platform/environment/node/argv.i18n.json @@ -16,6 +16,7 @@ "inspect-brk-extensions": "允許對擴展主機在啟動後暫停擴充功能進行除錯和分析。檢查開發工具中的連接 uri。", "reuseWindow": "強制在最近使用的視窗中開啟檔案或資料夾。", "userDataDir": "指定保留使用者資料的目錄,這在以根目錄身分執行時有用。", + "log": "使用的日誌級別。預設為\"訊息\"。允許的值是 \"關鍵\"、\"錯誤\"、\"警告\"、\"訊息\"、\"偵錯\"、\"追蹤\"、\"關閉\"。", "verbose": "列印詳細資訊輸出 (表示 --wait)。", "wait": "等候檔案在傳回前關閉。", "extensionHomePath": "設定擴充功能的根路徑。", diff --git a/i18n/cht/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/cht/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index 36f7fd1af50..3bffab1e21e 100644 --- a/i18n/cht/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/cht/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -18,6 +18,7 @@ "vscode.extension.activationEvents.onCommand": "當指定的命令被調用時激發該事件", "vscode.extension.activationEvents.onDebug": "當使用者正要開始偵錯或是設定偵錯組態時激發該事件", "vscode.extension.activationEvents.onDebugInitialConfigurations": "需要建立 \"launch.json\" 來觸發啟動事件 (並且需要呼叫所有 provideDebugConfigurations 方法)。", + "vscode.extension.activationEvents.onDebugResolve": "需要特定類型偵錯工作階段啟動來觸發啟動事件 (並且呼叫相對應 resolveDebugConfiguration 方法)", "vscode.extension.activationEvents.workspaceContains": "當開啟指定的文件夾包含glob模式匹配的文件時激發該事件", "vscode.extension.activationEvents.onView": "當指定的檢視被擴展時激發該事件", "vscode.extension.activationEvents.star": "當VS Code啟動時激發該事件,為了確保最好的使用者體驗,當您的擴充功能沒有其他組合作業時,請激活此事件.", diff --git a/i18n/cht/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json b/i18n/cht/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json index 45d26626e60..479d20a33a8 100644 --- a/i18n/cht/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json +++ b/i18n/cht/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "largeNumberBadge": "10 k +", "badgeTitle": "{0} - {1}", "additionalViews": "其他檢視", "numberBadge": "{0} ({1})", diff --git a/i18n/cht/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/cht/src/vs/workbench/electron-browser/actions.i18n.json index b7ccbfd6338..daf7e22d21e 100644 --- a/i18n/cht/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/cht/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "如需支援的語言清單,請參閱 {0}。", "restart": "改變設定值後需要重新啟動 VSCode.", "fail.createSettings": "無法建立 '{0}' ({1})。", + "openLogsFolder": "開啟紀錄資料夾", + "showLogs": "顯示紀錄...。", + "mainProcess": "主要", + "sharedProcess": "共用", + "rendererProcess": "轉譯器", + "extensionHost": "延伸主機", + "selectProcess": "選取程序", + "setLogLevel": "設定記錄層級", + "trace": "追蹤", "debug": "偵錯", "info": "資訊", "warn": "警告", - "err": "錯誤" + "err": "錯誤", + "critical": "關鍵", + "off": "關閉", + "selectLogLevel": "選擇日誌級別" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/electron-browser/main.contribution.i18n.json b/i18n/cht/src/vs/workbench/electron-browser/main.contribution.i18n.json index 26b36bafb2e..33e2c22230a 100644 --- a/i18n/cht/src/vs/workbench/electron-browser/main.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/electron-browser/main.contribution.i18n.json @@ -16,6 +16,7 @@ "workbench.editor.labelFormat.long": "顯示檔案的名稱,並在名稱後接著該檔案的絕對路徑。", "tabDescription": "控制編輯器的標籤格式。變更此設定具有多項優點,例如可讓檔案的位置更加清楚:\n-簡短: 'parent'\n-中等: 'workspace/src/parent'\n-完整: '/home/user/workspace/src/parent'\n-預設: '.../parent',當另一個索引標籤共用相同的標題,或若路徑停用,卻共用相關的工作區路徑時", "editorTabCloseButton": "控制編輯器的索引標籤關閉按鈕位置,或在設為 'off' 時將其停用。", + "tabSizing": "控制編輯器索引標籤的大小調整。設定為 \"最適大小\" 可讓索引標籤永遠保持足夠空間來顯示完整的編輯器標籤。設定為 \"縮小\" 可在可用空間不足以同時顯示所有索引標籤時,縮小索引標籤。", "showIcons": "控制開啟的編輯器是否搭配圖示顯示。這需要同時啟用圖示佈景主題。", "enablePreview": "控制已開啟的編輯器是否顯示為預覽。預覽編輯器會重複使用到被保留 (例如按兩下或進行編輯) 並以斜體字型樣式顯示為止。", "enablePreviewFromQuickOpen": "控制透過 Quick Open 所開啟的編輯器是否顯示為預覽。預覽編輯器會重複使用到被保留 (例如按兩下或進行編輯) 為止。", @@ -29,6 +30,7 @@ "statusBarVisibility": "控制 Workbench 底端狀態列的可視性。", "activityBarVisibility": "控制活動列在 workbench 中的可見度。", "closeOnFileDelete": "控制顯示檔案的編輯器是否應在其他處理序刪除或重新命名該檔案時自動關閉。若停用此選項,當發生前述狀況時,編輯器會保持開啟,並呈現已變更的狀態。請注意,從應用程式內刪除一律會關閉編輯器,但已變更的檔案在資料未儲存前一律不會關閉。", + "enableNaturalLanguageSettingsSearch": "控制是否啟用自然語言搜尋模式。", "fontAliasing": "在 Workbench 中控制字型鋸齒化的方法。- 預設: 子像素字型平滑處理。在大部分非 Retina 顯示器上會顯示出最銳利的文字- 已消除鋸齒: 相對於子像素,根據像素層級平滑字型。可讓字型整體顯得較細- 無: 停用字型平滑處理。文字會以鋸齒狀的銳邊顯示 ", "workbench.fontAliasing.default": "子像素字型平滑處理。在大部分非 Retina 顯示器上會顯示出最銳利的文字。", "workbench.fontAliasing.antialiased": "相對於子像素,根據像素層級平滑字型。可以讓字型整體顯得較細。", diff --git a/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index ff4a8ed693f..e0175b5cdfc 100644 --- a/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -20,6 +20,10 @@ "openExplorerOnEnd": "自動於偵錯工作階段結束時開啟總管檢視", "inlineValues": "在偵錯時於編輯器以內嵌方式顯示變數值", "hideActionBar": "控制是否應隱藏浮點偵錯動作列", + "never": "一律不在狀態列顯示偵錯", + "always": "遠用在狀態列中顯示偵錯", + "onFirstSessionStart": "只有第一次啟動偵錯後才在狀態列中顯示偵錯", "showInStatusBar": "控制何時應該顯示偵錯狀態列", + "openDebug": "控制偵錯 viewlet 是否在 debugging session 啟動時開啟。", "launch": "全域偵錯啟動組態。應當做在工作區之間共用的 'launch.json' 替代方案使用" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 4a1b22f7293..4deaf2ddc5d 100644 --- a/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "已新增中斷點,行 {0},檔案 {1}", "breakpointRemoved": "已移除中斷點,行 {0},檔案 {1}", "compoundMustHaveConfigurations": "複合必須設有 \"configurations\" 屬性,才能啟動多個組態。", + "configMissing": "'launch.json' 中遺漏組態 '{0}'。", + "launchJsonDoesNotExist": "'launch.json' 不存在。", "debugRequestNotSupported": "在選取的偵錯組態中,屬性 `{0}` 具有不支援的值 '{1}'。", "debugRequesMissing": "所選的偵錯組態遺漏屬性 '{0}'。", "debugTypeNotSupported": "不支援設定的偵錯類型 '{0}'。", diff --git a/i18n/cht/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/cht/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..64f3c6488bc 100644 --- a/i18n/cht/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "由 {0} 使用者評等", + "ratedBySingleUser": "由 1 位使用者評等" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..eb4eee28be7 100644 --- a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "按一下以停止性能分析。" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 67a25ef1452..aaea0f3774b 100644 --- a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "管理擴充功能", "galleryExtensionsCommands": "安裝資源庫擴充功能", "extension": "擴充功能", + "runtimeExtension": "正在執行擴充功能", "extensions": "擴充功能", "view": "檢視", "developer": "開發人員", "extensionsConfigurationTitle": "擴充功能", - "extensionsAutoUpdate": "自動更新擴充功能" + "extensionsAutoUpdate": "自動更新擴充功能", + "extensionsIgnoreRecommendations": "如果設定為 true,擴充功能建議通知將停止顯示。" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index 8ab5d602c3a..be9d2e3a838 100644 --- a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "回報問題" + "starActivation": "在開始時啟動", + "workspaceContainsGlobActivation": "已啟動,因為與 {0} 相符的檔案存在您的工作區中", + "workspaceContainsFileActivation": "已啟動,因為檔案 {0} 存在您的工作區中", + "languageActivation": "已啟動,因為您打開了 {0} 檔案", + "workspaceGenericActivation": "已在 {0} 上啟動", + "errors": "{0} 未攔截錯誤", + "extensionsInputName": "正在執行擴充功能", + "showRuntimeExtensions": "顯示正在執行的擴充功能", + "reportExtensionIssue": "回報問題", + "extensionHostProfileStart": "啟動延伸主機設定檔", + "extensionHostProfileStop": "停止延伸主機設定檔", + "saveExtensionHostProfile": "儲存延伸主機設定檔" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json b/i18n/cht/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json index 8b6ad71cd4e..898e5d9f617 100644 --- a/i18n/cht/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "totalProblems": "共 {0} 項問題", + "filteredProblems": "顯示 {1} 的 {0} 問題" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/markers/common/messages.i18n.json b/i18n/cht/src/vs/workbench/parts/markers/common/messages.i18n.json index af077309e72..7fd5ffaf852 100644 --- a/i18n/cht/src/vs/workbench/parts/markers/common/messages.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/markers/common/messages.i18n.json @@ -6,6 +6,7 @@ { "viewCategory": "檢視", "problems.view.toggle.label": "切換問題", + "problems.view.focus.label": "聚焦問題", "problems.panel.configuration.title": "[問題] 檢視", "problems.panel.configuration.autoreveal": "控制 [問題] 檢視是否應自動在開啟檔案時加以顯示", "markers.panel.title.problems": "問題", diff --git a/i18n/cht/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json b/i18n/cht/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json index 1d840c42cf9..450c57bf7a0 100644 --- a/i18n/cht/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defineKeybinding.initial": "按下所需按鍵組合,然後按 ENTER。", "defineKeybinding.chordsTo": "同步到" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json b/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json index 21867210659..f81891ad4c2 100644 --- a/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "openRawDefaultSettings": "開啟原始預設設置", "openGlobalSettings": "開啟使用者設定", "openGlobalKeybindings": "開啟鍵盤快速鍵", "openGlobalKeybindingsFile": "開啟鍵盤快速鍵檔案", diff --git a/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json b/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json index 5c3acf7c7d6..480c3d2dbc2 100644 --- a/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defaultSettingsFuzzyPrompt": "試試自然語言搜尋!", "defaultSettings": "將您的設定放置於右方編輯器中以覆寫。", "noSettingsFound": "找不到任何設定。", "settingsSwitcherBarAriaLabel": "設定切換器", "userSettings": "使用者設定", "workspaceSettings": "工作區設定", - "folderSettings": "資料夾設定" + "folderSettings": "資料夾設定", + "enableFuzzySearch": "啟用自然語言搜尋" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json index 9ec03d55932..c5e55c9814a 100644 --- a/i18n/cht/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json @@ -51,6 +51,7 @@ "TaslService.noEntryToRun": "找不到任何要執行的工作。請設定工作...", "TaskService.fetchingBuildTasks": "正在擷取組建工作...", "TaskService.pickBuildTask": "請選取要執行的組建工作", + "TaskService.noBuildTask": "找不到任何要執行的組建工作。請設定建置工作...", "TaskService.fetchingTestTasks": "正在擷取測試工作...", "TaskService.pickTestTask": "請選取要執行的測試工作", "TaskService.noTestTaskTerminal": "找不到任何要執行的測試工作。請設定工作...", diff --git a/i18n/cht/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json b/i18n/cht/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json index 381d6bbb407..6f6536eb134 100644 --- a/i18n/cht/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json @@ -14,6 +14,8 @@ "ConfigurationParser.noTypeDefinition": "錯誤: 沒有已註冊工作類型 '{0}'。您是否忘記安裝提供相應工作提供者的延伸模組?", "ConfigurationParser.missingRequiredProperty": "錯誤: 工作組態 '{0}' 缺少要求的屬性 '{1}'。會略過工作組態。", "ConfigurationParser.notCustom": "錯誤: 未將工作宣告為自訂工作。將會忽略該組態。\n{0}\n", + "ConfigurationParser.noTaskName": "錯誤: 一項工作必須提供標籤屬性。即將忽略此工作。\n{0}\n", + "taskConfiguration.shellArgs": "警告: 工作 '{0}' 是 shell 命令 ,其中一個引數可能有未逸出的空格。若要確保命令列正確引述,請將引數合併到命令中。", "taskConfiguration.noCommandOrDependsOn": "錯誤: 工作 '{0}' 未指定命令與 dependsOn 屬性。將會略過該工作。其定義為: \n{1}", "taskConfiguration.noCommand": "錯誤: 工作 '{0}' 未定義命令。即將略過該工作。其定義為:\n{1}", "TaskParse.noOsSpecificGlobalTasks": "工作版本 2.0.0 不支援全域 OS 特定工作。請使用 OS 特定命令來轉換這些工作。受影響的工作為:\n{0}" diff --git a/i18n/cht/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json index a6aa09a9aa9..80abe8bb59c 100644 --- a/i18n/cht/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json @@ -16,6 +16,7 @@ "terminal.integrated.rightClickCopyPaste": "如有設定,這會防止在終端機內按滑鼠右鍵時顯示操作功能表,而是在有選取項目時複製、沒有選取項目時貼上。", "terminal.integrated.fontFamily": "控制終端機的字型家族,預設為 editor.fontFamily 的值。", "terminal.integrated.fontSize": "控制終端機的字型大小 (以像素為單位)。", + "terminal.integrated.lineHeight": "控制終端機的行高,此數字會乘上終端機字型大小,以取得以像素為單位的實際行高。", "terminal.integrated.enableBold": "是否要在終端機中啟用粗體文字,請注意,此動作須有終端機殼層的支援。", "terminal.integrated.cursorBlinking": "控制終端機資料指標是否閃爍。", "terminal.integrated.cursorStyle": "控制終端機資料指標的樣式。", diff --git a/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index c2b6d2e3885..86e37b541ca 100644 --- a/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -7,6 +7,7 @@ "selectTheme.label": "色彩佈景主題", "themes.category.light": "淺色主題", "themes.category.dark": "深色主題", + "themes.category.hc": "高對比主題", "installColorThemes": "安裝其他的色彩佈景主題...", "themes.selectTheme": "選取色彩主題(上/下鍵預覽)", "selectIconTheme.label": "檔案圖示佈景主題", diff --git a/i18n/cht/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json b/i18n/cht/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json index 8b6ad71cd4e..40b81b62803 100644 --- a/i18n/cht/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json +++ b/i18n/cht/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "bubbleTitle": "包含強調項目" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..178e97d6b83 100644 --- a/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) 分析延伸主機..." +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index e9e580ceb96..86661838eb1 100644 --- a/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/cht/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "延伸主機意外終止。", "extensionHostProcess.unresponsiveCrash": "因為延伸主機沒有回應,所以意外終止。", "overwritingExtension": "正在以 {1} 覆寫延伸模組 {0}。", - "extensionUnderDevelopment": "正在載入位於 {0} 的開發延伸模組" + "extensionUnderDevelopment": "正在載入位於 {0} 的開發延伸模組", + "extensionCache.invalid": "擴充功能在磁碟上已修改。請重新載入視窗。" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json b/i18n/cht/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json index 6cf70a55716..3d15720ddec 100644 --- a/i18n/cht/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json +++ b/i18n/cht/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json @@ -21,5 +21,6 @@ "keybindings.json.command": "所要執行命令的名稱", "keybindings.json.when": "按鍵為使用中時的條件。", "keybindings.json.args": "要傳遞至命令加以執行的引數。", - "keyboardConfigurationTitle": "鍵盤" + "keyboardConfigurationTitle": "鍵盤", + "dispatch": "控制按下按鍵時的分派邏輯 (使用 'code' (建議使用) 或 'keyCode')。" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/services/textfile/common/textFileService.i18n.json b/i18n/cht/src/vs/workbench/services/textfile/common/textFileService.i18n.json index 8b6ad71cd4e..83d4c0bd31e 100644 --- a/i18n/cht/src/vs/workbench/services/textfile/common/textFileService.i18n.json +++ b/i18n/cht/src/vs/workbench/services/textfile/common/textFileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "files.backup.failSave": "檔案變更無法寫入備份區域 (錯誤: {0})。請嘗試儲存您的檔案後結束。" +} \ No newline at end of file diff --git a/i18n/deu/extensions/css/client/out/cssMain.i18n.json b/i18n/deu/extensions/css/client/out/cssMain.i18n.json index 139e2e24da4..254ee717d96 100644 --- a/i18n/deu/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/deu/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "CSS-Sprachserver" + "cssserver.name": "CSS-Sprachserver", + "folding.start": "Regionsanfang wird gefaltet", + "folding.end": "Regionsende wird gefaltet" } \ No newline at end of file diff --git a/i18n/deu/extensions/emmet/package.i18n.json b/i18n/deu/extensions/emmet/package.i18n.json index 0ad73e29fcc..079d0046ed5 100644 --- a/i18n/deu/extensions/emmet/package.i18n.json +++ b/i18n/deu/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Modifizierertrennzeichen für Klassen unter Verwendung des BEM-Filters", "emmetPreferencesFilterCommentBefore": "Eine Kommentardefinition, die vor dem abgeglichenen Element platziert werden muss, wenn ein Kommentarfilter angewendet wird.", "emmetPreferencesFilterCommentAfter": "Eine Kommentardefinition, die nach dem abgeglichenen Element platziert werden muss, wenn ein Kommentarfilter angewendet wird.", - "emmetPreferencesFilterCommentTrigger": "Eine durch Trennzeichen getrennte Liste von Attributnamen, die in abgekürzter Form vorliegen müssen, damit der Kommentarfilter angewendet werden kann" + "emmetPreferencesFilterCommentTrigger": "Eine durch Trennzeichen getrennte Liste von Attributnamen, die in abgekürzter Form vorliegen müssen, damit der Kommentarfilter angewendet werden kann", + "emmetPreferencesFormatNoIndentTags": "Ein Array von Tagnamen, die keinen inneren Einzug erhalten", + "emmetPreferencesFormatForceIndentTags": "Ein Array von Tagnamen, die immer einen inneren Einzug erhalten", + "emmetPreferencesAllowCompactBoolean": "Bei TRUE wird eine kompakte Notation boolescher Attribute erzeugt" } \ No newline at end of file diff --git a/i18n/deu/extensions/git/out/autofetch.i18n.json b/i18n/deu/extensions/git/out/autofetch.i18n.json index 304f70941e2..e1242008fcf 100644 --- a/i18n/deu/extensions/git/out/autofetch.i18n.json +++ b/i18n/deu/extensions/git/out/autofetch.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "yes": "Ja", - "no": "Nein" + "no": "Nein", + "not now": "Nicht jetzt", + "suggest auto fetch": "Möchten Sie das automatische Abrufen von Git-Repositorys aktivieren?" } \ No newline at end of file diff --git a/i18n/deu/extensions/git/out/commands.i18n.json b/i18n/deu/extensions/git/out/commands.i18n.json index 9f89fe2f662..27787e3b3d4 100644 --- a/i18n/deu/extensions/git/out/commands.i18n.json +++ b/i18n/deu/extensions/git/out/commands.i18n.json @@ -14,6 +14,7 @@ "cloning": "Git-Repository wird geklont...", "openrepo": "Repository öffnen", "proposeopen": "Möchten Sie das geklonte Repository öffnen?", + "init": "Arbeitsbereichsordner auswählen, in dem das Git-Repository initialisiert wird", "init repo": "Repository initialisieren", "create repo": "Repository initialisieren", "are you sure": "Erstellt ein Git-Repository unter '{0}'. Sind Sie sicher das Sie weiterfahren möchten?", @@ -59,6 +60,7 @@ "provide tag name": "Geben Sie einen Tagnamen an.", "tag message": "Nachricht", "provide tag message": "Geben Sie eine Meldung ein, um das Tag mit einer Anmerkung zu versehen.", + "no remotes to fetch": "In diesem Repository wurden keine Remoteelemente konfiguriert, aus denen ein Abrufen erfolgt.", "no remotes to pull": "In Ihrem Repository wurden keine Remoteelemente für den Pull konfiguriert.", "pick remote pull repo": "Remoteelement zum Pullen des Branch auswählen", "no remotes to push": "In Ihrem Repository wurden keine Remoteelemente für den Push konfiguriert.", @@ -75,6 +77,7 @@ "no stashes": "Es ist kein Stash zum Wiederherstellen vorhanden.", "pick stash to pop": "Wählen Sie einen Stash aus, für den ein Pop ausgeführt werden soll.", "clean repo": "Bereinigen Sie Ihre Repository-Arbeitsstruktur vor Auftragsabschluss.", + "cant push": "Verweise können nicht per Push an einen Remotespeicherort übertragen werden. Führen Sie zuerst \"Pull\" aus, um Ihre Änderungen zu integrieren.", "git error details": "Git: {0}", "git error": "Git-Fehler", "open git log": "Git-Protokoll öffnen" diff --git a/i18n/deu/extensions/git/out/main.i18n.json b/i18n/deu/extensions/git/out/main.i18n.json index 17a3a6ed127..49edb1c1faa 100644 --- a/i18n/deu/extensions/git/out/main.i18n.json +++ b/i18n/deu/extensions/git/out/main.i18n.json @@ -6,7 +6,9 @@ { "looking": "Suchen nach Git in: {0}", "using git": "Verwenden von Git {0} von {1}", + "downloadgit": "Git herunterladen", "neverShowAgain": "Nicht mehr anzeigen", + "notfound": "Git nicht gefunden. Installieren Sie es, oder konfigurieren Sie es mithilfe der Einstellung \"git.path\".", "updateGit": "Git aktualisieren", "git20": "Sie haben anscheinend Git {0} installiert. Code funktioniert am besten mit Git 2 oder neuer" } \ No newline at end of file diff --git a/i18n/deu/extensions/git/package.i18n.json b/i18n/deu/extensions/git/package.i18n.json index 88964f86644..de4d841dd21 100644 --- a/i18n/deu/extensions/git/package.i18n.json +++ b/i18n/deu/extensions/git/package.i18n.json @@ -36,6 +36,7 @@ "command.renameBranch": "Branch umbenennen...", "command.merge": "Branch zusammenführen...", "command.createTag": "Tag erstellen", + "command.fetch": "Abrufen", "command.pull": "Pull", "command.pullRebase": "Pull (Rebase)", "command.pullFrom": "Pullen von...", @@ -47,6 +48,7 @@ "command.publish": "Branch veröffentlichen", "command.showOutput": "Git-Ausgabe anzeigen", "command.ignore": "Datei zu .gitignore hinzufügen", + "command.stashIncludeUntracked": "Stash (einschließlich nicht verfolgt)", "command.stash": " Stash ausführen", "command.stashPop": "Pop für Stash ausführen...", "command.stashPopLatest": "Pop für letzten Stash ausführen", @@ -59,6 +61,7 @@ "config.countBadge": "Steuert die Git-Badgeanzahl. \"Alle\" zählt alle Änderungen. \"tracked\" (Nachverfolgt) zählt nur die nachverfolgten Änderungen. \"off\" (Aus) deaktiviert dies.", "config.checkoutType": "Steuert, welcher Branchtyp beim Ausführen von \"Auschecken an...\" aufgelistet wird. \"Alle\" zeigt alle Verweise an, \"Lokal\" nur die lokalen Branches, \"Tags\" zeigt nur Tags an, und \"Remote\" zeigt nur Remotebranches an.", "config.ignoreLegacyWarning": "Ignoriert die Legacy-Git-Warnung.", + "config.ignoreMissingGitWarning": "Ignoriert die Warnung, wenn Git fehlt", "config.ignoreLimitWarning": "Ignoriert Warnung bei zu hoher Anzahl von Änderungen in einem Repository", "config.defaultCloneDirectory": "Das Standard-Verzeichnis für einen Klon eines Git-Repositorys", "config.enableSmartCommit": "Alle Änderungen committen, wenn keine bereitgestellten Änderungen vorhanden sind.", diff --git a/i18n/deu/extensions/html/client/out/htmlMain.i18n.json b/i18n/deu/extensions/html/client/out/htmlMain.i18n.json index b2bef37b9bd..c3c7dcb8f4e 100644 --- a/i18n/deu/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/deu/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "HTML-Sprachserver" + "htmlserver.name": "HTML-Sprachserver", + "folding.start": "Regionsanfang wird gefaltet", + "folding.end": "Regionsende wird gefaltet" } \ No newline at end of file diff --git a/i18n/deu/extensions/markdown/out/security.i18n.json b/i18n/deu/extensions/markdown/out/security.i18n.json index 70533ed2e5d..cd3ce408e9b 100644 --- a/i18n/deu/extensions/markdown/out/security.i18n.json +++ b/i18n/deu/extensions/markdown/out/security.i18n.json @@ -11,5 +11,8 @@ "disable.title": "Deaktivieren", "disable.description": "Alle Inhalte und Skriptausführung zulassen. Nicht empfohlen.", "moreInfo.title": "Weitere Informationen", + "enableSecurityWarning.title": "Vorschau von Sicherheitswarnungen in diesem Arbeitsbereich aktivieren", + "disableSecurityWarning.title": "Vorschau von Sicherheitswarnungen in diesem Arbeitsbereich deaktivieren ", + "toggleSecurityWarning.description": "Kein Einfluss auf Inhalt Sicherheitsebene", "preview.showPreviewSecuritySelector.title": "Sicherheitseinstellungen für die Markdown-Vorschau in diesem Arbeitsbereich auswählen" } \ No newline at end of file diff --git a/i18n/deu/extensions/merge-conflict/package.i18n.json b/i18n/deu/extensions/merge-conflict/package.i18n.json index 5bbe54989dc..bc7d0cf1cf7 100644 --- a/i18n/deu/extensions/merge-conflict/package.i18n.json +++ b/i18n/deu/extensions/merge-conflict/package.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "command.category": "Merge-Konflikt", + "command.accept.all-current": "Alle aktuellen akzeptieren", "command.accept.all-incoming": "Alle eingehenden akzeptieren", "command.accept.all-both": "Alle beide akzeptieren", "command.accept.current": "Aktuelles akzeptieren", diff --git a/i18n/deu/extensions/typescript/out/features/completionItemProvider.i18n.json b/i18n/deu/extensions/typescript/out/features/completionItemProvider.i18n.json index 87035b7816f..fceb3f1c9e0 100644 --- a/i18n/deu/extensions/typescript/out/features/completionItemProvider.i18n.json +++ b/i18n/deu/extensions/typescript/out/features/completionItemProvider.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "selectCodeAction": "Anzuwendende Codeaktion auswählen", "acquiringTypingsLabel": "Eingaben werden abgerufen...", "acquiringTypingsDetail": "Eingabedefinitionen für IntelliSense werden abgerufen.", "autoImportLabel": "Automatischer Import von {0}" diff --git a/i18n/deu/extensions/typescript/package.i18n.json b/i18n/deu/extensions/typescript/package.i18n.json index 11769f91e3f..baf9f4497a7 100644 --- a/i18n/deu/extensions/typescript/package.i18n.json +++ b/i18n/deu/extensions/typescript/package.i18n.json @@ -39,6 +39,7 @@ "typescript.openTsServerLog.title": "TS Server-Protokolldatei öffnen", "typescript.restartTsServer": "TS Server neu starten", "typescript.selectTypeScriptVersion.title": "TypeScript-Version wählen", + "typescript.reportStyleChecksAsWarnings": "Formatvorlagenprüfungen als Warnungen melden", "jsDocCompletion.enabled": "Automatische JSDoc-Kommentare aktivieren/deaktivieren", "javascript.implicitProjectConfig.checkJs": "Aktiviert/deaktiviert die Semantikprüfung bei JavaScript-Dateien. Diese Einstellung wird von vorhandenen \"jsconfig.json\"- oder \"tsconfig.json\"-Dateien außer Kraft gesetzt. Erfordert TypeScript 2.3.1 oder höher.", "typescript.npm": "Gibt den Pfad zur ausführbaren NPM-Datei an, die für die automatische Typerfassung verwendet wird. Hierfür ist TypeScript 2.3.4 oder höher erforderlich.", diff --git a/i18n/deu/src/vs/code/electron-main/main.i18n.json b/i18n/deu/src/vs/code/electron-main/main.i18n.json index b88bee0ef1d..2a8df2c0128 100644 --- a/i18n/deu/src/vs/code/electron-main/main.i18n.json +++ b/i18n/deu/src/vs/code/electron-main/main.i18n.json @@ -4,5 +4,9 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "secondInstanceNoResponse": "Eine andere Instanz von {0} läuft, reagiert aber nicht", + "secondInstanceNoResponseDetail": "Bitte schließen Sie alle anderen Instanzen, und versuchen Sie es erneut.", + "secondInstanceAdmin": "Eine zweite Instanz von {0} wird bereits als Administrator ausgeführt.", + "secondInstanceAdminDetail": "Bitte schließen Sie die andere Instanz, und versuchen Sie es erneut.", "close": "&&Schließen" } \ No newline at end of file diff --git a/i18n/deu/src/vs/code/electron-main/menus.i18n.json b/i18n/deu/src/vs/code/electron-main/menus.i18n.json index 8dcc363ec8e..edb7ae515e1 100644 --- a/i18n/deu/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/deu/src/vs/code/electron-main/menus.i18n.json @@ -22,10 +22,12 @@ "miQuit": "{0} beenden", "miNewFile": "&&Neue Datei", "miOpen": "&&Öffnen...", + "miOpenWorkspace": "Ar&&beitsbereich öffnen...", "miOpenFolder": "&&Ordner öffnen...", "miOpenFile": "Datei &&öffnen...", "miOpenRecent": "&&Zuletzt verwendete öffnen", "miSaveWorkspaceAs": "Arbeitsbereich speichern unter...", + "miAddFolderToWorkspace": "Or&&dner zu Arbeitsbereich hinzufügen...", "miSave": "&&Speichern", "miSaveAs": "Speichern &&unter...", "miSaveAll": "A&&lles speichern", @@ -155,6 +157,7 @@ "mMergeAllWindows": "Alle Fenster zusammenführen", "miToggleDevTools": "&&Entwicklertools umschalten", "miAccessibilityOptions": "&&Optionen für erleichterte Bedienung", + "miReportIssue": "&&Problem melden", "miWelcome": "&&Willkommen", "miInteractivePlayground": "&&Interactive Spielwiese", "miDocumentation": "&&Dokumentation", @@ -181,6 +184,7 @@ "miDownloadingUpdate": "Das Update wird heruntergeladen...", "miInstallingUpdate": "Update wird installiert...", "miCheckForUpdates": "Nach Aktualisierungen suchen...", + "aboutDetail": "\nVersion {0}\nCommit {1}\nDatum {2}\nShell {3}\nRenderer {4}\nNode {5}\nArchitektur {6}", "okButton": "OK", "copy": "&&Kopieren" } \ No newline at end of file diff --git a/i18n/deu/src/vs/code/node/cliProcessMain.i18n.json b/i18n/deu/src/vs/code/node/cliProcessMain.i18n.json index 75e98b75983..27bf37c7820 100644 --- a/i18n/deu/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/deu/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "Die Erweiterung \"{0}\" ist nicht installiert.", "useId": "Stellen Sie sicher, dass Sie die vollständige Erweiterungs-ID (einschließlich des Herausgebers) verwenden. Beispiel: {0}", "successVsixInstall": "Die Erweiterung \"{0}\" wurde erfolgreich installiert.", + "cancelVsixInstall": "Installation der Erweiterung \"{0}\" abgebrochen.", "alreadyInstalled": "Die Erweiterung \"{0}\" ist bereits installiert.", "foundExtension": "\"{0}\" wurde in Marketplace gefunden.", "installing": "Wird installiert...", diff --git a/i18n/deu/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/deu/src/vs/editor/common/config/commonEditorConfig.i18n.json index 791032db29a..42d65e250ff 100644 --- a/i18n/deu/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/deu/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -17,7 +17,9 @@ "lineNumbers": "Steuert die Anzeige von Zeilennummern. Mögliche Werte sind \"Ein\", \"Aus\" und \"Relativ\".", "rulers": "Vertikale Linien nach einer bestimmten Anzahl von Monospace Zeichen zeichnen. Verwenden Sie mehrere Werte für mehrere Linien. Keine Linie wird gezeichnet, wenn das Array leer ist.", "wordSeparators": "Zeichen, die als Worttrennzeichen verwendet werden, wenn wortbezogene Navigationen oder Vorgänge ausgeführt werden.", + "tabSize": "Die Anzahl der Leerzeichen, denen ein Tabstopp entspricht. Diese Einstellung wird basierend auf dem Inhalt der Datei überschrieben, wenn \"editor.detectIndentation\" aktiviert ist.", "tabSize.errorMessage": "\"number\" wurde erwartet. Beachten Sie, dass der Wert \"auto\" durch die Einstellung \"editor.detectIndentation\" ersetzt wurde.", + "insertSpaces": "Fügt beim Drücken der TAB-TASTE Leerzeichen ein. Diese Einstellung wird basierend auf dem Inhalt der Datei überschrieben, wenn \"editor.detectIndentation\" aktiviert ist.", "insertSpaces.errorMessage": "\"boolean\" wurde erwartet. Beachten Sie, dass der Wert \"auto\" durch die Einstellung \"editor.detectIndentation\" ersetzt wurde.", "detectIndentation": "Beim Öffnen einer Datei werden \"editor.tabSize\" und \"editor.insertSpaces\" basierend auf den Dateiinhalten erkannt.", "roundedSelection": "Steuert, ob die Auswahl runde Ecken aufweist.", @@ -29,6 +31,7 @@ "minimap.maxColumn": "Breite der Minikarte beschränken, um höchstens eine bestimmte Anzahl von Spalten zu rendern", "find.seedSearchStringFromSelection": "Steuert, ob wir für die Suchzeichenfolge im Suchwidget aus der Editorauswahl ein Seeding ausführen.", "find.autoFindInSelection": "Steuert, ob die Kennzeichnung \"In Auswahl suchen\" aktiviert ist, wenn mehrere Zeichen oder Textzeilen im Editor ausgewählt wurden.", + "find.globalFindClipboard": "Steuert, ob das Widget Suchen lesen oder die gemeinsame Such-Zwischenablage im MacOS ändern soll.", "wordWrap.off": "Zeilenumbrüche erfolgen nie.", "wordWrap.on": "Der Zeilenumbruch erfolgt an der Breite des Anzeigebereichs.", "wordWrap.wordWrapColumn": "Der Zeilenbereich erfolgt bei \"editor.wordWrapColumn\".", diff --git a/i18n/deu/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/deu/src/vs/editor/contrib/folding/folding.i18n.json index 113379281fd..4e3ffb44a69 100644 --- a/i18n/deu/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/deu/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "Falten", "foldRecursivelyAction.label": "Rekursiv falten", "foldAllBlockComments.label": "Alle Blockkommentare falten", + "foldAllMarkerRegions.label": "Alle Regionen falten", + "unfoldAllMarkerRegions.label": "Alle Regionen auffalten", "foldAllAction.label": "Alle falten", "unfoldAllAction.label": "Alle auffalten", "foldLevelAction.label": "Faltebene {0}" diff --git a/i18n/deu/src/vs/platform/environment/node/argv.i18n.json b/i18n/deu/src/vs/platform/environment/node/argv.i18n.json index 25a0dca34be..e990ea9bf1e 100644 --- a/i18n/deu/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/deu/src/vs/platform/environment/node/argv.i18n.json @@ -12,8 +12,11 @@ "newWindow": "Erzwingt eine neue Instanz des Codes.", "performance": "Startet mit aktiviertem Befehl \"Developer: Startup Performance\".", "prof-startup": "CPU-Profiler beim Start ausführen", + "inspect-extensions": "Erlaubt Debugging und Profiling für Erweiterungen. Überprüfen Sie die Entwicklertools für die Verbindungs-URI.", + "inspect-brk-extensions": "Erlaubt Debugging und Profiling für Erweiterungen, wobei der Erweiterungs-Host nach dem Starten pausiert wird. Überprüfen Sie die Entwicklertools für die Verbindungs-URI.", "reuseWindow": "Erzwingt das Öffnen einer Datei oder eines Ordners im letzten aktiven Fenster.", "userDataDir": "Gibt das Verzeichnis an, in dem Benutzerdaten gespeichert werden. Nützlich, wenn die Ausführung als \"root\" erfolgt.", + "log": "Log-Level zu verwenden. Standardwert ist \"Info\". Zulässige Werte sind \"kritisch\", \"Fehler\", \"warnen\", \"Info\", \"debug\", \"verfolgen\", \"aus\".", "verbose": "Ausführliche Ausgabe (impliziert \"-wait\").", "wait": "Warten Sie, bis die Dateien geschlossen sind, bevor Sie zurück gehen können.", "extensionHomePath": "Legen Sie den Stammpfad für Extensions fest.", @@ -24,6 +27,7 @@ "experimentalApis": "Aktiviert vorgeschlagene API-Features für eine Erweiterung.", "disableExtensions": "Deaktiviert alle installierten Extensions.", "disableGPU": "Deaktiviert die GPU-Hardwarebeschleunigung.", + "status": "Prozessnutzungs- und Diagnose-Informationen ausgeben.", "version": "Gibt die Version aus.", "help": "Gibt die Syntax aus.", "usage": "Verwendung", diff --git a/i18n/deu/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json b/i18n/deu/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json index 185a438a129..256756a3fdd 100644 --- a/i18n/deu/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json +++ b/i18n/deu/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json @@ -6,8 +6,12 @@ { "invalidManifest": "Die Erweiterung ist ungültig: \"package.json\" ist keine JSON-Datei.", "restartCodeLocal": "Bitte starten Sie Code vor der Neuinstallation von {0} neu.", + "installingOutdatedExtension": "Eine neuere Version dieser Erweiterung ist bereits installiert. Möchten Sie diese mit der älteren Version überschreiben?", "override": "Überschreiben", "cancel": "Abbrechen", + "notFoundCompatible": "Kann nicht installiert werden, da die Erweiterung '{0}', die mit der aktuellen Version '{1}' von VS Code kompatibel ist, nicht gefunden werden kann.", + "quitCode": "Kann nicht installiert werden, da noch eine veraltete Instanz der Erweiterung ausgeführt wird. Bitte beenden und VS Code neu starten vor der Neuinstallation.", + "exitCode": "Kann nicht installiert werden, da noch eine veraltete Instanz der Erweiterung ausgeführt wird. Bitte beenden und VS Code neu starten vor der Neuinstallation.", "notFoundCompatibleDependency": "Kann nicht installiert werden, da die abhängige Erweiterung '{0}', die mit der aktuellen VS Code Version '{1}' kompatibel ist, nicht gefunden werden kann. ", "uninstallDependeciesConfirmation": "Möchten Sie nur \"{0}\" oder auch die zugehörigen Abhängigkeiten deinstallieren?", "uninstallOnly": "Nur", diff --git a/i18n/deu/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/deu/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index e0774394ce8..0e38f23ff50 100644 --- a/i18n/deu/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/deu/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -17,6 +17,8 @@ "vscode.extension.activationEvents.onLanguage": "Ein Aktivierungsereignis wird beim Öffnen einer Datei ausgegeben, die in die angegebene Sprache aufgelöst wird.", "vscode.extension.activationEvents.onCommand": "Ein Aktivierungsereignis wird beim Aufrufen des angegebenen Befehls ausgegeben.", "vscode.extension.activationEvents.onDebug": "Ein Aktivierungsereignis wird ausgesandt, wenn ein Benutzer eine Debugging startet, oder eine Debug-Konfiguration erstellt.", + "vscode.extension.activationEvents.onDebugInitialConfigurations": "Ein Aktivierungsereignis ausgegeben, wenn ein \"launch.json\" erstellt werden muss (und alle provideDebugConfigurations Methoden aufgerufen werden müssen).", + "vscode.extension.activationEvents.onDebugResolve": "Ein Aktivierungsereignis ausgegeben, wenn eine Debug-Sitzung mit dem spezifischen Typ gestartet wird (und eine entsprechende resolveDebugConfiguration-Methode aufgerufen werden muss).", "vscode.extension.activationEvents.workspaceContains": "Ein Aktivierungsereignis wird beim Öffnen eines Ordners ausgegeben, der mindestens eine Datei enthält, die mit dem angegebenen Globmuster übereinstimmt.", "vscode.extension.activationEvents.onView": "Ein Aktivierungsereignis wird beim Erweitern der angegebenen Ansicht ausgegeben.", "vscode.extension.activationEvents.star": "Ein Aktivierungsereignis wird beim Start von VS Code ausgegeben. Damit für die Endbenutzer eine bestmögliche Benutzerfreundlichkeit sichergestellt ist, verwenden Sie dieses Aktivierungsereignis in Ihrer Erweiterung nur dann, wenn in Ihrem Anwendungsfall keine andere Kombination an Aktivierungsereignissen funktioniert.", diff --git a/i18n/deu/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json b/i18n/deu/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json index c0039e0b445..b1c16b877d2 100644 --- a/i18n/deu/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json +++ b/i18n/deu/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "toggleTabs": "Registerkartensichtbarkeit umschalten", "view": "Anzeigen" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json b/i18n/deu/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json index ced21f95b89..22d3bb268e9 100644 --- a/i18n/deu/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json +++ b/i18n/deu/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "largeNumberBadge": "10k+", "badgeTitle": "{0} - {1}", "additionalViews": "Zusätzliche Ansichten", "numberBadge": "{0} ({1})", diff --git a/i18n/deu/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json b/i18n/deu/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json index 963d490097f..2b4ef55801b 100644 --- a/i18n/deu/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json +++ b/i18n/deu/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json @@ -6,6 +6,7 @@ { "editorCommand.activeEditorMove.description": "Aktiven Editor nach Tabstopps oder Gruppen verschieben", "editorCommand.activeEditorMove.arg.name": "Argument zum Verschieben des aktiven Editors", + "editorCommand.activeEditorMove.arg.description": "Argumenteigenschaften:\n\t* \"to\": Ein Zeichenfolgenwert, der das Ziel des Verschiebungsvorgangs angibt.\n\t* \"by\": Ein Zeichenfolgenwert, der die Einheit für die Verschiebung angibt (nach Registerkarte oder nach Gruppe).\n\t* \"value\": Ein Zahlenwert, der angibt, um wie viele Positionen verschoben wird. Es kann auch die absolute Position für die Verschiebung angegeben werden.\n", "commandDeprecated": "Der Befehl **{0}** wurde entfernt. Sie können stattdessen **{1}** verwenden.", "openKeybindings": "Tastenkombinationen konfigurieren" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json b/i18n/deu/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json index 56317ca59e3..afbb07198da 100644 --- a/i18n/deu/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json +++ b/i18n/deu/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json @@ -52,5 +52,6 @@ "screenReaderDetectedExplanation.question": "Verwenden Sie eine Sprachausgabe zum Bedienen von VS Code?", "screenReaderDetectedExplanation.answerYes": "Ja", "screenReaderDetectedExplanation.answerNo": "Nein", - "screenReaderDetectedExplanation.body1": "VS Code ist jetzt für die Verwendung mit einer Sprachausgabe optimiert. " + "screenReaderDetectedExplanation.body1": "VS Code ist jetzt für die Verwendung mit einer Sprachausgabe optimiert. ", + "screenReaderDetectedExplanation.body2": "Einige Editorfunktionen weisen ein anderes Verhalten auf, z. B. in Bezug auf den Zeilenumbruch, die Faltung usw." } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/deu/src/vs/workbench/electron-browser/actions.i18n.json index 9c274f7fb04..7331216a594 100644 --- a/i18n/deu/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/deu/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "Unter {0} finden Sie eine Liste der unterstützten Sprachen.", "restart": "Das Ändern dieses Wertes erfordert einen Neustart von VSCode.", "fail.createSettings": "{0} ({1}) kann nicht erstellt werden.", + "openLogsFolder": "Protokollordner öffnen", + "showLogs": "Protokolle anzeigen...", + "mainProcess": "Main", + "sharedProcess": "Geteilt", + "rendererProcess": "Renderer", + "extensionHost": "Erweiterungshost", + "selectProcess": "Prozess auswählen", + "setLogLevel": "Protokollstufe festlegen", + "trace": "Spur", "debug": "Debuggen", "info": "Info", "warn": "Warnung", - "err": "Fehler" + "err": "Fehler", + "critical": "Kritisch", + "off": "Aus", + "selectLogLevel": "Protokollstufe auswählen" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/electron-browser/main.contribution.i18n.json b/i18n/deu/src/vs/workbench/electron-browser/main.contribution.i18n.json index f90e322ae5f..2a2af0f320b 100644 --- a/i18n/deu/src/vs/workbench/electron-browser/main.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/electron-browser/main.contribution.i18n.json @@ -16,6 +16,7 @@ "workbench.editor.labelFormat.long": "Zeigt den Namen der Datei an, gefolgt von ihrem absoluten Pfad.", "tabDescription": "Steuert das Format der Beschriftung für einen Editor. Wenn Sie diese Einstellung ändern, ist beispielsweise der Speicherort einer Datei besser ersichtlich:\n- kurz: \"parent\"\n- mittel: \"workspace/src/parent\"\n- lang: \"/home/user/workspace/src/parent\"\n- Standard: \".../parent\", wenn eine andere Registerkarte denselben Titel hat, oder den relativen Arbeitsbereichspfad, wenn Registerkarten deaktiviert sind.", "editorTabCloseButton": "Steuert die Position der Schließen-Schaltflächen der Editor-Registerkarten oder deaktiviert sie bei der Einstellung \"off\".", + "tabSizing": "Steuert die Größe von Editor-Registerkarten. Bei \"Anpassen\" sind die Registerkarten immer groß genug, damit die gesamte Editor-Bezeichnung angezeigt wird. Mit \"Verkleinern\" werden die Registerkarten kleiner, wenn der verfügbare Platz nicht ausreicht, um alle Registerkarten gleichzeitig anzuzeigen.", "showIcons": "Steuert, ob geöffnete Editoren mit einem Symbol angezeigt werden sollen. Hierzu muss auch ein Symboldesign aktiviert werden.", "enablePreview": "Steuert, ob geöffnete Editoren als Vorschau angezeigt werden. Vorschau-Editoren werden wiederverwendet, bis sie gespeichert werden (z. B. über Doppelklicken oder Bearbeiten), und sie werden mit kursivem Schriftschnitt angezeigt.", "enablePreviewFromQuickOpen": "Steuert, ob geöffnete Editoren aus Quick Open als Vorschau angezeigt werden. Vorschau-Editoren werden wiederverwendet, bis sie gespeichert werden (z. B. über Doppelklicken oder Bearbeiten).", @@ -29,6 +30,7 @@ "statusBarVisibility": "Steuert die Sichtbarkeit der Statusleiste im unteren Bereich der Workbench.", "activityBarVisibility": "Steuert die Sichtbarkeit der Aktivitätsleiste in der Workbench.", "closeOnFileDelete": "Steuert, ob Editoren, die eine Datei anzeigen, automatisch geschlossen werden sollen, wenn die Datei von einem anderen Prozess umbenannt oder gelöscht wird. Wenn Sie diese Option deaktivieren, bleibt der Editor bei einem solchen Ereignis als geändert offen. Bei Löschvorgängen innerhalb der Anwendung wird der Editor immer geschlossen, und geänderte Dateien werden nie geschlossen, damit Ihre Daten nicht verloren gehen.", + "enableNaturalLanguageSettingsSearch": "Steuert, ob der Suchmodus mit natürlicher Sprache für die Einstellungen aktiviert werden soll.", "fontAliasing": "Steuert die Schriftartaliasingmethode in der Workbench.\n- default: Subpixel-Schriftartglättung. Auf den meisten Nicht-Retina-Displays wird Text bei dieser Einstellung am schärfsten dargestellt.\n- antialiased: Glättet die Schriftart auf der Pixelebene (im Gegensatz zur Subpixelebene). Bei dieser Einstellung kann die Schriftart insgesamt heller wirken.\n- none: Deaktiviert die Schriftartglättung. Text wird mit gezackten scharfen Kanten dargestellt.\n", "workbench.fontAliasing.default": "Subpixel-Schriftartglättung. Auf den meisten Nicht-Retina-Displays wird Text bei dieser Einstellung am schärfsten dargestellt.", "workbench.fontAliasing.antialiased": "Glättet die Schriftart auf der Pixelebene (im Gegensatz zur Subpixelebene). Bei dieser Einstellung kann die Schriftart insgesamt heller wirken.", diff --git a/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index e95c2dc9747..3906b91b973 100644 --- a/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -20,5 +20,10 @@ "openExplorerOnEnd": "Hiermit wird am Ende einer Debugsitzung automatisch eine Explorer-Ansicht geöffnet.", "inlineValues": "Variablenwerte beim Debuggen in Editor eingebunden anzeigen", "hideActionBar": "Steuert, ob die unverankerte Debugaktionsleiste ausgeblendet werden soll", + "never": "Debuggen nie in Statusleiste anzeigen", + "always": "Debuggen immer in Statusleiste anzeigen", + "onFirstSessionStart": "Debuggen nur in Statusleiste anzeigen, nachdem das Debuggen erstmals gestartet wurde", + "showInStatusBar": "Steuert, wann die Debug-Statusleiste angezeigt werden soll", + "openDebug": "Steuert, ob das Debug-Viewlet beim Start der Debugsitzung offen ist.", "launch": "Startkonfiguration für globales Debuggen. Sollte als Alternative zu \"launch.json\" verwendet werden, das übergreifend von mehreren Arbeitsbereichen genutzt wird" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 374a6cc5550..bbb736c2010 100644 --- a/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Der Haltepunkt wurde hinzugefügt. Zeile {0}, Datei \"{1}\".", "breakpointRemoved": "Der Haltepunkt wurde entfernt. Zeile {0}, Datei \"{1}\".", "compoundMustHaveConfigurations": "Für den Verbund muss das Attribut \"configurations\" festgelegt werden, damit mehrere Konfigurationen gestartet werden können.", + "configMissing": "Konfiguration \"{0}\" fehlt in \"launch.json\".", + "launchJsonDoesNotExist": "\"launch.json\" ist nicht vorhanden.", "debugRequestNotSupported": "Das Attribut \"{0}\" hat in der ausgewählten Debugkonfiguration den nicht unterstützten Wert \"{1}\".", "debugRequesMissing": "Das Attribut \"{0}\" fehlt in der ausgewählten Debugkonfiguration.", "debugTypeNotSupported": "Der konfigurierte Debugtyp \"{0}\" wird nicht unterstützt.", diff --git a/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json index 2d51a131a66..358babc2103 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json @@ -6,9 +6,11 @@ { "name": "Erweiterungsname", "extension id": "Erweiterungsbezeichner", + "preview": "Vorschau", "publisher": "Name des Herausgebers", "install count": "Installationsanzahl", "rating": "Bewertung", + "repository": "Repository", "license": "Lizenz", "details": "Details", "contributions": "Beiträge", diff --git a/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..d63121a5b96 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "Von {0} Benutzern bewertet", + "ratedBySingleUser": "Von 1 Benutzer bewertet" +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..25343125096 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "Klicken Sie, um die Profilerstellung zu beenden." +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 576288a75d3..6259913b4c2 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "Erweiterungen verwalten", "galleryExtensionsCommands": "Katalogerweiterungen installieren", "extension": "Erweiterung", + "runtimeExtension": "Laufende Erweiterungen", "extensions": "Erweiterungen", "view": "Anzeigen", "developer": "Entwickler", "extensionsConfigurationTitle": "Erweiterungen", - "extensionsAutoUpdate": "Erweiterungen automatisch aktualisieren" + "extensionsAutoUpdate": "Erweiterungen automatisch aktualisieren", + "extensionsIgnoreRecommendations": "Bei TRUE werden Benachrichtigungen für Erweiterungsempfehlungen nicht mehr angezeigt." } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index af3cf450594..3da4122d140 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "Problem melden" + "starActivation": "Beim Start aktiviert", + "workspaceContainsGlobActivation": "Aktiviert, da eine mit {0} übereinstimmende Datei in Ihrem Arbeitsbereich vorhanden ist", + "workspaceContainsFileActivation": "Aktiviert, da die Datei {0} in Ihrem Arbeitsbereich vorhanden ist", + "languageActivation": "Aktiviert, da Sie eine {0}-Datei geöffnet haben", + "workspaceGenericActivation": "Aktiviert am {0}", + "errors": "{0} nicht abgefangene Fehler", + "extensionsInputName": "Ausgeführte Erweiterungen", + "showRuntimeExtensions": "Ausgeführte Erweiterungen anzeigen", + "reportExtensionIssue": "Problem melden", + "extensionHostProfileStart": "Erweiterungshostprofil starten", + "extensionHostProfileStop": "Erweiterungshostprofil beenden", + "saveExtensionHostProfile": "Erweiterungshostprofil speichern" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json b/i18n/deu/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json index 146bfaf2e06..4d300a15634 100644 --- a/i18n/deu/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json @@ -69,5 +69,7 @@ "invalidFileNameError": "Der Name **{0}** ist als Datei- oder Ordnername ungültig. Bitte wählen Sie einen anderen Namen aus.", "filePathTooLongError": "Der Name **{0}** führt zu einem Pfad, der zu lang ist. Wählen Sie einen kürzeren Namen.", "compareWithSaved": "Aktive Datei mit gespeicherter Datei vergleichen", - "modifiedLabel": "{0} (auf Datenträger) ↔ {1}" + "modifiedLabel": "{0} (auf Datenträger) ↔ {1}", + "compareWithClipboard": "Aktive Datei mit Zwischenablage vergleichen", + "clipboardComparisonLabel": "Zwischenablage ↔ {0}" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json b/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json index 47a84100588..e3de54fe532 100644 --- a/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json @@ -7,6 +7,7 @@ "noWorkspace": "Es ist kein Ordner geöffnet.", "explorerSection": "Datei-Explorer-Abschnitt", "noWorkspaceHelp": "Sie haben noch keinen Ordner zum Arbeitsbereich hinzugefügt.", + "addFolder": "Ordner hinzufügen", "noFolderHelp": "Sie haben noch keinen Ordner geöffnet.", "openFolder": "Ordner öffnen" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json b/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json index 9ec12051af9..d10b1d29c06 100644 --- a/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json @@ -10,6 +10,7 @@ "dropFolder": "Möchten Sie den Ordner zum Arbeitsbereich hinzufügen?", "addFolders": "&&Ordner hinzufügen", "addFolder": "&&Ordner hinzufügen", + "confirmMove": "Möchten Sie \"{0}\" wirklich verschieben?", "doNotAskAgain": "Nicht erneut fragen", "moveButtonLabel": "&&Verschieben", "confirmOverwriteMessage": "{0} ist im Zielordner bereits vorhanden. Möchten Sie das Element ersetzen?", diff --git a/i18n/deu/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json b/i18n/deu/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json index 8b6ad71cd4e..7b31df50f74 100644 --- a/i18n/deu/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "totalProblems": "Insgesamt {0} Probleme", + "filteredProblems": "Zeigt {0} von {1} Problemen an" +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/markers/common/messages.i18n.json b/i18n/deu/src/vs/workbench/parts/markers/common/messages.i18n.json index 162edd8cdb4..f9df98f6f17 100644 --- a/i18n/deu/src/vs/workbench/parts/markers/common/messages.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/markers/common/messages.i18n.json @@ -6,6 +6,7 @@ { "viewCategory": "Anzeigen", "problems.view.toggle.label": "Probleme umschalten", + "problems.view.focus.label": "Probleme fokussieren", "problems.panel.configuration.title": "Ansicht \"Probleme\"", "problems.panel.configuration.autoreveal": "Steuert, ob die Ansicht \"Probleme\" automatisch Dateien anzeigen sollte, wenn diese geöffnet werden.", "markers.panel.title.problems": "Probleme", diff --git a/i18n/deu/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json b/i18n/deu/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json index 80ed5ad6589..ea9ccbd99b2 100644 --- a/i18n/deu/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defineKeybinding.initial": "Drücken Sie die gewünschte Tastenkombination, und betätigen Sie anschließend die EINGABETASTE.", "defineKeybinding.chordsTo": "Tastenkombination zu" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json b/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json index 503c8aa631f..fccfd818d55 100644 --- a/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "openRawDefaultSettings": "Raw-Standardeinstellungen öffnen", "openGlobalSettings": "Benutzereinstellungen öffnen", "openGlobalKeybindings": "Tastaturkurzbefehle öffnen", "openGlobalKeybindingsFile": "Datei mit Tastaturkurzbefehlen öffnen", diff --git a/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json b/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json index bd64052e34b..26e5d28d658 100644 --- a/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defaultSettingsFuzzyPrompt": "Testen Sie das Suchen mit natürlicher Sprache!", "defaultSettings": "Platzieren Sie Ihre Einstellungen zum Überschreiben im Editor auf der rechten Seite.", "noSettingsFound": "Keine Einstellungen gefunden.", "settingsSwitcherBarAriaLabel": "Einstellungsumschaltung", "userSettings": "Benutzereinstellungen", - "workspaceSettings": "Arbeitsbereichseinstellungen" + "workspaceSettings": "Arbeitsbereichseinstellungen", + "folderSettings": "Ordnereinstellungen", + "enableFuzzySearch": "Suchen mit natürlicher Sprache aktivieren" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json b/i18n/deu/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json index 103e50639f4..a3591ad9724 100644 --- a/i18n/deu/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "defaultLabel": "Eingabe" + "defaultLabel": "Eingabe", + "useExcludesAndIgnoreFilesDescription": "Ausschlusseinstellungen und Ignorieren von Dateien verwenden" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json index 00be7fe56b6..46c50def351 100644 --- a/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json @@ -8,6 +8,7 @@ "ConfigureTaskRunnerAction.label": "Aufgabe konfigurieren", "CloseMessageAction.label": "Schließen", "problems": "Probleme", + "building": "Wird gebaut...", "manyMarkers": "mehr als 99", "runningTasks": "Aktive Aufgaben anzeigen", "tasks": "Aufgaben", @@ -50,6 +51,7 @@ "TaslService.noEntryToRun": "Es wurde keine auszuführende Aufgabe gefunden. Aufgaben konfigurieren...", "TaskService.fetchingBuildTasks": "Buildaufgaben werden abgerufen...", "TaskService.pickBuildTask": "Auszuführende Buildaufgabe auswählen", + "TaskService.noBuildTask": "Keine auszuführende Buildaufgabe gefunden. Buildaufgabe konfigurieren...", "TaskService.fetchingTestTasks": "Testaufgaben werden abgerufen...", "TaskService.pickTestTask": "Auszuführende Testaufgabe auswählen", "TaskService.noTestTaskTerminal": "Es wurde keine auszuführende Testaufgabe gefunden. Aufgaben konfigurieren...", diff --git a/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json b/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json index 197256b0631..094d55db2fa 100644 --- a/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json @@ -7,6 +7,7 @@ "TerminalTaskSystem.unknownError": "Unbekannter Fehler beim Ausführen eines Tasks. Details finden Sie im Taskausgabeprotokoll.", "dependencyFailed": "Die abhängige Aufgabe \"{0}\" im Arbeitsbereichsordner \"{1}\" konnte nicht aufgelöst werden.", "TerminalTaskSystem.terminalName": "Aufgabe - {0}", + "closeTerminal": "Betätigen Sie eine beliebige Taste, um das Terminal zu schließen.", "reuseTerminal": "Das Terminal wird von Aufgaben wiederverwendet, drücken Sie zum Schließen eine beliebige Taste.", "TerminalTaskSystem": "Ein Shell-Befehl kann nicht auf einem UNC-Laufwerk ausgeführt werden.", "unkownProblemMatcher": "Der Problemabgleicher {0} kann nicht aufgelöst werden. Der Abgleicher wird ignoriert." diff --git a/i18n/deu/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json b/i18n/deu/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json index 5b756d8c221..0899849a39b 100644 --- a/i18n/deu/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json @@ -14,6 +14,8 @@ "ConfigurationParser.noTypeDefinition": "Fehler: Der registrierte Aufgabentyp \"{0}\" ist nicht vorhanden. Wurde möglicherweise eine Erweiterung nicht installiert, die den entsprechenden Aufgabenanbieter bereitstellt?", "ConfigurationParser.missingRequiredProperty": "Fehler: Die Aufgabenkonfiguration \"{0}\" enthält die erforderlich Eigenschaft \"{1}\" nicht. Die Aufgabenkonfiguration wird ignoriert.", "ConfigurationParser.notCustom": "Fehler: Die Aufgabe ist nicht als benutzerdefinierte Aufgabe deklariert. Die Konfiguration wird ignoriert.\n{0}\n", + "ConfigurationParser.noTaskName": "Fehler: Eine Aufgabe muss eine label-Eigenschaft angeben. Die Aufgabe wird ignoriert.\n{0}\n", + "taskConfiguration.shellArgs": "Warnung: Die Aufgabe \"{0}\" ist ein Shellbefehl, und eines seiner Argumente enthält Leerzeichen ohne Escapezeichen. Führen Sie Argumente im Befehl zusammen, um eine korrekte Angabe der Befehlszeile sicherzustellen.", "taskConfiguration.noCommandOrDependsOn": "Fehler: Aufgabe \"{0}\" definiert keinen Befehl bzw. keine depondsOn-Eigenschaft. Die Aufgabe wird ignoriert. Die Definition lautet:\n{1}", "taskConfiguration.noCommand": "Fehler: Aufgabe \"{0}\" definiert keinen Befehl. Die Aufgabe wird ignoriert. Die Definition lautet:\n{1}", "TaskParse.noOsSpecificGlobalTasks": "Die Aufgabenversion 2.0.0 unterstützt globale betriebssystemspezifische Aufgaben nicht. Konvertieren Sie sie in eine Aufgabe mit einem betriebssystemspezifischen Befehl. Folgende Aufgaben sind hiervon betroffen:\n{0}" diff --git a/i18n/deu/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json index aa687c62c7d..bc6dee18ad0 100644 --- a/i18n/deu/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json @@ -16,6 +16,7 @@ "terminal.integrated.rightClickCopyPaste": "Wenn dies festgelegt ist, erscheint das Kontextmenü bei einem Rechtsklick im Terminal nicht mehr. Stattdessen erfolgen die Vorgänge Kopieren, wenn eine Auswahl vorgenommen wurde, sowie Einfügen, wenn keine Auswahl vorgenommen wurde.", "terminal.integrated.fontFamily": "Steuert die Schriftartfamilie des Terminals. Der Standardwert ist \"editor.fontFamily\".", "terminal.integrated.fontSize": "Steuert den Schriftgrad des Terminals in Pixeln.", + "terminal.integrated.lineHeight": "Steuert die Zeilenhöhe für das Terminal. Dieser Wert wird mit dem Schriftgrad des Terminals multipliziert, um die tatsächliche Zeilenhöhe in Pixeln zu erhalten.", "terminal.integrated.enableBold": "Gibt an, ob Fettdruck im Terminal aktiviert wird. Dies muss durch die Terminalshell unterstützt werden.", "terminal.integrated.cursorBlinking": "Steuert, ob der Terminalcursor blinkt.", "terminal.integrated.cursorStyle": "Steuert den Stil des Terminalcursors.", diff --git a/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index ec4d01388da..b5dcbbd3ffc 100644 --- a/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,9 @@ // Do not edit this file. It is machine generated. { "selectTheme.label": "Farbdesign", + "themes.category.light": "Light Themen", + "themes.category.dark": "Dunkle Themen", + "themes.category.hc": "Hohe Kontrast Themen", "installColorThemes": "Zusätzliche Farbschemas installieren...", "themes.selectTheme": "Farbdesign auswählen (eine Vorschau wird mit den Tasten NACH OBEN/NACH UNTEN angezeigt)", "selectIconTheme.label": "Dateisymboldesign", diff --git a/i18n/deu/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json b/i18n/deu/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json index 8b6ad71cd4e..727ecbfc255 100644 --- a/i18n/deu/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json +++ b/i18n/deu/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "bubbleTitle": "Enthält hervorgehobene Elemente" +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..b92206c2f58 100644 --- a/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) Profilieren des Erweiterungshost..." +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index 9dfb4d17cca..37b2e129894 100644 --- a/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/deu/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "Der Erweiterungshost wurde unerwartet beendet.", "extensionHostProcess.unresponsiveCrash": "Der Erweiterungshost wurde beendet, weil er nicht reagiert hat.", "overwritingExtension": "Die Erweiterung \"{0}\" wird mit \"{1}\" überschrieben.", - "extensionUnderDevelopment": "Die Entwicklungserweiterung unter \"{0}\" wird geladen." + "extensionUnderDevelopment": "Die Entwicklungserweiterung unter \"{0}\" wird geladen.", + "extensionCache.invalid": "Erweiterungen wurden auf der Festplatte geändert. Bitte laden Sie das Fenster erneut." } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json b/i18n/deu/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json index 9e3cbe8e7cb..32304e03cea 100644 --- a/i18n/deu/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json +++ b/i18n/deu/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json @@ -21,5 +21,6 @@ "keybindings.json.command": "Der Name des auszuführenden Befehls.", "keybindings.json.when": "Die Bedingung, wann der Schlüssel aktiv ist.", "keybindings.json.args": "Argumente, die an den auszuführenden Befehl übergeben werden sollen.", - "keyboardConfigurationTitle": "Tastatur" + "keyboardConfigurationTitle": "Tastatur", + "dispatch": "Steuert die Abgangslogik, sodass bei einem Tastendruck entweder \"code\" (empfohlen) oder \"keyCode\" verwendet wird." } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/services/textfile/common/textFileService.i18n.json b/i18n/deu/src/vs/workbench/services/textfile/common/textFileService.i18n.json index 8b6ad71cd4e..aa9c6a50280 100644 --- a/i18n/deu/src/vs/workbench/services/textfile/common/textFileService.i18n.json +++ b/i18n/deu/src/vs/workbench/services/textfile/common/textFileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "files.backup.failSave": "Dateien, die geändert wurden, konnten nicht in den Sicherungsspeicherort geschrieben werden (Fehler: {0}). Speichern Sie zuerst Ihre Dateien, und beenden Sie dann den Vorgang." +} \ No newline at end of file diff --git a/i18n/esn/extensions/emmet/package.i18n.json b/i18n/esn/extensions/emmet/package.i18n.json index 5f790214764..eb90ccea6de 100644 --- a/i18n/esn/extensions/emmet/package.i18n.json +++ b/i18n/esn/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Separador de modificador utilizado para las clases cuando se utiliza el filtro BEM", "emmetPreferencesFilterCommentBefore": "Una definición de comentario que debe ser colocado antes de elemento emparejado cuando se aplica el filtro de comentarios.", "emmetPreferencesFilterCommentAfter": "Una definición de comentario que debe colocarse después de elemento emparejado cuando se aplica el filtro de comentarios.", - "emmetPreferencesFilterCommentTrigger": "Una lista separada por comas de nombres de atributos que debe existir en la abreviatura para el filtro de comentarios ser aplicado" + "emmetPreferencesFilterCommentTrigger": "Una lista separada por comas de nombres de atributos que debe existir en la abreviatura para el filtro de comentarios ser aplicado", + "emmetPreferencesFormatNoIndentTags": "Una matriz de nombres de etiqueta que no debería recibir una sangría interna", + "emmetPreferencesFormatForceIndentTags": "Una matriz de nombres de etiqueta que siempre debería recibir una sangría interna", + "emmetPreferencesAllowCompactBoolean": "Si es 'true', se produce una anotación compacta de atributos booleanos" } \ No newline at end of file diff --git a/i18n/esn/src/vs/code/node/cliProcessMain.i18n.json b/i18n/esn/src/vs/code/node/cliProcessMain.i18n.json index d6be2f08ce4..9ce3d98ae5d 100644 --- a/i18n/esn/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/esn/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "La extensión '{0}' no está instalada.", "useId": "Asegúrese de usar el identificador de extensión completo, incluido el publicador, por ejemplo: {0}.", "successVsixInstall": "La extensión '{0}' se ha instalado correctamente.", + "cancelVsixInstall": "Cancelar instalación de extensión '{0}'.", "alreadyInstalled": "La extensión '{0}' ya está instalada.", "foundExtension": "Se encontró '{0}' en Marketplace.", "installing": "Instalando...", diff --git a/i18n/esn/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/esn/src/vs/editor/common/config/commonEditorConfig.i18n.json index 97f2cfda15a..582dfe7c78f 100644 --- a/i18n/esn/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/esn/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "Limitar el ancho del minimapa para presentar como mucho un número de columnas determinado", "find.seedSearchStringFromSelection": "Controla si se inicializa la cadena de búsqueda en Buscar widget en la selección del editor", "find.autoFindInSelection": "Controla si el indicador Buscar en selección se activa cuando se seleccionan varios caracteres o líneas de texto en el editor", + "find.globalFindClipboard": "Controla si el widget de búsqueda debería leer o modificar el portapapeles de busqueda compartido en macOS", "wordWrap.off": "Las líneas no se ajustarán nunca.", "wordWrap.on": "Las líneas se ajustarán en el ancho de la ventanilla.", "wordWrap.wordWrapColumn": "Las líneas se ajustarán en \"editor.wordWrapColumn\".", diff --git a/i18n/esn/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/esn/src/vs/editor/contrib/folding/folding.i18n.json index 69ac193ad0d..0db9b485ab2 100644 --- a/i18n/esn/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/esn/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "Plegar", "foldRecursivelyAction.label": "Plegar de forma recursiva", "foldAllBlockComments.label": "Cerrar todos los comentarios de bloqueo", + "foldAllMarkerRegions.label": "Plegar todas las regiones", + "unfoldAllMarkerRegions.label": "Desplegar Todas las Regiones", "foldAllAction.label": "Plegar todo", "unfoldAllAction.label": "Desplegar todo", "foldLevelAction.label": "Nivel de plegamiento {0}" diff --git a/i18n/esn/src/vs/platform/environment/node/argv.i18n.json b/i18n/esn/src/vs/platform/environment/node/argv.i18n.json index e7d97465a8c..37027504432 100644 --- a/i18n/esn/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/esn/src/vs/platform/environment/node/argv.i18n.json @@ -16,6 +16,7 @@ "inspect-brk-extensions": "Permitir la depuración y el perfil de las extensiones con el host de la extensión pausado después del inicio. Revisar las herramientas de desarrollador para la conexión uri.", "reuseWindow": "Fuerce la apertura de un archivo o carpeta en la última ventana activa.", "userDataDir": "Especifica el directorio en que se conservan los datos de usuario; es útil cuando se ejecuta como raíz.", + "log": "Nivel de registro a utilizar. Por defecto es 'info'. Los valores permitidos son 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.", "verbose": "Imprima salidas detalladas (implica --wait).", "wait": "Espere a que los archivos sean cerrados antes de volver.", "extensionHomePath": "Establezca la ruta de acceso raíz para las extensiones.", diff --git a/i18n/esn/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/esn/src/vs/workbench/electron-browser/actions.i18n.json index 69c31d25805..81432663720 100644 --- a/i18n/esn/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/esn/src/vs/workbench/electron-browser/actions.i18n.json @@ -67,5 +67,6 @@ "warn": "Advertencia", "err": "Error", "critical": "Crítico", - "off": "Apagado" + "off": "Apagado", + "selectLogLevel": "Seleccione el nivel de registro" } \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/esn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index b7bc040a66a..4a2a19ab863 100644 --- a/i18n/esn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Punto de interrupción agregado, línea {0}, archivo {1}", "breakpointRemoved": "Punto de interrupción quitado, línea {0}, archivo {1}", "compoundMustHaveConfigurations": "El compuesto debe tener configurado el atributo \"configurations\" a fin de iniciar varias configuraciones.", + "configMissing": "La configuración \"{0}\" falta en \"launch.json\".", + "launchJsonDoesNotExist": "'launch.json' no existe.", "debugRequestNotSupported": "El atributo '{0}' tiene un valor no admitido '{1}' en la configuración de depuración seleccionada.", "debugRequesMissing": "El atributo '{0}' está ausente en la configuración de depuración elegida. ", "debugTypeNotSupported": "El tipo de depuración '{0}' configurado no es compatible.", diff --git a/i18n/esn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/esn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 9671e111767..4d8d31b0d54 100644 --- a/i18n/esn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "ratedByUsers": "Calificado por {0} usuarios" + "ratedByUsers": "Calificado por {0} usuarios", + "ratedBySingleUser": "Calificado por 1 usuario" } \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.i18n.json b/i18n/esn/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.i18n.json index e86ad1697c1..09f3e4dd167 100644 --- a/i18n/esn/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/terminal/browser/terminalQuickOpen.i18n.json @@ -6,6 +6,7 @@ { "termEntryAriaLabel": "{0}, selector de terminal", "termCreateEntryAriaLabel": "{0}, crear nueva terminal", + "workbench.action.terminal.newplus": "$(plus) Crear nueva terminal integrada", "noTerminalsMatching": "No hay terminales coincidentes", "noTerminalsFound": "No hay terminales abiertos" } \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index dad0465be35..ce5331764d6 100644 --- a/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -7,6 +7,7 @@ "selectTheme.label": "Tema de color", "themes.category.light": "temas claros", "themes.category.dark": "temas oscuros", + "themes.category.hc": "temas de alto contraste", "installColorThemes": "Instalar temas de color adicionales...", "themes.selectTheme": "Seleccione el tema de color (flecha arriba/abajo para vista previa)", "selectIconTheme.label": "Tema de icono de archivo", diff --git a/i18n/esn/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/esn/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..41edb4e2c27 100644 --- a/i18n/esn/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/esn/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "Perfiles del Host de Extensiones $(zap)... " +} \ No newline at end of file diff --git a/i18n/fra/extensions/emmet/package.i18n.json b/i18n/fra/extensions/emmet/package.i18n.json index 175d70aeb65..d8ebfe9b25c 100644 --- a/i18n/fra/extensions/emmet/package.i18n.json +++ b/i18n/fra/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Séparateur de modificateur utilisé pour les classes lorsque le filtre BEM est utilisé", "emmetPreferencesFilterCommentBefore": "Une définition de commentaire qui doit être placée avant l’élément correspondant quand le filtre de commentaire est appliqué.", "emmetPreferencesFilterCommentAfter": "Une définition de commentaire qui doit être placée après l’élément correspondant quand un filtre de commentaire est appliqué.", - "emmetPreferencesFilterCommentTrigger": "Une liste séparée par des virgules de noms d’attributs qui devraient exister en abrégé pour que le filtre de commentaire soit appliqué" + "emmetPreferencesFilterCommentTrigger": "Une liste séparée par des virgules de noms d’attributs qui devraient exister en abrégé pour que le filtre de commentaire soit appliqué", + "emmetPreferencesFormatNoIndentTags": "Un tableau de noms de balises qui ne devraient pas être indentées", + "emmetPreferencesFormatForceIndentTags": "Un tableau de noms de balises qui devraient toujours être indentées", + "emmetPreferencesAllowCompactBoolean": "Si true, la notation compacte des attributs booléens est produite" } \ No newline at end of file diff --git a/i18n/fra/src/vs/code/node/cliProcessMain.i18n.json b/i18n/fra/src/vs/code/node/cliProcessMain.i18n.json index 0a97aaf8721..839a1ac0317 100644 --- a/i18n/fra/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/fra/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "L'extension '{0}' n'est pas installée.", "useId": "Veillez à utiliser l'ID complet de l'extension (serveur de publication inclus). Exemple : {0}", "successVsixInstall": "L'extension '{0}' a été installée correctement !", + "cancelVsixInstall": "Installation annulée de l'Extension '{0}'.", "alreadyInstalled": "L'extension '{0}' est déjà installée.", "foundExtension": "'{0}' trouvé dans le Marketplace.", "installing": "Installation...", diff --git a/i18n/fra/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/fra/src/vs/editor/common/config/commonEditorConfig.i18n.json index f74e6c1fbc0..dd19016cb45 100644 --- a/i18n/fra/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/fra/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "Limiter la largeur de la minicarte pour afficher au maximum un certain nombre de colonnes", "find.seedSearchStringFromSelection": "Contrôle si nous remplissons la chaîne à rechercher dans le Widget Recherche à partir de la sélection de l'éditeur", "find.autoFindInSelection": "Contrôle si l'indicateur Rechercher dans la sélection est activé quand plusieurs caractères ou lignes de texte sont sélectionnés dans l'éditeur", + "find.globalFindClipboard": "Contrôle si le Widget Recherche doit lire ou modifier le presse-papiers partagé sur macOS", "wordWrap.off": "Le retour automatique à la ligne n'est jamais effectué.", "wordWrap.on": "Le retour automatique à la ligne s'effectue en fonction de la largeur de la fenêtre d'affichage.", "wordWrap.wordWrapColumn": "Le retour automatique à la ligne s'effectue en fonction de 'editor.wordWrapColumn'.", diff --git a/i18n/fra/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/fra/src/vs/editor/contrib/folding/folding.i18n.json index a019089db96..475f08c4a6f 100644 --- a/i18n/fra/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/fra/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "Plier", "foldRecursivelyAction.label": "Plier de manière récursive", "foldAllBlockComments.label": "Replier tous les commentaires de bloc", + "foldAllMarkerRegions.label": "Replier toutes les régions", + "unfoldAllMarkerRegions.label": "Déplier toutes les régions", "foldAllAction.label": "Plier tout", "unfoldAllAction.label": "Déplier tout", "foldLevelAction.label": "Niveau de pliage {0}" diff --git a/i18n/fra/src/vs/platform/environment/node/argv.i18n.json b/i18n/fra/src/vs/platform/environment/node/argv.i18n.json index f1c7f8375eb..f676e6c787f 100644 --- a/i18n/fra/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/fra/src/vs/platform/environment/node/argv.i18n.json @@ -16,6 +16,7 @@ "inspect-brk-extensions": "Autorise le débogage et le profilage des extensions avec l'hôte d'extensions en pause après le démarrage. Vérifier les outils de développement pour l'uri de connexion.", "reuseWindow": "Forcez l'ouverture d'un fichier ou dossier dans la dernière fenêtre active.", "userDataDir": "Spécifie le répertoire où sont conservées les données des utilisateurs. S'avère utile pour une exécution en tant que root.", + "log": "Niveau de journalisation à utiliser. La valeur par défaut est 'info'. Les valeurs autorisées sont 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off.", "verbose": "Affichez la sortie détaillée (implique --wait).", "wait": "Attendre que les fichiers soient fermés avant de retourner.", "extensionHomePath": "Définissez le chemin racine des extensions.", diff --git a/i18n/fra/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/fra/src/vs/workbench/electron-browser/actions.i18n.json index 0e453c54e87..541429a5408 100644 --- a/i18n/fra/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/fra/src/vs/workbench/electron-browser/actions.i18n.json @@ -67,5 +67,6 @@ "warn": "Avertissement", "err": "Erreur", "critical": "Critique", - "off": "Désactivé" + "off": "Désactivé", + "selectLogLevel": "Sélectionner le niveau de journalisation (log)" } \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/fra/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 3aa86189407..2833b451b28 100644 --- a/i18n/fra/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Point d'arrêt ajouté, ligne {0}, fichier {1}", "breakpointRemoved": "Point d'arrêt supprimé, ligne {0}, fichier {1}", "compoundMustHaveConfigurations": "L'attribut \"configurations\" du composé doit être défini pour permettre le démarrage de plusieurs configurations.", + "configMissing": "Il manque la configuration '{0}' dans 'launch.json'.", + "launchJsonDoesNotExist": "'launch.json' n’existe pas.", "debugRequestNotSupported": "L’attribut '{0}' a une valeur '{1}' non prise en charge dans la configuration de débogage sélectionnée.", "debugRequesMissing": "L’attribut '{0}' est introuvable dans la configuration de débogage choisie.", "debugTypeNotSupported": "Le type de débogage '{0}' configuré n'est pas pris en charge.", diff --git a/i18n/fra/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/fra/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index e686f1aac70..6a9b09723ba 100644 --- a/i18n/fra/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "ratedByUsers": "Évaluée par {0} utilisateurs" + "ratedByUsers": "Évaluée par {0} utilisateurs", + "ratedBySingleUser": "Évaluée par 1 utilisateur" } \ No newline at end of file diff --git a/i18n/hun/extensions/css/client/out/cssMain.i18n.json b/i18n/hun/extensions/css/client/out/cssMain.i18n.json index ded2c0dd476..e5331e1a5ac 100644 --- a/i18n/hun/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/hun/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "CSS nyelvi szerver" + "cssserver.name": "CSS nyelvi szerver", + "folding.start": "Összecsukható tartomány kezdete", + "folding.end": "Összecsukható tartomány vége" } \ No newline at end of file diff --git a/i18n/hun/extensions/emmet/package.i18n.json b/i18n/hun/extensions/emmet/package.i18n.json index 99db67d814e..65869470a93 100644 --- a/i18n/hun/extensions/emmet/package.i18n.json +++ b/i18n/hun/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Módosító elválasztó osztályok megadásánál BEM-szűrő használata esetén", "emmetPreferencesFilterCommentBefore": "Annak a megjegyzésnek a definíciója, ami az illeszkedő elem elé kerül a megjegyzésszűrő alkalmazása esetén.", "emmetPreferencesFilterCommentAfter": "Annak a megjegyzésnek a definíciója, ami az illeszkedő elem mögé kerül a megjegyzésszűrő alkalmazása esetén.", - "emmetPreferencesFilterCommentTrigger": "Attribútumnevek vesszővel elválasztott listája, amelyeknek léteznie kell a megjegyzésszűrő alkalmazásához." + "emmetPreferencesFilterCommentTrigger": "Attribútumnevek vesszővel elválasztott listája, amelyeknek léteznie kell a megjegyzésszűrő alkalmazásához.", + "emmetPreferencesFormatNoIndentTags": "Azon elemek neveit tartalmazó tömb, melyek nem kapnak belső indentálást", + "emmetPreferencesFormatForceIndentTags": "Azon elemek neveit tartalmazó tömb, melyek mindig kapnak belső indentálást", + "emmetPreferencesAllowCompactBoolean": "Ha az értéke true, a logikai értékeket tartalmazó attribútumok esetén a rövidített jelölés lesz használva" } \ No newline at end of file diff --git a/i18n/hun/extensions/git/out/commands.i18n.json b/i18n/hun/extensions/git/out/commands.i18n.json index 75a82e6ba00..d2dfa251e5d 100644 --- a/i18n/hun/extensions/git/out/commands.i18n.json +++ b/i18n/hun/extensions/git/out/commands.i18n.json @@ -14,6 +14,7 @@ "cloning": "Git-forráskódtár klónozása...", "openrepo": "Forráskódtár megnyitása", "proposeopen": "Szeretné megnyitni a klónozott forráskódtárat?", + "init": "Válasszon munkaterületi mappát a Git-forráskódtár inicializálásához!", "init repo": "Forráskódtár előkészítése", "create repo": "Forráskódtár előkészítése", "are you sure": "A művelet egy Git forráskódtárat hoz létre a következő helyen: '{0}. Biztosan szeretné folytatni?", diff --git a/i18n/hun/extensions/html/client/out/htmlMain.i18n.json b/i18n/hun/extensions/html/client/out/htmlMain.i18n.json index 3aff5dce6a0..c7c371e8738 100644 --- a/i18n/hun/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/hun/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "HTML nyelvi szerver" + "htmlserver.name": "HTML nyelvi szerver", + "folding.start": "Összecsukható tartomány kezdete", + "folding.end": "Összecsukható tartomány vége" } \ No newline at end of file diff --git a/i18n/hun/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/hun/src/vs/editor/contrib/folding/folding.i18n.json index ffe920fbb79..510a0072235 100644 --- a/i18n/hun/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/hun/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,7 +9,7 @@ "foldAction.label": "Bezárás", "foldRecursivelyAction.label": "Bezárás rekurzívan", "foldAllBlockComments.label": "Összes megjegyzésblokk bezárása", - "foldAllMarkerRegions.label": "Összes régió bezárása", + "foldAllMarkerRegions.label": "Összes tartomány bezárása", "unfoldAllMarkerRegions.label": "Összes régió kinyitása", "foldAllAction.label": "Az összes bezárása", "unfoldAllAction.label": "Az összes kinyitása", diff --git a/i18n/hun/src/vs/platform/environment/node/argv.i18n.json b/i18n/hun/src/vs/platform/environment/node/argv.i18n.json index 0346bdf3589..acbe400a835 100644 --- a/i18n/hun/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/hun/src/vs/platform/environment/node/argv.i18n.json @@ -16,7 +16,7 @@ "inspect-brk-extensions": "Hibakeresés és profilozás engedélyezése a kiegészítőkben, úgy, hogy a kiegészítő gazdafolyamata szüneteltetve lesz az indítás után. Ellenőrizze a fejlesztői eszközöket a csatlakozási URI-hoz. ", "reuseWindow": "Fájl vagy mappa megnyitása a legutoljára aktív ablakban.", "userDataDir": "Meghatározza a könyvtárat, ahol a felhasználói adatok vannak tárolva. Hasznás, ha rootként van futtatva.", - "log": "A naplózott események szintje. Lehetséges értékek: 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.", + "log": "A naplózott események szintje.Az 'info' az alapértelmezett értéke. Lehetséges értékek: 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.", "verbose": "Részletes kimenet kiírása (magába foglalja a --wait kapcsolót)", "wait": "Várjon a fájlok bezárására a visszatérés előtt.", "extensionHomePath": "A kiegészítők gyökérkönyvtárának beállítása.", diff --git a/i18n/hun/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/hun/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 99cb4104dec..8ea74e99bd6 100644 --- a/i18n/hun/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,7 @@ "breakpointAdded": "Töréspont hozzáadva, {0}. sor, fájl: {1}", "breakpointRemoved": "Töréspont eltávoíltva, {0}. sor, fájl: {1}", "compoundMustHaveConfigurations": "A kombinációk \"configurations\" tulajdonságát be kell állítani több konfiguráció elindításához.", + "configMissing": "A(z) '{0}' konfiguráció hiányzik a 'launch.json'-ból.", "launchJsonDoesNotExist": "A 'launch.json' nem létezik.", "debugRequestNotSupported": "A(z) `{0}` attribútumnak nem támogatott értéke van ('{1}') a kiválasztott hibakeresési konfigurációban.", "debugRequesMissing": "A(z) '{0}' attribútum hiányzik a kiválasztott hibakeresési konfigurációból.", diff --git a/i18n/hun/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/hun/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..9bf53a3a15a 100644 --- a/i18n/hun/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "{0} felhasználó értékelte", + "ratedBySingleUser": "1 felhasználó értékelte" +} \ No newline at end of file diff --git a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..e51e9974cf6 100644 --- a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "Kattintson a profilozás leállításához!" +} \ No newline at end of file diff --git a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 661261d0b48..871b3dc27b8 100644 --- a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "Kiegészítők kezelése", "galleryExtensionsCommands": "Kiegészítők telepítése a galériából", "extension": "Kiegészítő", + "runtimeExtension": "Futó kiegészítők", "extensions": "Kiegészítők", "view": "Nézet", "developer": "Fejlesztői", "extensionsConfigurationTitle": "Kiegészítők", - "extensionsAutoUpdate": "Kiegészítők automatikus frissítése" + "extensionsAutoUpdate": "Kiegészítők automatikus frissítése", + "extensionsIgnoreRecommendations": "Ha az értéke true, nem jelenik meg több kiegészítőajánlást tartalmazó értesítés." } \ No newline at end of file diff --git a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index 89b9332aaf7..43614d6d37b 100644 --- a/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "Probléma jelentése" + "starActivation": "Indulásnál aktiválódott", + "workspaceContainsGlobActivation": "Azért aktiválódott, mert a következőre illeszkedő fájl létezik a munkaterületen: {0}", + "workspaceContainsFileActivation": "Azért aktiválódott, mert {0} nevű fájl létezik a munkaterületen", + "languageActivation": "Azért aktiválódott, mert megnyitott egy {0} fájlt.", + "workspaceGenericActivation": "A következő miatt aktiválódott: {0}", + "errors": "{0} kezeletlen hiba", + "extensionsInputName": "Futó kiegészítők", + "showRuntimeExtensions": "Futó kiegészítők megjelenítése", + "reportExtensionIssue": "Probléma jelentése", + "extensionHostProfileStart": "Kiegészítő gazdafolyamat profilozásának elindítása", + "extensionHostProfileStop": "Kiegészítő gazdafolyamat profilozásának leállítása", + "saveExtensionHostProfile": "Kiegészítő gazdafolyamat profiljának elmentése" } \ No newline at end of file diff --git a/i18n/hun/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/hun/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index b4c839776a9..7ff857a3c18 100644 --- a/i18n/hun/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/hun/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -7,6 +7,7 @@ "selectTheme.label": "Színtéma", "themes.category.light": "világos témák", "themes.category.dark": "sötét témák", + "themes.category.hc": "kontrasztos témák", "installColorThemes": "További színtémák telepítése...", "themes.selectTheme": "Válasszon színtémát! (Előnézet a fel/le billentyűvel.)", "selectIconTheme.label": "Fájlikontéma", diff --git a/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..18419b7630f 100644 --- a/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) Kiegészítő gazdafolyamat profilozása..." +} \ No newline at end of file diff --git a/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index dd506bf2042..50fa8eeeefc 100644 --- a/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/hun/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "A kiegészítő gazdafolyamata váratlanul leállt.", "extensionHostProcess.unresponsiveCrash": "A kiegészítő gazdafolyamata le lett állítva, mert nem válaszolt.", "overwritingExtension": "A(z) {0} kiegészítő felülírása a következővel: {1}.", - "extensionUnderDevelopment": "A(z) {0} elérési úton található fejlesztői kiegészítő betöltése" + "extensionUnderDevelopment": "A(z) {0} elérési úton található fejlesztői kiegészítő betöltése", + "extensionCache.invalid": "A kiegészítők módosultak a lemezen. Töltse újra az ablakot!" } \ No newline at end of file diff --git a/i18n/ita/extensions/css/client/out/cssMain.i18n.json b/i18n/ita/extensions/css/client/out/cssMain.i18n.json index 0fb3f4a3ac8..ba180eb1499 100644 --- a/i18n/ita/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/ita/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "Server di linguaggio CSS" + "cssserver.name": "Server di linguaggio CSS", + "folding.start": "Inizio di una regione riducibile", + "folding.end": "Fine di una regione riducibile" } \ No newline at end of file diff --git a/i18n/ita/extensions/emmet/package.i18n.json b/i18n/ita/extensions/emmet/package.i18n.json index d4e654204c0..01c2ca68c29 100644 --- a/i18n/ita/extensions/emmet/package.i18n.json +++ b/i18n/ita/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Separatore di modificatore utilizzato per le classi quando si utilizza il filtro BEM", "emmetPreferencesFilterCommentBefore": "Una definizione di commento che deve essere inserita prima dell'elemento corrispondente quando viene applicato il filtro commenti.", "emmetPreferencesFilterCommentAfter": "Una definizione di commento che deve essere posizionato dopo l'elemento corrispondente quando viene applicato il filtro commenti.", - "emmetPreferencesFilterCommentTrigger": "Un elenco delimitato da virgole di nomi di attributi che dovrebbero esistere come abbreviazione per il filtro commenti da applicare" + "emmetPreferencesFilterCommentTrigger": "Un elenco delimitato da virgole di nomi di attributi che dovrebbero esistere come abbreviazione per il filtro commenti da applicare", + "emmetPreferencesFormatNoIndentTags": "Una matrice di nomi di tag che non dovrebbe ottenere il rientro interno", + "emmetPreferencesFormatForceIndentTags": "Una matrice di nomi di tag che dovrebbe sempre ottenere il rientro interno", + "emmetPreferencesAllowCompactBoolean": "Se true, viene prodotta una notazione compatta degli attributi booleani" } \ No newline at end of file diff --git a/i18n/ita/extensions/git/out/autofetch.i18n.json b/i18n/ita/extensions/git/out/autofetch.i18n.json index 93fbf7a8a39..b51df7e140d 100644 --- a/i18n/ita/extensions/git/out/autofetch.i18n.json +++ b/i18n/ita/extensions/git/out/autofetch.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "yes": "Sì", - "no": "No" + "no": "No", + "not now": "Non ora", + "suggest auto fetch": "Vorresti attivare il fetching automatico di repository Git?" } \ No newline at end of file diff --git a/i18n/ita/extensions/git/out/commands.i18n.json b/i18n/ita/extensions/git/out/commands.i18n.json index 3d1d3e71eae..b544cb5ca24 100644 --- a/i18n/ita/extensions/git/out/commands.i18n.json +++ b/i18n/ita/extensions/git/out/commands.i18n.json @@ -14,6 +14,7 @@ "cloning": "Clonazione del repository GIT...", "openrepo": "Apri repository", "proposeopen": "Aprire il repository clonato?", + "init": "Selezionare la cartella dell'area di lavoro in cui inizializzare il Git repo", "init repo": "Inizializza repository", "create repo": "Inizializza repository", "are you sure": "Questo creerà un repository Git in '{0}'. Sei sicuro di voler continuare?", @@ -59,6 +60,7 @@ "provide tag name": "Specificare un nome di tag", "tag message": "Messaggio", "provide tag message": "Specificare un messaggio per aggiungere un'annotazione per il tag", + "no remotes to fetch": "Questo repository non ha remote configurati da cui eseguire un fetch.", "no remotes to pull": "Il repository non contiene elementi remoti configurati come origini del pull.", "pick remote pull repo": "Selezionare un repository remoto da cui effettuare il pull del ramo", "no remotes to push": "Il repository non contiene elementi remoti configurati come destinazione del push.", @@ -75,6 +77,7 @@ "no stashes": "Non ci sono accantonamenti da ripristinare.", "pick stash to pop": "Scegli un accantonamento da prelevare", "clean repo": "Pulire l'albero di lavoro del repository prima dell'estrazione.", + "cant push": "Impossibile fare push dei ref su remoto. Provare a eseguire un 'Pull' prima, per integrare le modifiche.", "git error details": "GIT: {0}", "git error": "Errore GIT", "open git log": "Apri log GIT" diff --git a/i18n/ita/extensions/git/out/main.i18n.json b/i18n/ita/extensions/git/out/main.i18n.json index 7fc7257ad8e..657b68a5882 100644 --- a/i18n/ita/extensions/git/out/main.i18n.json +++ b/i18n/ita/extensions/git/out/main.i18n.json @@ -6,7 +6,9 @@ { "looking": "Ricerca di git in: {0}", "using git": "Uso di GIT {0} da {1}", + "downloadgit": "Scarica Git", "neverShowAgain": "Non visualizzare più questo messaggio", + "notfound": "Git non trovato. Installarlo o configurarlo utilizzando l'impostazione 'git.path'.", "updateGit": "Aggiorna GIT", "git20": "La versione installata di GIT è la {0}. Per il corretto funzionamento di Code è consigliabile usare una versione di GIT non inferiore alla 2." } \ No newline at end of file diff --git a/i18n/ita/extensions/git/package.i18n.json b/i18n/ita/extensions/git/package.i18n.json index 4c23aba5a2e..9e6cd77fdcb 100644 --- a/i18n/ita/extensions/git/package.i18n.json +++ b/i18n/ita/extensions/git/package.i18n.json @@ -36,6 +36,7 @@ "command.renameBranch": "Rinomina Branch...", "command.merge": "Merge ramo...", "command.createTag": "Crea tag", + "command.fetch": "Fetch", "command.pull": "Esegui pull", "command.pullRebase": "Esegui pull (Riassegna)", "command.pullFrom": "Pull da...", @@ -60,6 +61,7 @@ "config.countBadge": "Controlla il contatore delle notifiche git. Con `all` vengono conteggiate tutte le modifiche. Con `tracked` vengono conteggiate solo le revisioni. Con `off` il contatore è disattivato.", "config.checkoutType": "Controlla il tipo di branch mostrati eseguendo il comando `Checkout in...`. `all` mostra tutti i refs, `local` mostra solamente i branch locali, `tags` mostra solamente i tag e `remote` mostra solamente i branch remoti.", "config.ignoreLegacyWarning": "Ignora l'avvertimento legacy di Git", + "config.ignoreMissingGitWarning": "Ignora il messaggio di avviso quando manca Git", "config.ignoreLimitWarning": "Ignora il messaggio di avviso quando ci sono troppi cambiamenti in un repository", "config.defaultCloneDirectory": "Il percorso predefinito dove clonare un repository GIT", "config.enableSmartCommit": "Eseguire il commit di tutte le modifiche quando non ci sono modifiche preparate.", diff --git a/i18n/ita/extensions/html/client/out/htmlMain.i18n.json b/i18n/ita/extensions/html/client/out/htmlMain.i18n.json index dd2cc39c679..81abd3e7997 100644 --- a/i18n/ita/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/ita/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "Server di linguaggio HTML" + "htmlserver.name": "Server di linguaggio HTML", + "folding.start": "Inizio di una regione riducibile", + "folding.end": "Fine regione riducibile" } \ No newline at end of file diff --git a/i18n/ita/extensions/markdown/out/commands.i18n.json b/i18n/ita/extensions/markdown/out/commands.i18n.json index 2448cc1f0e0..385404beefc 100644 --- a/i18n/ita/extensions/markdown/out/commands.i18n.json +++ b/i18n/ita/extensions/markdown/out/commands.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "previewTitle": "Anteprima {0}", "onPreviewStyleLoadError": "Impossibile caricare 'markdown.styles': {0}" } \ No newline at end of file diff --git a/i18n/ita/extensions/markdown/out/security.i18n.json b/i18n/ita/extensions/markdown/out/security.i18n.json index 0b73b98c61c..82c72e77e6a 100644 --- a/i18n/ita/extensions/markdown/out/security.i18n.json +++ b/i18n/ita/extensions/markdown/out/security.i18n.json @@ -11,5 +11,8 @@ "disable.title": "Disabilita", "disable.description": "Consente l'esecuzione di tutti i contenuti e script. Scelta non consigliata", "moreInfo.title": "Altre informazioni", + "enableSecurityWarning.title": "Abilita anteprima degli avvisi di protezione in questa area di lavoro", + "disableSecurityWarning.title": "Disabilita anteprima degli avvisi di protezione in questa area di lavoro", + "toggleSecurityWarning.description": "Non influenza il livello di sicurezza del contenuto", "preview.showPreviewSecuritySelector.title": "Seleziona impostazioni di protezione per le anteprime Markdown in questa area di lavoro" } \ No newline at end of file diff --git a/i18n/ita/extensions/merge-conflict/package.i18n.json b/i18n/ita/extensions/merge-conflict/package.i18n.json index 323397967e1..fc64b94112d 100644 --- a/i18n/ita/extensions/merge-conflict/package.i18n.json +++ b/i18n/ita/extensions/merge-conflict/package.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "command.category": "Esegui merge del conflitto", + "command.accept.all-current": "Accettare tutte le modifiche correnti", "command.accept.all-incoming": "Accettare tutte le modifiche in ingresso", "command.accept.all-both": "Accettare tutte in entrambe", "command.accept.current": "Accettare corrente", diff --git a/i18n/ita/extensions/typescript/out/features/completionItemProvider.i18n.json b/i18n/ita/extensions/typescript/out/features/completionItemProvider.i18n.json index 70df4761df5..c662f167afb 100644 --- a/i18n/ita/extensions/typescript/out/features/completionItemProvider.i18n.json +++ b/i18n/ita/extensions/typescript/out/features/completionItemProvider.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "selectCodeAction": "Selezionare l'azione codice da applicare", "acquiringTypingsLabel": "Acquisizione dei file typings...", "acquiringTypingsDetail": "Acquisizione delle definizioni dei file typings per IntelliSense.", "autoImportLabel": "Importazione automatica da {0}" diff --git a/i18n/ita/src/vs/code/electron-main/main.i18n.json b/i18n/ita/src/vs/code/electron-main/main.i18n.json index 90fdc6f7bc0..9c1ef35e983 100644 --- a/i18n/ita/src/vs/code/electron-main/main.i18n.json +++ b/i18n/ita/src/vs/code/electron-main/main.i18n.json @@ -4,5 +4,9 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "secondInstanceNoResponse": "Un'altra istanza di {0} è in esecuzione ma non risponde", + "secondInstanceNoResponseDetail": "Chiudere tutte le altre istanze e riprovare.", + "secondInstanceAdmin": "Una seconda istanza di {0} è già in esecuzione come amministratore.", + "secondInstanceAdminDetail": "Chiudere l'altra istanza e riprovare.", "close": "&&Chiudi" } \ No newline at end of file diff --git a/i18n/ita/src/vs/code/electron-main/menus.i18n.json b/i18n/ita/src/vs/code/electron-main/menus.i18n.json index d476d47bdc5..728d24a7061 100644 --- a/i18n/ita/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/ita/src/vs/code/electron-main/menus.i18n.json @@ -26,6 +26,7 @@ "miOpenFolder": "Apri &&cartella...", "miOpenFile": "&&Apri file...", "miOpenRecent": "Apri &&recenti", + "miSaveWorkspaceAs": "Salva area di lavoro con nome...", "miAddFolderToWorkspace": "A&&ggiungere cartella all'area di lavoro...", "miSave": "&&Salva", "miSaveAs": "Salva con nome...", @@ -156,6 +157,7 @@ "mMergeAllWindows": "Unisci tutte le finestre", "miToggleDevTools": "&&Attiva/Disattiva strumenti di sviluppo", "miAccessibilityOptions": "&&Opzioni accessibilità", + "miReportIssue": "&&Segnala problema", "miWelcome": "&&Benvenuti", "miInteractivePlayground": "Playground &&interattivo", "miDocumentation": "&&Documentazione", @@ -182,6 +184,7 @@ "miDownloadingUpdate": "Download dell'aggiornamento...", "miInstallingUpdate": "Installazione dell'aggiornamento...", "miCheckForUpdates": "Verifica disponibilità aggiornamenti...", + "aboutDetail": "Versione {0}\nCommit {1}\nData {2}\nShell {3}\nRenderer {4}\nNodo {5}\nArchitettura {6}", "okButton": "OK", "copy": "&&Copia" } \ No newline at end of file diff --git a/i18n/ita/src/vs/code/node/cliProcessMain.i18n.json b/i18n/ita/src/vs/code/node/cliProcessMain.i18n.json index 814141efbe9..65d929e5fb3 100644 --- a/i18n/ita/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/ita/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "L'estensione '{0}' non è installata.", "useId": "Assicurarsi di usare l'ID estensione completo, incluso l'editore, ad esempio {0}", "successVsixInstall": "L'estensione '{0}' è stata installata.", + "cancelVsixInstall": "Installazione dell'estensione '{0}' annullata.", "alreadyInstalled": "L'estensione '{0}' è già installata.", "foundExtension": "L'estensione '{0}' è stata trovata nel Marketplace.", "installing": "Installazione...", diff --git a/i18n/ita/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/ita/src/vs/editor/common/config/commonEditorConfig.i18n.json index 1c04d1c06ae..99d10858e07 100644 --- a/i18n/ita/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/ita/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -17,7 +17,9 @@ "lineNumbers": "Controlla la visualizzazione dei numeri di riga. I valori possibili sono 'on', 'off' e 'relativi'.", "rulers": "Mostra righelli verticali dopo un certo numero di caratteri a spaziatura fissa. Utilizza più valori per più righelli. Nessun righello viene disegnati se la matrice è vuota", "wordSeparators": "Caratteri che verranno usati come separatori di parola quando si eseguono operazioni o spostamenti correlati a parole", + "tabSize": "Il numero di spazi corrispondenti ad un carattere Tab. Questa impostazione viene sottoposta a override in base al contenuto dei file quando 'editor.detectIndentation' è 'on'.", "tabSize.errorMessage": "È previsto 'number'. Nota: il valore \"auto\" è stato sostituito dall'impostazione `editor.detectIndentation`.", + "insertSpaces": "Inserire spazi quando si preme Tab. Questa impostazione viene sottoposta a override in base al contenuto dei file quando è 'editor.detectIndentation' è 'on'.", "insertSpaces.errorMessage": "È previsto 'boolean'. Nota: il valore \"auto\" è stato sostituito dall'impostazione `editor.detectIndentation`.", "detectIndentation": "All'apertura di un file, `editor.tabSize` e `editor.insertSpaces` verranno rilevati in base al contenuto del file.", "roundedSelection": "Controlla se gli angoli delle selezioni sono arrotondati", @@ -29,6 +31,7 @@ "minimap.maxColumn": "Limita la larghezza della mini mappa in modo da eseguire il rendering al massimo di un certo numero di colonne", "find.seedSearchStringFromSelection": "Controlla se inizializzare la stringa di ricerca nel Widget Trova con il testo selezionato nell'editor", "find.autoFindInSelection": "Controlla se l'impostazione Trova nella selezione è attivata quando vengono selezionati più caratteri o righe di testo nell'editor", + "find.globalFindClipboard": "Controlla se il widget Trova debba leggere o modificare gli appunti ricerche condivise su macOS", "wordWrap.off": "Il wrapping delle righe non viene eseguito.", "wordWrap.on": "Verrà eseguito il wrapping delle righe in base alla larghezza del viewport.", "wordWrap.wordWrapColumn": "Verrà eseguito il wrapping delle righe alla posizione corrispondente a `editor.wordWrapColumn`.", diff --git a/i18n/ita/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/ita/src/vs/editor/contrib/folding/folding.i18n.json index cebd48ad3bd..2a546151b45 100644 --- a/i18n/ita/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/ita/src/vs/editor/contrib/folding/folding.i18n.json @@ -8,6 +8,9 @@ "unFoldRecursivelyAction.label": "Espandi in modo ricorsivo", "foldAction.label": "Riduci", "foldRecursivelyAction.label": "Riduci in modo ricorsivo", + "foldAllBlockComments.label": "Riduci tutti i blocchi commento", + "foldAllMarkerRegions.label": "Riduci tutte le regioni", + "unfoldAllMarkerRegions.label": "Espandi tutte le regioni", "foldAllAction.label": "Riduci tutto", "unfoldAllAction.label": "Espandi tutto", "foldLevelAction.label": "Livello riduzione {0}" diff --git a/i18n/ita/src/vs/platform/environment/node/argv.i18n.json b/i18n/ita/src/vs/platform/environment/node/argv.i18n.json index 871d55bf6d3..194dc9d937c 100644 --- a/i18n/ita/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/ita/src/vs/platform/environment/node/argv.i18n.json @@ -12,8 +12,11 @@ "newWindow": "Forza una nuova istanza di Code.", "performance": "Eseguire l'avvio con il comando 'Developer: Startup Performance' abilitato.", "prof-startup": "Esegui il profiler della CPU durante l'avvio", + "inspect-extensions": "Consentire il debug e profiling delle estensioni. Controllare gli strumenti di sviluppo per l'uri di connessione.", + "inspect-brk-extensions": "Consentire il debug e profiling delle estensioni con l'host di estensione in pausa dopo inizio. Controllare gli strumenti di sviluppo per l'uri di connessione.", "reuseWindow": "Forza l'apertura di un file o di una cartella nell'ultima finestra attiva.", "userDataDir": "Consente di specificare la directory in cui si trovano i dati utente. Utile quando viene eseguito come root.", + "log": "Livello di logging da utilizzare. Il valore predefinito è 'info'. I valori consentiti sono 'critical, 'error', 'warn', 'info', 'debug', 'trace', 'off'.", "verbose": "Visualizza l'output dettagliato (implica --wait).", "wait": "Attendere la chiusura dei file prima della restituzione.", "extensionHomePath": "Impostare il percorso radice per le estensioni.", @@ -24,6 +27,7 @@ "experimentalApis": "Abilita funzionalità di API proposte per un'estensione specifica.", "disableExtensions": "Disabilita tutte le estensioni installate.", "disableGPU": "Disabilita l'accelerazione hardware della GPU.", + "status": "Stampare le informazioni di utilizzo e diagnostica di processo.", "version": "Visualizza la versione.", "help": "Visualizza la sintassi.", "usage": "Utilizzo", diff --git a/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..e535e7a4f8b 100644 --- a/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "notCompatibleDownload": "Impossibile scaricare perché non è stata trovata l'estensione compatibile con la versione corrente '{0}' di VS Code." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json b/i18n/ita/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json index 39a1c28e54e..f8230336461 100644 --- a/i18n/ita/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json +++ b/i18n/ita/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json @@ -6,7 +6,13 @@ { "invalidManifest": "Estensione non valida: package.json non è un file JSON.", "restartCodeLocal": "Riavviare Code prima di reinstallare {0}.", + "installingOutdatedExtension": "Una versione più recente di questa estensione è già installata. Vuoi eseguire l'override di questa con la versione precedente?", + "override": "Eseguire l'override", "cancel": "Annulla", + "notFoundCompatible": "Impossibile installare perché non è stata trovata l'estensione '{0}' compatibile con la versione corrente '{1}' di VS Code.", + "quitCode": "Impossibile installare perché un'istanza obsoleta dell'estensione è ancora in esecuzione. Si prega di uscire e riavviare VS Code prima di reinstallare.", + "exitCode": "Impossibile installare perché un'istanza obsoleta dell'estensione è ancora in esecuzione. Si prega di uscire e riavviare VS Code prima di reinstallare.", + "notFoundCompatibleDependency": "Impossibile installare perché non è stata trovata l'estensione dipendente '{0}' compatibile con la versione corrente '{1}' di VS Code.", "uninstallDependeciesConfirmation": "Disinstallare solo '{0}' o anche le relative dipendenze?", "uninstallOnly": "Solo", "uninstallAll": "Tutto", diff --git a/i18n/ita/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/ita/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index 20c377c8508..1eabda0d35d 100644 --- a/i18n/ita/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/ita/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -17,6 +17,8 @@ "vscode.extension.activationEvents.onLanguage": "Un evento di attivazione emesso ogni volta che viene aperto un file che risolve nella lingua specificata.", "vscode.extension.activationEvents.onCommand": "Un evento di attivazione emesso ogni volta che viene invocato il comando specificato.", "vscode.extension.activationEvents.onDebug": "Un evento di attivazione emesso ogni volta che un utente sta per avviare il debug o sta per impostare le configurazioni di debug.", + "vscode.extension.activationEvents.onDebugInitialConfigurations": "Un evento di attivazione emesso ogni volta che un \"launch.json\" deve essere creato (e tutti i metodi di provideDebugConfigurations devono essere chiamati).", + "vscode.extension.activationEvents.onDebugResolve": "Un evento di attivazione emesso ogni volta che una sessione di debug di tipo specifico sta per essere lanciata (e un corrispondente metodo resolveDebugConfiguration deve essere chiamato).", "vscode.extension.activationEvents.workspaceContains": "Un evento di attivazione emesso ogni volta che si apre una cartella che contiene almeno un file corrispondente al criterio GLOB specificato.", "vscode.extension.activationEvents.onView": "Un evento di attivazione emesso ogni volta che la visualizzazione specificata viene espansa.", "vscode.extension.activationEvents.star": "Un evento di attivazione emesso all'avvio di VS Code. Per garantire la migliore esperienza per l'utente finale, sei pregato di utilizzare questo evento di attivazione nella tua estensione solo quando nessun'altra combinazione di eventi di attivazione funziona nel tuo caso.", diff --git a/i18n/ita/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json b/i18n/ita/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json index 7564483e276..f174c284bff 100644 --- a/i18n/ita/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json +++ b/i18n/ita/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "largeNumberBadge": "Più di 10.000", "badgeTitle": "{0} - {1}", "additionalViews": "Visualizzazioni aggiuntive", "numberBadge": "{0} ({1})", diff --git a/i18n/ita/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json b/i18n/ita/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json index 29413ba4c6c..0e49511fa54 100644 --- a/i18n/ita/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json +++ b/i18n/ita/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json @@ -6,6 +6,7 @@ { "editorCommand.activeEditorMove.description": "Consente di spostare l'editor attivo per schede o gruppi", "editorCommand.activeEditorMove.arg.name": "Argomento per spostamento editor attivo", + "editorCommand.activeEditorMove.arg.description": "Proprietà degli argomenti:\n\t* 'to': valore stringa che specifica dove eseguire lo spostamento.\n\t* 'by': valore stringa che specifica l'unità per lo spostamento, ovvero per scheda o per gruppo.\n\t* 'value': valore numerico che specifica il numero di posizioni o una posizione assoluta per lo spostamento.", "commandDeprecated": "Il comando **{0}** è stato rimosso. In alternativa, usare **{1}**", "openKeybindings": "Configura tasti di scelta rapida" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/ita/src/vs/workbench/electron-browser/actions.i18n.json index 702864ad03a..ac85b55822e 100644 --- a/i18n/ita/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/ita/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "Per un elenco delle lingue supportate, vedere {0}.", "restart": "Se si modifica il valore, è necessario riavviare VSCode.", "fail.createSettings": "Non è possibile creare '{0}' ({1}).", + "openLogsFolder": "Apri cartella dei log", + "showLogs": "Mostra log...", + "mainProcess": "Principale", + "sharedProcess": "Condiviso", + "rendererProcess": "Renderer", + "extensionHost": "Host dell'estensione", + "selectProcess": "Seleziona il processo", + "setLogLevel": "Imposta livello log", + "trace": "Analisi", "debug": "Debug", "info": "Informazioni", "warn": "Avviso", - "err": "Errore" + "err": "Errore", + "critical": "Errori critici", + "off": "Disattivato", + "selectLogLevel": "Seleziona il livello log" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/electron-browser/main.contribution.i18n.json b/i18n/ita/src/vs/workbench/electron-browser/main.contribution.i18n.json index 0f9e509d30b..14e407b8a5d 100644 --- a/i18n/ita/src/vs/workbench/electron-browser/main.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/electron-browser/main.contribution.i18n.json @@ -16,6 +16,7 @@ "workbench.editor.labelFormat.long": "Visualizza il nome del file seguito dal relativo percorso assoluto.", "tabDescription": "Controlla il formato dell'etichetta per un editor. Se si modifica questa impostazione, ad esempio, risulterà più agevole individuare il percorso di un file:\n- short: 'parent'\n- medium: 'workspace/src/parent'\n- long: '/home/user/workspace/src/parent'\n- default: '.../parent', quando un'altra scheda condivide lo stesso titolo, oppure il percorso relativo dell'area di lavoro se le schede sono disabilitate", "editorTabCloseButton": "Controlla la posizione dei pulsanti di chiusura delle schede dell'editor oppure li disabilita quando è impostata su 'off'.", + "tabSizing": "Controlla il ridimensionamento delle schede dell'editor. Impostare su 'fit' per adattare le dimensioni delle schede in modo che l'intera etichetta dell'editor sia visibile. Impostare su 'shrink' per consentire il ridimensionamento delle schede qaundo lo spazio disponibile è insufficiente per visualizzare tutte le schede contemporaneamente.", "showIcons": "Controlla se visualizzare o meno un'icona per gli editor aperti. Richiede l'abilitazione anche di un tema dell'icona.", "enablePreview": "Controlla se gli editor aperti vengono visualizzati come anteprima. Le anteprime editor vengono riutilizzate finché vengono mantenute (ad esempio tramite doppio clic o modifica) e vengono visualizzate in corsivo.", "enablePreviewFromQuickOpen": "Controlla se gli editor aperti da Quick Open vengono visualizzati come anteprima. Le anteprime editor vengono riutilizzate finché vengono mantenute, ad esempio tramite doppio clic o modifica.", @@ -29,6 +30,7 @@ "statusBarVisibility": "Controlla la visibilità della barra di stato nella parte inferiore del workbench.", "activityBarVisibility": "Controlla la visibilità della barra attività nel workbench.", "closeOnFileDelete": "Controlla se gli editor che visualizzano un file devono chiudersi automaticamente quando il file viene eliminato o rinominato da un altro processo. Se si disabilita questa opzione, in una simile circostanza l'editor verrà aperto e i file risulteranno modificati ma non salvati. Nota: se si elimina il file dall'interno dell'applicazione, l'editor verrà sempre chiuso e i file modificati ma non salvati non verranno mai chiusi allo scopo di salvaguardare i dati.", + "enableNaturalLanguageSettingsSearch": "Controlla se abilitare la modalità di ricerca in linguaggio naturale per le impostazioni.", "fontAliasing": "Controlla il metodo di aliasing dei caratteri nell'area di lavoro.\n- impostazione predefinita: anti-aliasing dei caratteri a livello di sub-pixel. Nella maggior parte delle visualizzazioni non retina consentirà di ottenere un testo con il massimo contrasto.\n- anti-aliasing: anti-aliasing dei caratteri a livello di pixel, invece che a livello di sub-pixel. Consente di visualizzare i caratteri più chiari.\n- nessuno: disabilita l'anti-aliasing dei caratteri. Il testo verrà visualizzato con contorni irregolari.", "workbench.fontAliasing.default": "Anti-aliasing dei caratteri a livello di sub-pixel. Nella maggior parte delle visualizzazioni non retina consentirà di ottenere un testo con il massimo contrasto.", "workbench.fontAliasing.antialiased": "Anti-aliasing dei caratteri a livello di pixel, invece che a livello di sub-pixel. Consente di visualizzare i caratteri più chiari.", diff --git a/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index 7a0c9c59e95..54fcb973a53 100644 --- a/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -20,6 +20,10 @@ "openExplorerOnEnd": "Apre automaticamente la visualizzazione di esplorazione al termine di una sessione di debug", "inlineValues": "Mostra i valori delle variabili inline nell'editor durante il debug", "hideActionBar": "Controlla se nascondere la barra delle azioni mobile di debug", + "never": "Non mostrare mai debug nella barra di stato", + "always": "Visualizzare sempre debug nella barra di stato", + "onFirstSessionStart": "Mostra debug nella barra solo stato dopo il primo avvio del debug", + "showInStatusBar": "Controlla se rendere visibile la barra di stato del debug", "openDebug": "Controlla se la viewlet di debug debba essere aperta all'avvio della sessione di debug.", "launch": "Configurazione globale per l'esecuzione del debug. Può essere usata come un'alternativa a \"launch.json\" " } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index c4da42088b5..f162ddc21b6 100644 --- a/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Aggiunto un punto di interruzione a riga {0} del file {1}", "breakpointRemoved": "Rimosso un punto di interruzione a riga {0} del file {1}", "compoundMustHaveConfigurations": "Per avviare più configurazioni, deve essere impostato l'attributo \"configurations\" dell'elemento compounds.", + "configMissing": "In 'launch.json' manca la configurazione '{0}'.", + "launchJsonDoesNotExist": "'launch.json' non esiste.", "debugRequestNotSupported": "Nella configurazione di debug scelta l'attributo '{0}' ha un valore non supportato '{1}'.", "debugRequesMissing": "Nella configurazione di debug scelta manca l'attributo '{0}'.", "debugTypeNotSupported": "Il tipo di debug configurato '{0}' non è supportato.", diff --git a/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json index 64cacf41da1..0f0d43dc77d 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json @@ -6,9 +6,11 @@ { "name": "Nome dell'estensione", "extension id": "Identificatore dell'estensione", + "preview": "Anteprima", "publisher": "Nome dell'editore", "install count": "Conteggio delle installazioni", "rating": "Valutazione", + "repository": "Repository", "license": "Licenza", "details": "Dettagli", "contributions": "Contributi", diff --git a/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..e6237e881bd 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "Valutato da {0} utenti", + "ratedBySingleUser": "Valutato da 1 utente" +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..d3895e27f4d 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "Fare clic per arrestare la profilatura." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index ada39f95c93..4664107eeb2 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "Gestisci le estensioni", "galleryExtensionsCommands": "Installa estensioni della raccolta", "extension": "Estensione", + "runtimeExtension": "Estensioni in esecuzione", "extensions": "Estensioni", "view": "Visualizza", "developer": "Sviluppatore", "extensionsConfigurationTitle": "Estensioni", - "extensionsAutoUpdate": "Aggiorna automaticamente le estensioni" + "extensionsAutoUpdate": "Aggiorna automaticamente le estensioni", + "extensionsIgnoreRecommendations": "Se impostato a true, le notifiche delle raccomandazioni dell'estensione non verranno più mostrate." } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index d7a84eb6e65..ef42850ea00 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "Segnala problema" + "starActivation": "Attivata all'avvio", + "workspaceContainsGlobActivation": "Attivata perché nell'area di lavoro è presente un file corrispondente a {0}", + "workspaceContainsFileActivation": "Attivata perché nell'area di lavoro è presente il file {0}", + "languageActivation": "Attivata perché è stato aperto un file {0}", + "workspaceGenericActivation": "Data di attivazione: {0}", + "errors": "{0} errori non rilevati", + "extensionsInputName": "Estensioni in esecuzione", + "showRuntimeExtensions": "Mostra estensioni in esecuzione", + "reportExtensionIssue": "Segnala problema", + "extensionHostProfileStart": "Avvia profilo host dell'estensione", + "extensionHostProfileStop": "Arresta profilo host dell'estensione", + "saveExtensionHostProfile": "Salva profilo host dell'estensione" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.i18n.json index 08176692fa7..979ee1896bb 100644 --- a/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "filesCategory": "File", - "revealInSideBar": "Visualizza nella barra laterale" + "revealInSideBar": "Visualizza nella barra laterale", + "acceptLocalChanges": "Utilizzare le modifiche e sovrascrivere il contenuto del disco", + "revertLocalChanges": "Annullare le modifiche e tornare al contenuto sul disco" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json b/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json index 976c0a1a9f7..7d23a38e703 100644 --- a/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json @@ -39,6 +39,7 @@ "compareSource": "Seleziona per il confronto", "globalCompareFile": "Confronta file attivo con...", "openFileToCompare": "Aprire prima un file per confrontarlo con un altro file.", + "compareWith": "Confronta '{0}' con '{1}'", "compareFiles": "Confronta file", "refresh": "Aggiorna", "save": "Salva", @@ -68,5 +69,7 @@ "invalidFileNameError": "Il nome **{0}** non è valido per un nome file o un nome di cartella. Scegliere un nome diverso.", "filePathTooLongError": "Con il nome **{0}** il percorso diventa troppo lungo. Scegliere un nome più breve.", "compareWithSaved": "Confronta file attivo con file salvato", - "compareWithClipboard": "Confronta il file attivo con gli appunti" + "modifiedLabel": "{0} (su disco) ↔ {1}", + "compareWithClipboard": "Confronta il file attivo con gli appunti", + "clipboardComparisonLabel": "Appunti ↔ {0}" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/electron-browser/files.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/files/electron-browser/files.contribution.i18n.json index d240e8fc075..6fb07e8966e 100644 --- a/i18n/ita/src/vs/workbench/parts/files/electron-browser/files.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/electron-browser/files.contribution.i18n.json @@ -48,5 +48,6 @@ "sortOrder.type": "I file e le cartelle vengono ordinati in ordine alfabetico in base all'estensione. Le cartelle vengono visualizzate prima dei file.", "sortOrder.modified": "I file e le cartelle vengono ordinati in ordine decrescente in base alla data dell'ultima modifica. Le cartelle vengono visualizzate prima dei file.", "sortOrder": "Controlla l'ordinamento di file e cartelle in Esplora risorse. Oltre all'ordinamento predefinito, è possibile impostare l'ordine su 'mixed' (file e cartelle vengono ordinati insieme), 'type' (in base al tipo di file), 'modified' (in base alla data dell'ultima modifica) o 'filesFirst' (i file vengono ordinati prima delle cartelle).", + "explorer.decorations.colors": "Controlla l'uso dei colori negli effetti del file.", "explorer.decorations.badges": "Controlli se decorazioni file devono utilizzare badge." } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json b/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json index 4427bde01ef..0fc4ed5c41b 100644 --- a/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json @@ -7,6 +7,7 @@ "noWorkspace": "Nessuna cartella aperta", "explorerSection": "Sezione Esplora file", "noWorkspaceHelp": "Non hai ancora aggiunto cartelle nell'area di lavoro", + "addFolder": "Aggiungi cartella", "noFolderHelp": "Non ci sono ancora cartelle aperte.", "openFolder": "Apri cartella" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.i18n.json b/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.i18n.json index e0b8c74653b..cee0d8e205d 100644 --- a/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/electron-browser/views/explorerDecorationsProvider.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "label": "Esplora risorse" + "label": "Esplora risorse", + "canNotResolve": "Non è possibile risolvere la cartella dell'area di lavoro" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json b/i18n/ita/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json index 8b6ad71cd4e..3c7de140782 100644 --- a/i18n/ita/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "totalProblems": "Totale {0} problemi", + "filteredProblems": "Mostrando {0} di {1} problemi" +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/markers/common/messages.i18n.json b/i18n/ita/src/vs/workbench/parts/markers/common/messages.i18n.json index e60e5ecc052..5e585c6ea40 100644 --- a/i18n/ita/src/vs/workbench/parts/markers/common/messages.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/markers/common/messages.i18n.json @@ -6,6 +6,7 @@ { "viewCategory": "Visualizza", "problems.view.toggle.label": "Attiva/disattiva problemi", + "problems.view.focus.label": "Problemi di Focus", "problems.panel.configuration.title": "Visualizzazione Problemi", "problems.panel.configuration.autoreveal": "Controlla se la visualizzazione Problemi deve visualizzare automaticamente i file durante l'apertura", "markers.panel.title.problems": "Problemi", diff --git a/i18n/ita/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json b/i18n/ita/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json index 4d00ef4a863..6e8f4c9cca2 100644 --- a/i18n/ita/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defineKeybinding.initial": "Premere la combinazione di tasti desiderata, quindi INVIO.", "defineKeybinding.chordsTo": "premi contemporaneamente per" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json b/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json index a5a6e8a1bba..344b356cf42 100644 --- a/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "openRawDefaultSettings": "Apri impostazioni predefinite non elaborate", "openGlobalSettings": "Apri impostazioni utente", "openGlobalKeybindings": "Apri tasti di scelta rapida", "openGlobalKeybindingsFile": "Apri file dei tasti di scelta rapida", diff --git a/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json b/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json index 4669fa3cd56..7c1a3a7d309 100644 --- a/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json @@ -4,10 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defaultSettingsFuzzyPrompt": "Prova la ricerca in linguaggio naturale.", "defaultSettings": "Inserire le impostazioni nell'editor di lato destro per eseguire l'override.", "noSettingsFound": "Non sono state trovate impostazioni.", "settingsSwitcherBarAriaLabel": "Selezione impostazioni", "userSettings": "Impostazioni utente", "workspaceSettings": "Impostazioni area di lavoro", - "folderSettings": "Impostazioni cartella" + "folderSettings": "Impostazioni cartella", + "enableFuzzySearch": "Abilita la ricerca in linguaggio naturale" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json b/i18n/ita/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json index 6022523a4a4..b3c0576f340 100644 --- a/i18n/ita/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json @@ -14,6 +14,7 @@ "ConfigurationParser.noTypeDefinition": "Errore: non ci sono attività registrate di tipo '{0}'. Non è stata installata un'estensione che fornisce un provider di task corrispondente?", "ConfigurationParser.missingRequiredProperty": "Errore: nella configurazione di attività '{0}' manca la proprietà obbligatoria '{1}'. La configurazione dell'attività verrà ignorata.", "ConfigurationParser.notCustom": "Errore: tasks non è dichiarato come un'attività personalizzata. La configurazione verrà ignorata.\n{0}\n", + "ConfigurationParser.noTaskName": "Errore: un'attività deve specificare una proprietà label. L'attività verrà ignorata.\n{0}\n", "taskConfiguration.shellArgs": "Avviso: l'attività '{0}' è un comando di shell e uno dei suoi argomenti potrebbe avere spazi indesiderati. Per garantire la correttezza della riga di comando unire args nel comando stesso.", "taskConfiguration.noCommandOrDependsOn": "Errore: l'attività '{0}' non specifica un comando né una proprietà dependsOn. L'attività verrà ignorata. La sua definizione è:\n{1}", "taskConfiguration.noCommand": "Errore: l'attività '{0}' non definisce un comando. L'attività verrà ignorata. Definizione dell'attività:\n{1}", diff --git a/i18n/ita/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json index f823b4b682f..42ec40f0ebd 100644 --- a/i18n/ita/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json @@ -16,6 +16,7 @@ "terminal.integrated.rightClickCopyPaste": "Se impostata, impedirà la visualizzazione del menu di scelta rapida quando si fa clic con il pulsante destro del mouse all'interno del terminale, ma eseguirà il comando Copia in presenza di una selezione e il comando Incolla in assenza di una selezione.", "terminal.integrated.fontFamily": "Controlla la famiglia di caratteri del terminale. L'impostazione predefinita è il valore di editor.fontFamily.", "terminal.integrated.fontSize": "Consente di controllare le dimensioni del carattere in pixel del terminale.", + "terminal.integrated.lineHeight": "Controlla l'altezza della riga del terminale. Questo numero è moltiplicato per la dimensione del carattere del terminale per ottenere l'effettiva altezza della riga in pixel.", "terminal.integrated.enableBold": "Per abilitare il grassetto del testo all'interno del terminale, è necessario il supporto della shell del terminale.", "terminal.integrated.cursorBlinking": "Controlla se il cursore del terminale è intermittente o meno.", "terminal.integrated.cursorStyle": "Controlla lo stile del cursore del terminale.", diff --git a/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 551ccc06ba2..7fdd1a182be 100644 --- a/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -7,6 +7,7 @@ "selectTheme.label": "Tema colori", "themes.category.light": "temi chiari", "themes.category.dark": "temi scuri", + "themes.category.hc": "temi a contrasto elevato", "installColorThemes": "Installa temi colori aggiuntivi...", "themes.selectTheme": "Selezionare il Tema colori (tasti su/giù per anteprima)", "selectIconTheme.label": "Tema icona file", diff --git a/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..3d8be0b14b4 100644 --- a/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) Host profilatura estensione..." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index 037da6258e4..fed34b0e75d 100644 --- a/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/ita/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "L'host dell'estensione è stato terminato in modo imprevisto.", "extensionHostProcess.unresponsiveCrash": "L'host dell'estensione è stato terminato perché non rispondeva.", "overwritingExtension": "Sovrascrittura dell'estensione {0} con {1}.", - "extensionUnderDevelopment": "Caricamento dell'estensione di sviluppo in {0}" + "extensionUnderDevelopment": "Caricamento dell'estensione di sviluppo in {0}", + "extensionCache.invalid": "Le estensioni sono state modificate sul disco. Si prega di ricaricare la finestra." } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json b/i18n/ita/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json index 3f4aae3a6f0..b92ebc6840a 100644 --- a/i18n/ita/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json +++ b/i18n/ita/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json @@ -21,5 +21,6 @@ "keybindings.json.command": "Nome del comando da eseguire", "keybindings.json.when": "Condizione quando il tasto è attivo.", "keybindings.json.args": "Argomenti da passare al comando da eseguire.", - "keyboardConfigurationTitle": "Tastiera" + "keyboardConfigurationTitle": "Tastiera", + "dispatch": "Controlla la logica di invio delle pressioni di tasti da usare, tra `code` (scelta consigliata) e `keyCode`." } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/services/textfile/common/textFileService.i18n.json b/i18n/ita/src/vs/workbench/services/textfile/common/textFileService.i18n.json index 8b6ad71cd4e..0c1130350d8 100644 --- a/i18n/ita/src/vs/workbench/services/textfile/common/textFileService.i18n.json +++ b/i18n/ita/src/vs/workbench/services/textfile/common/textFileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "files.backup.failSave": "Non è stato possibile scrivere i file che sono stati modificati nel percorso di backup (errore: {0}). provare a salvare i file prima e quindi uscire." +} \ No newline at end of file diff --git a/i18n/jpn/extensions/emmet/package.i18n.json b/i18n/jpn/extensions/emmet/package.i18n.json index e29ab462a72..b5d41898982 100644 --- a/i18n/jpn/extensions/emmet/package.i18n.json +++ b/i18n/jpn/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "BEM フィルターを利用時にクラス使用する Modifier の区切り文字", "emmetPreferencesFilterCommentBefore": "コメント フィルター使用時、一致した要素の前に配置するコメントの定義。 ", "emmetPreferencesFilterCommentAfter": "コメント フィルター使用時、一致した要素の後に配置するコメントの定義。", - "emmetPreferencesFilterCommentTrigger": "コメント フィルターに適用される略語に存在する属性名のカンマ区切りのリスト" + "emmetPreferencesFilterCommentTrigger": "コメント フィルターに適用される略語に存在する属性名のカンマ区切りのリスト", + "emmetPreferencesFormatNoIndentTags": "内部インデントを取得しないタグ名の配列", + "emmetPreferencesFormatForceIndentTags": "内部インデントを常に取得するタグ名の配列", + "emmetPreferencesAllowCompactBoolean": "true の場合、 Boolean 型属性の短縮表記が生成されます" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/code/node/cliProcessMain.i18n.json b/i18n/jpn/src/vs/code/node/cliProcessMain.i18n.json index e5e7244d33a..17e8239c8b5 100644 --- a/i18n/jpn/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/jpn/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "拡張機能 '{0}' がインストールされていません。", "useId": "発行元などの完全な拡張機能 ID を使用していることをご確認ください。例: {0}", "successVsixInstall": "拡張機能 '{0}' が正常にインストールされました。", + "cancelVsixInstall": "拡張機能 '{0}' のインストールをキャンセルしました。", "alreadyInstalled": "拡張機能 '{0}' は既にインストールされています。", "foundExtension": "マーケットプレースで '{0}' が見つかりました。", "installing": "インストールしています...", diff --git a/i18n/jpn/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/jpn/src/vs/editor/common/config/commonEditorConfig.i18n.json index f8dc120d025..ccf74d0af32 100644 --- a/i18n/jpn/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/jpn/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "表示するミニマップの最大幅を特定の桁数に制限します", "find.seedSearchStringFromSelection": "エディターの選択から検索ウィジェット内の検索文字列を与えるかどうかを制御します", "find.autoFindInSelection": "エディター内で複数の文字もしくは行が選択されているときに選択範囲を検索するフラグを有効にするかどうかを制御します", + "find.globalFindClipboard": "macOS で検索ウィジェットが共有の検索クリップボードを読み取りまたは変更するかどうかを制御します", "wordWrap.off": "行を折り返しません。", "wordWrap.on": "行をビューポートの幅で折り返します。", "wordWrap.wordWrapColumn": "行を 'editor.wordWrapColumn' で折り返します。", diff --git a/i18n/jpn/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/jpn/src/vs/editor/contrib/folding/folding.i18n.json index 9d34d92b926..c6a310be421 100644 --- a/i18n/jpn/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/jpn/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "折りたたみ", "foldRecursivelyAction.label": "再帰的に折りたたむ", "foldAllBlockComments.label": "すべてのブロック コメントの折りたたみ", + "foldAllMarkerRegions.label": "すべての領域を折りたたむ", + "unfoldAllMarkerRegions.label": "すべての領域を展開", "foldAllAction.label": "すべて折りたたみ", "unfoldAllAction.label": "すべて展開", "foldLevelAction.label": "折りたたみレベル {0}" diff --git a/i18n/jpn/src/vs/platform/environment/node/argv.i18n.json b/i18n/jpn/src/vs/platform/environment/node/argv.i18n.json index f118af6afc0..e5dd71eb7e2 100644 --- a/i18n/jpn/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/jpn/src/vs/platform/environment/node/argv.i18n.json @@ -16,6 +16,7 @@ "inspect-brk-extensions": "起動後に一時停止されている拡張ホストとの拡張機能のデバッグとプロファイリングを許可します。接続 URI を開発者ツールでチェックします。", "reuseWindow": "最後のアクティブ ウィンドウにファイルまたはフォルダーを強制的に開きます。", "userDataDir": "ユーザー データを保持するディレクトリを指定します。ルートで実行している場合に役立ちます。", + "log": "使用するログレベル。既定値は 'info' です。利用可能な値は 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off' です。", "verbose": "詳細出力を表示します (--wait を含みます)。", "wait": "現在のファイルが閉じられるまで待機します。", "extensionHomePath": "拡張機能のルート パスを設定します。", diff --git a/i18n/jpn/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/jpn/src/vs/workbench/electron-browser/actions.i18n.json index 2da6de5555b..e4698109a33 100644 --- a/i18n/jpn/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/jpn/src/vs/workbench/electron-browser/actions.i18n.json @@ -67,5 +67,6 @@ "warn": "警告", "err": "エラー", "critical": "重大", - "off": "オフ" + "off": "オフ", + "selectLogLevel": "ログ レベルを選択" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 2c8374a3999..c5bae21c735 100644 --- a/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "ブレークポイントを追加しました。行 {0}、ファイル {1}", "breakpointRemoved": "ブレークポイントを削除しました。行 {0}、ファイル {1}", "compoundMustHaveConfigurations": "複合構成を開始するには、複合に \"configurations\" 属性が設定されている必要があります。", + "configMissing": "構成 '{0}' が 'launch.json' 内にありません。", + "launchJsonDoesNotExist": "'launch.json' は存在しません。", "debugRequestNotSupported": "選択しているデバッグ構成で `{0}` 属性はサポートされない値 '{1}' を指定しています。", "debugRequesMissing": "選択しているデバッグ構成に属性 '{0}' が含まれていません。", "debugTypeNotSupported": "構成されているデバッグの種類 '{0}' はサポートされていません。", diff --git a/i18n/jpn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/jpn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 2fd0e4b1fd4..41dd62310a4 100644 --- a/i18n/jpn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "ratedByUsers": "{0} 人が評価" + "ratedByUsers": "{0} 人が評価", + "ratedBySingleUser": "1 人が評価" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/jpn/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index e2aef1055ae..d5997e6170c 100644 --- a/i18n/jpn/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/jpn/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -10,5 +10,5 @@ "extensionHostProcess.unresponsiveCrash": "拡張機能のホストが応答しないため終了しました。", "overwritingExtension": "拡張機能 {0} を {1} で上書きしています。", "extensionUnderDevelopment": "開発の拡張機能を {0} に読み込んでいます", - "extensionCache.invalid": "拡張機能がディスク上で変更されています。ウィンドウを再度読み込んでください。" + "extensionCache.invalid": "拡張機能がディスク上で変更されています。ウィンドウを再読み込みしてください。" } \ No newline at end of file diff --git a/i18n/kor/extensions/css/client/out/cssMain.i18n.json b/i18n/kor/extensions/css/client/out/cssMain.i18n.json index 7ecfcb46fd7..64beafb9245 100644 --- a/i18n/kor/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/kor/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "CSS 언어 서버" + "cssserver.name": "CSS 언어 서버", + "folding.start": "영역 접기 시작", + "folding.end": "접기 영역 끝" } \ No newline at end of file diff --git a/i18n/kor/extensions/emmet/package.i18n.json b/i18n/kor/extensions/emmet/package.i18n.json index 10291752ac2..92dd938196e 100644 --- a/i18n/kor/extensions/emmet/package.i18n.json +++ b/i18n/kor/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "BEM 필터 사용시 변경된 구분자를 클래스로 사용합니다.", "emmetPreferencesFilterCommentBefore": "코멘트 필터가 적용 될때 코맨트 표시는 해당된 요소 앞에 배치 해야합니다.", "emmetPreferencesFilterCommentAfter": "코멘트 필터가 적용 될때 코맨트 표시는 해당된 요소 뒤에 배치 해야합니다.", - "emmetPreferencesFilterCommentTrigger": "콤마로 구분된 리스트의 속성은 코멘트 필터 약어로 존재해야 합니다." + "emmetPreferencesFilterCommentTrigger": "콤마로 구분된 리스트의 속성은 코멘트 필터 약어로 존재해야 합니다.", + "emmetPreferencesFormatNoIndentTags": "내부 들여쓰기하면 안 되는 태그 이름 배열", + "emmetPreferencesFormatForceIndentTags": "항상 내부 들여쓰기를 해야 하는 태그 이름의 배열", + "emmetPreferencesAllowCompactBoolean": "true인 경우 부울 속성의 축소된 표기법이 생성됩니다." } \ No newline at end of file diff --git a/i18n/kor/extensions/git/out/autofetch.i18n.json b/i18n/kor/extensions/git/out/autofetch.i18n.json index 9ecba049193..a02c668eb9d 100644 --- a/i18n/kor/extensions/git/out/autofetch.i18n.json +++ b/i18n/kor/extensions/git/out/autofetch.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "yes": "예", - "no": "아니요" + "no": "아니요", + "not now": "나중에", + "suggest auto fetch": "Git 리포지토리 자동 페치하기를 사용하도록 설정하시겠습니까?" } \ No newline at end of file diff --git a/i18n/kor/extensions/git/out/commands.i18n.json b/i18n/kor/extensions/git/out/commands.i18n.json index 2dc481e046b..15fcc99ffbe 100644 --- a/i18n/kor/extensions/git/out/commands.i18n.json +++ b/i18n/kor/extensions/git/out/commands.i18n.json @@ -9,9 +9,12 @@ "create branch": "$(plus) 새 분기 생성", "repourl": "리포지토리 URL", "parent": "부모 디렉터리", + "cancel": "$(sync~spin) 리포지토리를 복제하는 중... 취소하려면 클릭하세요.", + "cancel tooltip": "복제 취소", "cloning": "Git 리포지토리를 복제하는 중...", "openrepo": "리포지토리 열기", "proposeopen": "복제된 리포지토리를 여시겠습니까?", + "init": "Git 리포지토리를 초기화할 작업 영역 폴더 선택", "init repo": "리포지토리 초기화", "create repo": "리포지토리 초기화", "are you sure": "'{0}'에서 Git 리포지토리가 만들어집니다. 계속하시겠습니까?", @@ -49,12 +52,15 @@ "select branch to delete": "삭제할 분기 선택", "confirm force delete branch": "'{0}' 분기가 완벽히 병합되지 않았습니다. 그래도 삭제할까요?", "delete branch": "분기 삭제", + "invalid branch name": "잘못된 분기 이름", + "branch already exists": "이름이 '{0}'인 분기가 이미 있습니다.", "select a branch to merge from": "병합할 분기 선택", "merge conflicts": "병합 충돌이 있습니다. 해결한 후 계속하십시오.", "tag name": "태그 이름", "provide tag name": "태그 이름을 입력하세요.", "tag message": "메시지", "provide tag message": "태그에 주석을 달 메시지를 입력하세요.", + "no remotes to fetch": "이 리포지토리에 페치할 원격 항목이 구성되어 있지 않습니다.", "no remotes to pull": "리포지토리에 풀하도록 구성된 원격 항목이 없습니다.", "pick remote pull repo": "분기를 가져올 원격 선택", "no remotes to push": "리포지토리에 푸시하도록 구성된 원격이 없습니다.", @@ -71,6 +77,7 @@ "no stashes": "복원할 스태시가 없습니다.", "pick stash to pop": "표시할 스태시 선택", "clean repo": "체크 아웃하기 전에 리포지토리 작업 트리를 정리하세요.", + "cant push": "참조를 원격에 푸시할 수 없습니다. 먼저 '풀'을 실행하여 변경 내용을 통합하세요.", "git error details": "Git: {0}", "git error": "Git 오류", "open git log": "Git 로그 열기" diff --git a/i18n/kor/extensions/git/out/main.i18n.json b/i18n/kor/extensions/git/out/main.i18n.json index 1d7b6e3355c..e152fcdc537 100644 --- a/i18n/kor/extensions/git/out/main.i18n.json +++ b/i18n/kor/extensions/git/out/main.i18n.json @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "looking": "다음에서 git을 찾는 중: {0}", "using git": "{1}에서 git {0}을(를) 사용하는 중", + "downloadgit": "Git 다운로드", "neverShowAgain": "다시 표시 안 함", + "notfound": "Git을 찾을 수 없습니다. 'git.path'를 사용하여 Git을 설치하거나 구성합니다.", "updateGit": "Git 업데이트", "git20": "Git {0}이(가) 설치된 것 같습니다. 코드는 2 이하의 Git에서 최적으로 작동합니다." } \ No newline at end of file diff --git a/i18n/kor/extensions/git/package.i18n.json b/i18n/kor/extensions/git/package.i18n.json index 8499c412903..33448449d9e 100644 --- a/i18n/kor/extensions/git/package.i18n.json +++ b/i18n/kor/extensions/git/package.i18n.json @@ -33,8 +33,10 @@ "command.checkout": "다음으로 체크 아웃...", "command.branch": "분기 만들기...", "command.deleteBranch": "분기 삭제...", + "command.renameBranch": "분기 이름 바꾸기...", "command.merge": "분기 병합...", "command.createTag": "태그 생성", + "command.fetch": "페치", "command.pull": "풀", "command.pullRebase": "풀(다시 지정)", "command.pullFrom": "가져올 위치...", @@ -42,9 +44,11 @@ "command.pushTo": "다음으로 푸시...", "command.pushWithTags": "태그로 푸시", "command.sync": "동기화", + "command.syncRebase": "동기화(다시 지정)", "command.publish": "분기 게시", "command.showOutput": "Git 출력 표시", "command.ignore": ".gitignore에 파일 추가", + "command.stashIncludeUntracked": "스태시(미추적 포함)", "command.stash": "스태시", "command.stashPop": "스태시 표시...", "command.stashPopLatest": "최신 슬래시 표시", @@ -57,6 +61,7 @@ "config.countBadge": "Git 배지 카운터를 제어합니다. `all`이면 변경 내용을 모두 계산하고, `tracked`이면 추적된 변경 내용만 계산하고, `off`이면 해제합니다.", "config.checkoutType": "`다음으로 체크 아웃...`을 실행할 때 나열되는 분기 유형을 제어합니다. `all`이면 모든 참조를 표시하고, `local`이면 로컬 분기만 표시하고, `tags`이면 태그만 표시하고, `remote`이면 원격 분기만 표시합니다.", "config.ignoreLegacyWarning": "레거시 Git 경고를 무시합니다.", + "config.ignoreMissingGitWarning": "Git이 없으면 경고를 무시합니다.", "config.ignoreLimitWarning": "리포지토리에 변경 내용이 너무 많으면 경고를 무시합니다.", "config.defaultCloneDirectory": "git 리포지토리를 복제할 기본 위치", "config.enableSmartCommit": "단계적 변경 사항이 없는 경우 모든 변경 사항을 저장합니다.", diff --git a/i18n/kor/extensions/html/client/out/htmlMain.i18n.json b/i18n/kor/extensions/html/client/out/htmlMain.i18n.json index 5be38446dc7..596b2f4e3b5 100644 --- a/i18n/kor/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/kor/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "HTML 언어 서버" + "htmlserver.name": "HTML 언어 서버", + "folding.start": "영역 접기 시작", + "folding.end": "접기 영역 끝" } \ No newline at end of file diff --git a/i18n/kor/extensions/markdown/out/commands.i18n.json b/i18n/kor/extensions/markdown/out/commands.i18n.json index f38833da5da..055ede2dd13 100644 --- a/i18n/kor/extensions/markdown/out/commands.i18n.json +++ b/i18n/kor/extensions/markdown/out/commands.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "previewTitle": "{0} 미리 보기", "onPreviewStyleLoadError": "'markdown.styles': {0}을 불러올 수 없음" } \ No newline at end of file diff --git a/i18n/kor/extensions/markdown/out/security.i18n.json b/i18n/kor/extensions/markdown/out/security.i18n.json index 6dd50f40273..dcffab2e9ec 100644 --- a/i18n/kor/extensions/markdown/out/security.i18n.json +++ b/i18n/kor/extensions/markdown/out/security.i18n.json @@ -11,5 +11,8 @@ "disable.title": "사용 안 함", "disable.description": "모든 콘텐츠 및 스크립트 실행을 허용합니다. 권장하지 않습니다.", "moreInfo.title": "추가 정보", + "enableSecurityWarning.title": "이 작업 영역에서 미리 보기 보안 경고 사용", + "disableSecurityWarning.title": "이 작업 영역에서 미리보기 보안 경고 사용 안 함", + "toggleSecurityWarning.description": "콘텐츠 보안 수준에 영향을 주지 않습니다.", "preview.showPreviewSecuritySelector.title": "이 작업 영역에 대해 Markdown 미리 보기의 보안 설정 선택" } \ No newline at end of file diff --git a/i18n/kor/extensions/merge-conflict/package.i18n.json b/i18n/kor/extensions/merge-conflict/package.i18n.json index ceb1a3d504d..9416111e4bb 100644 --- a/i18n/kor/extensions/merge-conflict/package.i18n.json +++ b/i18n/kor/extensions/merge-conflict/package.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "command.category": "충돌 병합", + "command.accept.all-current": "모든 현재 사항 수락", "command.accept.all-incoming": "수신 모두 수락", "command.accept.all-both": "둘 다 모두 수락", "command.accept.current": "현재 수락", diff --git a/i18n/kor/extensions/typescript/out/features/completionItemProvider.i18n.json b/i18n/kor/extensions/typescript/out/features/completionItemProvider.i18n.json index bfc413e861a..a9f19fc8665 100644 --- a/i18n/kor/extensions/typescript/out/features/completionItemProvider.i18n.json +++ b/i18n/kor/extensions/typescript/out/features/completionItemProvider.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "selectCodeAction": "적용할 코드 동작 선택", "acquiringTypingsLabel": "typings를 가져오는 중...", "acquiringTypingsDetail": "IntelliSense에 대한 typings 정의를 가져오는 중입니다.", "autoImportLabel": "{0}에서 자동으로 가져오기" diff --git a/i18n/kor/extensions/typescript/package.i18n.json b/i18n/kor/extensions/typescript/package.i18n.json index 8a1fd73f72f..7b99823e951 100644 --- a/i18n/kor/extensions/typescript/package.i18n.json +++ b/i18n/kor/extensions/typescript/package.i18n.json @@ -39,6 +39,7 @@ "typescript.openTsServerLog.title": "TS 서버 로그 열기", "typescript.restartTsServer": "TS 서버 다시 시작", "typescript.selectTypeScriptVersion.title": "TypeScript 버전 선택", + "typescript.reportStyleChecksAsWarnings": "스타일 검사를 경고로 보고", "jsDocCompletion.enabled": "자동 JSDoc 주석 사용/사용 안 함", "javascript.implicitProjectConfig.checkJs": "JavaScript 파일의 의미 체계 검사를 사용/사용하지 않습니다. 기존 jsconfig.json 또는 tsconfig.json 파일은 이 설정을 재정의합니다. TypeScript >=2.3.1이 필요합니다. ", "typescript.npm": "자동 입력 인식에 사용된 NPM 실행 파일 경로를 지정합니다. TypeScript >= 2.3.4가 필요합니다.", diff --git a/i18n/kor/src/vs/code/electron-main/main.i18n.json b/i18n/kor/src/vs/code/electron-main/main.i18n.json index d259f7872ca..e77916a201a 100644 --- a/i18n/kor/src/vs/code/electron-main/main.i18n.json +++ b/i18n/kor/src/vs/code/electron-main/main.i18n.json @@ -4,5 +4,9 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "secondInstanceNoResponse": "{0}의 다른 인스턴스가 실행 중이지만 응답하지 않음", + "secondInstanceNoResponseDetail": "다른 인스턴스를 모두 닫고 다시 시도하세요.", + "secondInstanceAdmin": "{0}의 두 번째 인스턴스가 이미 관리자 권한으로 실행되고 있습니다.", + "secondInstanceAdminDetail": "다른 인스턴스를 닫고 다시 시도하세요.", "close": "닫기(&&C)" } \ No newline at end of file diff --git a/i18n/kor/src/vs/code/electron-main/menus.i18n.json b/i18n/kor/src/vs/code/electron-main/menus.i18n.json index 08a780b6810..ea36f9e14c0 100644 --- a/i18n/kor/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/kor/src/vs/code/electron-main/menus.i18n.json @@ -22,10 +22,12 @@ "miQuit": "{0} 종료", "miNewFile": "새 파일(&&N)", "miOpen": "열기(&&O)...", + "miOpenWorkspace": "작업 영역 열기(&&K)...", "miOpenFolder": "폴더 열기(&&F)...", "miOpenFile": "파일 열기(&&O)...", "miOpenRecent": "최근 항목 열기(&&R)", "miSaveWorkspaceAs": "작업 영역을 다른 이름으로 저장", + "miAddFolderToWorkspace": "작업 영역에 폴더 추가(&&D)...", "miSave": "저장(&&S)", "miSaveAs": "다른 이름으로 저장(&&A)...", "miSaveAll": "모두 저장(&&L)", @@ -155,6 +157,7 @@ "mMergeAllWindows": "모든 창 병합", "miToggleDevTools": "개발자 도구 설정/해제(&&T)", "miAccessibilityOptions": "접근성 옵션(&&O)", + "miReportIssue": "문제 보고(&&I)", "miWelcome": "시작(&&W)", "miInteractivePlayground": "대화형 실습(&&I)", "miDocumentation": "설명서(&&D)", @@ -181,6 +184,7 @@ "miDownloadingUpdate": "업데이트를 다운로드하는 중...", "miInstallingUpdate": "업데이트를 설치하는 중...", "miCheckForUpdates": "업데이트 확인...", + "aboutDetail": "버전 {0}\n커밋 {1}\n날짜 {2}\n셸 {3}\n렌더러 {4}\n노드 {5}\n아키텍처 {6}", "okButton": "확인", "copy": "복사(&&C)" } \ No newline at end of file diff --git a/i18n/kor/src/vs/code/node/cliProcessMain.i18n.json b/i18n/kor/src/vs/code/node/cliProcessMain.i18n.json index c8b96ea3f8f..1fa2ec087e3 100644 --- a/i18n/kor/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/kor/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "'{0}' 확장이 설치되어 있지 않습니다.", "useId": "게시자를 포함한 전체 확장 ID(예: {0})를 사용하세요.", "successVsixInstall": "확장 '{0}'이(가) 설치되었습니다!", + "cancelVsixInstall": "'{0}' 확장 설치를 취소했습니다.", "alreadyInstalled": "'{0}' 확장이 이미 설치되어 있습니다.", "foundExtension": "마켓플레이스에서 '{0}'을(를) 찾았습니다.", "installing": "설치 중...", diff --git a/i18n/kor/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/kor/src/vs/editor/common/config/commonEditorConfig.i18n.json index a5e25a8bb9a..1fe9d415d3a 100644 --- a/i18n/kor/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/kor/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "최대 특정 수의 열을 렌더링하도록 미니맵의 너비를 제한합니다.", "find.seedSearchStringFromSelection": "편집기 선택에서 Find Widget의 검색 문자열을 시딩할지 설정합니다.", "find.autoFindInSelection": "편집기에서 여러 글자 또는 행을 선택했을 때 Find in Selection 플래그를 켤지 설정합니다.", + "find.globalFindClipboard": "macOS에서 Find Widget이 공유 클립보드 찾기를 읽거나 수정해야 할지 설정합니다.", "wordWrap.off": "줄이 바뀌지 않습니다.", "wordWrap.on": "뷰포트 너비에서 줄이 바뀝니다.", "wordWrap.wordWrapColumn": "`editor.wordWrapColumn`에서 줄이 바뀝니다.", diff --git a/i18n/kor/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/kor/src/vs/editor/contrib/folding/folding.i18n.json index 0d8ac009a87..64113ae45bc 100644 --- a/i18n/kor/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/kor/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "접기", "foldRecursivelyAction.label": "재귀적으로 접기", "foldAllBlockComments.label": "모든 블록 코멘트를 접기", + "foldAllMarkerRegions.label": "모든 영역 접기", + "unfoldAllMarkerRegions.label": "모든 영역 펼치기", "foldAllAction.label": "모두 접기", "unfoldAllAction.label": "모두 펼치기", "foldLevelAction.label": "수준 {0} 접기" diff --git a/i18n/kor/src/vs/platform/environment/node/argv.i18n.json b/i18n/kor/src/vs/platform/environment/node/argv.i18n.json index b8dae1071a7..43b38b984db 100644 --- a/i18n/kor/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/kor/src/vs/platform/environment/node/argv.i18n.json @@ -12,8 +12,11 @@ "newWindow": "Code의 새 인스턴스를 강제 적용합니다.", "performance": "'Developer: Startup Performance' 명령을 사용하여 시작합니다.", "prof-startup": "시작하는 동안 CPU 프로파일러 실행", + "inspect-extensions": "디버깅 및 확장 프로파일링을 허용합니다. 연결 uri에 대한 개발자 도구를 확인하십시오.", + "inspect-brk-extensions": "시작 후 일시 중시된 확장 호스트에서 디버깅 및 확장 프로파일링을 허용합니다. 연결 URL은 개발자 도구를 확인하세요.", "reuseWindow": "마지막 활성 창에서 파일 또는 폴더를 강제로 엽니다.", "userDataDir": "사용자 데이터가 저장되는 디렉터리를 지정합니다(루트로 실행할 경우 유용함).", + "log": "사용할 로그 수준이며 기본값은 'info'입니다. 허용되는 값은 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'입니다.", "verbose": "자세한 정보 표시를 출력합니다(--wait를 의미).", "wait": "파일이 닫힐 때 까지 기다린 후 돌아갑니다.", "extensionHomePath": "확장의 루트 경로를 설정합니다.", @@ -24,6 +27,7 @@ "experimentalApis": "확장에 대해 제안된 API 기능을 사용하도록 설정합니다.", "disableExtensions": "설치된 모든 확장을 사용하지 않도록 설정합니다.", "disableGPU": "GPU 하드웨어 가속을 사용하지 않도록 설정합니다.", + "status": "프로세스 사용 및 진단 정보를 인쇄합니다.", "version": "버전을 출력합니다.", "help": "사용법을 출력합니다.", "usage": "사용법", diff --git a/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..3a3afcd8c69 100644 --- a/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "notCompatibleDownload": "VS Code의 현재 버전 '{0}'과(와) 호환되는 확장을 찾을 수 없으므로 다운로드할 수 없습니다." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json b/i18n/kor/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json index 1f9083afa00..64578b6f8a5 100644 --- a/i18n/kor/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json +++ b/i18n/kor/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json @@ -6,7 +6,13 @@ { "invalidManifest": "잘못된 확장: package.json이 JSON 파일이 아닙니다.", "restartCodeLocal": "{0}을(를) 다시 설치하기 전에 Code를 다시 시작하세요.", + "installingOutdatedExtension": "이 확장의 최신 버전이 이미 설치되어 있습니다. 이 버전을 이전 버전으로 재정의하시겠습니까?", + "override": "재정의", "cancel": "취소", + "notFoundCompatible": "VS Code의 현재 버전 '{1}'과(와) 호환되는 '{0}' 확장을 찾을 수 없으므로 설치할 수 없습니다.", + "quitCode": "확장의 사용되지 않는 인스턴스가 계속 실행 중이므로 설치할 수 없습니다. 다시 설치하기 전에 VS Code를 종료했다가 다시 시작하세요.", + "exitCode": "확장의 사용되지 않는 인스턴스가 계속 실행 중이므로 설치할 수 없습니다. 다시 설치하기 전에 VS Code를 종료했다가 다시 시작하세요.", + "notFoundCompatibleDependency": "VS Code의 현재 버전 '{1}'과(와) 호환되는 종속된 확장 '{0}'을(를) 찾을 수 없으므로 설치할 수 없습니다.", "uninstallDependeciesConfirmation": "'{0}'만 제거할까요, 아니면 종속성도 제거할까요?", "uninstallOnly": "만", "uninstallAll": "모두", diff --git a/i18n/kor/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/kor/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index cb72ee5dd24..ff477458670 100644 --- a/i18n/kor/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/kor/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -17,6 +17,8 @@ "vscode.extension.activationEvents.onLanguage": "지정된 언어로 확인되는 파일을 열 때마다 활성화 이벤트가 발송됩니다.", "vscode.extension.activationEvents.onCommand": "지정된 명령을 호출할 때마다 활성화 이벤트가 발송됩니다.", "vscode.extension.activationEvents.onDebug": "사용자가 디버깅을 시작하거나 디버그 구성을 설정하려고 할 때마다 활성화 이벤트를 내보냅니다.", + "vscode.extension.activationEvents.onDebugInitialConfigurations": "\"launch.json\"을 만들어야 할 때마다(그리고 모든 provideDebugConfigurations 메서드를 호출해야 할 때마다) 발생하는 활성화 이벤트입니다.", + "vscode.extension.activationEvents.onDebugResolve": "특정 유형의 디버그 세션이 시작하려고 할 때마다(그리고 해당하는 resolveDebugConfiguration 메서드를 호출해야 할 때마다) 발생하는 활성화 이벤트입니다.", "vscode.extension.activationEvents.workspaceContains": "지정된 glob 패턴과 일치하는 파일이 하나 이상 있는 폴더를 열 때마다 활성화 알림이 발송됩니다.", "vscode.extension.activationEvents.onView": "지정된 뷰가 확장될 때마다 활성화 이벤트가 내보내 집니다.", "vscode.extension.activationEvents.star": "VS Code 시작 시 활성화 이벤트가 발송됩니다. 훌륭한 최종 사용자 경험을 보장하려면 사용 케이스에서 다른 활성화 이벤트 조합이 작동하지 않을 때에만 확장에서 이 활성화 이벤트를 사용하세요.", diff --git a/i18n/kor/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json b/i18n/kor/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json index 091e1d88413..78f9f16be46 100644 --- a/i18n/kor/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json +++ b/i18n/kor/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "toggleTabs": "탭 표시 설정/해제", "view": "보기" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json b/i18n/kor/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json index 4691263e486..8f69ba1a1bc 100644 --- a/i18n/kor/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json +++ b/i18n/kor/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "largeNumberBadge": "10k+", "badgeTitle": "{0} - {1}", "additionalViews": "추가 뷰", "numberBadge": "{0}({1})", diff --git a/i18n/kor/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json b/i18n/kor/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json index 9b3b3941512..6c0c854e4bf 100644 --- a/i18n/kor/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json +++ b/i18n/kor/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json @@ -6,6 +6,7 @@ { "editorCommand.activeEditorMove.description": "활성 편집기를 탭 또는 그룹 단위로 이동", "editorCommand.activeEditorMove.arg.name": "활성 편집기 이동 인수", + "editorCommand.activeEditorMove.arg.description": "인수 속성: * '를': 문자열 값을 제공 하 고 위치를 이동.\n\t* ' 의해': 문자열 이동에 대 한 단위를 제공 하는 값. 탭 또는 그룹.\n\t* ' value': 얼마나 많은 위치 또는 이동 하는 절대 위치를 제공 하는 숫자 값.", "commandDeprecated": "**{0}** 명령이 제거되었습니다. 대신 **{1}** 명령을 사용할 수 있습니다.", "openKeybindings": "바로 가기 키 구성" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json b/i18n/kor/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json index 8c2c5a8883c..68d8989959f 100644 --- a/i18n/kor/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json +++ b/i18n/kor/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json @@ -52,5 +52,6 @@ "screenReaderDetectedExplanation.question": "VS Code를 작동하기 위해 화면 읽기 프로그램을 사용하십니까?", "screenReaderDetectedExplanation.answerYes": "예", "screenReaderDetectedExplanation.answerNo": "아니요", - "screenReaderDetectedExplanation.body1": "VS Code가 이제 화면 읽기 프로그램과 사용하는 데 최적화되었습니다." + "screenReaderDetectedExplanation.body1": "VS Code가 이제 화면 읽기 프로그램과 사용하는 데 최적화되었습니다.", + "screenReaderDetectedExplanation.body2": "줄바꿈, 접기 등의 일부 편집기 기능은 동작이 다릅니다." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/kor/src/vs/workbench/electron-browser/actions.i18n.json index 248d9a80226..96f43aced40 100644 --- a/i18n/kor/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/kor/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "지원되는 언어 목록은 {0} 을(를) 참조하세요.", "restart": "값을 변경하려면 VSCode를 다시 시작해야 합니다.", "fail.createSettings": "{0}'({1})을(를) 만들 수 없습니다.", + "openLogsFolder": "로그 폴더 열기", + "showLogs": "로그 표시...", + "mainProcess": "기본", + "sharedProcess": "공유", + "rendererProcess": "렌더러", + "extensionHost": "확장 호스트", + "selectProcess": "프로세스 선택", + "setLogLevel": "로그 수준 설정", + "trace": "Trace", "debug": "디버그", "info": "정보", "warn": "경고", - "err": "오류" + "err": "오류", + "critical": "Critical", + "off": "Off", + "selectLogLevel": "로그 수준 선택" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/electron-browser/main.contribution.i18n.json b/i18n/kor/src/vs/workbench/electron-browser/main.contribution.i18n.json index a78356cadf9..b3ca18516e9 100644 --- a/i18n/kor/src/vs/workbench/electron-browser/main.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/electron-browser/main.contribution.i18n.json @@ -16,6 +16,7 @@ "workbench.editor.labelFormat.long": "절대 경로 앞에 오는 파일의 이름을 표시합니다.", "tabDescription": "편집기의 레이블 형식을 제어합니다. 예를 들어 이 설정을 변경하면 파일의 위치를 더 쉽게 파악할 수 있습니다.:\n- 짧게: 'parent'\n- 중간: 'workspace/src/parent'\n- 길게: '/home/user/workspace/src/parent'\n- 기본값: '.../parent', 다른 탭이 동일한 제목을 공유하거나, 탭을 사용하지 않도록 설정한 경우 작업 영역 상대 경로", "editorTabCloseButton": "편집기의 탭 닫기 단추의 위치를 제어하거나 'off'로 설정된 경우 이 단추를 사용하지 않도록 설정합니다.", + "tabSizing": "편집기 탭의 크기를 제어합니다. 탭이 항상 전체 텍스트 레이블을 표시할 수 있게 확대되도록 하려면 '맞춤'으로 설정합니다. 사용 가능한 공간이 부족히 모든 탭을 한 번에 표시할 수 없는 경우 탭이 축소되도록 하려면 '축소'로 설정합니다.", "showIcons": "열린 편집기를 아이콘과 함께 표시할지 여부를 제어합니다. 이를 위해서는 아이콘 테마도 사용하도록 설정해야 합니다.", "enablePreview": "열려 있는 편집기를 미리 보기로 표시할지 여부를 제어합니다. 미리 보기 편집기는 유지된 상태까지(예: 두 번 클릭 또는 편집을 통해) 다시 사용되며 기울임꼴 글꼴 스타일로 표시됩니다.", "enablePreviewFromQuickOpen": "Quick Open에서 연 편집기를 미리 보기로 표시할지 여부를 제어합니다. 미리 보기 편집기는 유지된 상태까지(예: 두 번 클릭 또는 편집을 통해) 다시 사용됩니다.", @@ -29,6 +30,7 @@ "statusBarVisibility": "워크벤치 아래쪽에서 상태 표시줄의 표시 유형을 제어합니다.", "activityBarVisibility": "워크벤치에서 작업 막대의 표시 유형을 제어합니다.", "closeOnFileDelete": "일부 다른 프로세스에서 파일을 삭제하거나 이름을 바꿀 때 파일을 표시하는 편집기를 자동으로 닫을지 여부를 제어합니다. 사용하지 않도록 설정하는 경우 이러한 이벤트가 발생하면 편집기가 더티 상태로 계속 열려 있습니다. 응용 프로그램 내에서 삭제하면 항상 편집기가 닫히고 데이터를 유지하기 위해 더티 파일은 닫히지 않습니다.", + "enableNaturalLanguageSettingsSearch": "설정에 대한 자연어 검색 모드를 사용할지 여부를 제어합니다.", "fontAliasing": "워크벤치에서 글꼴 앨리어싱 방식을 제어합니다.\n- 기본: 서브 픽셀 글꼴 다듬기. 대부분의 일반 디스플레이에서 가장 선명한 글꼴 제공\n- 안티앨리어싱: 서브 픽셀이 아닌 픽셀 단위에서 글꼴 다듬기. 전반적으로 더 밝은 느낌을 줄 수 있음\n- 없음: 글꼴 다듬기 사용 안 함. 텍스트 모서리가 각지게 표시됨", "workbench.fontAliasing.default": "서브 픽셀 글꼴 다듬기. 대부분의 일반 디스플레이에서 가장 선명한 텍스트를 제공합니다. ", "workbench.fontAliasing.antialiased": "서브 픽셀이 아닌 픽셀 수준에서 글꼴을 다듬습니다. 전반적으로 글꼴이 더 밝게 표시됩니다.", diff --git a/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index 00b72c7e934..ae09d1a8d81 100644 --- a/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -20,5 +20,10 @@ "openExplorerOnEnd": "디버그 세션 끝에 탐색기 뷰를 자동으로 엽니다.", "inlineValues": "디버그하는 동안 편집기에서 변수 값을 인라인으로 표시합니다.", "hideActionBar": "부동 디버그 작업 모음을 숨길지 여부를 제거합니다.", + "never": "상태 표시줄에 디버그 표시 안 함", + "always": "상태 표시줄에 디버그 항상 표시", + "onFirstSessionStart": "디버그가 처음으로 시작된 후에만 상태 표시줄에 디버그 표시", + "showInStatusBar": "디버그 상태 표시줄을 표시할 시기를 제어합니다.", + "openDebug": "디버깅 세션 시작 시 디버그 뷰렛을 열지 여부를 제어합니다.", "launch": "전역 디버그 시작 구성입니다. 작업 영역에서 공유되는 \n 'launch.json'에 대한 대체로 사용되어야 합니다." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 9b138b5c585..dd7daafffa8 100644 --- a/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "파일 {1}, 줄 {0}에 중단점이 추가되었습니다.", "breakpointRemoved": "파일 {1}, 줄 {0}에서 중단점이 제거되었습니다.", "compoundMustHaveConfigurations": "여러 구성을 시작하려면 복합에 \"configurations\" 특성 집합이 있어야 합니다.", + "configMissing": "'{0}' 구성이 'launch.json'에 없습니다.", + "launchJsonDoesNotExist": "'launch.json'이 없습니다.", "debugRequestNotSupported": "선택한 디버그 구성에서 특성 '{0}'에 지원되지 않는 값 '{1}'이(가) 있습니다.", "debugRequesMissing": "선택한 디버그 구성에 특성 '{0}'이(가) 없습니다.", "debugTypeNotSupported": "구성된 디버그 형식 '{0}'은(는) 지원되지 않습니다.", diff --git a/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json index 7092740fd0c..863a81f423e 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json @@ -6,9 +6,11 @@ { "name": "확장 이름", "extension id": "확장 ID", + "preview": "미리 보기", "publisher": "게시자 이름", "install count": "설치 수", "rating": "등급", + "repository": "리포지토리", "license": "라이선스", "details": "세부 정보", "contributions": "기여", diff --git a/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..a428c8145db 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "{0}명의 사용자가 등급을 매김", + "ratedBySingleUser": "1명의 사용자가 등급을 매김" +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..e0b45ad88d7 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "프로파일링을 중지하려면 클릭하세요." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 9742da49372..8dcad97c6a8 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "확장 관리", "galleryExtensionsCommands": "갤러리 확장 설치", "extension": "확장", + "runtimeExtension": "실행 중인 확장", "extensions": "확장", "view": "보기", "developer": "개발자", "extensionsConfigurationTitle": "확장", - "extensionsAutoUpdate": "자동으로 확장 업데이트" + "extensionsAutoUpdate": "자동으로 확장 업데이트", + "extensionsIgnoreRecommendations": "True로 설정하는 경우 확장 권장 사항에 대한 알림 표시가 중지됩니다." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index 4b0a8e8d046..0496d738c05 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "문제 보고" + "starActivation": "시작 시 활성화됨", + "workspaceContainsGlobActivation": "{0}과(와) 일치하는 파일이 작업 영역에 있으므로 활성화됨", + "workspaceContainsFileActivation": "{0} 파일이 작업 영역에 있으므로 활성화됨", + "languageActivation": "{0} 파일을 열었으므로 활성화됨", + "workspaceGenericActivation": "{0}에서 활성화됨", + "errors": "Catch되지 않은 오류 {0}개", + "extensionsInputName": "실행 중인 확장", + "showRuntimeExtensions": "실행 중인 확장 표시", + "reportExtensionIssue": "문제 보고", + "extensionHostProfileStart": "확장 호스트 프로필 시작", + "extensionHostProfileStop": "확장 호스트 프로필 중지", + "saveExtensionHostProfile": "확장 호스트 프로필 저장" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json b/i18n/kor/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json index b962da52ed9..87d00a4c77a 100644 --- a/i18n/kor/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json @@ -69,5 +69,7 @@ "invalidFileNameError": "**{0}**(이)라는 이름은 파일 또는 폴더 이름으로 올바르지 않습니다. 다른 이름을 선택하세요.", "filePathTooLongError": "**{0}**(이)라는 이름을 사용하면 경로가 너무 길어집니다. 짧은 이름을 선택하세요.", "compareWithSaved": "활성 파일을 저장된 파일과 비교", - "modifiedLabel": "{0}(디스크) ↔ {1}" + "modifiedLabel": "{0}(디스크) ↔ {1}", + "compareWithClipboard": "클립보드와 활성 파일 비교", + "clipboardComparisonLabel": "클립보드 ↔ {0}" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json b/i18n/kor/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json index 4ce15b3e32b..639701925b4 100644 --- a/i18n/kor/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json @@ -7,6 +7,7 @@ "noWorkspace": "열린 폴더 없음", "explorerSection": "파일 탐색기 섹션", "noWorkspaceHelp": "작업 영역에 아직 폴더를 추가하지 않았습니다.", + "addFolder": "폴더 추가", "noFolderHelp": "아직 폴더를 열지 않았습니다.", "openFolder": "폴더 열기" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json b/i18n/kor/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json index 8b6ad71cd4e..ce74fbfe7d0 100644 --- a/i18n/kor/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "totalProblems": "총 {0}개 문제", + "filteredProblems": "{1}개 중 {0}개 문제 표시" +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/markers/common/messages.i18n.json b/i18n/kor/src/vs/workbench/parts/markers/common/messages.i18n.json index fb8699cf20f..f9e867de14d 100644 --- a/i18n/kor/src/vs/workbench/parts/markers/common/messages.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/markers/common/messages.i18n.json @@ -6,6 +6,7 @@ { "viewCategory": "보기", "problems.view.toggle.label": "설정/해제 문제", + "problems.view.focus.label": "포커스 문제", "problems.panel.configuration.title": "문제 보기", "problems.panel.configuration.autoreveal": "문제 보기를 열 때 문제 보기에 자동으로 파일이 표시되어야 하는지를 제어합니다.", "markers.panel.title.problems": "문제", diff --git a/i18n/kor/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json b/i18n/kor/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json index edf361223f6..4a2bff216ea 100644 --- a/i18n/kor/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defineKeybinding.initial": "원하는 키 조합을 누르고 키를 누르세요.", "defineKeybinding.chordsTo": "현" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json index 3ded239f7d2..4ccbbd8cacf 100644 --- a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "openRawDefaultSettings": "원시 기본 설정 열기", "openGlobalSettings": "사용자 설정 열기", "openGlobalKeybindings": "바로 가기 키 열기", "openGlobalKeybindingsFile": "바로 가기 키 파일 열기", diff --git a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesEditor.i18n.json b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesEditor.i18n.json index 0356863191a..8e8e066cc1a 100644 --- a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesEditor.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesEditor.i18n.json @@ -12,6 +12,7 @@ "settingsFound": "{0}개 설정 일치함", "totalSettingsMessage": "총 {0}개 설정", "defaultSettings": "기본 설정", + "defaultFolderSettings": "기본 폴더 설정", "defaultEditorReadonly": "기본값을 재정의하려면 오른쪽 편집기를 편집하세요.", "preferencesAriaLabel": "기본 설정. 읽기 전용 텍스트 편집기입니다." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json index 7a4c308d03c..188fd9c37da 100644 --- a/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defaultSettingsFuzzyPrompt": "자연어 검색을 사용해 보세요!", "defaultSettings": "설정을 오른쪽 편집기에 넣어서 덮어씁니다.", "noSettingsFound": "설정을 찾을 수 없습니다.", "settingsSwitcherBarAriaLabel": "설정 전환기", "userSettings": "사용자 설정", - "workspaceSettings": "작업 영역 설정" + "workspaceSettings": "작업 영역 설정", + "folderSettings": "폴더 설정", + "enableFuzzySearch": "자연어 검색 사용" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json b/i18n/kor/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json index 25d60399f44..583bab22864 100644 --- a/i18n/kor/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "defaultLabel": "입력" + "defaultLabel": "입력", + "useExcludesAndIgnoreFilesDescription": "제외 설정 및 파일 무시 사용" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json b/i18n/kor/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json index 8b6ad71cd4e..32615898bd4 100644 --- a/i18n/kor/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "findInFolder": "폴더에서 찾기...", + "findInWorkspace": "작업 영역에서 찾기..." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json index e0344b0285b..ad0d8faf650 100644 --- a/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json @@ -8,6 +8,7 @@ "ConfigureTaskRunnerAction.label": "작업 구성", "CloseMessageAction.label": "닫기", "problems": "문제", + "building": "빌드하고 있습니다...", "manyMarkers": "99+", "runningTasks": "실행 중인 작업 표시", "tasks": "작업", @@ -50,6 +51,7 @@ "TaslService.noEntryToRun": "실행할 작업이 없습니다. 작업 구성...", "TaskService.fetchingBuildTasks": "빌드 작업을 페치하는 중...", "TaskService.pickBuildTask": "실행할 빌드 작업 선택", + "TaskService.noBuildTask": "실행할 빌드 작업을 찾을 수 없습니다. 빌드 작업 구성...", "TaskService.fetchingTestTasks": "테스트 작업을 페치하는 중...", "TaskService.pickTestTask": "실행할 테스트 작업 선택", "TaskService.noTestTaskTerminal": "실행할 테스트 작업이 없습니다. 작업 구성...", diff --git a/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json b/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json index b052f09dc9e..a17354af37d 100644 --- a/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json @@ -7,6 +7,7 @@ "TerminalTaskSystem.unknownError": "작업을 실행하는 동안 알 수 없는 오류가 발생했습니다. 자세한 내용은 작업 출력 로그를 참조하세요.", "dependencyFailed": "작업 영역 폴더 '{1}'에서 종속 작업 '{0}'을(를) 확인할 수 없습니다. ", "TerminalTaskSystem.terminalName": "작업 - {0}", + "closeTerminal": "터미널을 종료하려면 아무 키나 누르세요.", "reuseTerminal": "터미널이 작업에서 다시 사용됩니다. 닫으려면 아무 키나 누르세요.", "TerminalTaskSystem": "UNC 드라이브에서 셸 명령을 실행할 수 없습니다.", "unkownProblemMatcher": "문제 선택기 {0}을(를) 확인할 수 없습니다. 이 선택기는 무시됩니다." diff --git a/i18n/kor/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json b/i18n/kor/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json index 7638704dea1..c62cb2cd281 100644 --- a/i18n/kor/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json @@ -14,6 +14,8 @@ "ConfigurationParser.noTypeDefinition": "오류: 등록된 작업 형식 '{0}'이(가) 없습니다. 해당하는 작업 공급자를 제공하는 확장을 설치하지 않으셨습니까?", "ConfigurationParser.missingRequiredProperty": "오류: 작업 구성 '{0}'에 필요한 속성 '{1}'이(가) 없습니다. 작업 구성이 무시됩니다.", "ConfigurationParser.notCustom": "오류: 작업이 사용자 지정 작업으로 선언되지 않았습니다. 이 구성은 무시됩니다.\n{0}\n", + "ConfigurationParser.noTaskName": "오류: 작업에서 레이블 속성을 제공해야 합니다. 이 작업은 무시됩니다.\n{0}\n", + "taskConfiguration.shellArgs": "경고: '{0}' 작업은 셸 명령이며 인수 중 하나에 이스케이프되지 않은 공백이 있을 수 있습니다. 올바른 명령줄 인용인지 확인하려면 인수를 명령으로 병합하세요.", "taskConfiguration.noCommandOrDependsOn": "오류: 작업 '{0}'에서 명령이나 dependsOn 속성을 지정하지 않습니다. 이 작업은 무시됩니다. 해당 작업의 정의는 {1}입니다.", "taskConfiguration.noCommand": "오류: 작업 '{0}'에서 명령을 정의하지 않습니다. 이 작업은 무시됩니다. 해당 작업의 정의는\n{1}입니다.", "TaskParse.noOsSpecificGlobalTasks": "작업 버전 2.0.0은 글로벌 OS별 작업을 지원하지 않습니다. OS별 명령을 사용하여 작업으로 변환하세요. 영향을 받는 작업::\n{0}" diff --git a/i18n/kor/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json index 1a66c6cd537..c8be10020cf 100644 --- a/i18n/kor/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json @@ -16,6 +16,7 @@ "terminal.integrated.rightClickCopyPaste": "설정하는 경우 터미널 내에서 마우스 오른쪽 단추를 클릭할 때 상황에 맞는 메뉴가 표시되지 않고 대신 선택 항목이 있으면 복사하고 선택 항목이 없으면 붙여넣습니다.", "terminal.integrated.fontFamily": "터미널의 글꼴 패밀리를 제어하며, 기본값은 editor.fontFamily의 값입니다.", "terminal.integrated.fontSize": "터미널의 글꼴 크기(픽셀)를 제어합니다.", + "terminal.integrated.lineHeight": "터미널의 줄 높이를 제어합니다. 이 숫자에 터미널 글꼴 크기를 곱해 실제 줄 높이(픽셀)를 얻습니다.", "terminal.integrated.enableBold": "터미널 내에서 굵은 텍스트를 사용하도록 설정할지 여부이며, 터미널 셸의 지원이 필요합니다.", "terminal.integrated.cursorBlinking": "터미널 커서 깜박임 여부를 제어합니다.", "terminal.integrated.cursorStyle": "터미널 커서의 스타일을 제어합니다.", diff --git a/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 43a0bba1a52..c71597794ca 100644 --- a/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,9 @@ // Do not edit this file. It is machine generated. { "selectTheme.label": "색 테마", + "themes.category.light": "밝은 테마", + "themes.category.dark": "어두운 테마", + "themes.category.hc": "고대비 테마", "installColorThemes": "추가 색 테마 설치...", "themes.selectTheme": "색 테마 선택(미리 보려면 위로/아래로 키 사용)", "selectIconTheme.label": "파일 아이콘 테마", diff --git a/i18n/kor/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json b/i18n/kor/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json index 8b6ad71cd4e..69a95ffd567 100644 --- a/i18n/kor/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json +++ b/i18n/kor/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "bubbleTitle": "강조 표시한 항목 포함" +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..0df1b9294ba 100644 --- a/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) 확장 호스트를 프로파일링하는 중..." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index 6f9c095c667..2f308f7c3f8 100644 --- a/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/kor/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "확장 호스트가 예기치 않게 종료되었습니다.", "extensionHostProcess.unresponsiveCrash": "확장 호스트가 응답하지 않아서 종료되었습니다.", "overwritingExtension": "확장 {0}을(를) {1}(으)로 덮어쓰는 중입니다.", - "extensionUnderDevelopment": "{0}에서 개발 확장 로드 중" + "extensionUnderDevelopment": "{0}에서 개발 확장 로드 중", + "extensionCache.invalid": "확장이 디스크에서 수정되었습니다. 창을 다시 로드하세요." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json b/i18n/kor/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json index 10bf5a9ef4f..4af8c158d1f 100644 --- a/i18n/kor/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json +++ b/i18n/kor/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json @@ -21,5 +21,6 @@ "keybindings.json.command": "실행할 명령의 이름", "keybindings.json.when": "키가 활성화되는 조건입니다.", "keybindings.json.args": "실행할 명령에 전달할 인수입니다.", - "keyboardConfigurationTitle": "키보드" + "keyboardConfigurationTitle": "키보드", + "dispatch": "`code`(권장) 또는 `keyCode`를 사용하는 키 누름에 대한 디스패치 논리를 제어합니다." } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/services/textfile/common/textFileService.i18n.json b/i18n/kor/src/vs/workbench/services/textfile/common/textFileService.i18n.json index 8b6ad71cd4e..bcfc393f961 100644 --- a/i18n/kor/src/vs/workbench/services/textfile/common/textFileService.i18n.json +++ b/i18n/kor/src/vs/workbench/services/textfile/common/textFileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "files.backup.failSave": "변경된 내용이 있는 파일을 백업 위치에 쓸 수 없습니다(오류: {0}). 먼저 파일을 저장한 다음 종료해 보세요." +} \ No newline at end of file diff --git a/i18n/ptb/extensions/emmet/package.i18n.json b/i18n/ptb/extensions/emmet/package.i18n.json index 93bb46e1ff4..41745410f9b 100644 --- a/i18n/ptb/extensions/emmet/package.i18n.json +++ b/i18n/ptb/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Separador de modificador usado para classes quando utilizar o filtro BEM", "emmetPreferencesFilterCommentBefore": "Uma definição de comentário que deve ser colocado antes de elemento correspondente quando um filtro de comentário é aplicado.", "emmetPreferencesFilterCommentAfter": "Uma definição de comentário que deve ser colocado após o elemento correspondente quando um filtro de comentário é aplicado.", - "emmetPreferencesFilterCommentTrigger": "Uma lista separada por vírgulas de nomes de atributo que deve existir em abreviações para o filtro de comentário a ser aplicado" + "emmetPreferencesFilterCommentTrigger": "Uma lista separada por vírgulas de nomes de atributo que deve existir em abreviações para o filtro de comentário a ser aplicado", + "emmetPreferencesFormatNoIndentTags": "Uma matriz de nomes de abas que não devem ter recuo interno", + "emmetPreferencesFormatForceIndentTags": "Uma matriz de nomes de abas que deve sempre devem ter recuo interno", + "emmetPreferencesAllowCompactBoolean": "Se verdadeiro, a notação compacta de atributos booleanos são produzidos" } \ No newline at end of file diff --git a/i18n/ptb/src/vs/code/node/cliProcessMain.i18n.json b/i18n/ptb/src/vs/code/node/cliProcessMain.i18n.json index ee0b74f6e41..ec57d526c98 100644 --- a/i18n/ptb/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/ptb/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "Extensão '{0}' não está instalada.", "useId": "Certifique-se de usar a ID de extensão completa, incluindo o editor, por exemplo: {0}", "successVsixInstall": "Extensão '{0}' foi instalada com sucesso!", + "cancelVsixInstall": "Cancelada a instalação da extensão '{0}'.", "alreadyInstalled": "Extensão '{0}' já está instalada.", "foundExtension": "Encontrado '{0}' na loja VS Code.", "installing": "Instalando...", diff --git a/i18n/ptb/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/ptb/src/vs/editor/common/config/commonEditorConfig.i18n.json index db6da099ec4..52ffce48a73 100644 --- a/i18n/ptb/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/ptb/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "Limitar o tamanho de um mini-mapa para renderizar no máximo um número determinado de colunas", "find.seedSearchStringFromSelection": "Controla se nós inicializamos a string de pesquisa na Ferramenta de Pesquisa a partir da seleção do editor", "find.autoFindInSelection": "Controla se a configuração Find in Selection deve estar ativada quando vários caracteres ou linhas de texto estão selecionados no editor", + "find.globalFindClipboard": "Controla se a ferramenta Localizar deve ler ou modificar a área de transfência de busca compartilhada no macOS", "wordWrap.off": "As linhas nunca serão quebradas.", "wordWrap.on": "As linhas serão quebradas na largura de visualização", "wordWrap.wordWrapColumn": "As linhas serão quebradas em `editor.wordWrapColumn`.", diff --git a/i18n/ptb/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/ptb/src/vs/editor/contrib/folding/folding.i18n.json index eb3341e2a4d..51b3d0dbab7 100644 --- a/i18n/ptb/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/ptb/src/vs/editor/contrib/folding/folding.i18n.json @@ -9,6 +9,8 @@ "foldAction.label": "Colapsar", "foldRecursivelyAction.label": "Colapsar recursivamente", "foldAllBlockComments.label": "Fechar Todos os Comentários de Bloco", + "foldAllMarkerRegions.label": "Fechar Todas as Regiões", + "unfoldAllMarkerRegions.label": "Abrir Todas as Regiões", "foldAllAction.label": "Colapsar tudo", "unfoldAllAction.label": "Abrir tudo", "foldLevelAction.label": "Nível de colapsamento {0}" diff --git a/i18n/ptb/src/vs/platform/environment/node/argv.i18n.json b/i18n/ptb/src/vs/platform/environment/node/argv.i18n.json index eb886a026f7..ed9e5aca99c 100644 --- a/i18n/ptb/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/ptb/src/vs/platform/environment/node/argv.i18n.json @@ -16,6 +16,7 @@ "inspect-brk-extensions": "Permitir depuração e criação de perfil de extensões com o host de extensão em pausa após o início. Verifique as ferramentas do desenvolvedor para a conexão uri.", "reuseWindow": "Forçar a abertura de um arquivo ou pasta na última janela ativa", "userDataDir": "Especifica o diretório que os dados do usuário serão mantidos, útil quando estiver rodando como root.", + "log": "Nível de log a ser utilizado. O padrão é 'info'. Os valores permitidos são 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.", "verbose": "Imprimir a saída detalhada (Implica -- esperar).", "wait": "Espere pelos arquivos a serem fechados antes de retornar.", "extensionHomePath": "Defina o caminho raíz para as extensões.", diff --git a/i18n/ptb/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/ptb/src/vs/workbench/electron-browser/actions.i18n.json index 04de1ce8cef..67b112045f5 100644 --- a/i18n/ptb/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/ptb/src/vs/workbench/electron-browser/actions.i18n.json @@ -67,5 +67,6 @@ "warn": "Aviso", "err": "Erro", "critical": "Crítico", - "off": "Desligado" + "off": "Desligado", + "selectLogLevel": "Selecione o nível de log" } \ No newline at end of file diff --git a/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index b576d0807c8..82aa2a51714 100644 --- a/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -21,6 +21,8 @@ "inlineValues": "Mostrar valores de variáveis em linha no editor durante a depuração", "hideActionBar": "Controlar se a barra de ação flutuante do depurador deve ser ocultada", "never": "Nunca mostrar debug na barra de status", + "always": "Sempre mostrar depurar na barra de status", + "onFirstSessionStart": "Mostrar depurar na barra de status somente após a depuração ser iniciada pela primeira vez", "showInStatusBar": "Controla quando a barra de status de depuração deve ser visível", "openDebug": "Controla se o depurador viewlet deve ser aberto no início de sessão de depuração.", "launch": "Configuração global do lançamento do depurador. Deve ser usado como uma alternativa para o arquivo 'launch.json' que é compartilhado entre os espaços de trabalho" diff --git a/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 9d0ec3a464b..de45eedf221 100644 --- a/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/ptb/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Adicionado ponto de interrupção, linha {0}, arquivo {1}", "breakpointRemoved": "Ponto de interrupção removido, linha {0}, arquivo {1}", "compoundMustHaveConfigurations": "Composição deve ter o atributo \"configurations\" definido para iniciar várias configurações.", + "configMissing": "Configuração '{0}' não tem 'launch.json'.", + "launchJsonDoesNotExist": "'launch.json' não existe.", "debugRequestNotSupported": "Atributo '{0}' tem um valor sem suporte '{1}' na configuração de depuração escolhida.", "debugRequesMissing": "Atributo '{0}' está faltando para a configuração de depuração escolhida.", "debugTypeNotSupported": "Tipo de depuração configurado '{0}' não é suportado.", diff --git a/i18n/ptb/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/ptb/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index d82c863fc27..75103d39319 100644 --- a/i18n/ptb/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/ptb/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "ratedByUsers": "Avaliado por {0} usuários" + "ratedByUsers": "Avaliado por {0} usuários", + "ratedBySingleUser": "Avaliado por 1 usuário" } \ No newline at end of file diff --git a/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 77e6de1fbbc..9e3b4ca9b5b 100644 --- a/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -12,5 +12,6 @@ "view": "Exibir", "developer": "Desenvolvedor", "extensionsConfigurationTitle": "Extensões", - "extensionsAutoUpdate": "Atualizar extensões automaticamente" + "extensionsAutoUpdate": "Atualizar extensões automaticamente", + "extensionsIgnoreRecommendations": "Se definido como verdadeiro, as notificações para recomendações de extensão irão parar de aparecer." } \ No newline at end of file diff --git a/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index bec798884ff..a02e2630fde 100644 --- a/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/ptb/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -12,5 +12,8 @@ "errors": "{0} erros não capturados", "extensionsInputName": "Executando extensões", "showRuntimeExtensions": "Mostrar extensões em execução", - "reportExtensionIssue": "Reportar Problema" + "reportExtensionIssue": "Reportar Problema", + "extensionHostProfileStart": "Iniciar o Perfil de Host de Extensão", + "extensionHostProfileStop": "Parar o Perfil de Host de Extensão", + "saveExtensionHostProfile": "Salvar o Perfil de Host de Extensão" } \ No newline at end of file diff --git a/i18n/ptb/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/ptb/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..ff3ad330c91 100644 --- a/i18n/ptb/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/ptb/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) Perfil de Host de Extensão..." +} \ No newline at end of file diff --git a/i18n/rus/extensions/css/client/out/cssMain.i18n.json b/i18n/rus/extensions/css/client/out/cssMain.i18n.json index 73454bf8b97..237c382d200 100644 --- a/i18n/rus/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/rus/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "Языковой сервер CSS" + "cssserver.name": "Языковой сервер CSS", + "folding.start": "Начало сворачиваемого региона", + "folding.end": "Окончание сворачиваемого региона" } \ No newline at end of file diff --git a/i18n/rus/extensions/emmet/package.i18n.json b/i18n/rus/extensions/emmet/package.i18n.json index 49fa561f309..2638e01cf80 100644 --- a/i18n/rus/extensions/emmet/package.i18n.json +++ b/i18n/rus/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "Разделитель модификатора для классов, используемый с фильтром BEM", "emmetPreferencesFilterCommentBefore": "Определение комментария, который должен быть размещен перед соответствующим элементом при применении фильтра комментария.", "emmetPreferencesFilterCommentAfter": "Определение комментария, который должен быть размещен после соответствующего элемента при применении фильтра комментария. ", - "emmetPreferencesFilterCommentTrigger": "Разделителями запятыми список имен атрибутов, которые должны присутствовать в сокращении для применения фильтра комментария" + "emmetPreferencesFilterCommentTrigger": "Разделителями запятыми список имен атрибутов, которые должны присутствовать в сокращении для применения фильтра комментария", + "emmetPreferencesFormatNoIndentTags": "Массив имен тегов, для которых не следует использовать внутренние отступы", + "emmetPreferencesFormatForceIndentTags": "Массив имен тегов, для которых всегда следует использовать внутренние отступы", + "emmetPreferencesAllowCompactBoolean": "Если этот параметр имеет значение true, формируется компактная запись логических атрибутов" } \ No newline at end of file diff --git a/i18n/rus/extensions/git/out/autofetch.i18n.json b/i18n/rus/extensions/git/out/autofetch.i18n.json index 0f8fdf4d3aa..1e461cf4629 100644 --- a/i18n/rus/extensions/git/out/autofetch.i18n.json +++ b/i18n/rus/extensions/git/out/autofetch.i18n.json @@ -5,5 +5,7 @@ // Do not edit this file. It is machine generated. { "yes": "Да", - "no": "Нет" + "no": "Нет", + "not now": "Не сейчас", + "suggest auto fetch": "Вы хотите включить автоматическое получение для репозиториев Git?" } \ No newline at end of file diff --git a/i18n/rus/extensions/git/out/commands.i18n.json b/i18n/rus/extensions/git/out/commands.i18n.json index 149e1af6b09..d8d6feaa35d 100644 --- a/i18n/rus/extensions/git/out/commands.i18n.json +++ b/i18n/rus/extensions/git/out/commands.i18n.json @@ -9,9 +9,12 @@ "create branch": "$(plus) Создать новую ветвь", "repourl": "URL-адрес репозитория", "parent": "Родительский каталог", + "cancel": "$(sync~spin) Клонирование репозитория... Чтобы отменить эту операцию, щелкните здесь", + "cancel tooltip": "Отменить клонирование", "cloning": "Клонируется репозиторий Git...", "openrepo": "Открыть репозиторий", "proposeopen": "Вы хотите открыть клонированный репозиторий?", + "init": "Выберите папку рабочей области для инициализации репозитория Git", "init repo": "Инициализировать репозиторий", "create repo": "Инициализировать репозиторий", "are you sure": "В '{0}' будет создан репозиторий Git. Вы действительно хотите продолжить?", @@ -49,12 +52,15 @@ "select branch to delete": "Выберите ветвь для удаления", "confirm force delete branch": "Ветвь '{0}' объединена не полностью. Удалить ее?", "delete branch": "Удалить ветвь", + "invalid branch name": "Недопустимое имя ветви", + "branch already exists": "Ветви с именем '{0}' уже существует", "select a branch to merge from": "Выберите ветвь для слияния", "merge conflicts": "Обнаружены конфликты слияния. Устраните их перед фиксацией.", "tag name": "Имя тега", "provide tag name": "Укажите имя тега", "tag message": "Сообщение", "provide tag message": "Укажите сообщение для аннотирования тега", + "no remotes to fetch": "Для этого репозитория не настроены удаленные репозитории для получения данных.", "no remotes to pull": "Для вашего репозитория не настроены удаленные репозитории для получения данных.", "pick remote pull repo": "Выберите удаленный компьютер, с которого следует загрузить ветвь", "no remotes to push": "Для вашего репозитория не настроены удаленные репозитории для отправки данных.", @@ -71,6 +77,7 @@ "no stashes": "Отсутствуют спрятанные изменения, которые необходимо восстановить.", "pick stash to pop": "Выберите спрятанное изменение для отображения", "clean repo": "Очистите рабочее дерево репозитория перед извлечением.", + "cant push": "Не удается отправить ссылки в удаленный репозиторий. Сначала выберите \"Извлечь\", чтобы интегрировать изменения.", "git error details": "Git: {0}", "git error": "Ошибка Git", "open git log": "Открыть журнал GIT" diff --git a/i18n/rus/extensions/git/out/main.i18n.json b/i18n/rus/extensions/git/out/main.i18n.json index 85416c1538a..f4e90ccb987 100644 --- a/i18n/rus/extensions/git/out/main.i18n.json +++ b/i18n/rus/extensions/git/out/main.i18n.json @@ -4,8 +4,11 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "looking": "Поиск Git в: {0}", "using git": "Использование GIT {0} из {1}", + "downloadgit": "Скачать Git", "neverShowAgain": "Больше не показывать", + "notfound": "Git не найден. Установите Git или укажите путь к нему в параметре 'git.path'.", "updateGit": "Обновить Git", "git20": "У вас установлен Git {0}. Код лучше всего работает с Git >= 2." } \ No newline at end of file diff --git a/i18n/rus/extensions/git/package.i18n.json b/i18n/rus/extensions/git/package.i18n.json index bb861a75c0c..dcff3e1c9f0 100644 --- a/i18n/rus/extensions/git/package.i18n.json +++ b/i18n/rus/extensions/git/package.i18n.json @@ -33,8 +33,10 @@ "command.checkout": "Извлечь в...", "command.branch": "Создать ветвь...", "command.deleteBranch": "Удалить ветвь...", + "command.renameBranch": "Переименовать ветвь...", "command.merge": "Объединить ветвь...", "command.createTag": "Создать тег", + "command.fetch": "Получить", "command.pull": "Получить", "command.pullRebase": "Получить (переместить изменения из одной ветви в другую)", "command.pullFrom": "Загрузить с...", @@ -42,9 +44,11 @@ "command.pushTo": "Отправить в:", "command.pushWithTags": "Отправить с тегами", "command.sync": "Синхронизация", + "command.syncRebase": "Синхронизация (перемещение изменений из одной ветви в другую)", "command.publish": "Опубликовать ветвь", "command.showOutput": "Показать выходные данные GIT", "command.ignore": "Добавить файл в .gitignore", + "command.stashIncludeUntracked": "Спрятать (включить неотслеживаемые)", "command.stash": "Спрятать", "command.stashPop": "Извлечь спрятанное", "command.stashPopLatest": "Извлечь последнее спрятанное", @@ -57,6 +61,7 @@ "config.countBadge": "\nУправляет счетчиком Git. При указании значения \"all\" подсчитываются все изменения, при указании значения \"tracked\" — только отслеживаемые изменения, при указании значения \"off\" счетчик отключается.", "config.checkoutType": "Определяет типы ветвей, которые выводятся при выборе пункта меню \"Извлечь в...\". При указании значения \"all\" отображаются все ссылки, \"local\" — только локальные ветви, \"tags\" — только теги, а \"remote\" — только удаленные ветви.", "config.ignoreLegacyWarning": "Игнорирует предупреждение об устаревшей версии Git", + "config.ignoreMissingGitWarning": "Игнорирует предупреждение об отсутствии Git", "config.ignoreLimitWarning": "Игнорировать предупреждение, когда в репозитории слишком много изменений", "config.defaultCloneDirectory": "Расположение по умолчанию, в которое будет клонирован репозиторий Git", "config.enableSmartCommit": "Зафиксировать все изменения при отсутствии промежуточных изменений.", diff --git a/i18n/rus/extensions/html/client/out/htmlMain.i18n.json b/i18n/rus/extensions/html/client/out/htmlMain.i18n.json index 364064582bf..27921a772ec 100644 --- a/i18n/rus/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/rus/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "Языковой сервер HTML" + "htmlserver.name": "Языковой сервер HTML", + "folding.start": "Начало сворачиваемого региона", + "folding.end": "Окончание сворачиваемого региона" } \ No newline at end of file diff --git a/i18n/rus/extensions/markdown/out/commands.i18n.json b/i18n/rus/extensions/markdown/out/commands.i18n.json index 5c5b3690dc6..e2752845d0f 100644 --- a/i18n/rus/extensions/markdown/out/commands.i18n.json +++ b/i18n/rus/extensions/markdown/out/commands.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "previewTitle": "Предварительный просмотр {0}", "onPreviewStyleLoadError": "Не удалось загрузить 'markdown.styles': {0}" } \ No newline at end of file diff --git a/i18n/rus/extensions/markdown/out/security.i18n.json b/i18n/rus/extensions/markdown/out/security.i18n.json index 11a39b993e2..17f7b23073a 100644 --- a/i18n/rus/extensions/markdown/out/security.i18n.json +++ b/i18n/rus/extensions/markdown/out/security.i18n.json @@ -11,5 +11,8 @@ "disable.title": "Отключить", "disable.description": "Разрешить все содержимое и выполнение сценариев. Не рекомендуется", "moreInfo.title": "Дополнительные сведения", + "enableSecurityWarning.title": "Включить предварительный просмотр предупреждений системы безопасности в этой рабочей области", + "disableSecurityWarning.title": "Отключить предварительный просмотр предупреждений системы безопасности в этой рабочей области", + "toggleSecurityWarning.description": "Не влияет на уровень безопасности содержимого", "preview.showPreviewSecuritySelector.title": "Установите параметры безопасности для предварительного просмотра Markdown в этой рабочей области" } \ No newline at end of file diff --git a/i18n/rus/extensions/merge-conflict/package.i18n.json b/i18n/rus/extensions/merge-conflict/package.i18n.json index 209c32b7d11..b792fefd678 100644 --- a/i18n/rus/extensions/merge-conflict/package.i18n.json +++ b/i18n/rus/extensions/merge-conflict/package.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "command.category": "Объединить конфликт", + "command.accept.all-current": "Принять все текущие", "command.accept.all-incoming": "Принять все входящие", "command.accept.all-both": "Принять все входящие и текущие", "command.accept.current": "Принять текущее", diff --git a/i18n/rus/extensions/typescript/out/features/completionItemProvider.i18n.json b/i18n/rus/extensions/typescript/out/features/completionItemProvider.i18n.json index 758fad83856..baf52ec9de3 100644 --- a/i18n/rus/extensions/typescript/out/features/completionItemProvider.i18n.json +++ b/i18n/rus/extensions/typescript/out/features/completionItemProvider.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "selectCodeAction": "Выберите применяемое действие кода", "acquiringTypingsLabel": "Получение typings...", "acquiringTypingsDetail": "Получение определений typings для IntelliSense.", "autoImportLabel": "Автоматический импорт из {0}" diff --git a/i18n/rus/extensions/typescript/package.i18n.json b/i18n/rus/extensions/typescript/package.i18n.json index 537a583b5e3..2980026d6d1 100644 --- a/i18n/rus/extensions/typescript/package.i18n.json +++ b/i18n/rus/extensions/typescript/package.i18n.json @@ -39,6 +39,7 @@ "typescript.openTsServerLog.title": "Открыть журнал сервера TS", "typescript.restartTsServer": "Перезапустить сервер TS", "typescript.selectTypeScriptVersion.title": "Выберите версию TypeScript.", + "typescript.reportStyleChecksAsWarnings": "Отображать ошибки при проверке стиля в виде предупреждений", "jsDocCompletion.enabled": "Включить или отключить JSDoc коментарии", "javascript.implicitProjectConfig.checkJs": "Включает/отключает семантическую проверку файлов JavaScript. Этот параметр может переопределяться в файле jsconfig.json или tsconfig.json. Требуется TypeScript 2.3.1 или более поздней версии.", "typescript.npm": "Указывает путь к исполняемому файлу NPM, используемому для автоматического получения типа. Требуется TypeScript версии 2.3.4 или более поздней версии.", diff --git a/i18n/rus/src/vs/code/electron-main/main.i18n.json b/i18n/rus/src/vs/code/electron-main/main.i18n.json index 3a698517ea1..1cf3dee66aa 100644 --- a/i18n/rus/src/vs/code/electron-main/main.i18n.json +++ b/i18n/rus/src/vs/code/electron-main/main.i18n.json @@ -4,5 +4,9 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "secondInstanceNoResponse": "Еще один экземпляр {0} запущен, но не отвечает", + "secondInstanceNoResponseDetail": "Закройте все остальные экземпляры и повторите попытку.", + "secondInstanceAdmin": "Уже запущен второй экземпляр {0} от имени администратора.", + "secondInstanceAdminDetail": "Закройте другой экземпляр и повторите попытку.", "close": "&&Закрыть" } \ No newline at end of file diff --git a/i18n/rus/src/vs/code/electron-main/menus.i18n.json b/i18n/rus/src/vs/code/electron-main/menus.i18n.json index 194c4240f25..937574f1898 100644 --- a/i18n/rus/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/rus/src/vs/code/electron-main/menus.i18n.json @@ -22,9 +22,12 @@ "miQuit": "Выйти из {0}", "miNewFile": "&&Новый файл", "miOpen": "Открыть...", + "miOpenWorkspace": "Открыть рабочую &&область...", "miOpenFolder": "Открыть &&папку...", "miOpenFile": "&&Открыть файл...", "miOpenRecent": "Открыть &&последние", + "miSaveWorkspaceAs": "Сохранить рабочую область как...", + "miAddFolderToWorkspace": "Доб&&авить папку в рабочую область...", "miSave": "Сохранить", "miSaveAs": "Сохранить &&как...", "miSaveAll": "Сохранить &&все", @@ -154,6 +157,7 @@ "mMergeAllWindows": "Объединить все окна", "miToggleDevTools": "&&Показать/скрыть средства разработчика", "miAccessibilityOptions": "Специальные &&возможности", + "miReportIssue": "Сообщить о &&проблеме", "miWelcome": "&&Приветствие", "miInteractivePlayground": "&&Интерактивная площадка", "miDocumentation": "&&Документация", @@ -180,6 +184,7 @@ "miDownloadingUpdate": "Скачивается обновление...", "miInstallingUpdate": "Идет установка обновления...", "miCheckForUpdates": "Проверить наличие обновлений...", + "aboutDetail": "Версия {0}\nФиксация {1}\nДата {2}\nОболочка {3}\nОтрисовщик {4}\nУзел {5}\nАрхитектура {6}", "okButton": "ОК", "copy": "Копировать" } \ No newline at end of file diff --git a/i18n/rus/src/vs/code/node/cliProcessMain.i18n.json b/i18n/rus/src/vs/code/node/cliProcessMain.i18n.json index 89660ffbf41..15ebcfee358 100644 --- a/i18n/rus/src/vs/code/node/cliProcessMain.i18n.json +++ b/i18n/rus/src/vs/code/node/cliProcessMain.i18n.json @@ -8,6 +8,7 @@ "notInstalled": "Расширение \"{0}\" не установлено.", "useId": "Используйте полный идентификатор расширения, включающий издателя, например: {0}", "successVsixInstall": "Расширение \"{0}\" успешно установлено.", + "cancelVsixInstall": "Установка расширения '{0}' отменена.", "alreadyInstalled": "Расширение \"{0}\" уже установлено.", "foundExtension": "Найдено \"{0}\" в Marketplace.", "installing": "Установка...", diff --git a/i18n/rus/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/rus/src/vs/editor/common/config/commonEditorConfig.i18n.json index 83b7b87dd66..f265f8d9eaa 100644 --- a/i18n/rus/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/rus/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -10,9 +10,16 @@ "fontSize": "Управляет размером шрифта в пикселях.", "lineHeight": "Управляет высотой строк. Укажите 0 для вычисления высоты строки по размеру шрифта.", "letterSpacing": "Управляет интервалом между буквами в пикселях.", + "lineNumbers.off": "Номера строк не отображаются.", + "lineNumbers.on": "Отображаются абсолютные номера строк.", + "lineNumbers.relative": "Отображаемые номера строк вычисляются как расстояние в строках до положения курсора.", + "lineNumbers.interval": "Номера строк отображаются каждые 10 строк.", + "lineNumbers": "Управляет отображением номеров строк. Возможные значения: \"on\", \"off\" и \"relative\".", "rulers": "Отображать вертикальные линейки после определенного числа моноширинных символов. Для отображения нескольких линеек укажите несколько значений. Если не указано ни одного значения, вертикальные линейки отображаться не будут.", "wordSeparators": "Символы, которые будут использоваться как разделители слов при выполнении навигации или других операций, связанных со словами.", + "tabSize": "Число пробелов в табуляции. Этот параметр переопределяется на основе содержимого файла, если установлен параметр \"editor.detectIndentation\".", "tabSize.errorMessage": "Ожидается число. Обратите внимание, что значение auto заменено параметром editor.detectIndentation.", + "insertSpaces": "Вставлять пробелы при нажатии клавиши TAB. Этот параметр переопределяется на основе содержимого файла, если установлен параметр \"editor.detectIndentation\". ", "insertSpaces.errorMessage": "Ожидается логическое значение. Обратите внимание, что значение auto заменено параметром editor.detectIndentation.", "detectIndentation": "При открытии файла editor.tabSize и editor.insertSpaces будут определяться на основе содержимого файла.", "roundedSelection": "Определяет, будут ли выделения иметь скругленные углы.", @@ -24,6 +31,7 @@ "minimap.maxColumn": "Ограничивает ширину мини-карты для отображения числа столбцов не больше определенного.", "find.seedSearchStringFromSelection": "Определяет, можно ли передать строку поиска в мини-приложение поиска из текста, выделенного в редакторе", "find.autoFindInSelection": "Определяет, будет ли снят флажок \"Поиск в выделенном\", когда в редакторе выбрано несколько символов или строк текста", + "find.globalFindClipboard": "Определяет, должно ли мини-приложение поиска считывать или изменять общий буфер обмена поиска в macOS", "wordWrap.off": "Строки не будут переноситься никогда.", "wordWrap.on": "Строки будут переноситься по ширине окна просмотра.", "wordWrap.wordWrapColumn": "Строки будут переноситься по \"editor.wordWrapColumn\".", diff --git a/i18n/rus/src/vs/editor/contrib/folding/folding.i18n.json b/i18n/rus/src/vs/editor/contrib/folding/folding.i18n.json index 5fe065ae0dc..affa88cd5b8 100644 --- a/i18n/rus/src/vs/editor/contrib/folding/folding.i18n.json +++ b/i18n/rus/src/vs/editor/contrib/folding/folding.i18n.json @@ -8,6 +8,9 @@ "unFoldRecursivelyAction.label": "Развернуть рекурсивно", "foldAction.label": "Свернуть", "foldRecursivelyAction.label": "Свернуть рекурсивно", + "foldAllBlockComments.label": "Свернуть все блоки комментариев", + "foldAllMarkerRegions.label": "Свернуть все регионы", + "unfoldAllMarkerRegions.label": "Развернуть все регионы", "foldAllAction.label": "Свернуть все", "unfoldAllAction.label": "Развернуть все", "foldLevelAction.label": "Уровень папки {0}" diff --git a/i18n/rus/src/vs/platform/environment/node/argv.i18n.json b/i18n/rus/src/vs/platform/environment/node/argv.i18n.json index eb6770aa30b..7314aa06087 100644 --- a/i18n/rus/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/rus/src/vs/platform/environment/node/argv.i18n.json @@ -12,8 +12,11 @@ "newWindow": "Принудительно запустить новый экземпляр Code.", "performance": "Запустите с включенной командой \"Developer: Startup Performance\".", "prof-startup": "Запустить профилировщик ЦП при запуске", + "inspect-extensions": "Разрешить отладку и профилирование расширений. Проверьте URI подключения для инструментов разработчика.", + "inspect-brk-extensions": "Разрешить отладку и профилирование расширений, когда узел расширения приостановлен после запуска. Проверьте URI подключения для инструментов разработчика. ", "reuseWindow": "Принудительно открыть файл или папку в последнем активном окне.", "userDataDir": "Указывает каталог, в котором хранятся данные пользователей, используется в случае выполнения от имени привилегированного пользователя.", + "log": "Используемый уровень ведения журнала. Значение по умолчанию — \"info\". Допустимые значения: \"critical\", \"error\", \"warn\", \"info\", \"debug\", \"trace\", \"off\".", "verbose": "Печать подробного вывода (подразумевает использование параметра \"--wait\").", "wait": "Дождаться закрытия файлов перед возвратом.", "extensionHomePath": "Задайте корневой путь для расширений.", @@ -24,6 +27,7 @@ "experimentalApis": "Включает предложенные функции API для расширения.", "disableExtensions": "Отключить все установленные расширения.", "disableGPU": "Отключить аппаратное ускорение GPU.", + "status": "Выводить сведения об использовании процесса и диагностическую информацию.", "version": "Печать версии.", "help": "Распечатать данные об использовании.", "usage": "Использование", diff --git a/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..c704c03956d 100644 --- a/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "notCompatibleDownload": "Не удается выполнить скачивание, так как не найдено расширение, совместимое с текущей версией VS Code '{0}'. " +} \ No newline at end of file diff --git a/i18n/rus/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json b/i18n/rus/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json index 44292b6a1de..c25a9a85ff7 100644 --- a/i18n/rus/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json +++ b/i18n/rus/src/vs/platform/extensionManagement/node/extensionManagementService.i18n.json @@ -6,7 +6,13 @@ { "invalidManifest": "Недопустимое расширение: package.json не является файлом JSON.", "restartCodeLocal": "Перезапустите код перед переустановкой {0}.", + "installingOutdatedExtension": "Уже установлена более новая версия этого расширения. Вы хотите переопределить ее более старой версией?", + "override": "Переопределить", "cancel": "Отмена", + "notFoundCompatible": "Не удается выполнить установку, так как не найдено расширение '{0}', совместимое с текущей версией VS Code '{1}'.", + "quitCode": "Не удается выполнить установку, так как устаревший экземпляр расширения еще запущен. Закройте и снова откройте VS Code, затем запустите установку повторно.", + "exitCode": "Не удается выполнить установку, так как устаревший экземпляр расширения еще запущен. Закройте и снова откройте VS Code, затем запустите установку повторно. ", + "notFoundCompatibleDependency": "Не удается выполнить установку, так как не найдено зависимое расширение '{0}', совместимое с текущей версией VS Code '{1}'. ", "uninstallDependeciesConfirmation": "Вы хотите удалить \"{0}\" отдельно или вместе с зависимостями?", "uninstallOnly": "Только", "uninstallAll": "Все", diff --git a/i18n/rus/src/vs/platform/extensions/common/extensionsRegistry.i18n.json b/i18n/rus/src/vs/platform/extensions/common/extensionsRegistry.i18n.json index c5f432f019c..b6fede714dc 100644 --- a/i18n/rus/src/vs/platform/extensions/common/extensionsRegistry.i18n.json +++ b/i18n/rus/src/vs/platform/extensions/common/extensionsRegistry.i18n.json @@ -17,6 +17,8 @@ "vscode.extension.activationEvents.onLanguage": "Событие активации выдается каждый раз, когда открывается файл, который разрешается к указанному языку.", "vscode.extension.activationEvents.onCommand": "Событие активации выдается каждый раз при вызове указанной команды.", "vscode.extension.activationEvents.onDebug": "Событие активации выдается каждый раз, когда пользователь запускает отладку или собирается установить конфигурацию отладки.", + "vscode.extension.activationEvents.onDebugInitialConfigurations": "Событие активации выдается каждый раз, когда необходимо создать файл \"launch.json\" (и вызывать все методы provideDebugConfigurations).", + "vscode.extension.activationEvents.onDebugResolve": "Событие активации выдается каждый раз при запуске сеанса отладки указанного типа (и при вызове соответствующего метода resolveDebugConfiguration).", "vscode.extension.activationEvents.workspaceContains": "Событие активации выдается каждый раз при открытии папки, содержащей по крайней мере один файл, который соответствует указанной стандартной маске.", "vscode.extension.activationEvents.onView": "Событие активации выдается каждый раз при развертывании указанного окна.", "vscode.extension.activationEvents.star": "Событие активации выдается при запуске VS Code. Для удобства пользователя используйте это событие в своем расширении только в том случае, если другие сочетания событий не подходят.", diff --git a/i18n/rus/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json b/i18n/rus/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json index 305c47b7195..d46e47e4591 100644 --- a/i18n/rus/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json +++ b/i18n/rus/src/vs/workbench/browser/actions/toggleTabsVisibility.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "toggleTabs": "Изменить видимость вкладки", "view": "Просмотр" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json b/i18n/rus/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json index 60289bc43b8..1556822923d 100644 --- a/i18n/rus/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json +++ b/i18n/rus/src/vs/workbench/browser/parts/compositebar/compositeBarActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "largeNumberBadge": "10000 и выше", "badgeTitle": "{0} - {1}", "additionalViews": "Дополнительные представления", "numberBadge": "{0} ({1})", diff --git a/i18n/rus/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json b/i18n/rus/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json index 769c14c870b..32e566863c8 100644 --- a/i18n/rus/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json +++ b/i18n/rus/src/vs/workbench/browser/parts/editor/editorCommands.i18n.json @@ -6,6 +6,7 @@ { "editorCommand.activeEditorMove.description": "Перемещение активного редактора по вкладкам или группам", "editorCommand.activeEditorMove.arg.name": "Аргумент перемещения активного редактора", + "editorCommand.activeEditorMove.arg.description": "Свойства аргумента:\n\t* 'to': строковое значение, указывающее направление перемещения.\n\t* 'by': строковое значение, указывающее единицу перемещения (вкладка или группа).\n\t* 'value': числовое значение, указывающее количество позиций перемещения или абсолютную позицию для перемещения.", "commandDeprecated": "Команда **{0}** удалена. Вместо нее можно использовать **{1}**", "openKeybindings": "Настройка сочетаний клавиш" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json b/i18n/rus/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json index b72594a677c..eabbcf197e7 100644 --- a/i18n/rus/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json +++ b/i18n/rus/src/vs/workbench/browser/parts/editor/editorStatus.i18n.json @@ -52,5 +52,6 @@ "screenReaderDetectedExplanation.question": "Вы используете средство чтения с экрана в VS Code?", "screenReaderDetectedExplanation.answerYes": "Да", "screenReaderDetectedExplanation.answerNo": "Нет", - "screenReaderDetectedExplanation.body1": "Теперь среда VS Code оптимизирована для средства чтения с экрана." + "screenReaderDetectedExplanation.body1": "Теперь среда VS Code оптимизирована для средства чтения с экрана.", + "screenReaderDetectedExplanation.body2": "Некоторые функции редактора (например, перенос слов, сворачивание и т.д.) будут работать по-другому." } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/rus/src/vs/workbench/electron-browser/actions.i18n.json index 0af36ba16b2..2380d05741c 100644 --- a/i18n/rus/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/rus/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "Список поддерживаемых языков см. в {0}.", "restart": "Для изменения значения требуется перезапуск VSCode.", "fail.createSettings": "Невозможно создать \"{0}\" ({1}).", + "openLogsFolder": "Открыть папку журналов", + "showLogs": "Показать журналы...", + "mainProcess": "Главный", + "sharedProcess": "Общий", + "rendererProcess": "Отрисовщик", + "extensionHost": "Узел расширения", + "selectProcess": "Выберите процесс", + "setLogLevel": "Установите уровень ведения журнала", + "trace": "Трассировка", "debug": "Отладка", "info": "Сведения", "warn": "Предупреждение", - "err": "Ошибка" + "err": "Ошибка", + "critical": "Критический", + "off": "Отключено", + "selectLogLevel": "Установите уровень ведения журнала" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/electron-browser/main.contribution.i18n.json b/i18n/rus/src/vs/workbench/electron-browser/main.contribution.i18n.json index fc3f719ff28..f0c8c83bedf 100644 --- a/i18n/rus/src/vs/workbench/electron-browser/main.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/electron-browser/main.contribution.i18n.json @@ -16,6 +16,7 @@ "workbench.editor.labelFormat.long": "Отображать имя файла и абсолютный путь.", "tabDescription": "Определяет формат метки редактора. Изменив этот параметр, можно сделать более наглядным расположение файла:\n- короткий формат: 'parent'\n- средний формат: 'workspace/src/parent'\n- длинный формат: '/home/user/workspace/src/parent'\n- по умолчанию: '.../parent', если другая вкладка имеет такой же заголовок или относительный путь к рабочей области, если вкладки отключены", "editorTabCloseButton": "Определяет положение кнопок закрытия вкладок редактора или отключает их, если задано значение off.", + "tabSizing": "Управляет размером вкладок редактора. При установке значения \"fit\" вкладки будут оставаться достаточно большими, чтобы в них можно было разместить полную метку редактора. При установке значения \"shrink\" вкладки будут сжиматься, если доступного места на экране недостаточно. Это позволяет отображать все вкладки в одном окне.", "showIcons": "Определяет, должны ли открытые редакторы отображаться со значком. Требует включить тему значков.", "enablePreview": "Определяет, отображаются ли открытые редакторы в режиме предварительного просмотра. Редакторы в режиме предварительного просмотра можно использовать, пока они открыты (например, с помощью двойного щелчка мыши или изменения). Текст в таких редакторах отображается курсивом.", "enablePreviewFromQuickOpen": "Определяет, отображаются ли редакторы из Quick Open в режиме предварительного просмотра. Редакторы в режиме предварительного просмотра повторно используются до сохранения (например, с помощью двойного щелчка или изменения).", @@ -29,6 +30,7 @@ "statusBarVisibility": "Управляет видимостью строки состояния в нижней части рабочего места.", "activityBarVisibility": "Управляет видимостью панели действий на рабочем месте.", "closeOnFileDelete": "Определяет, следует ли автоматически закрывать редакторы, когда отображаемый в них файл удален или переименован другим процессом. При отключении этой функции редактор остается открытым в качестве черновика. Обратите внимание, что при удалении из приложения редактор закрывается всегда и что файлы черновиков никогда не закрываются для сохранения данных.", + "enableNaturalLanguageSettingsSearch": "Определяет, следует ли включить режим поиска естественного языка для параметров.", "fontAliasing": "Управляет методом сглаживания шрифтов в рабочей области.-по умолчанию: субпиксельное сглаживание шрифтов; позволит добиться максимальной четкости текста на большинстве дисплеев за исключением Retina - сглаживание: сглаживание шрифтов на уровне пикселей, в отличие от субпиксельного сглаживания; позволит сделать шрифт более светлым в целом - нет: сглаживание шрифтов отключено; текст будет отображаться с неровными острыми краями ", "workbench.fontAliasing.default": "Субпиксельное сглаживание шрифтов; позволит добиться максимальной четкости текста на большинстве дисплеев за исключением Retina.", "workbench.fontAliasing.antialiased": "Сглаживание шрифтов на уровне пикселей, в отличие от субпиксельного сглаживания. Может сделать шрифт светлее в целом.", diff --git a/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json index dcf24056a01..67f670f43d4 100644 --- a/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debug.contribution.i18n.json @@ -20,5 +20,10 @@ "openExplorerOnEnd": "Автоматически открывать представление обозревателя в конце сеанса отладки", "inlineValues": "Показывать значения переменных в редакторе во время отладки", "hideActionBar": "Определяет, следует ли скрыть всплывающую панель действий отладки.", + "never": "Никогда не отображать отладку в строке состояния", + "always": "Всегда отображать отладку в строке состояния", + "onFirstSessionStart": "Отображать отладку в строке состояния только после первого запуска отладки", + "showInStatusBar": "Определяет видимость для строки состояния отладки", + "openDebug": "Определяет, следует ли открыть окно просмотра отладки в начале сеанса отладки.", "launch": "Глобальная конфигурация запуска отладки. Должна использоваться в качестве альтернативы для конфигурации \"launch.json\", которая является общей для рабочих пространств" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 1f995f9f60b..4fb2a6b6681 100644 --- a/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,6 +11,8 @@ "breakpointAdded": "Добавлена точка останова: строка {0}, файл {1}", "breakpointRemoved": "Удалена точка останова: строка {0}, файл {1}", "compoundMustHaveConfigurations": "Для составного элемента должен быть задан атрибут configurations для запуска нескольких конфигураций.", + "configMissing": "Конфигурация \"{0}\" отсутствует в launch.json.", + "launchJsonDoesNotExist": "Файл \"launch.json\" не существует.", "debugRequestNotSupported": "Атрибут '{0}' имеет неподдерживаемое значение '{1}' в выбранной конфигурации отладки.", "debugRequesMissing": "В выбранной конфигурации отладки отсутствует атрибут '{0}'.", "debugTypeNotSupported": "Настроенный тип отладки \"{0}\" не поддерживается.", diff --git a/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json index 00a5bd00b41..3bc095205a3 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionEditor.i18n.json @@ -6,9 +6,11 @@ { "name": "Имя расширения", "extension id": "Идентификатор расширений", + "preview": "Предварительный просмотр", "publisher": "Имя издателя", "install count": "Число установок", "rating": "Оценка", + "repository": "Репозиторий", "license": "Лицензия", "details": "Подробности", "contributions": "Вклады", diff --git a/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..f52c6f67cb9 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "Оценено пользователями: {0} ", + "ratedBySingleUser": "Оценено 1 пользователем" +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..4de94d4af23 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "Щелкните здесь, чтобы остановить профилирование." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index ecf99790051..31eb35b5dce 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "Управление расширениями", "galleryExtensionsCommands": "Установить расширения из коллекции", "extension": "Расширение", + "runtimeExtension": "Запущенные расширения", "extensions": "Расширения", "view": "Просмотреть", "developer": "Разработчик", "extensionsConfigurationTitle": "Расширения", - "extensionsAutoUpdate": "Автоматически обновлять расширения" + "extensionsAutoUpdate": "Автоматически обновлять расширения", + "extensionsIgnoreRecommendations": "Если этот параметр установлен в значение true, оповещения о рекомендациях по расширениям перестанут отображаться." } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index d813ae7a27e..ef7b1463fd4 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "Сообщить об ошибке" + "starActivation": "Активируется при запуске", + "workspaceContainsGlobActivation": "Активируется, так как соответствующий файл {0} отсутствует в вашей рабочей области", + "workspaceContainsFileActivation": "Активируется, так как файл {0} отсутствует в вашей рабочей области", + "languageActivation": "Активируется, так как был открыт файл {0}", + "workspaceGenericActivation": "Активируется при {0}", + "errors": "Необработанных ошибок: {0}", + "extensionsInputName": "Запущенные расширения", + "showRuntimeExtensions": "Показать запущенные расширения", + "reportExtensionIssue": "Сообщить об ошибке", + "extensionHostProfileStart": "Запустить профиль узла расширения", + "extensionHostProfileStop": "Остановить профиль узла расширения", + "saveExtensionHostProfile": "Сохранить профиль узла расширения" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json b/i18n/rus/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json index c601c781bde..8e87ab6930a 100644 --- a/i18n/rus/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/files/electron-browser/fileActions.i18n.json @@ -46,6 +46,7 @@ "saveAs": "Сохранить как...", "saveAll": "Сохранить все", "saveAllInGroup": "Сохранить все в группе", + "saveFiles": "Сохранить все файлы", "revert": "Отменить изменения в файле", "focusOpenEditors": "Фокус на представлении открытых редакторов", "focusFilesExplorer": "Фокус на проводнике", @@ -68,5 +69,7 @@ "invalidFileNameError": "Имя **{0}** недопустимо для файла или папки. Выберите другое имя.", "filePathTooLongError": "Из-за использования имени **{0}** путь слишком длинный. Выберите более короткое имя.", "compareWithSaved": "Сравнить активный файл с сохраненным", - "modifiedLabel": "{0} (на диске) ↔ {1}" + "modifiedLabel": "{0} (на диске) ↔ {1}", + "compareWithClipboard": "Сравнить активный файл с буфером обмена", + "clipboardComparisonLabel": "Буфер обмена ↔ {0}" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json b/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json index 5c9b67b2669..82dd74c7e2d 100644 --- a/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/emptyView.i18n.json @@ -6,6 +6,8 @@ { "noWorkspace": "Нет открытой папки", "explorerSection": "Раздел проводника", + "noWorkspaceHelp": "Вы еще не добавили папку в рабочую область.", + "addFolder": "Добавить папку", "noFolderHelp": "Вы еще не открыли папку.", "openFolder": "Открыть папку" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json b/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json index 620852a6f18..1aa57ff69b5 100644 --- a/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.i18n.json @@ -6,6 +6,8 @@ { "fileInputAriaLabel": "Введите имя файла. Нажмите клавишу ВВОД, чтобы подтвердить введенные данные, или ESCAPE для отмены.", "filesExplorerViewerAriaLabel": "{0}, Проводник", + "dropFolders": "Вы хотите действительно добавить папки в эту рабочую область?", + "dropFolder": "Вы хотите действительно добавить папку в эту рабочую область?", "addFolders": "&&Добавить папки", "addFolder": "&&Добавить папку", "confirmMove": "Вы действительно хотите переместить '{0}'?", diff --git a/i18n/rus/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json b/i18n/rus/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json index 8b6ad71cd4e..55325cd6d49 100644 --- a/i18n/rus/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/markers/browser/markersPanel.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "totalProblems": "Всего проблем: {0}", + "filteredProblems": "Показано проблем: {0} из {1}" +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/markers/common/messages.i18n.json b/i18n/rus/src/vs/workbench/parts/markers/common/messages.i18n.json index 2729294f21e..4a0ebeccf87 100644 --- a/i18n/rus/src/vs/workbench/parts/markers/common/messages.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/markers/common/messages.i18n.json @@ -6,6 +6,7 @@ { "viewCategory": "Просмотреть", "problems.view.toggle.label": "Показать/скрыть проблемы", + "problems.view.focus.label": "Проблемы с фокусом", "problems.panel.configuration.title": "Представление \"Проблемы\"", "problems.panel.configuration.autoreveal": "Определяет, следует ли представлению \"Проблемы\" отображать файлы при их открытии", "markers.panel.title.problems": "Проблемы", diff --git a/i18n/rus/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json b/i18n/rus/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json index 209030a10b2..56bc0ec3ec3 100644 --- a/i18n/rus/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/preferences/browser/keybindingWidgets.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defineKeybinding.initial": "Нажмите нужное сочетание клавиш, а затем клавишу ВВОД.", "defineKeybinding.chordsTo": "Аккорд для" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json b/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json index db8d5850260..3853973af14 100644 --- a/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesActions.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "openRawDefaultSettings": "Открыть исходные параметры по умолчанию", "openGlobalSettings": "Открыть пользовательские параметры", "openGlobalKeybindings": "Открыть сочетания клавиш", "openGlobalKeybindingsFile": "Открыть файл сочетаний клавиш", diff --git a/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json b/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json index ef7dc4d8783..cdba5fc34b2 100644 --- a/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/preferences/browser/preferencesWidgets.i18n.json @@ -4,9 +4,12 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "defaultSettingsFuzzyPrompt": "Попробуйте режим поиска естественного языка!", "defaultSettings": "Чтобы переопределить параметры по умолчанию, укажите свои параметры в области справа.", "noSettingsFound": "Параметры не найдены.", "settingsSwitcherBarAriaLabel": "Переключатель параметров", "userSettings": "Параметры пользователя", - "workspaceSettings": "Параметры рабочей области" + "workspaceSettings": "Параметры рабочей области", + "folderSettings": "Параметры папок", + "enableFuzzySearch": "Включить режим поиска естественного языка" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json b/i18n/rus/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json index cd29f7f88b8..a1365d40ac3 100644 --- a/i18n/rus/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/search/browser/patternInputWidget.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "defaultLabel": "ввод" + "defaultLabel": "ввод", + "useExcludesAndIgnoreFilesDescription": "Использовать параметры исключения и игнорировать файлы" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json b/i18n/rus/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json index 8b6ad71cd4e..576dd7ba274 100644 --- a/i18n/rus/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/search/electron-browser/searchActions.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "findInFolder": "Найти в папке...", + "findInWorkspace": "Найти в рабочей области..." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json index 2cba25f0f5e..11d41b01847 100644 --- a/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/task.contribution.i18n.json @@ -8,6 +8,7 @@ "ConfigureTaskRunnerAction.label": "Настроить задачу", "CloseMessageAction.label": "Закрыть", "problems": "Проблемы", + "building": "Сборка...", "manyMarkers": "99+", "runningTasks": "Показать выполняемые задачи", "tasks": "Задачи", @@ -50,6 +51,7 @@ "TaslService.noEntryToRun": "Задача для запуска не найдена. Настройте задачи...", "TaskService.fetchingBuildTasks": "Получение задач сборки...", "TaskService.pickBuildTask": "Выберите задачу сборки для запуска", + "TaskService.noBuildTask": "Задача сборки для запуска отсутствует. Настройте задачи сборки...", "TaskService.fetchingTestTasks": "Получение задач тестирования...", "TaskService.pickTestTask": "Выберите задачу тестирования для запуска", "TaskService.noTestTaskTerminal": "Тестовая задача для запуска не найдена. Настройте задачи...", diff --git a/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json b/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json index 05102c96f17..2249170ee87 100644 --- a/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.i18n.json @@ -7,6 +7,7 @@ "TerminalTaskSystem.unknownError": "При выполнении задачи произошла неизвестная ошибка. Подробности см. в журнале выходных данных задач.", "dependencyFailed": "Не удалось разрешить зависимую задачу '{0}' в папке рабочей области '{1}'", "TerminalTaskSystem.terminalName": "Задача — {0}", + "closeTerminal": "Нажмите любую клавишу, чтобы закрыть терминал.", "reuseTerminal": "Терминал будет повторно использоваться задачами. Чтобы закрыть его, нажмите любую клавишу.", "TerminalTaskSystem": "Невозможно выполнить команду оболочки на диске UNC.", "unkownProblemMatcher": "Не удается разрешить сопоставитель проблем {0}. Сопоставитель будет проигнорирован" diff --git a/i18n/rus/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json b/i18n/rus/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json index 1b0a682834e..5f308b15ff2 100644 --- a/i18n/rus/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/tasks/node/taskConfiguration.i18n.json @@ -14,6 +14,8 @@ "ConfigurationParser.noTypeDefinition": "Ошибка: тип задачи '{0}' не зарегистрирован. Возможно, вы не установили расширение, которое предоставляет соответствующий поставщик задач.", "ConfigurationParser.missingRequiredProperty": "Ошибка: в конфигурации задачи '{0}' отсутствует необходимое свойство '{1}'. Конфигурация задачи будет проигнорирована.", "ConfigurationParser.notCustom": "Ошибка: задачи не объявлены в качестве пользовательской задачи. Конфигурация будет проигнорирована.\n{0}\n", + "ConfigurationParser.noTaskName": "Ошибка: в задаче должно быть указано свойство метки. Задача будет проигнорирована.\n{0}\n", + "taskConfiguration.shellArgs": "Предупреждение: задача \"{0}\" является командой оболочки, и один из ее аргументов содержит пробелы без escape-последовательности. Чтобы обеспечить правильную расстановку кавычек в командной строке, объедините аргументы в команде.", "taskConfiguration.noCommandOrDependsOn": "Ошибка: в задаче \"{0}\" не указаны ни команда, ни свойство dependsOn. Задача будет проигнорирована. Определение задачи:\n{1}", "taskConfiguration.noCommand": "Ошибка: задача \"{0}\" не определяет команду. Задача будет игнорироваться. Ее определение:\n{1}", "TaskParse.noOsSpecificGlobalTasks": "Версия задач 2.0.0 не поддерживает глобальные задачи для конкретных ОС. Преобразуйте их в задачи с помощью команд для конкретных ОС.\nЗатронутые задачи: {0}" diff --git a/i18n/rus/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json index b8da8b233eb..045380be606 100644 --- a/i18n/rus/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.i18n.json @@ -16,6 +16,7 @@ "terminal.integrated.rightClickCopyPaste": "Если задано, блокирует отображение контекстного меню при щелчке правой кнопкой мыши в терминале. Вместо этого будет выполняться копирование выбранного элемента и вставка в область, в которой нет выбранных элементов.", "terminal.integrated.fontFamily": "Определяет семейство шрифтов терминала, значение по умолчанию — editor.fontFamily.", "terminal.integrated.fontSize": "Определяет размер шрифта (в пикселях) для терминала.", + "terminal.integrated.lineHeight": "Определяет высоту строки терминала; это число умножается на размер шрифта терминала, что дает фактическую высоту строки в пикселях.", "terminal.integrated.enableBold": "Следует ли разрешить полужирный текст в терминале. Эта функция должна поддерживаться оболочкой терминала.", "terminal.integrated.cursorBlinking": "Управляет миганием курсора терминала.", "terminal.integrated.cursorStyle": "Определяет стиль курсора терминала.", diff --git a/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 1296713c724..116db87d73e 100644 --- a/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,9 @@ // Do not edit this file. It is machine generated. { "selectTheme.label": "Цветовая тема", + "themes.category.light": "светлые темы", + "themes.category.dark": "темные темы", + "themes.category.hc": "темы с высоким контрастом", "installColorThemes": "Установить дополнительные цветовые темы...", "themes.selectTheme": "Выберите цветовую тему (используйте клавиши стрелок вверх и вниз для предварительного просмотра)", "selectIconTheme.label": "Тема значков файлов", diff --git a/i18n/rus/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json b/i18n/rus/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json index 8b6ad71cd4e..dc2cdb3183b 100644 --- a/i18n/rus/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json +++ b/i18n/rus/src/vs/workbench/services/decorations/browser/decorationsService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "bubbleTitle": "Содержит выделенные элементы" +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json b/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json index 8b6ad71cd4e..928a4957cab 100644 --- a/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json +++ b/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionHostProfiler.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "message": "$(zap) Профилирование узла расширений..." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json b/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json index 592a962b099..eda56c60fde 100644 --- a/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json +++ b/i18n/rus/src/vs/workbench/services/extensions/electron-browser/extensionService.i18n.json @@ -9,5 +9,6 @@ "extensionHostProcess.crash": "Хост-процесс для расширений неожиданно завершил работу.", "extensionHostProcess.unresponsiveCrash": "Работа хост-процесса для расширений была завершена, так как он перестал отвечать на запросы.", "overwritingExtension": "Идет перезапись расширения {0} на {1}.", - "extensionUnderDevelopment": "Идет загрузка расширения разработки в {0}." + "extensionUnderDevelopment": "Идет загрузка расширения разработки в {0}.", + "extensionCache.invalid": "Расширения были изменены на диске. Обновите окно." } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json b/i18n/rus/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json index 297bca0ac5f..1ea5d9e9c9c 100644 --- a/i18n/rus/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json +++ b/i18n/rus/src/vs/workbench/services/keybinding/electron-browser/keybindingService.i18n.json @@ -21,5 +21,6 @@ "keybindings.json.command": "Имя выполняемой команды", "keybindings.json.when": "Условие, когда клавиша нажата.", "keybindings.json.args": "Аргументы, передаваемые в выполняемую команду.", - "keyboardConfigurationTitle": "Клавиатура" + "keyboardConfigurationTitle": "Клавиатура", + "dispatch": "Управляет логикой диспетчеризации для нажатий клавиш \"code\" (рекомендуется) или \"keyCode\"." } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/services/textfile/common/textFileService.i18n.json b/i18n/rus/src/vs/workbench/services/textfile/common/textFileService.i18n.json index 8b6ad71cd4e..07d83c760a5 100644 --- a/i18n/rus/src/vs/workbench/services/textfile/common/textFileService.i18n.json +++ b/i18n/rus/src/vs/workbench/services/textfile/common/textFileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "files.backup.failSave": "Не удалось записать измененные файлы в расположение резервной копии (ошибка: {0}). Попробуйте сохранить файлы и выйти." +} \ No newline at end of file diff --git a/i18n/trk/extensions/css/client/out/cssMain.i18n.json b/i18n/trk/extensions/css/client/out/cssMain.i18n.json index 25a60c39828..efa3cfefe20 100644 --- a/i18n/trk/extensions/css/client/out/cssMain.i18n.json +++ b/i18n/trk/extensions/css/client/out/cssMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "cssserver.name": "CSS Dil Sunucusu" + "cssserver.name": "CSS Dil Sunucusu", + "folding.start": "Katlama Bölgesi Başlangıcı", + "folding.end": "Katlama Bölgesi Sonu" } \ No newline at end of file diff --git a/i18n/trk/extensions/emmet/package.i18n.json b/i18n/trk/extensions/emmet/package.i18n.json index 6ce7f81e59f..a3ef0340c58 100644 --- a/i18n/trk/extensions/emmet/package.i18n.json +++ b/i18n/trk/extensions/emmet/package.i18n.json @@ -49,5 +49,8 @@ "emmetPreferencesBemModifierSeparator": "BEM filtresi kullanırken sınıflar için kullanılacak niteleyici ayrıcı", "emmetPreferencesFilterCommentBefore": "Yorum filtresi uygulandığında eşleşen öğenin önüne yerleştirilmesi gereken yorumun tanımı.", "emmetPreferencesFilterCommentAfter": "Yorum filtresi uygulandığında eşleşen öğenin ardına yerleştirilmesi gereken yorumun tanımı.", - "emmetPreferencesFilterCommentTrigger": "Yorum filterinin uygulanması için kısaltmada bulunması gereken virgülle ayrılmış öznitelik adları listesi" + "emmetPreferencesFilterCommentTrigger": "Yorum filterinin uygulanması için kısaltmada bulunması gereken virgülle ayrılmış öznitelik adları listesi", + "emmetPreferencesFormatNoIndentTags": "İçe girintilenmemesi gereken bir etiket adları dizisi", + "emmetPreferencesFormatForceIndentTags": "Her zaman içe girintilenmesi gereken bir etiket adları dizisi", + "emmetPreferencesAllowCompactBoolean": "Doğruysa, boole niteliklerinin öz gösterimi üretilir" } \ No newline at end of file diff --git a/i18n/trk/extensions/html/client/out/htmlMain.i18n.json b/i18n/trk/extensions/html/client/out/htmlMain.i18n.json index 768300d269b..885fe117726 100644 --- a/i18n/trk/extensions/html/client/out/htmlMain.i18n.json +++ b/i18n/trk/extensions/html/client/out/htmlMain.i18n.json @@ -4,5 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "htmlserver.name": "HTML Dil Sunucusu" + "htmlserver.name": "HTML Dil Sunucusu", + "folding.start": "Katlama Bölgesi Başlangıcı", + "folding.end": "Katlama Bölgesi Sonu" } \ No newline at end of file diff --git a/i18n/trk/src/vs/editor/common/config/commonEditorConfig.i18n.json b/i18n/trk/src/vs/editor/common/config/commonEditorConfig.i18n.json index 33eec63f82e..3a5d4006fd7 100644 --- a/i18n/trk/src/vs/editor/common/config/commonEditorConfig.i18n.json +++ b/i18n/trk/src/vs/editor/common/config/commonEditorConfig.i18n.json @@ -31,6 +31,7 @@ "minimap.maxColumn": "Hazırlanacak mini haritanın azami genişliğini belirli sayıda sütunla sınırla", "find.seedSearchStringFromSelection": "Bulma Araç Çubuğu'ndaki arama metninin, düzenleyicideki seçili alandan beslenmesini denetler", "find.autoFindInSelection": "Seçimde bul işaretçisinin, editördeki metnin birden çok karakteri veya satırı seçildiğinde açılmasını denetler.", + "find.globalFindClipboard": "macOS'da Bulma Aracı'nın paylaşılan panoyu okuyup okumamasını veya değiştirip değiştirmemesini denetler", "wordWrap.off": "Satırlar hiçbir zaman bir sonraki satıra kaydırılmayacak.", "wordWrap.on": "Satırlar görüntü alanı genişliğinde bir sonraki satıra kaydırılacak.", "wordWrap.wordWrapColumn": "Satırlar `editor.wordWrapColumn` değerinde bir sonraki satıra kaydırılacak.", diff --git a/i18n/trk/src/vs/platform/environment/node/argv.i18n.json b/i18n/trk/src/vs/platform/environment/node/argv.i18n.json index 7d132e007de..a4c4cd296d5 100644 --- a/i18n/trk/src/vs/platform/environment/node/argv.i18n.json +++ b/i18n/trk/src/vs/platform/environment/node/argv.i18n.json @@ -16,7 +16,7 @@ "inspect-brk-extensions": "Eklentilerde hata ayıklama ve ayrımlamaya eklenti sunucusu başladıktan hemen sonra duraklatılacak şekilde izin ver. Bağlantı URI'ı için geliştirici araçlarını kontrol edin.", "reuseWindow": "Bir dosya veya klasörü son etkin pencerede açmaya zorlayın.", "userDataDir": "Kullanıcı verilerinin tutulacağı klasörü belirtir, root olarak çalışırken yararlıdır.", - "log": "Kullanılacak günlüğe yazma seviyesi. İzin verilen değerler 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off' şeklindedir.", + "log": "Kullanılacak günlüğe yazma düzeyi. Varsayılan değer 'info'dur. İzin verilen değerler 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off' şeklindedir.", "verbose": "Ayrıntılı çıktı oluştur (--wait anlamına gelir).", "wait": "Geri dönmeden önce dosyaların kapanmasını bekle.", "extensionHomePath": "Eklentilerin kök dizinini belirle.", diff --git a/i18n/trk/src/vs/workbench/electron-browser/actions.i18n.json b/i18n/trk/src/vs/workbench/electron-browser/actions.i18n.json index 7c4352f507b..e3e29379b77 100644 --- a/i18n/trk/src/vs/workbench/electron-browser/actions.i18n.json +++ b/i18n/trk/src/vs/workbench/electron-browser/actions.i18n.json @@ -53,8 +53,20 @@ "doc": "Desteklenen dillerin listesi için göz atın: {0}", "restart": "Değeri değiştirirseniz VSCode'u yeniden başlatmanız gerekir.", "fail.createSettings": " '{0}' oluşturulamadı ({1}).", + "openLogsFolder": "Günlük Klasörünü Aç", + "showLogs": "Günlükleri Göster...", + "mainProcess": "Ana", + "sharedProcess": "Paylaşılan", + "rendererProcess": "Render Alan", + "extensionHost": "Eklenti Sunucusu", + "selectProcess": "İşlem seçin", + "setLogLevel": "Günlük Düzeyini Ayarla", + "trace": "İzle", "debug": "Hata Ayıklama", "info": "Bilgi", "warn": "Uyarı", - "err": "Hata" + "err": "Hata", + "critical": "Kritik", + "off": "Kapalı", + "selectLogLevel": "Günlük düzeyini seçin" } \ No newline at end of file diff --git a/i18n/trk/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json b/i18n/trk/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json index 957aae1111b..ecfc10e7ef8 100644 --- a/i18n/trk/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json +++ b/i18n/trk/src/vs/workbench/parts/debug/electron-browser/debugService.i18n.json @@ -11,7 +11,7 @@ "breakpointAdded": "Kesme noktası eklendi, {0}. satır, {1} dosyası", "breakpointRemoved": "Kesme noktası kaldırıldı, {0}. satır, {1} dosyası", "compoundMustHaveConfigurations": "Bileşik, birden çok yapılandırmayı başlatmak için \"configurations\" özniteliği bulundurmalıdır.", - "configMissing": "'launch.json' mevcut değil veya '{0}' yapılandırmasını içermiyor.", + "configMissing": "'launch.json' dosyasında '{0}' yapılandırması eksik.", "launchJsonDoesNotExist": "'launch.json' mevcut değil.", "debugRequestNotSupported": "Seçilen hata ayıklama yapılandırılmasındaki `{0}` özniteliği desteklenmeyen `{1}` değeri içeriyor.", "debugRequesMissing": "'{0}' özniteliği seçilen hata ayıklama yapılandırılmasında eksik.", diff --git a/i18n/trk/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json b/i18n/trk/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json index 8b6ad71cd4e..4ae85e568bf 100644 --- a/i18n/trk/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json +++ b/i18n/trk/src/vs/workbench/parts/extensions/browser/extensionsWidgets.i18n.json @@ -3,4 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "ratedByUsers": "{0} kullanıcı tarafından derecelendirildi", + "ratedBySingleUser": "1 kullanıcı tarafından derecelendirildi" +} \ No newline at end of file diff --git a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json index 8b6ad71cd4e..4de7cbfa69c 100644 --- a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json +++ b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensionProfileService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "selectAndStartDebug": "Ayrımlamayı durdurmak için tıklayın." +} \ No newline at end of file diff --git a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json index 31d65218f0e..067c8285667 100644 --- a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json +++ b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.i18n.json @@ -7,9 +7,11 @@ "extensionsCommands": "Eklentileri Yönet", "galleryExtensionsCommands": "Galeri Eklentileri Yükle", "extension": "Eklenti", + "runtimeExtension": "Eklentiler Çalıştırılıyor", "extensions": "Eklentiler", "view": "Görüntüle", "developer": "Geliştirici", "extensionsConfigurationTitle": "Eklentiler", - "extensionsAutoUpdate": "Eklentileri otomatik olarak güncelle" + "extensionsAutoUpdate": "Eklentileri otomatik olarak güncelle", + "extensionsIgnoreRecommendations": "\"Doğru\" olarak ayarlanırsa, eklenti tavsiyeleri bildirimleri artık gösterilmez." } \ No newline at end of file diff --git a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json index cbbd312d9f3..244821c9761 100644 --- a/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json +++ b/i18n/trk/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.i18n.json @@ -4,5 +4,16 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "reportExtensionIssue": "Sorun Bildir" + "starActivation": "Başlangıçta etkinleştirildi", + "workspaceContainsGlobActivation": "Çalışma alanınızda bir {0} dosya eşleşmesi mevcut olduğu için etkinleştirildi", + "workspaceContainsFileActivation": "Çalışma alanınızda {0} dosyası mevcut olduğu için etkinleştirildi", + "languageActivation": "{0} dosyasını açtığınız için etkinleştirildi", + "workspaceGenericActivation": "{0} eyleminden dolayı etkinleştirildi", + "errors": "{0} yakalanmayan hata", + "extensionsInputName": "Eklentiler Çalıştırılıyor", + "showRuntimeExtensions": "Çalışan Eklentileri Göster", + "reportExtensionIssue": "Sorun Bildir", + "extensionHostProfileStart": "Eklenti Sunucusu Ayrımlamayı Başlat", + "extensionHostProfileStop": "Eklenti Sunucusu Ayrımlamayı Durdur", + "saveExtensionHostProfile": "Eklenti Sunucusu Ayrımlamayı Kaydet" } \ No newline at end of file diff --git a/package.json b/package.json index 8535979bdf1..c25c90837b2 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "smoketest": "cd test/smoke && mocha" }, "dependencies": { - "applicationinsights": "0.17.1", + "applicationinsights": "0.18.0", "fast-plist": "0.1.2", "gc-signals": "^0.0.1", "getmac": "1.0.7", @@ -32,18 +32,20 @@ "jschardet": "1.6.0", "keytar": "^4.0.5", "minimist": "1.2.0", + "native-is-elevated": "^0.2.1", "native-keymap": "1.2.5", "native-watchdog": "0.3.0", "node-pty": "0.7.4", "nsfw": "1.0.16", "semver": "4.3.6", - "spdlog": "0.3.7", + "spdlog": "0.5.0", + "sudo-prompt": "^8.0.0", "v8-inspect-profiler": "^0.0.7", "vscode-chokidar": "1.6.2", "vscode-debugprotocol": "1.25.0", - "vscode-ripgrep": "^0.6.0-patch.0.5", + "vscode-ripgrep": "^0.7.1-patch.0", "vscode-textmate": "^3.2.0", - "xterm": "Tyriar/xterm.js#vscode-release/1.19", + "vscode-xterm": "3.0.0-beta8", "yauzl": "2.8.0" }, "devDependencies": { diff --git a/resources/linux/bin/code.sh b/resources/linux/bin/code.sh index 088bcf8b2ab..ca71a94db9e 100755 --- a/resources/linux/bin/code.sh +++ b/resources/linux/bin/code.sh @@ -3,15 +3,15 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. -# If root, ensure that --user-data-dir is specified +# If root, ensure that --user-data-dir or --file-write is specified if [ "$(id -u)" = "0" ]; then for i in $@ do - if [[ $i == --user-data-dir=* ]]; then - DATA_DIR_SET=1 + if [[ $i == --user-data-dir=* || $i == --file-write ]]; then + CAN_LAUNCH_AS_ROOT=1 fi done - if [ -z $DATA_DIR_SET ]; then + if [ -z $CAN_LAUNCH_AS_ROOT ]; then echo "You are trying to start vscode as a super user which is not recommended. If you really want to, you must specify an alternate user data directory using the --user-data-dir argument." 1>&2 exit 1 fi diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index f62b80f5f79..b9a95fa62c9 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -10,14 +10,21 @@ set VSCODEUSERDATADIR=%TMP%\vscodeuserfolder-%RANDOM%-%TIME:~6,5% :: Tests in the extension host call .\scripts\code.bat %~dp0\..\extensions\vscode-api-tests\testWorkspace --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% + call .\scripts\code.bat %~dp0\..\extensions\vscode-colorize-tests\test --extensionDevelopmentPath=%~dp0\..\extensions\vscode-colorize-tests --extensionTestsPath=%~dp0\..\extensions\vscode-colorize-tests\out --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% + call .\scripts\code.bat $%~dp0\..\extensions\emmet\test-fixtures --extensionDevelopmentPath=%~dp0\..\extensions\emmet --extensionTestsPath=%~dp0\..\extensions\emmet\out\test --disableExtensions --user-data-dir=%VSCODEUSERDATADIR% +if %errorlevel% neq 0 exit /b %errorlevel% :: Integration & performance tests in AMD call .\scripts\test.bat --runGlob **\*.integrationTest.js %* +if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in commonJS (language servers tests...) call .\scripts\node-electron.bat .\node_modules\mocha\bin\_mocha .\extensions\html\server\out\test\ +if %errorlevel% neq 0 exit /b %errorlevel% rmdir /s /q %VSCODEUSERDATADIR% diff --git a/src/main.js b/src/main.js index 3192f1f086c..6e29b5203a6 100644 --- a/src/main.js +++ b/src/main.js @@ -219,7 +219,6 @@ var nodeCachedDataDir = getNodeCachedDataDir().then(function (value) { // Load our code once ready app.once('ready', function () { perf.mark('main:appReady'); - global.perfAppReady = Date.now(); var nlsConfig = getNLSConfiguration(); process.env['VSCODE_NLS_CONFIG'] = JSON.stringify(nlsConfig); diff --git a/src/tsconfig.monaco.json b/src/tsconfig.monaco.json index 3dba5dc239d..81bd7677f76 100644 --- a/src/tsconfig.monaco.json +++ b/src/tsconfig.monaco.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/tsconfig", + "$schema": "https://schemastore.azurewebsites.net/schemas/json/tsconfig.json", "compilerOptions": { "noEmit": true, "module": "amd", diff --git a/src/typings/native-is-elevated.d.ts b/src/typings/native-is-elevated.d.ts new file mode 100644 index 00000000000..1b79764d6a0 --- /dev/null +++ b/src/typings/native-is-elevated.d.ts @@ -0,0 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'native-is-elevated' { + function isElevated(): boolean; + + export = isElevated; +} \ No newline at end of file diff --git a/src/typings/spdlog.d.ts b/src/typings/spdlog.d.ts index ef93a9cf800..4762b11143f 100644 --- a/src/typings/spdlog.d.ts +++ b/src/typings/spdlog.d.ts @@ -28,6 +28,10 @@ declare module 'spdlog' { error(message: string); critical(message: string); setLevel(level: number); + clearFormatters(); + /** + * A synchronous operation to flush the contents into file + */ flush(): void; drop(): void; } diff --git a/src/typings/sudo-prompt.d.ts b/src/typings/sudo-prompt.d.ts new file mode 100644 index 00000000000..4a204229051 --- /dev/null +++ b/src/typings/sudo-prompt.d.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +declare module 'sudo-prompt' { + + export function exec(cmd: string, options: { name?: string, icns?: string }, callback: (error: string, stdout: string, stderr: string) => void); +} \ No newline at end of file diff --git a/src/typings/xterm.d.ts b/src/typings/vscode-xterm.d.ts similarity index 90% rename from src/typings/xterm.d.ts rename to src/typings/vscode-xterm.d.ts index 0f26b76a5aa..a8000eec916 100644 --- a/src/typings/xterm.d.ts +++ b/src/typings/vscode-xterm.d.ts @@ -7,11 +7,11 @@ * to be stable and consumed by external programs. */ -declare module 'xterm' { +declare module 'vscode-xterm' { /** * An object containing start up options for the terminal. */ - interface ITerminalOptions { + export interface ITerminalOptions { /** * A data uri of the sound to use for the bell (needs bellStyle = 'sound'). */ @@ -20,7 +20,7 @@ declare module 'xterm' { /** * The type of the bell notification the terminal will use. */ - bellStyle?: 'none' | 'visual' | 'sound' | 'both'; + bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/; /** * The number of columns in the terminal. @@ -57,6 +57,11 @@ declare module 'xterm' { */ fontFamily?: string; + /** + * The spacing in whole pixels between characters.. + */ + letterSpacing?: number; + /** * The line height used to render text. */ @@ -67,6 +72,13 @@ declare module 'xterm' { */ rows?: number; + /** + * Whether screen reader support is enabled. When on this will expose + * supporting elements in the DOM to support NVDA on Windows and VoiceOver + * on macOS. + */ + screenReaderMode?: boolean; + /** * The amount of scrollback in the terminal. Scrollback is the amount of rows * that are retained when lines are scrolled beyond the initial viewport. @@ -87,17 +99,17 @@ declare module 'xterm' { /** * Contains colors to theme the terminal with. */ - interface ITheme { + export interface ITheme { /** The default foreground color */ foreground?: string, /** The default background color */ background?: string, /** The cursor color */ cursor?: string, - /** The selection color (can be transparent) */ - selection?: string, /** The accent color of the cursor (used as the foreground color for a block cursor) */ cursorAccent?: string, + /** The selection color (can be transparent) */ + selection?: string, /** ANSI black (eg. `\x1b[30m`) */ black?: string, /** ANSI red (eg. `\x1b[31m`) */ @@ -135,7 +147,7 @@ declare module 'xterm' { /** * An object containing options for a link matcher. */ - interface ILinkMatcherOptions { + export interface ILinkMatcherOptions { /** * The index of the link from the regex.match(text) call. This defaults to 0 * (for regular expressions without capture groups). @@ -213,7 +225,7 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: 'blur' | 'focus' | 'lineFeed', listener: () => void): void; + on(type: 'blur' | 'focus' | 'linefeed' | 'selection', listener: () => void): void; /** * Registers an event listener. * @param type The type of the event. @@ -237,13 +249,13 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - on(type: 'refresh', listener: (data?: { start: number, end: number }) => void): void; + on(type: 'refresh', listener: (data?: {start: number, end: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. * @param listener The listener. */ - on(type: 'resize', listener: (data?: { cols: number, rows: number }) => void): void; + on(type: 'resize', listener: (data?: {cols: number, rows: number}) => void): void; /** * Registers an event listener. * @param type The type of the event. @@ -268,7 +280,7 @@ declare module 'xterm' { * @param type The type of the event. * @param listener The listener. */ - off(type: 'blur' | 'focus' | 'lineFeed' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; + off(type: 'blur' | 'focus' | 'linefeed' | 'selection' | 'data' | 'key' | 'keypress' | 'keydown' | 'refresh' | 'resize' | 'scroll' | 'title' | string, listener: (...args: any[]) => void): void; /** * Resizes the terminal. @@ -285,7 +297,9 @@ declare module 'xterm' { /** * Opens the terminal within an element. - * @param parent The element to create the terminal within. + * @param parent The element to create the terminal within. This element + * must be visible (have dimensions) when `open` is called as several DOM- + * based measurements need to be performed when this function is called. */ open(parent: HTMLElement): void; @@ -412,12 +426,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: 'geometry'): [number, number]; + getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -464,13 +473,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cols' | 'fontSize' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback', value: number): void; - /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ - setOption(key: 'geometry', value: [number, number]): void; + setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; /** * Sets an option on the terminal. * @param key The option key. @@ -504,11 +507,11 @@ declare module 'xterm' { reset(): void /** - * Loads an addon, attaching it to the Terminal prototype and making it - * available to all newly created Terminals. - * @param addon The addon to load. + * Applies an addon to the Terminal prototype, making it available to all + * newly created Terminals. + * @param addon The addon to apply. */ - static loadAddon(addon: 'attach' | 'fit' | 'fullscreen' | 'search' | 'terminado' | 'winptyCompat'): void; + static applyAddon(addon: any): void; diff --git a/src/vs/base/browser/dnd.ts b/src/vs/base/browser/dnd.ts index 7ca472fe69e..b95e397071f 100644 --- a/src/vs/base/browser/dnd.ts +++ b/src/vs/base/browser/dnd.ts @@ -39,4 +39,23 @@ export class DelayedDragHandler { public dispose(): void { this.clearDragTimeout(); } -} \ No newline at end of file +} + +// Common data transfers +export const DataTransfers = { + + /** + * Application specific resource transfer type. + */ + URL: 'URL', + + /** + * Browser specific transfer type to download. + */ + DOWNLOAD_URL: 'DownloadURL', + + /** + * Typicaly transfer type for copy/paste transfers. + */ + TEXT: 'text/plain' +}; \ No newline at end of file diff --git a/src/vs/base/browser/event.ts b/src/vs/base/browser/event.ts index 010e7b03247..190efe95538 100644 --- a/src/vs/base/browser/event.ts +++ b/src/vs/base/browser/event.ts @@ -126,7 +126,12 @@ export const domEvent: IDomEvent = (element: EventHandler, type: string, useCapt return emitter.event; }; -export function stop(event: _Event): _Event { +export interface CancellableEvent { + preventDefault(); + stopPropagation(); +} + +export function stop(event: _Event): _Event { return mapEvent(event, e => { e.preventDefault(); e.stopPropagation(); diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 1a0097aadf8..49eb0112767 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -8,7 +8,6 @@ import * as DOM from 'vs/base/browser/dom'; import { defaultGenerator } from 'vs/base/common/idGenerator'; import { escape } from 'vs/base/common/strings'; -import { TPromise } from 'vs/base/common/winjs.base'; import { removeMarkdownEscapes, IMarkdownString } from 'vs/base/common/htmlContent'; import { marked } from 'vs/base/common/marked/marked'; import { IMouseEvent } from 'vs/base/browser/mouseEvent'; @@ -17,7 +16,8 @@ export interface RenderOptions { className?: string; inline?: boolean; actionCallback?: (content: string, event?: IMouseEvent) => void; - codeBlockRenderer?: (modeId: string, value: string) => string | TPromise; + codeBlockRenderer?: (modeId: string, value: string) => Thenable; + codeBlockRenderCallback?: () => void; } function createElement(options: RenderOptions): HTMLElement { @@ -53,7 +53,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions // signal to code-block render that the // element has been created let signalInnerHTML: Function; - const withInnerHTML = new TPromise(c => signalInnerHTML = c); + const withInnerHTML = new Promise(c => signalInnerHTML = c); const renderer = new marked.Renderer(); renderer.image = (href: string, title: string, text: string) => { @@ -118,27 +118,24 @@ export function renderMarkdown(markdown: IMarkdownString, options: RenderOptions if (options.codeBlockRenderer) { renderer.code = (code, lang) => { const value = options.codeBlockRenderer(lang, code); - if (typeof value === 'string') { - return value; + // when code-block rendering is async we return sync + // but update the node with the real result later. + const id = defaultGenerator.nextId(); + const promise = Promise.all([value, withInnerHTML]).then(values => { + const strValue = values[0]; + const span = element.querySelector(`div[data-code="${id}"]`); + if (span) { + span.innerHTML = strValue; + } + }).catch(err => { + // ignore + }); + + if (options.codeBlockRenderCallback) { + promise.then(options.codeBlockRenderCallback); } - if (TPromise.is(value)) { - // when code-block rendering is async we return sync - // but update the node with the real result later. - const id = defaultGenerator.nextId(); - TPromise.join([value, withInnerHTML]).done(values => { - const strValue = values[0] as string; - const span = element.querySelector(`div[data-code="${id}"]`); - if (span) { - span.innerHTML = strValue; - } - }, err => { - // ignore - }); - return `
${escape(code)}
`; - } - - return code; + return `
${escape(code)}
`; }; } diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 41eb283271e..2d6c9985280 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -17,6 +17,7 @@ import types = require('vs/base/common/types'); import { EventType, Gesture } from 'vs/base/browser/touch'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; import Event, { Emitter } from 'vs/base/common/event'; export interface IActionItem { @@ -755,9 +756,10 @@ export class SelectActionItem extends BaseActionItem { protected selectBox: SelectBox; protected toDispose: lifecycle.IDisposable[]; - constructor(ctx: any, action: IAction, options: string[], selected: number) { + constructor(ctx: any, action: IAction, options: string[], selected: number, contextViewProvider: IContextViewProvider + ) { super(ctx, action); - this.selectBox = new SelectBox(options, selected); + this.selectBox = new SelectBox(options, selected, contextViewProvider); this.toDispose = []; this.toDispose.push(this.selectBox); diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 3936eb41473..c0aadf8e400 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -52,10 +52,12 @@ interface IItem { export interface IListViewOptions { useShadows?: boolean; + verticalScrollMode?: ScrollbarVisibility; } const DefaultOptions: IListViewOptions = { - useShadows: true + useShadows: true, + verticalScrollMode: ScrollbarVisibility.Auto }; export class ListView implements ISpliceable, IDisposable { @@ -106,7 +108,7 @@ export class ListView implements ISpliceable, IDisposable { this.scrollableElement = new ScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, - vertical: ScrollbarVisibility.Auto, + vertical: getOrDefault(options, o => o.verticalScrollMode, DefaultOptions.verticalScrollMode), useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows) }); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index d2c737a6691..d5450e24c9d 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -19,6 +19,7 @@ import { IDelegate, IRenderer, IListEvent, IListContextMenuEvent, IListMouseEven import { ListView, IListViewOptions } from './listView'; import { Color } from 'vs/base/common/color'; import { mixin } from 'vs/base/common/objects'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ISpliceable } from 'vs/base/common/sequence'; export interface IIdentityProvider { @@ -498,6 +499,7 @@ export interface IListOptions extends IListViewOptions, IListStyles { selectOnMouseDown?: boolean; focusOnMouseDown?: boolean; keyboardSupport?: boolean; + verticalScrollMode?: ScrollbarVisibility; multipleSelectionSupport?: boolean; } diff --git a/src/vs/base/browser/ui/selectBox/selectBox.ts b/src/vs/base/browser/ui/selectBox/selectBox.ts index 5630d626d4a..c9939222f77 100644 --- a/src/vs/base/browser/ui/selectBox/selectBox.ts +++ b/src/vs/base/browser/ui/selectBox/selectBox.ts @@ -6,18 +6,39 @@ import 'vs/css!./selectBox'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import Event, { Emitter } from 'vs/base/common/event'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import Event from 'vs/base/common/event'; import { Widget } from 'vs/base/browser/ui/widget'; -import * as dom from 'vs/base/browser/dom'; -import * as arrays from 'vs/base/common/arrays'; import { Color } from 'vs/base/common/color'; -import { deepClone } from 'vs/base/common/objects'; +import { deepClone, mixin } from 'vs/base/common/objects'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { IListStyles } from 'vs/base/browser/ui/list/listWidget'; +import { SelectBoxNative } from 'vs/base/browser/ui/selectBox/selectBoxNative'; +import { SelectBoxList } from 'vs/base/browser/ui/selectBox/selectBoxCustom'; +import { isMacintosh } from 'vs/base/common/platform'; -export interface ISelectBoxStyles { +// Public SelectBox interface - Calls routed to appropriate select implementation class + +export interface ISelectBoxDelegate { + + // Public SelectBox Interface + readonly onDidSelect: Event; + setOptions(options: string[], selected?: number, disabled?: number): void; + select(index: number): void; + focus(): void; + blur(): void; + dispose(): void; + + // Delegated Widget interface + render(container: HTMLElement): void; + style(styles: ISelectBoxStyles): void; + applyStyles(): void; +} + +export interface ISelectBoxStyles extends IListStyles { selectBackground?: Color; selectForeground?: Color; selectBorder?: Color; + focusBorder?: Color; } export const defaultStyles = { @@ -31,125 +52,66 @@ export interface ISelectData { index: number; } -export class SelectBox extends Widget { - - private selectElement: HTMLSelectElement; - private options: string[]; - private selected: number; - private _onDidSelect: Emitter; +export class SelectBox extends Widget implements ISelectBoxDelegate { private toDispose: IDisposable[]; - private selectBackground: Color; - private selectForeground: Color; - private selectBorder: Color; + private styles: ISelectBoxStyles; + private selectBoxDelegate: ISelectBoxDelegate; - constructor(options: string[], selected: number, styles: ISelectBoxStyles = deepClone(defaultStyles)) { + constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles = deepClone(defaultStyles)) { super(); - this.selectElement = document.createElement('select'); - this.selectElement.className = 'select-box'; - - this.setOptions(options, selected); this.toDispose = []; - this._onDidSelect = new Emitter(); - this.selectBackground = styles.selectBackground; - this.selectForeground = styles.selectForeground; - this.selectBorder = styles.selectBorder; + mixin(this.styles, defaultStyles, false); - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { - this.selectElement.title = e.target.value; - this._onDidSelect.fire({ - index: e.target.selectedIndex, - selected: e.target.value - }); - })); - this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { - if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) { - // Space is used to expand select box, do not propagate it (prevent action bar action run) - e.stopPropagation(); - } - })); + // Instantiate select implementation based on platform + if (isMacintosh) { + this.selectBoxDelegate = new SelectBoxNative(options, selected, styles); + } else { + this.selectBoxDelegate = new SelectBoxList(options, selected, contextViewProvider, styles); + } + + this.toDispose.push(this.selectBoxDelegate); } + // Public SelectBox Methods - routed through delegate interface + public get onDidSelect(): Event { - return this._onDidSelect.event; + return this.selectBoxDelegate.onDidSelect; } public setOptions(options: string[], selected?: number, disabled?: number): void { - if (!this.options || !arrays.equals(this.options, options)) { - this.options = options; - - this.selectElement.options.length = 0; - let i = 0; - this.options.forEach((option) => { - this.selectElement.add(this.createOption(option, disabled === i++)); - }); - } - this.select(selected); + this.selectBoxDelegate.setOptions(options, selected, disabled); } public select(index: number): void { - if (index >= 0 && index < this.options.length) { - this.selected = index; - } else if (this.selected < 0) { - this.selected = 0; - } - - this.selectElement.selectedIndex = this.selected; - this.selectElement.title = this.options[this.selected]; + this.selectBoxDelegate.select(index); } public focus(): void { - if (this.selectElement) { - this.selectElement.focus(); - } + this.selectBoxDelegate.focus(); } public blur(): void { - if (this.selectElement) { - this.selectElement.blur(); - } + this.selectBoxDelegate.blur(); } - public render(container: HTMLElement): void { - dom.addClass(container, 'select-container'); - container.appendChild(this.selectElement); - this.setOptions(this.options, this.selected); + // Public Widget Methods - routed through delegate interface - this.applyStyles(); + public render(container: HTMLElement): void { + this.selectBoxDelegate.render(container); } public style(styles: ISelectBoxStyles): void { - this.selectBackground = styles.selectBackground; - this.selectForeground = styles.selectForeground; - this.selectBorder = styles.selectBorder; - - this.applyStyles(); + this.selectBoxDelegate.style(styles); } - protected applyStyles(): void { - if (this.selectElement) { - const background = this.selectBackground ? this.selectBackground.toString() : null; - const foreground = this.selectForeground ? this.selectForeground.toString() : null; - const border = this.selectBorder ? this.selectBorder.toString() : null; - - this.selectElement.style.backgroundColor = background; - this.selectElement.style.color = foreground; - this.selectElement.style.borderColor = border; - } - } - - private createOption(value: string, disabled?: boolean): HTMLOptionElement { - let option = document.createElement('option'); - option.value = value; - option.text = value; - option.disabled = disabled; - - return option; + public applyStyles(): void { + this.selectBoxDelegate.applyStyles(); } public dispose(): void { this.toDispose = dispose(this.toDispose); super.dispose(); } -} +} \ No newline at end of file diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.css b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css new file mode 100644 index 00000000000..e50a700fa55 --- /dev/null +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.css @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +/* Require .monaco-shell for ContextView dropdown */ + +.monaco-shell .select-box-dropdown-container { + display: none; +} + +.monaco-shell .select-box-dropdown-container.visible { + display: flex; + flex-direction: column; + text-align: left; + width: 1px; + overflow: hidden; +} + +.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container { + flex: 0 0 auto; + align-self: flex-start; + padding-bottom: 1px; + padding-top: 1px; + padding-left: 1px; + padding-right: 1px; + width: 100%; + overflow: hidden; + -webkit-box-sizing: border-box; + -o-box-sizing: border-box; + -moz-box-sizing: border-box; + -ms-box-sizing: border-box; + box-sizing: border-box; +} + +.monaco-shell.hc-black .select-box-dropdown-container > .select-box-dropdown-list-container { + padding-bottom: 4px; + padding-top: 3px; +} + +.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row > .option-text { + text-overflow: ellipsis; + overflow: hidden; + padding-left: 3.5px; + white-space: nowrap; +} + +.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control { + flex: 1 1 auto; + align-self: flex-start; + opacity: 0; +} + +.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div { + overflow: hidden; + max-height: 0px; +} + +.monaco-shell .select-box-dropdown-container > .select-box-dropdown-container-width-control > .width-control-div > .option-text-width-control { + padding-left: 4px; + padding-right: 8px; + white-space: nowrap; +} diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts new file mode 100644 index 00000000000..b087da27605 --- /dev/null +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -0,0 +1,681 @@ +/*--------------------------------------------------------------------------------------------- + * 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!./selectBoxCustom'; + +import * as nls from 'vs/nls'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import Event, { Emitter, chain } from 'vs/base/common/event'; +import { KeyCode, KeyCodeUtils } from 'vs/base/common/keyCodes'; +import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; +import * as dom from 'vs/base/browser/dom'; +import * as arrays from 'vs/base/common/arrays'; +import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview'; +import { List } from 'vs/base/browser/ui/list/listWidget'; +import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; +import { domEvent } from 'vs/base/browser/event'; +import { ScrollbarVisibility } from 'vs/base/common/scrollable'; +import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; +import { isMacintosh } from 'vs/base/common/platform'; + +const $ = dom.$; + +const SELECT_OPTION_ENTRY_TEMPLATE_ID = 'selectOption.entry.template'; + +export interface ISelectOptionItem { + optionText: string; + optionDisabled: boolean; +} + +interface ISelectListTemplateData { + root: HTMLElement; + optionText: HTMLElement; + disposables: IDisposable[]; +} + +class SelectListRenderer implements IRenderer { + + get templateId(): string { return SELECT_OPTION_ENTRY_TEMPLATE_ID; } + + constructor() { } + + renderTemplate(container: HTMLElement): any { + const data = Object.create(null); + data.disposables = []; + data.root = container; + data.optionText = dom.append(container, $('.option-text')); + + return data; + } + + renderElement(element: ISelectOptionItem, index: number, templateData: ISelectListTemplateData): void { + const data = templateData; + const optionText = (element).optionText; + const optionDisabled = (element).optionDisabled; + + data.optionText.textContent = optionText; + data.root.setAttribute('aria-label', nls.localize('selectAriaOption', "{0}", optionText)); + + // pseudo-select disabled option + if (optionDisabled) { + dom.addClass((data.root), 'option-disabled'); + } + } + + disposeTemplate(templateData: ISelectListTemplateData): void { + templateData.disposables = dispose(templateData.disposables); + } +} + +export class SelectBoxList implements ISelectBoxDelegate, IDelegate { + + private static SELECT_DROPDOWN_BOTTOM_MARGIN = 10; + + private _isVisible: boolean; + private selectElement: HTMLSelectElement; + private options: string[]; + private selected: number; + private disabledOptionIndex: number; + private _onDidSelect: Emitter; + private toDispose: IDisposable[]; + private styles: ISelectBoxStyles; + private listRenderer: SelectListRenderer; + private contextViewProvider: IContextViewProvider; + private selectDropDownContainer: HTMLElement; + private styleElement: HTMLStyleElement; + private selectList: List; + private selectDropDownListContainer: HTMLElement; + private widthControlElement: HTMLElement; + + constructor(options: string[], selected: number, contextViewProvider: IContextViewProvider, styles: ISelectBoxStyles) { + + this.toDispose = []; + this._isVisible = false; + + this.selectElement = document.createElement('select'); + this.selectElement.className = 'select-box'; + + this._onDidSelect = new Emitter(); + this.styles = styles; + + this.registerListeners(); + this.constructSelectDropDown(contextViewProvider); + + this.setOptions(options, selected); + } + + // IDelegate - List renderer + + getHeight(): number { + return 18; + } + + getTemplateId(): string { + return SELECT_OPTION_ENTRY_TEMPLATE_ID; + } + + private constructSelectDropDown(contextViewProvider: IContextViewProvider) { + + // SetUp ContextView container to hold select Dropdown + this.contextViewProvider = contextViewProvider; + this.selectDropDownContainer = dom.$('.select-box-dropdown-container'); + + // Setup list for drop-down select + this.createSelectList(this.selectDropDownContainer); + + // Create span flex box item/div we can measure and control + let widthControlOuterDiv = dom.append(this.selectDropDownContainer, $('.select-box-dropdown-container-width-control')); + let widthControlInnerDiv = dom.append(widthControlOuterDiv, $('.width-control-div')); + this.widthControlElement = document.createElement('span'); + this.widthControlElement.className = 'option-text-width-control'; + dom.append(widthControlInnerDiv, this.widthControlElement); + + // Inline stylesheet for themes + this.styleElement = dom.createStyleSheet(this.selectDropDownContainer); + } + + private registerListeners() { + + // Parent native select keyboard listeners + + this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { + this.selectElement.title = e.target.value; + this._onDidSelect.fire({ + index: e.target.selectedIndex, + selected: e.target.value + }); + })); + + // Have to implement both keyboard and mouse controllers to handle disabled options + // Intercept mouse events to override normal select actions on parents + + this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.CLICK, (e) => { + dom.EventHelper.stop(e); + + if (this._isVisible) { + this.hideSelectDropDown(true); + } else { + this.showSelectDropDown(); + } + })); + + this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.MOUSE_DOWN, (e) => { + dom.EventHelper.stop(e); + })); + + // Intercept keyboard handling + + this.toDispose.push(dom.addDisposableListener(this.selectElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + const event = new StandardKeyboardEvent(e); + let showDropDown = false; + + // Create and drop down select list on keyboard select + if (isMacintosh) { + if (event.keyCode === KeyCode.DownArrow || event.keyCode === KeyCode.UpArrow || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) { + showDropDown = true; + } + } else { + if (event.keyCode === KeyCode.DownArrow && event.altKey || event.keyCode === KeyCode.UpArrow && event.altKey || event.keyCode === KeyCode.Space || event.keyCode === KeyCode.Enter) { + showDropDown = true; + } + } + + if (showDropDown) { + this.showSelectDropDown(); + dom.EventHelper.stop(e); + } + })); + } + + public get onDidSelect(): Event { + return this._onDidSelect.event; + } + + public setOptions(options: string[], selected?: number, disabled?: number): void { + + if (!this.options || !arrays.equals(this.options, options)) { + this.options = options; + this.selectElement.options.length = 0; + + let i = 0; + this.options.forEach((option) => { + this.selectElement.add(this.createOption(option, i, disabled === i++)); + }); + + // Mirror options in drop-down + // Populate select list for non-native select mode + if (this.selectList && !!this.options) { + let listEntries: ISelectOptionItem[]; + + listEntries = []; + if (disabled !== undefined) { + this.disabledOptionIndex = disabled; + } + for (let index = 0; index < this.options.length; index++) { + const element = this.options[index]; + let optionDisabled: boolean; + index === this.disabledOptionIndex ? optionDisabled = true : optionDisabled = false; + listEntries.push({ optionText: element, optionDisabled: optionDisabled }); + } + + this.selectList.splice(0, this.selectList.length, listEntries); + } + } + + if (selected !== undefined) { + this.select(selected); + } + } + + public select(index: number): void { + + if (index >= 0 && index < this.options.length) { + this.selected = index; + } else if (this.selected < 0) { + this.selected = 0; + } + + this.selectElement.selectedIndex = this.selected; + this.selectElement.title = this.options[this.selected]; + } + + public focus(): void { + if (this.selectElement) { + this.selectElement.focus(); + } + } + + public blur(): void { + if (this.selectElement) { + this.selectElement.blur(); + } + } + + public render(container: HTMLElement): void { + dom.addClass(container, 'select-container'); + container.appendChild(this.selectElement); + this.setOptions(this.options, this.selected); + this.applyStyles(); + } + + public style(styles: ISelectBoxStyles): void { + + const content: string[] = []; + + this.styles = styles; + + // Style non-native select mode + + if (this.styles.listFocusBackground) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { background-color: ${this.styles.listFocusBackground} !important; }`); + } + + if (this.styles.listFocusForeground) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused:not(:hover) { color: ${this.styles.listFocusForeground} !important; }`); + } + + // Hover foreground - ignore for disabled options + if (this.styles.listHoverForeground) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover { color: ${this.styles.listHoverForeground} !important; }`); + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.listActiveSelectionForeground} !important; }`); + } + + // Hover background - ignore for disabled options + if (this.styles.listHoverBackground) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:not(.option-disabled):not(.focused):hover { background-color: ${this.styles.listHoverBackground} !important; }`); + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { background-color: ${this.styles.selectBackground} !important; }`); + } + + // Match quickOpen outline styles - ignore for disabled options + if (this.styles.listFocusOutline) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.focused { outline: 1.6px dotted ${this.styles.listFocusOutline} !important; outline-offset: -1.6px !important; }`); + } + + if (this.styles.listHoverOutline) { + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row:hover:not(.focused) { outline: 1.6px dashed ${this.styles.listHoverOutline} !important; outline-offset: -1.6px !important; }`); + content.push(`.monaco-shell .select-box-dropdown-container > .select-box-dropdown-list-container .monaco-list .monaco-list-row.option-disabled:hover { outline: none !important; }`); + } + + this.styleElement.innerHTML = content.join('\n'); + + this.applyStyles(); + } + + public applyStyles(): void { + + // Style parent select + if (this.selectElement) { + const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null; + const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null; + const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null; + + this.selectElement.style.backgroundColor = background; + this.selectElement.style.color = foreground; + this.selectElement.style.borderColor = border; + } + + // Style drop down select list (non-native mode only) + + if (this.selectList) { + this.selectList.style({}); + + const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null; + this.selectDropDownListContainer.style.backgroundColor = background; + const optionsBorder = this.styles.focusBorder ? this.styles.focusBorder.toString() : null; + this.selectDropDownContainer.style.outlineColor = optionsBorder; + this.selectDropDownContainer.style.outlineOffset = '-1px'; + } + } + + private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { + let option = document.createElement('option'); + option.value = value; + option.text = value; + option.disabled = disabled; + + return option; + } + + // Non-native select list handling + // ContextView dropdown methods + + private showSelectDropDown() { + if (!this.contextViewProvider || this._isVisible) { + return; + } + + this._isVisible = true; + this.cloneElementFont(this.selectElement, this.selectDropDownContainer); + this.contextViewProvider.showContextView({ + getAnchor: () => this.selectElement, + render: (container: HTMLElement) => { return this.renderSelectDropDown(container); }, + layout: () => this.layoutSelectDropDown(), + onHide: () => { + dom.toggleClass(this.selectDropDownContainer, 'visible', false); + dom.toggleClass(this.selectElement, 'synthetic-focus', false); + } + }); + } + + private hideSelectDropDown(focusSelect: boolean) { + if (!this.contextViewProvider || !this._isVisible) { + return; + } + + this._isVisible = false; + + if (focusSelect) { + this.selectElement.focus(); + } + this.contextViewProvider.hideContextView(); + } + + private renderSelectDropDown(container: HTMLElement) { + dom.append(container, this.selectDropDownContainer); + this.layoutSelectDropDown(); + return null; + } + + private layoutSelectDropDown() { + + // Layout ContextView drop down select list and container + // Have to manage our vertical overflow, sizing + // Need to be visible to measure + + dom.toggleClass(this.selectDropDownContainer, 'visible', true); + + const selectWidth = dom.getTotalWidth(this.selectElement); + const selectPosition = dom.getDomNodePagePosition(this.selectElement); + + // Set container height to max from select bottom to margin above status bar + const statusBarHeight = dom.getTotalHeight(document.getElementById('workbench.parts.statusbar')); + const maxSelectDropDownHeight = (window.innerHeight - selectPosition.top - selectPosition.height - statusBarHeight - SelectBoxList.SELECT_DROPDOWN_BOTTOM_MARGIN); + + // SetUp list dimensions and layout - account for container padding + if (this.selectList) { + this.selectList.layout(); + let listHeight = this.selectList.contentHeight; + const listContainerHeight = dom.getTotalHeight(this.selectDropDownListContainer); + const totalVerticalListPadding = listContainerHeight - listHeight; + + // Always show complete list items - never more than Max available vertical height + if (listContainerHeight > maxSelectDropDownHeight) { + listHeight = ((Math.floor((maxSelectDropDownHeight - totalVerticalListPadding) / this.getHeight())) * this.getHeight()); + } + + this.selectList.layout(listHeight); + this.selectList.domFocus(); + + // Finally set focus on selected item + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selectList.getFocus()[0]); + + // Set final container height after adjustments + this.selectDropDownContainer.style.height = (listHeight + totalVerticalListPadding) + 'px'; + + // Determine optimal width - min(longest option), opt(parent select), max(ContextView controlled) + const selectMinWidth = this.setWidthControlElement(this.widthControlElement); + const selectOptimalWidth = Math.max(selectMinWidth, Math.round(selectWidth)).toString() + 'px'; + + this.selectDropDownContainer.style.minWidth = selectOptimalWidth; + + // Maintain focus outline on parent select as well as list container - tabindex for focus + this.selectDropDownListContainer.setAttribute('tabindex', '0'); + dom.toggleClass(this.selectElement, 'synthetic-focus', true); + dom.toggleClass(this.selectDropDownContainer, 'synthetic-focus', true); + } + } + + private setWidthControlElement(container: HTMLElement): number { + let elementWidth = 0; + + if (container && !!this.options) { + let longest = 0; + + for (let index = 0; index < this.options.length; index++) { + if (this.options[index].length > this.options[longest].length) { + longest = index; + } + } + + container.innerHTML = this.options[longest]; + elementWidth = dom.getTotalWidth(container); + } + + return elementWidth; + } + + private cloneElementFont(source: HTMLElement, target: HTMLElement) { + const fontSize = window.getComputedStyle(source, null).getPropertyValue('font-size'); + const fontFamily = window.getComputedStyle(source, null).getPropertyValue('font-family'); + target.style.fontFamily = fontFamily; + target.style.fontSize = fontSize; + } + + private createSelectList(parent: HTMLElement): void { + + // SetUp container for list + this.selectDropDownListContainer = dom.append(parent, $('.select-box-dropdown-list-container')); + + this.listRenderer = new SelectListRenderer(); + + this.selectList = new List(this.selectDropDownListContainer, this, [this.listRenderer], { + useShadows: false, + selectOnMouseDown: false, + verticalScrollMode: ScrollbarVisibility.Visible, + keyboardSupport: false, + mouseSupport: false + }); + + // SetUp list keyboard controller - control navigation, disabled items, focus + const onSelectDropDownKeyDown = chain(domEvent(this.selectDropDownListContainer, 'keydown')) + .filter(() => this.selectList.length > 0) + .map(e => new StandardKeyboardEvent(e)); + + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Enter).on(e => this.onEnter(e), this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Escape).on(e => this.onEscape(e), this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.UpArrow).on(this.onUpArrow, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.DownArrow).on(this.onDownArrow, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageDown).on(this.onPageDown, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.PageUp).on(this.onPageUp, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.Home).on(this.onHome, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => e.keyCode === KeyCode.End).on(this.onEnd, this, this.toDispose); + onSelectDropDownKeyDown.filter(e => (e.keyCode >= KeyCode.KEY_0 && e.keyCode <= KeyCode.KEY_Z) || (e.keyCode >= KeyCode.US_SEMICOLON && e.keyCode <= KeyCode.NUMPAD_DIVIDE)).on(this.onCharacter, this, this.toDispose); + + // SetUp list mouse controller - control navigation, disabled items, focus + + chain(domEvent(this.selectList.getHTMLElement(), 'mouseup')) + .filter(() => this.selectList.length > 0) + .on(e => this.onMouseUp(e), this, this.toDispose); + + this.toDispose.push(this.selectList.onDidBlur(e => this.onListBlur())); + } + + // List methods + + // List mouse controller - active exit, select option, fire onDidSelect, return focus to parent select + private onMouseUp(e: MouseEvent): void { + + // Check our mouse event is on an option (not scrollbar) + if (!e.toElement.classList.contains('option-text')) { + return; + } + + const listRowElement = e.toElement.parentElement; + const index = Number(listRowElement.getAttribute('data-index')); + const disabled = listRowElement.classList.contains('option-disabled'); + + // Ignore mouse selection of disabled options + if (index >= 0 && index < this.options.length && !disabled) { + this.selected = index; + this.select(this.selected); + + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selectList.getFocus()[0]); + this._onDidSelect.fire({ + index: this.selectElement.selectedIndex, + selected: this.selectElement.title + }); + this.hideSelectDropDown(true); + } + dom.EventHelper.stop(e); + } + + // List Exit - passive - hide drop-down, fire onDidSelect + private onListBlur(): void { + + this._onDidSelect.fire({ + index: this.selectElement.selectedIndex, + selected: this.selectElement.title + }); + + this.hideSelectDropDown(false); + } + + // List keyboard controller + // List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect + private onEscape(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + this.hideSelectDropDown(true); + + this._onDidSelect.fire({ + index: this.selectElement.selectedIndex, + selected: this.selectElement.title + }); + } + + // List exit - active - hide ContextView dropdown, return focus to parent select, fire onDidSelect + private onEnter(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + this.hideSelectDropDown(true); + this._onDidSelect.fire({ + index: this.selectElement.selectedIndex, + selected: this.selectElement.title + }); + } + + // List navigation - have to handle a disabled option (jump over) + private onDownArrow(): void { + if (this.selected < this.options.length - 1) { + // Skip disabled options + if ((this.selected + 1) === this.disabledOptionIndex && this.options.length > this.selected + 2) { + this.selected += 2; + } else { + this.selected++; + } + // Set focus/selection - only fire event when closing drop-down or on blur + this.select(this.selected); + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selectList.getFocus()[0]); + } + } + + private onUpArrow(): void { + if (this.selected > 0) { + // Skip disabled options + if ((this.selected - 1) === this.disabledOptionIndex && this.selected > 1) { + this.selected -= 2; + } else { + this.selected--; + } + // Set focus/selection - only fire event when closing drop-down or on blur + this.select(this.selected); + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selectList.getFocus()[0]); + } + } + + private onPageUp(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + this.selectList.focusPreviousPage(); + + // Allow scrolling to settle + setTimeout(() => { + this.selected = this.selectList.getFocus()[0]; + + // Shift selection down if we land on a disabled option + if (this.selected === this.disabledOptionIndex && this.selected < this.options.length - 1) { + this.selected++; + this.selectList.setFocus([this.selected]); + } + this.selectList.reveal(this.selected); + this.select(this.selected); + }, 1); + } + + private onPageDown(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + this.selectList.focusNextPage(); + + // Allow scrolling to settle + setTimeout(() => { + this.selected = this.selectList.getFocus()[0]; + + // Shift selection up if we land on a disabled option + if (this.selected === this.disabledOptionIndex && this.selected > 0) { + this.selected--; + this.selectList.setFocus([this.selected]); + } + this.selectList.reveal(this.selected); + this.select(this.selected); + }, 1); + } + + private onHome(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + if (this.options.length < 2) { + return; + } + this.selected = 0; + if (this.selected === this.disabledOptionIndex && this.selected > 1) { + this.selected++; + } + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selected); + this.select(this.selected); + } + + private onEnd(e: StandardKeyboardEvent): void { + dom.EventHelper.stop(e); + + if (this.options.length < 2) { + return; + } + this.selected = this.options.length - 1; + if (this.selected === this.disabledOptionIndex && this.selected > 1) { + this.selected--; + } + this.selectList.setFocus([this.selected]); + this.selectList.reveal(this.selected); + this.select(this.selected); + } + + // Mimic option first character navigation of native select + private onCharacter(e: StandardKeyboardEvent): void { + const ch = KeyCodeUtils.toString(e.keyCode); + let optionIndex = -1; + + for (let i = 0; i < this.options.length - 1; i++) { + optionIndex = (i + this.selected + 1) % this.options.length; + if (this.options[optionIndex].charAt(0).toUpperCase() === ch) { + this.select(optionIndex); + this.selectList.setFocus([optionIndex]); + this.selectList.reveal(this.selectList.getFocus()[0]); + dom.EventHelper.stop(e); + break; + } + } + } + + public dispose(): void { + this.hideSelectDropDown(false); + this.toDispose = dispose(this.toDispose); + } +} diff --git a/src/vs/base/browser/ui/selectBox/selectBoxNative.ts b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts new file mode 100644 index 00000000000..0360fc0662b --- /dev/null +++ b/src/vs/base/browser/ui/selectBox/selectBoxNative.ts @@ -0,0 +1,153 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import Event, { Emitter } from 'vs/base/common/event'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import * as dom from 'vs/base/browser/dom'; +import * as arrays from 'vs/base/common/arrays'; +import { ISelectBoxDelegate, ISelectBoxStyles, ISelectData } from 'vs/base/browser/ui/selectBox/selectBox'; +import { isMacintosh } from 'vs/base/common/platform'; + +export class SelectBoxNative implements ISelectBoxDelegate { + + private selectElement: HTMLSelectElement; + private options: string[]; + private selected: number; + private _onDidSelect: Emitter; + private toDispose: IDisposable[]; + private styles: ISelectBoxStyles; + + constructor(options: string[], selected: number, styles: ISelectBoxStyles) { + + this.toDispose = []; + + this.selectElement = document.createElement('select'); + this.selectElement.className = 'select-box'; + + this._onDidSelect = new Emitter(); + + this.styles = styles; + + this.registerListeners(); + + this.setOptions(options, selected); + } + + private registerListeners() { + + this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'change', (e) => { + this.selectElement.title = e.target.value; + this._onDidSelect.fire({ + index: e.target.selectedIndex, + selected: e.target.value + }); + })); + + this.toDispose.push(dom.addStandardDisposableListener(this.selectElement, 'keydown', (e) => { + let showSelect = false; + + if (isMacintosh) { + if (e.keyCode === KeyCode.DownArrow || e.keyCode === KeyCode.UpArrow || e.keyCode === KeyCode.Space) { + showSelect = true; + } + } else { + if (e.keyCode === KeyCode.DownArrow && e.altKey || e.keyCode === KeyCode.Space || e.keyCode === KeyCode.Enter) { + showSelect = true; + } + } + + if (showSelect) { + // Space, Enter, is used to expand select box, do not propagate it (prevent action bar action run) + e.stopPropagation(); + } + })); + } + + public get onDidSelect(): Event { + return this._onDidSelect.event; + } + + public setOptions(options: string[], selected?: number, disabled?: number): void { + + if (!this.options || !arrays.equals(this.options, options)) { + this.options = options; + this.selectElement.options.length = 0; + + let i = 0; + this.options.forEach((option) => { + this.selectElement.add(this.createOption(option, i, disabled === i++)); + }); + + } + + if (selected !== undefined) { + this.select(selected); + } + } + + public select(index: number): void { + if (index >= 0 && index < this.options.length) { + this.selected = index; + } else if (this.selected < 0) { + this.selected = 0; + } + + this.selectElement.selectedIndex = this.selected; + this.selectElement.title = this.options[this.selected]; + } + + public focus(): void { + if (this.selectElement) { + this.selectElement.focus(); + } + } + + public blur(): void { + if (this.selectElement) { + this.selectElement.blur(); + } + } + + public render(container: HTMLElement): void { + dom.addClass(container, 'select-container'); + container.appendChild(this.selectElement); + this.setOptions(this.options, this.selected); + this.applyStyles(); + } + + public style(styles: ISelectBoxStyles): void { + this.styles = styles; + this.applyStyles(); + } + + public applyStyles(): void { + + // Style native select + if (this.selectElement) { + const background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null; + const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null; + const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null; + + this.selectElement.style.backgroundColor = background; + this.selectElement.style.color = foreground; + this.selectElement.style.borderColor = border; + } + + } + + private createOption(value: string, index: number, disabled?: boolean): HTMLOptionElement { + let option = document.createElement('option'); + option.value = value; + option.text = value; + option.disabled = disabled; + + return option; + } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } +} diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index 525365b8638..9c6657154b8 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -95,6 +95,7 @@ export class SplitView implements IDisposable { get length(): number { return this.viewItems.length; } + constructor(container: HTMLElement, options: ISplitViewOptions = {}) { this.orientation = types.isUndefined(options.orientation) ? Orientation.VERTICAL : options.orientation; diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 9eff176c713..90e4c4ffdd6 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -349,13 +349,14 @@ export function always(promise: TPromise, f: Function): TPromise { * Runs the provided list of promise factories in sequential order. The returned * promise will complete to an array of results from each promise. */ -export function sequence(promiseFactories: ITask>[]): TPromise { + +export function sequence(promiseFactories: ITask>[]): TPromise { const results: T[] = []; // reverse since we start with last element using pop() promiseFactories = promiseFactories.reverse(); - function next(): Promise { + function next(): Thenable { if (promiseFactories.length) { return promiseFactories.pop()(); } @@ -363,7 +364,7 @@ export function sequence(promiseFactories: ITask>[]): TPromise { if (result !== undefined && result !== null) { results.push(result); } @@ -692,4 +693,4 @@ export class ThrottledEmitter extends Emitter { this.hasLastEvent = false; this.lastEvent = void 0; } -} \ No newline at end of file +} diff --git a/src/vs/base/common/diff/diff2.ts b/src/vs/base/common/diff/diff2.ts deleted file mode 100644 index 3bb52c68b94..00000000000 --- a/src/vs/base/common/diff/diff2.ts +++ /dev/null @@ -1,325 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { DiffChange } from 'vs/base/common/diff/diffChange'; - -export interface ISequence { - getLength(): number; - getElementHash(index: number): string; - [index: number]: string; -} - -export interface IDiffChange { - /** - * The position of the first element in the original sequence which - * this change affects. - */ - originalStart: number; - - /** - * The number of elements from the original sequence which were - * affected. - */ - originalLength: number; - - /** - * The position of the first element in the modified sequence which - * this change affects. - */ - modifiedStart: number; - - /** - * The number of elements from the modified sequence which were - * affected (added). - */ - modifiedLength: number; -} - -export interface IContinueProcessingPredicate { - (furthestOriginalIndex: number, originalSequence: ISequence, matchLengthOfLongest: number): boolean; -} - -export interface IHashFunction { - (sequence: ISequence, index: number): string; -} - -/** - * An implementation of the difference algorithm described by Hirschberg - */ -export class LcsDiff2 { - - private x: ISequence; - private y: ISequence; - - private ids_for_x: number[]; - private ids_for_y: number[]; - - private resultX: boolean[]; - private resultY: boolean[]; - private forwardPrev: number[]; - private forwardCurr: number[]; - private backwardPrev: number[]; - private backwardCurr: number[]; - - constructor(originalSequence: ISequence, newSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate, hashFunc: IHashFunction) { - this.x = originalSequence; - this.y = newSequence; - this.ids_for_x = []; - this.ids_for_y = []; - - this.resultX = []; - this.resultY = []; - this.forwardPrev = []; - this.forwardCurr = []; - this.backwardPrev = []; - this.backwardCurr = []; - - for (let i = 0, length = this.x.getLength(); i < length; i++) { - this.resultX[i] = false; - } - - for (let i = 0, length = this.y.getLength(); i <= length; i++) { - this.resultY[i] = false; - } - - this.ComputeUniqueIdentifiers(); - } - - private ComputeUniqueIdentifiers() { - let xLength = this.x.getLength(); - let yLength = this.y.getLength(); - this.ids_for_x = new Array(xLength); - this.ids_for_y = new Array(yLength); - - // Create a new hash table for unique elements from the original - // sequence. - let hashTable: { [key: string]: number; } = {}; - let currentUniqueId = 1; - let i: number; - - // Fill up the hash table for unique elements - for (i = 0; i < xLength; i++) { - let xElementHash = this.x.getElementHash(i); - if (!hashTable.hasOwnProperty(xElementHash)) { - // No entry in the hashtable so this is a new unique element. - // Assign the element a new unique identifier and add it to the - // hash table - this.ids_for_x[i] = currentUniqueId++; - hashTable[xElementHash] = this.ids_for_x[i]; - } else { - this.ids_for_x[i] = hashTable[xElementHash]; - } - } - - // Now match up modified elements - for (i = 0; i < yLength; i++) { - let yElementHash = this.y.getElementHash(i); - if (!hashTable.hasOwnProperty(yElementHash)) { - this.ids_for_y[i] = currentUniqueId++; - hashTable[yElementHash] = this.ids_for_y[i]; - } else { - this.ids_for_y[i] = hashTable[yElementHash]; - } - } - } - - private ElementsAreEqual(xIndex: number, yIndex: number): boolean { - return this.ids_for_x[xIndex] === this.ids_for_y[yIndex]; - } - - public ComputeDiff(): IDiffChange[] { - let xLength = this.x.getLength(); - let yLength = this.y.getLength(); - - this.execute(0, xLength - 1, 0, yLength - 1); - - // Construct the changes - let i = 0; - let j = 0; - let xChangeStart: number, yChangeStart: number; - let changes: DiffChange[] = []; - while (i < xLength && j < yLength) { - if (this.resultX[i] && this.resultY[j]) { - // No change - i++; - j++; - } else { - xChangeStart = i; - yChangeStart = j; - while (i < xLength && !this.resultX[i]) { - i++; - } - while (j < yLength && !this.resultY[j]) { - j++; - } - changes.push(new DiffChange(xChangeStart, i - xChangeStart, yChangeStart, j - yChangeStart)); - } - } - if (i < xLength) { - changes.push(new DiffChange(i, xLength - i, yLength, 0)); - } - if (j < yLength) { - changes.push(new DiffChange(xLength, 0, j, yLength - j)); - } - return changes; - } - - private forward(xStart: number, xStop: number, yStart: number, yStop: number): number[] { - let prev = this.forwardPrev, - curr = this.forwardCurr, - tmp: number[], - i: number, - j: number; - - // First line - prev[yStart] = this.ElementsAreEqual(xStart, yStart) ? 1 : 0; - for (j = yStart + 1; j <= yStop; j++) { - prev[j] = this.ElementsAreEqual(xStart, j) ? 1 : prev[j - 1]; - } - - for (i = xStart + 1; i <= xStop; i++) { - // First column - curr[yStart] = this.ElementsAreEqual(i, yStart) ? 1 : prev[yStart]; - - for (j = yStart + 1; j <= yStop; j++) { - if (this.ElementsAreEqual(i, j)) { - curr[j] = prev[j - 1] + 1; - } else { - curr[j] = prev[j] > curr[j - 1] ? prev[j] : curr[j - 1]; - } - } - - // Swap prev & curr - tmp = curr; - curr = prev; - prev = tmp; - } - - // Result is always in prev - return prev; - } - - private backward(xStart: number, xStop: number, yStart: number, yStop: number): number[] { - let prev = this.backwardPrev, - curr = this.backwardCurr, - tmp: number[], - i: number, - j: number; - - // Last line - prev[yStop] = this.ElementsAreEqual(xStop, yStop) ? 1 : 0; - for (j = yStop - 1; j >= yStart; j--) { - prev[j] = this.ElementsAreEqual(xStop, j) ? 1 : prev[j + 1]; - } - - for (i = xStop - 1; i >= xStart; i--) { - // Last column - curr[yStop] = this.ElementsAreEqual(i, yStop) ? 1 : prev[yStop]; - - for (j = yStop - 1; j >= yStart; j--) { - if (this.ElementsAreEqual(i, j)) { - curr[j] = prev[j + 1] + 1; - } else { - curr[j] = prev[j] > curr[j + 1] ? prev[j] : curr[j + 1]; - } - } - - // Swap prev & curr - tmp = curr; - curr = prev; - prev = tmp; - } - - // Result is always in prev - return prev; - } - - private findCut(xStart: number, xStop: number, yStart: number, yStop: number, middle: number): number { - let L1 = this.forward(xStart, middle, yStart, yStop); - let L2 = this.backward(middle + 1, xStop, yStart, yStop); - - // First cut - let max = L2[yStart], cut = yStart - 1; - - // Middle cut - for (let j = yStart; j < yStop; j++) { - if (L1[j] + L2[j + 1] > max) { - max = L1[j] + L2[j + 1]; - cut = j; - } - } - - // Last cut - if (L1[yStop] > max) { - max = L1[yStop]; - cut = yStop; - } - - return cut; - } - - private execute(xStart: number, xStop: number, yStart: number, yStop: number) { - // Do some prefix trimming - while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStart, yStart)) { - this.resultX[xStart] = true; - xStart++; - this.resultY[yStart] = true; - yStart++; - } - - // Do some suffix trimming - while (xStart <= xStop && yStart <= yStop && this.ElementsAreEqual(xStop, yStop)) { - this.resultX[xStop] = true; - xStop--; - this.resultY[yStop] = true; - yStop--; - } - - if (xStart > xStop || yStart > yStop) { - return; - } - - let found: number, i: number; - if (xStart === xStop) { - found = -1; - for (i = yStart; i <= yStop; i++) { - if (this.ElementsAreEqual(xStart, i)) { - found = i; - break; - } - } - if (found >= 0) { - this.resultX[xStart] = true; - this.resultY[found] = true; - } - } else if (yStart === yStop) { - found = -1; - for (i = xStart; i <= xStop; i++) { - if (this.ElementsAreEqual(i, yStart)) { - found = i; - break; - } - } - - if (found >= 0) { - this.resultX[found] = true; - this.resultY[yStart] = true; - } - } else { - let middle = Math.floor((xStart + xStop) / 2); - let cut = this.findCut(xStart, xStop, yStart, yStop, middle); - - if (yStart <= cut) { - this.execute(xStart, middle, yStart, cut); - } - - if (cut + 1 <= yStop) { - this.execute(middle + 1, xStop, cut + 1, yStop); - } - } - } - -} diff --git a/src/vs/base/common/marshalling.ts b/src/vs/base/common/marshalling.ts index ab5adf12626..2c7acfe7d82 100644 --- a/src/vs/base/common/marshalling.ts +++ b/src/vs/base/common/marshalling.ts @@ -32,7 +32,7 @@ function replacer(key: string, value: any): any { return value; } -function revive(obj: any, depth: number): any { +export function revive(obj: any, depth: number): any { if (!obj || depth > 200) { return obj; @@ -55,4 +55,3 @@ function revive(obj: any, depth: number): any { return obj; } - diff --git a/src/vs/base/common/performance.d.ts b/src/vs/base/common/performance.d.ts index 804f290b7f2..e8fdbf4ef12 100644 --- a/src/vs/base/common/performance.d.ts +++ b/src/vs/base/common/performance.d.ts @@ -11,7 +11,8 @@ export interface PerformanceEntry { } export function mark(name: string): void; -export function measure(name: string, from?: string, to?: string): void; + +export function measure(name: string, from?: string, to?: string): PerformanceEntry; /** * Time something, shorthant for `mark` and `measure` @@ -23,6 +24,9 @@ export function time(name: string): { stop(): void }; */ export function getEntries(type: 'mark' | 'measure'): PerformanceEntry[]; +export function getEntry(type: 'mark' | 'measure', name: string): PerformanceEntry; + +export function getDuration(from: string, to: string): number; type ExportData = any[]; export function importEntries(data: ExportData): void; diff --git a/src/vs/base/common/performance.js b/src/vs/base/common/performance.js index c4cd3c7a544..a7ebc4c627c 100644 --- a/src/vs/base/common/performance.js +++ b/src/vs/base/common/performance.js @@ -31,14 +31,14 @@ define([], function () { } function exportEntries() { - return global._performanceEntries.splice(0); + return global._performanceEntries.slice(0); } - function getEntries(type) { + function getEntries(type, name) { const result = []; const entries = global._performanceEntries; for (let i = 0; i < entries.length; i += 4) { - if (entries[i] === type) { + if (entries[i] === type && (name === void 0 || entries[i + 1] === name)) { result.push({ type: entries[i], name: entries[i + 1], @@ -53,6 +53,39 @@ define([], function () { }); } + function getEntry(type, name) { + const entries = global._performanceEntries; + for (let i = 0; i < entries.length; i += 4) { + if (entries[i] === type && entries[i + 1] === name) { + return { + type: entries[i], + name: entries[i + 1], + startTime: entries[i + 2], + duration: entries[i + 3], + }; + } + } + } + + function getDuration(from, to) { + const entries = global._performanceEntries; + let name = from; + let startTime = 0; + for (let i = 0; i < entries.length; i += 4) { + if (entries[i + 1] === name) { + if (name === from) { + // found `from` (start of interval) + name = to; + startTime = entries[i + 2]; + } else { + // from `to` (end of interval) + return entries[i + 2] - startTime; + } + } + } + return 0; + } + function mark(name) { global._performanceEntries.push('mark', name, _now(), 0); if (typeof console.timeStamp === 'function') { @@ -103,6 +136,8 @@ define([], function () { measure: measure, time: time, getEntries: getEntries, + getEntry: getEntry, + getDuration: getDuration, importEntries: importEntries, exportEntries: exportEntries }; diff --git a/src/vs/base/common/uri.ts b/src/vs/base/common/uri.ts index d99a0e403db..5bff36d302a 100644 --- a/src/vs/base/common/uri.ts +++ b/src/vs/base/common/uri.ts @@ -174,27 +174,27 @@ export default class URI implements UriComponents { if (scheme === void 0) { scheme = this.scheme; } else if (scheme === null) { - scheme = ''; + scheme = _empty; } if (authority === void 0) { authority = this.authority; } else if (authority === null) { - authority = ''; + authority = _empty; } if (path === void 0) { path = this.path; } else if (path === null) { - path = ''; + path = _empty; } if (query === void 0) { query = this.query; } else if (query === null) { - query = ''; + query = _empty; } if (fragment === void 0) { fragment = this.fragment; } else if (fragment === null) { - fragment = ''; + fragment = _empty; } if (scheme === this.scheme @@ -315,10 +315,16 @@ export default class URI implements UriComponents { } static revive(data: UriComponents | any): URI { - let result = new _URI(data); - result._fsPath = (data).fsPath; - result._formatted = (data).external; - return result; + if (!data) { + return data; + } else if (data instanceof URI) { + return data; + } else { + let result = new _URI(data); + result._fsPath = (data).fsPath; + result._formatted = (data).external; + return result; + } } } diff --git a/src/vs/base/node/encoding.ts b/src/vs/base/node/encoding.ts index 03a87492c34..4177f3ffab5 100644 --- a/src/vs/base/node/encoding.ts +++ b/src/vs/base/node/encoding.ts @@ -181,6 +181,7 @@ const windowsTerminalEncodings = { '865': 'cp865', // Nordic '866': 'cp866', // Russian '869': 'cp869', // Modern Greek + '936': 'cp936', // Simplified Chinese '1252': 'cp1252' // West European Latin }; diff --git a/src/vs/base/node/extfs.ts b/src/vs/base/node/extfs.ts index b01051d4d93..b3a4121079e 100644 --- a/src/vs/base/node/extfs.ts +++ b/src/vs/base/node/extfs.ts @@ -327,14 +327,12 @@ export function mv(source: string, target: string, callback: (error: Error) => v // See https://github.com/nodejs/node/blob/v5.10.0/lib/fs.js#L1194 let canFlush = true; export function writeFileAndFlush(path: string, data: string | NodeBuffer, options: { mode?: number; flag?: string; }, callback: (error: Error) => void): void { + options = ensureOptions(options); + if (!canFlush) { return fs.writeFile(path, data, options, callback); } - if (!options) { - options = { mode: 0o666, flag: 'w' }; - } - // Open the file with same flags and mode as fs.writeFile() fs.open(path, options.flag, options.mode, (openError, fd) => { if (openError) { @@ -364,14 +362,12 @@ export function writeFileAndFlush(path: string, data: string | NodeBuffer, optio } export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, options?: { mode?: number; flag?: string; }): void { + options = ensureOptions(options); + if (!canFlush) { return fs.writeFileSync(path, data, options); } - if (!options) { - options = { mode: 0o666, flag: 'w' }; - } - // Open the file with same flags and mode as fs.writeFile() const fd = fs.openSync(path, options.flag, options.mode); @@ -392,6 +388,24 @@ export function writeFileAndFlushSync(path: string, data: string | NodeBuffer, o } } +function ensureOptions(options?: { mode?: number; flag?: string; }): { mode: number, flag: string } { + if (!options) { + return { mode: 0o666, flag: 'w' }; + } + + const ensuredOptions = { mode: options.mode, flag: options.flag }; + + if (typeof ensuredOptions.mode !== 'number') { + ensuredOptions.mode = 0o666; + } + + if (typeof ensuredOptions.flag !== 'string') { + ensuredOptions.flag = 'w'; + } + + return ensuredOptions; +} + /** * Copied from: https://github.com/Microsoft/vscode-node-debug/blob/master/src/node/pathUtilities.ts#L83 * diff --git a/src/vs/base/node/ps.ts b/src/vs/base/node/ps.ts index 5f161533b5b..bab3f3ef833 100644 --- a/src/vs/base/node/ps.ts +++ b/src/vs/base/node/ps.ts @@ -61,12 +61,30 @@ export function listProcesses(rootPid: number): Promise { function findName(cmd: string): string { const RENDERER_PROCESS_HINT = /--disable-blink-features=Auxclick/; - const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper.exe/; + const WINDOWS_WATCHER_HINT = /\\watcher\\win32\\CodeHelper\.exe/; + const WINDOWS_CRASH_REPORTER = /--crashes-directory/; + const WINDOWS_PTY = /\\pipe\\winpty-control/; + const WINDOWS_CONSOLE_HOST = /conhost\.exe/; const TYPE = /--type=([a-zA-Z-]+)/; // find windows file watcher if (WINDOWS_WATCHER_HINT.exec(cmd)) { - return 'watcherService'; + return 'watcherService '; + } + + // find windows crash reporter + if (WINDOWS_CRASH_REPORTER.exec(cmd)) { + return 'electron-crash-reporter'; + } + + // find windows pty process + if (WINDOWS_PTY.exec(cmd)) { + return 'winpty-process'; + } + + //find windows console host process + if (WINDOWS_CONSOLE_HOST.exec(cmd)) { + return 'console-window-host (Windows internal process)'; } // find "--type=xxxx" @@ -140,10 +158,10 @@ export function listProcesses(rootPid: number): Promise { } }; - const execMain = path.basename(process.execPath).replace(/ /g, '` '); - const script = URI.parse(require.toUrl('vs/base/node/ps-win.ps1')).fsPath.replace(/ /g, '` '); - const commandLine = `${script} -ProcessName ${execMain} -MaxSamples 3`; - const cmd = spawn('powershell.exe', ['-ExecutionPolicy', 'Bypass', '-Command', commandLine]); + const execMain = path.basename(process.execPath); + const script = URI.parse(require.toUrl('vs/base/node/ps-win.ps1')).fsPath; + const commandLine = `& {& '${script}' -ProcessName '${execMain}' -MaxSamples 3}`; + const cmd = spawn('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', commandLine]); let stdout = ''; let stderr = ''; @@ -205,6 +223,7 @@ export function listProcesses(rootPid: number): Promise { reject(new Error(`Root process ${rootPid} not found`)); } } catch (error) { + console.log(stdout); reject(error); } }); diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index 773a4b79b4d..914878e5454 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -171,8 +171,13 @@ export class QuickOpenEntry { return false; } - public isFile(): boolean { - return false; // TODO@Ben debt with editor history merging + /** + * Determines if this quick open entry should merge with the editor history in quick open. If set to true + * and the resource of this entry is the same as the resource for an editor history, it will not show up + * because it is considered to be a duplicate of an editor history. + */ + public mergeWithEditorHistory(): boolean { + return false; } } @@ -412,8 +417,6 @@ class Renderer implements IRenderer { data.actionBar.context = entry; // make sure the context is the current element this.actionProvider.getActions(null, entry).then((actions) => { - // TODO@Ben this will not work anymore as soon as quick open has more actions - // but as long as there is only one are ok if (data.actionBar.isEmpty() && actions && actions.length > 0) { data.actionBar.push(actions, { icon: true, label: false }); } else if (!data.actionBar.isEmpty() && (!actions || actions.length === 0)) { diff --git a/src/vs/base/parts/tree/browser/treeDnd.ts b/src/vs/base/parts/tree/browser/treeDnd.ts index abae2bcfd18..7b2fa8c4b1a 100644 --- a/src/vs/base/parts/tree/browser/treeDnd.ts +++ b/src/vs/base/parts/tree/browser/treeDnd.ts @@ -10,6 +10,7 @@ import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults'; import URI from 'vs/base/common/uri'; import { basename } from 'vs/base/common/paths'; import { getPathLabel } from 'vs/base/common/labels'; +import { DataTransfers } from 'vs/base/browser/dnd'; export class ElementsDragAndDropData implements _.IDragAndDropData { @@ -116,7 +117,7 @@ export class SimpleFileResourceDragAndDrop extends DefaultDragAndDrop { // Apply some datatransfer types to allow for dragging the element outside of the application const resource = this.toResource(source); if (resource) { - originalEvent.dataTransfer.setData('text/plain', getPathLabel(resource)); + originalEvent.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(resource)); } } } \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index f15a5b120df..5f331083499 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -24,6 +24,7 @@ import _ = require('vs/base/parts/tree/browser/tree'); import { KeyCode } from 'vs/base/common/keyCodes'; import Event, { Emitter } from 'vs/base/common/event'; import { IDomNodePagePosition } from 'vs/base/browser/dom'; +import { DataTransfers } from 'vs/base/browser/dnd'; export interface IRow { element: HTMLElement; @@ -825,7 +826,7 @@ export class TreeView extends HeightMap { public getScrollPosition(): number { const height = this.getTotalHeight() - this.viewHeight; - return height <= 0 ? 0 : this.scrollTop / height; + return height <= 0 ? 1 : this.scrollTop / height; } public setScrollPosition(pos: number): void { @@ -1291,7 +1292,7 @@ export class TreeView extends HeightMap { } e.dataTransfer.effectAllowed = 'copyMove'; - e.dataTransfer.setData('URL', item.uri); + e.dataTransfer.setData(DataTransfers.URL, item.uri); if (e.dataTransfer.setDragImage) { let label: string; @@ -1652,4 +1653,4 @@ export class TreeView extends HeightMap { super.dispose(); } -} \ No newline at end of file +} diff --git a/src/vs/base/test/common/arrays.test.ts b/src/vs/base/test/common/arrays.test.ts index 6368c5b29c5..f57418537bc 100644 --- a/src/vs/base/test/common/arrays.test.ts +++ b/src/vs/base/test/common/arrays.test.ts @@ -7,6 +7,7 @@ import * as assert from 'assert'; import { TPromise } from 'vs/base/common/winjs.base'; import arrays = require('vs/base/common/arrays'); +import { coalesce } from 'vs/base/common/arrays'; suite('Arrays', () => { test('findFirst', function () { @@ -269,5 +270,41 @@ suite('Arrays', () => { }); }); } + + test('coalesce', function () { + let a = coalesce([null, 1, null, 2, 3]); + assert.equal(a.length, 3); + assert.equal(a[0], 1); + assert.equal(a[1], 2); + assert.equal(a[2], 3); + + coalesce([null, 1, null, void 0, undefined, 2, 3]); + assert.equal(a.length, 3); + assert.equal(a[0], 1); + assert.equal(a[1], 2); + assert.equal(a[2], 3); + + let b = []; + b[10] = 1; + b[20] = 2; + b[30] = 3; + b = coalesce(b); + assert.equal(b.length, 3); + assert.equal(b[0], 1); + assert.equal(b[1], 2); + assert.equal(b[2], 3); + + let sparse = []; + sparse[0] = 1; + sparse[1] = 1; + sparse[17] = 1; + sparse[1000] = 1; + sparse[1001] = 1; + + assert.equal(sparse.length, 1002); + + sparse = coalesce(sparse); + assert.equal(sparse.length, 5); + }); }); diff --git a/src/vs/base/test/common/diff/diff.test.ts b/src/vs/base/test/common/diff/diff.test.ts index a822586e7c4..0c07a30b7c9 100644 --- a/src/vs/base/test/common/diff/diff.test.ts +++ b/src/vs/base/test/common/diff/diff.test.ts @@ -7,7 +7,6 @@ import * as assert from 'assert'; import { LcsDiff, IDiffChange } from 'vs/base/common/diff/diff'; -import { LcsDiff2 } from 'vs/base/common/diff/diff2'; class StringDiffSequence { @@ -116,11 +115,6 @@ suite('Diff', () => { this.timeout(10000); lcsTests(LcsDiff); }); - - test('LcsDiff2 - different strings tests', function () { - this.timeout(10000); - lcsTests(LcsDiff2); - }); }); suite('Diff - Ported from VS', () => { diff --git a/src/vs/code/buildfile.js b/src/vs/code/buildfile.js index 2781667fc3a..f94e19b95b8 100644 --- a/src/vs/code/buildfile.js +++ b/src/vs/code/buildfile.js @@ -12,6 +12,7 @@ function createModuleDescription(name, exclude) { excludes = excludes.concat(exclude); } result.exclude= excludes; + return result; } @@ -20,6 +21,6 @@ exports.collectModules= function() { createModuleDescription('vs/code/electron-main/main', []), createModuleDescription('vs/code/node/cli', []), createModuleDescription('vs/code/node/cliProcessMain', ['vs/code/node/cli']), - createModuleDescription('vs/code/electron-browser/sharedProcessMain', []) + createModuleDescription('vs/code/electron-browser/sharedProcess/sharedProcessMain', []) ]; }; \ No newline at end of file diff --git a/src/vs/code/electron-main/auth.html b/src/vs/code/electron-browser/proxy/auth.html similarity index 100% rename from src/vs/code/electron-main/auth.html rename to src/vs/code/electron-browser/proxy/auth.html diff --git a/src/vs/code/electron-browser/contrib/contributions.ts b/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts similarity index 93% rename from src/vs/code/electron-browser/contrib/contributions.ts rename to src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts index acb31a18450..9690ef75a7c 100644 --- a/src/vs/code/electron-browser/contrib/contributions.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/contributions.ts @@ -5,7 +5,7 @@ 'use strict'; -import { NodeCachedDataCleaner } from 'vs/code/electron-browser/contrib/nodeCachedDataCleaner'; +import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export function createSharedProcessContributions(service: IInstantiationService): void { diff --git a/src/vs/code/electron-browser/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts similarity index 100% rename from src/vs/code/electron-browser/contrib/nodeCachedDataCleaner.ts rename to src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts diff --git a/src/vs/code/electron-browser/sharedProcess.html b/src/vs/code/electron-browser/sharedProcess/sharedProcess.html similarity index 90% rename from src/vs/code/electron-browser/sharedProcess.html rename to src/vs/code/electron-browser/sharedProcess/sharedProcess.html index be70dede202..26890a9fc6b 100644 --- a/src/vs/code/electron-browser/sharedProcess.html +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.html @@ -11,7 +11,7 @@ Shared Process - + \ No newline at end of file diff --git a/src/vs/code/electron-browser/sharedProcess.js b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js similarity index 96% rename from src/vs/code/electron-browser/sharedProcess.js rename to src/vs/code/electron-browser/sharedProcess/sharedProcess.js index 2e39f94b2e5..5f4f1dae5bd 100644 --- a/src/vs/code/electron-browser/sharedProcess.js +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcess.js @@ -89,7 +89,7 @@ function main() { }); } - require(['vs/code/electron-browser/sharedProcessMain'], function (sharedProcess) { + require(['vs/code/electron-browser/sharedProcess/sharedProcessMain'], function (sharedProcess) { sharedProcess.startup({ machineId: configuration.machineId }); diff --git a/src/vs/code/electron-browser/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts similarity index 96% rename from src/vs/code/electron-browser/sharedProcessMain.ts rename to src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index f28c43680c1..2e79206ac40 100644 --- a/src/vs/code/electron-browser/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +'use strict'; + import * as fs from 'fs'; import * as platform from 'vs/base/common/platform'; import product from 'vs/platform/node/product'; @@ -34,7 +36,7 @@ import { IWindowsService } from 'vs/platform/windows/common/windows'; import { WindowsChannelClient } from 'vs/platform/windows/common/windowsIpc'; import { ipcRenderer } from 'electron'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { createSharedProcessContributions } from 'vs/code/electron-browser/contrib/contributions'; +import { createSharedProcessContributions } from 'vs/code/electron-browser/sharedProcess/contrib/contributions'; import { createLogService } from 'vs/platform/log/node/spdlogService'; import { ILogService } from 'vs/platform/log/common/log'; @@ -94,8 +96,12 @@ function main(server: Server, initData: ISharedProcessInitData, configuration: I services.set(IWindowsService, windowsService); const activeWindowManager = new ActiveWindowManager(windowsService); - - const choiceChannel = server.getChannel('choice', { route: () => activeWindowManager.activeClientId }); + const choiceChannel = server.getChannel('choice', { + route: () => { + logService.info('Routing choice request to the client', activeWindowManager.activeClientId); + return activeWindowManager.activeClientId; + } + }); services.set(IChoiceService, new ChoiceChannelClient(choiceChannel)); const instantiationService = new InstantiationService(services); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index c16afdc0796..b72b6847723 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -5,7 +5,7 @@ 'use strict'; -import { app, ipcMain as ipc, BrowserWindow, dialog } from 'electron'; +import { app, ipcMain as ipc, BrowserWindow } from 'electron'; import * as platform from 'vs/base/common/platform'; import { WindowsManager } from 'vs/code/electron-main/windows'; import { IWindowsService, OpenContext } from 'vs/platform/windows/common/windows'; @@ -264,7 +264,7 @@ export class CodeApplication { // Spawn shared process this.sharedProcess = new SharedProcess(this.environmentService, machineId, this.userEnv); this.toDispose.push(this.sharedProcess); - this.sharedProcessClient = TPromise.timeout(5000).then(() => this.sharedProcess.whenReady()).then(() => connect(this.environmentService.sharedIPCHandle, 'main')); + this.sharedProcessClient = this.sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); // Services const appInstantiationService = this.initServices(machineId); @@ -376,6 +376,7 @@ export class CodeApplication { private afterWindowOpen(accessor: ServicesAccessor): void { const appInstantiationService = accessor.get(IInstantiationService); + const windowsMainService = accessor.get(IWindowsMainService); let windowsMutex: Mutex = null; if (platform.isWindows) { @@ -387,7 +388,7 @@ export class CodeApplication { this.toDispose.push({ dispose: () => windowsMutex.release() }); } catch (e) { if (!this.environmentService.isBuilt) { - dialog.showMessageBox({ + windowsMainService.showMessageBox({ title: product.nameLong, type: 'warning', message: 'Failed to load windows-mutex!', @@ -403,7 +404,7 @@ export class CodeApplication { require.__$__nodeRequire('windows-foreground-love'); } catch (e) { if (!this.environmentService.isBuilt) { - dialog.showMessageBox({ + windowsMainService.showMessageBox({ title: product.nameLong, type: 'warning', message: 'Failed to load windows-foreground-love!', diff --git a/src/vs/code/electron-main/auth.ts b/src/vs/code/electron-main/auth.ts index 45985c3f4d6..5b2e51b8bba 100644 --- a/src/vs/code/electron-main/auth.ts +++ b/src/vs/code/electron-main/auth.ts @@ -68,7 +68,7 @@ export class ProxyAuthHandler { const win = new BrowserWindow(opts); const config = {}; - const baseUrl = require.toUrl('./auth.html'); + const baseUrl = require.toUrl('vs/code/electron-browser/proxy/auth.html'); const url = `${baseUrl}?config=${encodeURIComponent(JSON.stringify(config))}`; const proxyUrl = `${authInfo.host}:${authInfo.port}`; const title = localize('authRequire', "Proxy Authentication Required"); diff --git a/src/vs/code/electron-main/diagnostics.ts b/src/vs/code/electron-main/diagnostics.ts index 82673450fe9..23813aeaec6 100644 --- a/src/vs/code/electron-main/diagnostics.ts +++ b/src/vs/code/electron-main/diagnostics.ts @@ -124,7 +124,7 @@ function formatEnvironment(info: IMainProcessInfo): string { const output: string[] = []; output.push(`Version: ${pkg.name} ${pkg.version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'})`); - output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()})`); + output.push(`OS Version: ${os.type()} ${os.arch()} ${os.release()}`); const cpus = os.cpus(); if (cpus && cpus.length > 0) { output.push(`CPUs: ${cpus[0].model} (${cpus.length} x ${cpus[0].speed})`); @@ -135,6 +135,7 @@ function formatEnvironment(info: IMainProcessInfo): string { } output.push(`VM: ${Math.round((virtualMachineHint.value() * 100))}%`); output.push(`Screen Reader: ${app.isAccessibilitySupportEnabled() ? 'yes' : 'no'}`); + output.push(`Process Argv: ${info.mainArguments.join(' ')}`); return output.join('\n'); } @@ -145,7 +146,7 @@ function formatProcessList(info: IMainProcessInfo, rootProcess: ProcessItem): st const output: string[] = []; - output.push('CPU %\tMem MB\tProcess'); + output.push('CPU %\tMem MB\t PID\tProcess'); formatProcessItem(mapPidToWindowTitle, output, rootProcess, 0); @@ -169,7 +170,7 @@ function formatProcessItem(mapPidToWindowTitle: Map, output: str } } const memory = process.platform === 'win32' ? item.mem : (os.totalmem() * (item.mem / 100)); - output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${name}`); + output.push(`${pad(Number(item.load.toFixed(0)), 5, ' ')}\t${pad(Number((memory / MB).toFixed(0)), 6, ' ')}\t${pad(Number((item.pid).toFixed(0)), 6, ' ')}\t${name}`); // Recurse into children if any if (Array.isArray(item.children)) { diff --git a/src/vs/code/electron-main/launch.ts b/src/vs/code/electron-main/launch.ts index 31125cac105..86324ae6c63 100644 --- a/src/vs/code/electron-main/launch.ts +++ b/src/vs/code/electron-main/launch.ts @@ -33,6 +33,7 @@ export interface IWindowInfo { export interface IMainProcessInfo { mainPID: number; + mainArguments: string[]; windows: IWindowInfo[]; } @@ -105,6 +106,16 @@ export class LaunchService implements ILaunchService { this.logService.trace('Received data from other instance: ', args, userEnv); // Check early for open-url which is handled in URL service + const openUrl = this.startOpenUrl(args); + if (openUrl) { + return openUrl; + } + + // Otherwise handle in windows service + return this.startOpenWindow(args, userEnv); + } + + private startOpenUrl(args: ParsedArgs): TPromise { const openUrlArg = args['open-url'] || []; const openUrl = typeof openUrlArg === 'string' ? [openUrlArg] : openUrlArg; if (openUrl.length > 0) { @@ -113,7 +124,10 @@ export class LaunchService implements ILaunchService { return TPromise.as(null); } - // Otherwise handle in windows service + return void 0; + } + + private startOpenWindow(args: ParsedArgs, userEnv: IProcessEnvironment): TPromise { const context = !!userEnv['VSCODE_CLI'] ? OpenContext.CLI : OpenContext.DESKTOP; let usedWindows: ICodeWindow[]; if (!!args.extensionDevelopmentPath) { @@ -159,6 +173,7 @@ export class LaunchService implements ILaunchService { return TPromise.wrap({ mainPID: process.pid, + mainArguments: process.argv, windows: this.windowsMainService.getWindows().map(window => { return this.getWindowInfo(window); }) diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index d9f1c0b0081..3c042ea5a67 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -236,7 +236,7 @@ function setupIPC(accessor: ServicesAccessor): TPromise { } function showStartupWarningDialog(message: string, detail: string): void { - dialog.showMessageBox(null, { + dialog.showMessageBox({ title: product.nameLong, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index a3207fa5aa6..59229fbc739 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -9,7 +9,7 @@ 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 { ipcMain as ipc, app, shell, dialog, Menu, MenuItem, BrowserWindow, clipboard } from 'electron'; +import { ipcMain as ipc, app, shell, Menu, MenuItem, BrowserWindow, clipboard } from 'electron'; import { OpenContext, IRunActionInWindowRequest } from 'vs/platform/windows/common/windows'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { AutoSaveConfiguration } from 'vs/platform/files/common/files'; @@ -1097,11 +1097,6 @@ export class CodeMenu { const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsMainService.getWindowCount() > 0; const checked = typeof arg4 === 'boolean' ? arg4 : false; - let commandId: string; - if (typeof arg2 === 'string') { - commandId = arg2; - } - const options: Electron.MenuItemConstructorOptions = { label, click, @@ -1113,6 +1108,13 @@ export class CodeMenu { 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)); } @@ -1215,20 +1217,18 @@ export class CodeMenu { buttons.push(mnemonicButtonLabel(nls.localize({ key: 'copy', comment: ['&& denotes a mnemonic'] }, "&&Copy"))); // https://github.com/Microsoft/vscode/issues/37608 } - const result = dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, { + this.windowsMainService.showMessageBox({ title: product.nameLong, type: 'info', message: product.nameLong, detail: `\n${detail}`, buttons, noLink: true + }, lastActiveWindow).then(result => { + if (isWindows && result.button === 1) { + clipboard.writeText(detail); + } }); - - if (isWindows && result === 1) { - clipboard.writeText(detail); - } - - this.reportMenuActionTelemetry('showAboutDialog'); } private openUrl(url: string, id: string): void { diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 7c1edf75906..ff9f904cb68 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -43,7 +43,7 @@ export class SharedProcess implements ISharedProcess { userEnv: this.userEnv }); - const url = `${require.toUrl('vs/code/electron-browser/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; + const url = `${require.toUrl('vs/code/electron-browser/sharedProcess/sharedProcess.html')}?config=${encodeURIComponent(JSON.stringify(config))}`; this.window.loadURL(url); // Prevent the window from dying diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 6f0871deb7a..4c37dd6e993 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -194,7 +194,7 @@ export class CodeWindow implements ICodeWindow { this._win = new BrowserWindow(options); this._id = this._win.id; - // TODO@Ben Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups, + // Bug in Electron (https://github.com/electron/electron/issues/10862). On multi-monitor setups, // it can happen that the position we set to the window is not the correct one on the display. // To workaround, we ask the window for its position and set it again if not matching. // This only applies if the window is not fullscreen or maximized and multiple monitors are used. @@ -563,6 +563,9 @@ export class CodeWindow implements ICodeWindow { private getUrl(windowConfiguration: IWindowConfiguration): string { + // Set window ID + windowConfiguration.windowId = this._win.id; + // Set zoomlevel const windowConfig = this.configurationService.getValue('window'); const zoomLevel = windowConfig && windowConfig.zoomLevel; @@ -584,7 +587,6 @@ export class CodeWindow implements ICodeWindow { // Perf Counters windowConfiguration.perfEntries = exportEntries(); windowConfiguration.perfStartTime = global.perfStartTime; - windowConfiguration.perfAppReady = global.perfAppReady; windowConfiguration.perfWindowLoadTime = Date.now(); // Config (combination of process.argv and window configuration) diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 1561c4bb792..0ac89554809 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -19,13 +19,13 @@ import { IPathWithLineAndColumn, parseLineAndColumnAware } from 'vs/code/node/pa import { ILifecycleService, UnloadReason, IWindowUnloadEvent } from 'vs/platform/lifecycle/electron-main/lifecycleMain'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ILogService } from 'vs/platform/log/common/log'; -import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; +import { IWindowSettings, OpenContext, IPath, IWindowConfiguration, INativeOpenDialogOptions, ReadyState, IPathsToWaitFor, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows'; import { getLastActiveWindow, findBestWindowOrFolderForFile, findWindowOnWorkspace, findWindowOnExtensionDevelopmentPath, findWindowOnWorkspaceOrFolderPath } from 'vs/code/node/windowsFinder'; import CommonEvent, { Emitter } from 'vs/base/common/event'; import product from 'vs/platform/node/product'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { isEqual } from 'vs/base/common/paths'; -import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent } from 'vs/platform/windows/electron-main/windows'; +import { IWindowsMainService, IOpenConfiguration, IWindowsCountChangedEvent, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; import { IHistoryMainService } from 'vs/platform/history/common/history'; import { IProcessEnvironment, isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -35,6 +35,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { Schemas } from 'vs/base/common/network'; import { normalizeNFC } from 'vs/base/common/strings'; import URI from 'vs/base/common/uri'; +import { Queue } from 'vs/base/common/async'; enum WindowError { UNRESPONSIVE, @@ -45,10 +46,6 @@ interface INewWindowState extends ISingleWindowState { hasDefaultState?: boolean; } -interface ILegacyWindowState extends IWindowState { - workspacePath?: string; -} - interface IWindowState { workspace?: IWorkspaceIdentifier; folderPath?: string; @@ -56,10 +53,6 @@ interface IWindowState { uiState: ISingleWindowState; } -interface ILegacyWindowsState extends IWindowsState { - openedFolders?: IWindowState[]; -} - interface IWindowsState { lastActiveWindow?: IWindowState; lastPluginDevelopmentHostWindow?: IWindowState; @@ -116,7 +109,7 @@ export class WindowsManager implements IWindowsMainService { private windowsState: IWindowsState; private lastClosedWindowState: IWindowState; - private fileDialog: FileDialog; + private dialogs: Dialogs; private workspacesManager: WorkspacesManager; private _onWindowReady = new Emitter(); @@ -151,39 +144,12 @@ export class WindowsManager implements IWindowsMainService { @IInstantiationService private instantiationService: IInstantiationService ) { this.windowsState = this.stateService.getItem(WindowsManager.windowsStateStorageKey) || { openedWindows: [] }; - - this.fileDialog = new FileDialog(environmentService, telemetryService, stateService, this); - this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this); - - this.migrateLegacyWindowState(); - } - - private migrateLegacyWindowState(): void { - const state: ILegacyWindowsState = this.windowsState; - - // TODO@Ben migration from previous openedFolders to new openedWindows property - if (Array.isArray(state.openedFolders) && state.openedFolders.length > 0) { - state.openedWindows = state.openedFolders; - state.openedFolders = void 0; - } else if (!state.openedWindows) { - state.openedWindows = []; + if (!Array.isArray(this.windowsState.openedWindows)) { + this.windowsState.openedWindows = []; } - // TODO@Ben migration from previous workspacePath in window state to folderPath - const states: ILegacyWindowState[] = []; - states.push(state.lastActiveWindow); - states.push(state.lastPluginDevelopmentHostWindow); - states.push(...state.openedWindows); - states.forEach(state => { - if (!state) { - return; - } - - if (typeof state.workspacePath === 'string') { - state.folderPath = state.workspacePath; - state.workspacePath = void 0; - } - }); + this.dialogs = new Dialogs(environmentService, telemetryService, stateService, this, this.logService); + this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, environmentService, this); } public ready(initialUserEnv: IProcessEnvironment): void { @@ -277,7 +243,7 @@ export class WindowsManager implements IWindowsMainService { // - closeAll(2): onBeforeWindowClose(2, false), onBeforeWindowClose(2, false), onBeforeQuit(0) // private onBeforeQuit(): void { - const currentWindowsState: ILegacyWindowsState = { + const currentWindowsState: IWindowsState = { openedWindows: [], lastPluginDevelopmentHostWindow: this.windowsState.lastPluginDevelopmentHostWindow, lastActiveWindow: this.lastClosedWindowState @@ -825,12 +791,7 @@ export class WindowsManager implements IWindowsMainService { noLink: true }; - const activeWindow = BrowserWindow.getFocusedWindow(); - if (activeWindow) { - dialog.showMessageBox(activeWindow, options); - } else { - dialog.showMessageBox(options); - } + this.dialogs.showMessageBox(options, this.getFocusedWindow()); } return path; @@ -940,10 +901,6 @@ export class WindowsManager implements IWindowsMainService { const windowConfig = this.configurationService.getValue('window'); restoreWindows = ((windowConfig && windowConfig.restoreWindows) || 'one') as RestoreWindowsSetting; - if (restoreWindows === 'one' /* default */ && windowConfig && windowConfig.reopenFolders) { - restoreWindows = windowConfig.reopenFolders; // TODO@Ben migration from deprecated window.reopenFolders setting - } - if (['all', 'folders', 'one', 'none'].indexOf(restoreWindows) === -1) { restoreWindows = 'one'; } @@ -1369,7 +1326,7 @@ export class WindowsManager implements IWindowsMainService { } // Handle untitled workspaces with prompt as needed - this.workspacesManager.promptToSaveUntitledWorkspace(e, workspace); + e.veto(this.workspacesManager.promptToSaveUntitledWorkspace(this.getWindowById(e.window.id), workspace)); } public focusLastActive(cli: ParsedArgs, context: OpenContext): CodeWindow { @@ -1457,48 +1414,48 @@ export class WindowsManager implements IWindowsMainService { // Unresponsive if (error === WindowError.UNRESPONSIVE) { - const result = dialog.showMessageBox(window.win, { + this.dialogs.showMessageBox({ title: product.nameLong, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'wait', comment: ['&& denotes a mnemonic'] }, "&&Keep Waiting")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message: localize('appStalled', "The window is no longer responding"), detail: localize('appStalledDetail', "You can reopen or close the window or keep waiting."), noLink: true + }, window).then(result => { + if (!window.win) { + return; // Return early if the window has been going down already + } + + if (result.button === 0) { + window.reload(); + } else if (result.button === 2) { + this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually + window.win.destroy(); // make sure to destroy the window as it is unresponsive + } }); - - if (!window.win) { - return; // Return early if the window has been going down already - } - - if (result === 0) { - window.reload(); - } else if (result === 2) { - this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually - window.win.destroy(); // make sure to destroy the window as it is unresponsive - } } // Crashed else { - const result = dialog.showMessageBox(window.win, { + this.dialogs.showMessageBox({ title: product.nameLong, type: 'warning', buttons: [mnemonicButtonLabel(localize({ key: 'reopen', comment: ['&& denotes a mnemonic'] }, "&&Reopen")), mnemonicButtonLabel(localize({ key: 'close', comment: ['&& denotes a mnemonic'] }, "&&Close"))], message: localize('appCrashed', "The window has crashed"), detail: localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), noLink: true + }, window).then(result => { + if (!window.win) { + return; // Return early if the window has been going down already + } + + if (result.button === 0) { + window.reload(); + } else if (result.button === 1) { + this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually + window.win.destroy(); // make sure to destroy the window as it has crashed + } }); - - if (!window.win) { - return; // Return early if the window has been going down already - } - - if (result === 0) { - window.reload(); - } else if (result === 1) { - this.onBeforeWindowClose(window); // 'close' event will not be fired on destroy(), so run it manually - window.win.destroy(); // make sure to destroy the window as it has crashed - } } } @@ -1558,7 +1515,19 @@ export class WindowsManager implements IWindowsMainService { } } - this.fileDialog.pickAndOpen(internalOptions); + this.dialogs.pickAndOpen(internalOptions); + } + + public showMessageBox(options: Electron.MessageBoxOptions, win?: CodeWindow): TPromise { + return this.dialogs.showMessageBox(options, win); + } + + public showSaveDialog(options: Electron.SaveDialogOptions, win?: CodeWindow): TPromise { + return this.dialogs.showSaveDialog(options, win); + } + + public showOpenDialog(options: Electron.OpenDialogOptions, win?: CodeWindow): TPromise { + return this.dialogs.showOpenDialog(options, win); } public quit(): void { @@ -1584,20 +1553,26 @@ interface IInternalNativeOpenDialogOptions extends INativeOpenDialogOptions { pickFiles?: boolean; } -class FileDialog { +class Dialogs { private static readonly workingDirPickerStorageKey = 'pickerWorkingDir'; + private mapWindowToDialogQueue: Map>; + private noWindowDialogQueue: Queue; + constructor( private environmentService: IEnvironmentService, private telemetryService: ITelemetryService, private stateService: IStateService, - private windowsMainService: IWindowsMainService + private windowsMainService: IWindowsMainService, + private logService: ILogService // TODO@Ben remove logging when no longer needed ) { + this.mapWindowToDialogQueue = new Map>(); + this.noWindowDialogQueue = new Queue(); } public pickAndOpen(options: INativeOpenDialogOptions): void { - this.getFileOrFolderPaths(options, (paths: string[]) => { + this.getFileOrFolderPaths(options).then(paths => { const numberOfPaths = paths ? paths.length : 0; // Telemetry @@ -1623,7 +1598,7 @@ class FileDialog { }); } - private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions, clb: (paths: string[]) => void): void { + private getFileOrFolderPaths(options: IInternalNativeOpenDialogOptions): TPromise { // Ensure dialog options if (!options.dialogOptions) { @@ -1632,7 +1607,7 @@ class FileDialog { // Ensure defaultPath if (!options.dialogOptions.defaultPath) { - options.dialogOptions.defaultPath = this.stateService.getItem(FileDialog.workingDirPickerStorageKey); + options.dialogOptions.defaultPath = this.stateService.getItem(Dialogs.workingDirPickerStorageKey); } // Ensure properties @@ -1654,20 +1629,93 @@ class FileDialog { // Show Dialog const focusedWindow = this.windowsMainService.getWindowById(options.windowId) || this.windowsMainService.getFocusedWindow(); - let paths = dialog.showOpenDialog(focusedWindow && focusedWindow.win, options.dialogOptions); - if (paths && paths.length > 0) { - if (isMacintosh) { + + return this.showOpenDialog(options.dialogOptions, focusedWindow).then(paths => { + if (paths && paths.length > 0) { + + // Remember path in storage for next time + this.stateService.setItem(Dialogs.workingDirPickerStorageKey, dirname(paths[0])); + + return paths; + } + + return void 0; + }); + } + + private getDialogQueue(window?: ICodeWindow): Queue { + if (!window) { + this.logService.info('getDialogQueue: using NO WINDOW queue. size: ', this.noWindowDialogQueue.size); + return this.noWindowDialogQueue; + } + + let windowDialogQueue = this.mapWindowToDialogQueue.get(window.id); + if (!windowDialogQueue) { + this.logService.info('getDialogQueue: creating window dialog queue for window:', window.id); + windowDialogQueue = new Queue(); + this.mapWindowToDialogQueue.set(window.id, windowDialogQueue); + } else { + this.logService.info('getDialogQueue: found existing window dialog queue for window:', window.id); + } + + this.logService.info('getDialogQueue: size: ', windowDialogQueue.size); + + return windowDialogQueue; + } + + public showMessageBox(options: Electron.MessageBoxOptions, window?: ICodeWindow): TPromise { + this.logService.info('showMessageBox begin: ', options, window ? window.id : 'No Window'); + return this.getDialogQueue(window).queue(() => { + return new TPromise((c, e) => { + this.logService.info('showMessageBox opening'); + dialog.showMessageBox(window ? window.win : void 0, options, (response: number, checkboxChecked: boolean) => { + this.logService.info('showMessageBox closed, response: ', response, checkboxChecked); + c({ button: response, checkboxChecked }); + }); + }); + }); + } + + public showSaveDialog(options: Electron.SaveDialogOptions, window?: ICodeWindow): TPromise { + function normalizePath(path: string): string { + if (path && isMacintosh) { + path = normalizeNFC(path); // normalize paths returned from the OS + } + + return path; + } + + this.logService.info('showSaveDialog begin: ', options, window ? window.id : 'No Window'); + return this.getDialogQueue(window).queue(() => { + return new TPromise((c, e) => { + this.logService.info('showSaveDialog opening'); + dialog.showSaveDialog(window ? window.win : void 0, options, path => { + this.logService.info('showSaveDialog closed, response: ', path); + c(normalizePath(path)); + }); + }); + }); + } + + public showOpenDialog(options: Electron.OpenDialogOptions, window?: ICodeWindow): TPromise { + function normalizePaths(paths: string[]): string[] { + if (paths && paths.length > 0 && isMacintosh) { paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS } - // Remember path in storage for next time - this.stateService.setItem(FileDialog.workingDirPickerStorageKey, dirname(paths[0])); - - // Return - return clb(paths); + return paths; } - return clb(void (0)); + this.logService.info('showOpenDialog begin: ', options, window ? window.id : 'No Window'); + return this.getDialogQueue(window).queue(() => { + return new TPromise((c, e) => { + this.logService.info('showOpenDialog opening'); + dialog.showOpenDialog(window ? window.win : void 0, options, paths => { + this.logService.info('showOpenDialog closed, response: ', paths); + c(normalizePaths(paths)); + }); + }); + }); } } @@ -1690,22 +1738,29 @@ class WorkspacesManager { } public createAndEnterWorkspace(window: CodeWindow, folders?: IWorkspaceFolderCreationData[], path?: string): TPromise { - if (!window || !window.win || window.readyState !== ReadyState.READY || !this.isValidTargetWorkspacePath(window, path)) { + if (!window || !window.win || window.readyState !== ReadyState.READY) { return TPromise.as(null); // return early if the window is not ready or disposed } - return this.workspacesService.createWorkspace(folders).then(workspace => { - return this.doSaveAndOpenWorkspace(window, workspace, path); + return this.isValidTargetWorkspacePath(window, path).then(isValid => { + if (!isValid) { + return TPromise.as(null); // return early if the workspace is not valid + } + + return this.workspacesService.createWorkspace(folders).then(workspace => { + return this.doSaveAndOpenWorkspace(window, workspace, path); + }); }); + } - private isValidTargetWorkspacePath(window: CodeWindow, path?: string): boolean { + private isValidTargetWorkspacePath(window: CodeWindow, path?: string): TPromise { if (!path) { - return true; + return TPromise.wrap(true); } if (window.openedWorkspace && window.openedWorkspace.configPath === path) { - return false; // window is already opened on a workspace with that path + return TPromise.wrap(false); // window is already opened on a workspace with that path } // Prevent overwriting a workspace that is currently opened in another window @@ -1719,17 +1774,10 @@ class WorkspacesManager { noLink: true }; - const activeWindow = BrowserWindow.getFocusedWindow(); - if (activeWindow) { - dialog.showMessageBox(activeWindow, options); - } else { - dialog.showMessageBox(options); - } - - return false; + return this.windowsMainService.showMessageBox(options, this.windowsMainService.getFocusedWindow()).then(() => false); } - return true; // OK + return TPromise.wrap(true); // OK } private doSaveAndOpenWorkspace(window: CodeWindow, workspace: IWorkspaceIdentifier, path?: string): TPromise { @@ -1776,7 +1824,7 @@ class WorkspacesManager { }); } - public promptToSaveUntitledWorkspace(e: IWindowUnloadEvent, workspace: IWorkspaceIdentifier): void { + public promptToSaveUntitledWorkspace(window: ICodeWindow, workspace: IWorkspaceIdentifier): TPromise { enum ConfirmResult { SAVE, DONT_SAVE, @@ -1810,41 +1858,35 @@ class WorkspacesManager { options.defaultId = 2; } - const res = dialog.showMessageBox(e.window.win, options); + return this.windowsMainService.showMessageBox(options, window).then(res => { + switch (buttons[res.button].result) { - switch (buttons[res].result) { + // Cancel: veto unload + case ConfirmResult.CANCEL: + return true; - // Cancel: veto unload - case ConfirmResult.CANCEL: - e.veto(true); - break; + // Don't Save: delete workspace + case ConfirmResult.DONT_SAVE: + this.workspacesService.deleteUntitledWorkspaceSync(workspace); + return false; - // Don't Save: delete workspace - case ConfirmResult.DONT_SAVE: - this.workspacesService.deleteUntitledWorkspaceSync(workspace); - e.veto(false); - break; + // Save: save workspace, but do not veto unload + case ConfirmResult.SAVE: { + return this.windowsMainService.showSaveDialog({ + buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), + title: localize('saveWorkspace', "Save Workspace"), + filters: WORKSPACE_FILTER, + defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace) + }, window).then(target => { + if (target) { + return this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false); + } - // Save: save workspace, but do not veto unload - case ConfirmResult.SAVE: { - let target = dialog.showSaveDialog(e.window.win, { - buttonLabel: mnemonicButtonLabel(localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), - title: localize('saveWorkspace', "Save Workspace"), - filters: WORKSPACE_FILTER, - defaultPath: this.getUntitledWorkspaceSaveDialogDefaultPath(workspace) - }); - - if (target) { - if (isMacintosh) { - target = normalizeNFC(target); // normalize paths returned from the OS - } - - e.veto(this.workspacesService.saveWorkspace(workspace, target).then(() => false, () => false)); - } else { - e.veto(true); // keep veto if no target was provided + return true; // keep veto if no target was provided + }); } } - } + }); } private getUntitledWorkspaceSaveDialogDefaultPath(workspace?: IWorkspaceIdentifier | ISingleFolderWorkspaceIdentifier): string { diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index fe645ff4e0d..b47a0015178 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -17,6 +17,8 @@ import { whenDeleted } from 'vs/base/node/pfs'; import { findFreePort } from 'vs/base/node/ports'; import { resolveTerminalEncoding } from 'vs/base/node/encoding'; import * as iconv from 'iconv-lite'; +import { writeFileAndFlushSync } from 'vs/base/node/extfs'; +import { isWindows } from 'vs/base/common/platform'; function shouldSpawnCliProcess(argv: ParsedArgs): boolean { return !!argv['install-source'] @@ -55,20 +57,73 @@ export async function main(argv: string[]): TPromise { return mainCli.then(cli => cli.main(args)); } + // Write File + else if (args['file-write']) { + const source = args._[0]; + const target = args._[1]; + + // Validate + if ( + !source || !target || source === target || // make sure source and target are provided and are not the same + !paths.isAbsolute(source) || !paths.isAbsolute(target) || // make sure both source and target are absolute paths + !fs.existsSync(source) || !fs.statSync(source).isFile() || // make sure source exists as file + !fs.existsSync(target) || !fs.statSync(target).isFile() // make sure target exists as file + ) { + return TPromise.wrapError(new Error('Using --file-write with invalid arguments.')); + } + + try { + + // Check for readonly status and chmod if so if we are told so + let targetMode: number; + let restoreMode = false; + if (!!args['file-chmod']) { + targetMode = fs.statSync(target).mode; + if (!(targetMode & 128) /* readonly */) { + fs.chmodSync(target, targetMode | 128); + restoreMode = true; + } + } + + // Write source to target + const data = fs.readFileSync(source); + try { + writeFileAndFlushSync(target, data); + } catch (error) { + // On Windows and if the file exists with an EPERM error, we try a different strategy of saving the file + // by first truncating the file and then writing with r+ mode. This helps to save hidden files on Windows + // (see https://github.com/Microsoft/vscode/issues/931) + if (isWindows && error.code === 'EPERM') { + fs.truncateSync(target, 0); + writeFileAndFlushSync(target, data, { flag: 'r+' }); + } else { + throw error; + } + } + + // Restore previous mode as needed + if (restoreMode) { + fs.chmodSync(target, targetMode); + } + } catch (error) { + return TPromise.wrapError(new Error(`Using --file-write resulted in an error: ${error}`)); + } + + return TPromise.as(null); + } + // Just Code else { const env = assign({}, process.env, { - // this will signal Code that it was spawned from this module - 'VSCODE_CLI': '1', + 'VSCODE_CLI': '1', // this will signal Code that it was spawned from this module 'ELECTRON_NO_ATTACH_CONSOLE': '1' }); delete env['ELECTRON_RUN_AS_NODE']; - let processCallbacks: ((child: ChildProcess) => Thenable)[] = []; + const processCallbacks: ((child: ChildProcess) => Thenable)[] = []; const verbose = args.verbose || args.status; - if (verbose) { env['ELECTRON_ENABLE_LOGGING'] = '1'; @@ -80,46 +135,90 @@ export async function main(argv: string[]): TPromise { }); } - // If we are running with input from stdin, pipe that into a file and - // open this file via arguments. Ignore this when we are passed with - // paths to open. - let isReadingFromStdin: boolean; + let stdinWithoutTty: boolean; try { - isReadingFromStdin = args._.length === 0 && !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304 + stdinWithoutTty = !process.stdin.isTTY; // Via https://twitter.com/MylesBorins/status/782009479382626304 } catch (error) { // Windows workaround for https://github.com/nodejs/node/issues/11656 } + const readFromStdin = args._.some(a => a === '-'); + if (readFromStdin) { + // remove the "-" argument when we read from stdin + args._ = args._.filter(a => a !== '-'); + argv = argv.filter(a => a !== '-'); + } + let stdinFilePath: string; - if (isReadingFromStdin) { - let stdinFileError: Error; - stdinFilePath = paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); - try { - const stdinFileStream = fs.createWriteStream(stdinFilePath); - resolveTerminalEncoding(verbose).done(encoding => { + if (stdinWithoutTty) { + + // Read from stdin: we require a single "-" argument to be passed in order to start reading from + // stdin. We do this because there is no reliable way to find out if data is piped to stdin. Just + // checking for stdin being connected to a TTY is not enough (https://github.com/Microsoft/vscode/issues/40351) + if (args._.length === 0 && readFromStdin) { + + // prepare temp file to read stdin to + stdinFilePath = paths.join(os.tmpdir(), `code-stdin-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 3)}.txt`); + + // open tmp file for writing + let stdinFileError: Error; + let stdinFileStream: fs.WriteStream; + try { + stdinFileStream = fs.createWriteStream(stdinFilePath); + } catch (error) { + stdinFileError = error; + } + + if (!stdinFileError) { // Pipe into tmp file using terminals encoding - const converterStream = iconv.decodeStream(encoding); - process.stdin.pipe(converterStream).pipe(stdinFileStream); - }); + resolveTerminalEncoding(verbose).done(encoding => { + const converterStream = iconv.decodeStream(encoding); + process.stdin.pipe(converterStream).pipe(stdinFileStream); + }); - // Make sure to open tmp file - argv.push(stdinFilePath); + // Make sure to open tmp file + argv.push(stdinFilePath); - // Enable --wait to get all data and ignore adding this to history - argv.push('--wait'); - argv.push('--skip-add-to-recently-opened'); - args.wait = true; - } catch (error) { - stdinFileError = error; + // Enable --wait to get all data and ignore adding this to history + argv.push('--wait'); + argv.push('--skip-add-to-recently-opened'); + args.wait = true; + } + + if (verbose) { + if (stdinFileError) { + console.error(`Failed to create file to read via stdin: ${stdinFileError.toString()}`); + } else { + console.log(`Reading from stdin via: ${stdinFilePath}`); + } + } } - if (verbose) { - if (stdinFileError) { - console.error(`Failed to create file to read via stdin: ${stdinFileError.toString()}`); - } else { - console.log(`Reading from stdin via: ${stdinFilePath}`); - } + // If the user pipes data via stdin but forgot to add the "-" argument, help by printing a message + // if we detect that data flows into via stdin after a certain timeout. + else if (args._.length === 0) { + processCallbacks.push(child => new TPromise(c => { + const dataListener = () => { + if (isWindows) { + console.log(`Run with '${product.applicationName} -' to read output from another program (e.g. 'echo Hello World | ${product.applicationName} -').`); + } else { + console.log(`Run with '${product.applicationName} -' to read from stdin (e.g. 'ps aux | grep code | ${product.applicationName} -').`); + } + + c(void 0); + }; + + // wait for 1s maximum... + setTimeout(() => { + process.stdin.removeListener('data', dataListener); + + c(void 0); + }, 1000); + + // ...but finish early if we detect data + process.stdin.once('data', dataListener); + })); } } diff --git a/src/vs/editor/browser/controller/coreCommands.ts b/src/vs/editor/browser/controller/coreCommands.ts index 313479b1766..be5019013e0 100644 --- a/src/vs/editor/browser/controller/coreCommands.ts +++ b/src/vs/editor/browser/controller/coreCommands.ts @@ -455,10 +455,7 @@ export namespace CoreNavigationCommands { cursors.setStates( source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.move(cursors.context, cursors.getAll(), args) - ) + CursorMoveCommands.move(cursors.context, cursors.getAll(), args) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -707,7 +704,7 @@ export namespace CoreNavigationCommands { public runCoreEditorCommand(cursors: ICursors, args: any): void { const context = cursors.context; - if (context.config.readOnly || context.model.hasEditableRange()) { + if (context.config.readOnly) { return; } @@ -772,7 +769,7 @@ export namespace CoreNavigationCommands { public runCoreEditorCommand(cursors: ICursors, args: any): void { const context = cursors.context; - if (context.config.readOnly || context.model.hasEditableRange()) { + if (context.config.readOnly) { return; } @@ -804,10 +801,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.moveToBeginningOfLine(cursors.context, cursors.getAll(), this._inSelectionMode) - ) + CursorMoveCommands.moveToBeginningOfLine(cursors.context, cursors.getAll(), this._inSelectionMode) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -856,10 +850,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - this._exec(cursors.context, cursors.getAll()) - ) + this._exec(cursors.context, cursors.getAll()) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -889,10 +880,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.moveToEndOfLine(cursors.context, cursors.getAll(), this._inSelectionMode) - ) + CursorMoveCommands.moveToEndOfLine(cursors.context, cursors.getAll(), this._inSelectionMode) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -941,10 +929,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - this._exec(cursors.context, cursors.getAll()) - ) + this._exec(cursors.context, cursors.getAll()) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -975,10 +960,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.moveToBeginningOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode) - ) + CursorMoveCommands.moveToBeginningOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -1022,10 +1004,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.moveToEndOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode) - ) + CursorMoveCommands.moveToEndOfBuffer(cursors.context, cursors.getAll(), this._inSelectionMode) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } @@ -1272,7 +1251,7 @@ export namespace CoreNavigationCommands { public runCoreEditorCommand(cursors: ICursors, args: any): void { const context = cursors.context; - if (context.config.readOnly || context.model.hasEditableRange()) { + if (context.config.readOnly) { return; } @@ -1335,7 +1314,7 @@ export namespace CoreNavigationCommands { public runCoreEditorCommand(cursors: ICursors, args: any): void { const context = cursors.context; - if (context.config.readOnly || context.model.hasEditableRange()) { + if (context.config.readOnly) { return; } @@ -1383,10 +1362,7 @@ export namespace CoreNavigationCommands { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - cursors.context, - CursorMoveCommands.expandLineSelection(cursors.context, cursors.getAll()) - ) + CursorMoveCommands.expandLineSelection(cursors.context, cursors.getAll()) ); cursors.reveal(true, RevealTarget.Primary, editorCommon.ScrollType.Smooth); } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index ff6bc23c170..0e21b9588b5 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -694,7 +694,7 @@ export class MouseTargetFactory { return request.fulfill(MouseTargetType.CONTENT_EMPTY, pos, void 0, EMPTY_CONTENT_IN_LINES); } - let visibleRange = ctx.visibleRangeForPosition2(lineNumber, column); + const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column); if (!visibleRange) { return request.fulfill(MouseTargetType.UNKNOWN, pos); @@ -706,33 +706,35 @@ export class MouseTargetFactory { return request.fulfill(MouseTargetType.CONTENT_TEXT, pos); } - let mouseIsBetween: boolean; + // Let's define a, b, c and check if the offset is in between them... + interface OffsetColumn { offset: number; column: number; } + + let points: OffsetColumn[] = []; + points.push({ offset: visibleRange.left, column: column }); if (column > 1) { - let prevColumnHorizontalOffset = visibleRange.left; - mouseIsBetween = false; - mouseIsBetween = mouseIsBetween || (prevColumnHorizontalOffset < request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset < columnHorizontalOffset); // LTR case - mouseIsBetween = mouseIsBetween || (columnHorizontalOffset < request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset < prevColumnHorizontalOffset); // RTL case - if (mouseIsBetween) { - let rng = new EditorRange(lineNumber, column, lineNumber, column - 1); + const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column - 1); + if (visibleRange) { + points.push({ offset: visibleRange.left, column: column - 1 }); + } + } + const lineMaxColumn = ctx.model.getLineMaxColumn(lineNumber); + if (column < lineMaxColumn) { + const visibleRange = ctx.visibleRangeForPosition2(lineNumber, column + 1); + if (visibleRange) { + points.push({ offset: visibleRange.left, column: column + 1 }); + } + } + + points.sort((a, b) => a.offset - b.offset); + + for (let i = 1; i < points.length; i++) { + const prev = points[i - 1]; + const curr = points[i]; + if (prev.offset <= request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset <= curr.offset) { + const rng = new EditorRange(lineNumber, prev.column, lineNumber, curr.column); return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng); } } - - let lineMaxColumn = ctx.model.getLineMaxColumn(lineNumber); - if (column < lineMaxColumn) { - let nextColumnVisibleRange = ctx.visibleRangeForPosition2(lineNumber, column + 1); - if (nextColumnVisibleRange) { - let nextColumnHorizontalOffset = nextColumnVisibleRange.left; - mouseIsBetween = false; - mouseIsBetween = mouseIsBetween || (columnHorizontalOffset < request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset < nextColumnHorizontalOffset); // LTR case - mouseIsBetween = mouseIsBetween || (nextColumnHorizontalOffset < request.mouseContentHorizontalOffset && request.mouseContentHorizontalOffset < columnHorizontalOffset); // RTL case - if (mouseIsBetween) { - let rng = new EditorRange(lineNumber, column, lineNumber, column + 1); - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos, rng); - } - } - } - return request.fulfill(MouseTargetType.CONTENT_TEXT, pos); } @@ -940,4 +942,4 @@ export class MouseTargetFactory { hitTarget: null }; } -} \ No newline at end of file +} diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 8b3888eb9ee..ce528a8fdba 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -51,6 +51,40 @@ class VisibleTextAreaData { const canUseZeroSizeTextarea = (browser.isEdgeOrIE || browser.isFirefox); +interface LocalClipboardMetadata { + lastCopiedValue: string; + isFromEmptySelection: boolean; + multicursorText: string[]; +} + +/** + * Every time we write to the clipboard, we record a bit of extra metadata here. + * Every time we read from the cipboard, if the text matches our last written text, + * we can fetch the previous metadata. + */ +class LocalClipboardMetadataManager { + public static INSTANCE = new LocalClipboardMetadataManager(); + + private _lastState: LocalClipboardMetadata; + + constructor() { + this._lastState = null; + } + + public set(state: LocalClipboardMetadata): void { + this._lastState = state; + } + + public get(pastedText: string): LocalClipboardMetadata { + if (this._lastState && this._lastState.lastCopiedValue === pastedText) { + // match! + return this._lastState; + } + this._lastState = null; + return null; + } +} + export class TextAreaHandler extends ViewPart { private readonly _viewController: ViewController; @@ -70,8 +104,6 @@ export class TextAreaHandler extends ViewPart { */ private _visibleTextArea: VisibleTextAreaData; private _selections: Selection[]; - private _lastCopiedValue: string; - private _lastCopiedValueIsFromEmptySelection: boolean; public readonly textArea: FastDomNode; public readonly textAreaCover: FastDomNode; @@ -97,8 +129,6 @@ export class TextAreaHandler extends ViewPart { this._visibleTextArea = null; this._selections = [new Selection(1, 1, 1, 1)]; - this._lastCopiedValue = null; - this._lastCopiedValueIsFromEmptySelection = false; // Text Area (The focus will always be in the textarea when the cursor is blinking) this.textArea = createFastDomNode(document.createElement('textarea')); @@ -132,21 +162,29 @@ export class TextAreaHandler extends ViewPart { const textAreaInputHost: ITextAreaInputHost = { getPlainTextToCopy: (): string => { - const whatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard); + const rawWhatToCopy = this._context.model.getPlainTextToCopy(this._selections, this._emptySelectionClipboard); + const newLineCharacter = this._context.model.getEOL(); - if (this._emptySelectionClipboard) { - if (browser.isFirefox) { - // When writing "LINE\r\n" to the clipboard and then pasting, - // Firefox pastes "LINE\n", so let's work around this quirk - this._lastCopiedValue = whatToCopy.replace(/\r\n/g, '\n'); - } else { - this._lastCopiedValue = whatToCopy; - } + const isFromEmptySelection = (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty()); + const multicursorText = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy : null); + const whatToCopy = (Array.isArray(rawWhatToCopy) ? rawWhatToCopy.join(newLineCharacter) : rawWhatToCopy); - let selections = this._selections; - this._lastCopiedValueIsFromEmptySelection = (selections.length === 1 && selections[0].isEmpty()); + let metadata: LocalClipboardMetadata = null; + if (isFromEmptySelection || multicursorText) { + // Only store the non-default metadata + + // When writing "LINE\r\n" to the clipboard and then pasting, + // Firefox pastes "LINE\n", so let's work around this quirk + const lastCopiedValue = (browser.isFirefox ? whatToCopy.replace(/\r\n/g, '\n') : whatToCopy); + metadata = { + lastCopiedValue: lastCopiedValue, + isFromEmptySelection: (this._emptySelectionClipboard && this._selections.length === 1 && this._selections[0].isEmpty()), + multicursorText: multicursorText + }; } + LocalClipboardMetadataManager.INSTANCE.set(metadata); + return whatToCopy; }, @@ -199,11 +237,15 @@ export class TextAreaHandler extends ViewPart { })); this._register(this._textAreaInput.onPaste((e: IPasteData) => { + const metadata = LocalClipboardMetadataManager.INSTANCE.get(e.text); + let pasteOnNewLine = false; - if (this._emptySelectionClipboard) { - pasteOnNewLine = (e.text === this._lastCopiedValue && this._lastCopiedValueIsFromEmptySelection); + let multicursorText: string[] = null; + if (metadata) { + pasteOnNewLine = (this._emptySelectionClipboard && metadata.isFromEmptySelection); + multicursorText = metadata.multicursorText; } - this._viewController.paste('keyboard', e.text, pasteOnNewLine); + this._viewController.paste('keyboard', e.text, pasteOnNewLine, multicursorText); })); this._register(this._textAreaInput.onCut(() => { diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 5dfe1c8c4c5..e6d9eaf8e93 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -210,6 +210,8 @@ const _CSS_MAP = { borderStyle: 'border-style:{0};', borderWidth: 'border-width:{0};', + fontStyle: 'font-style:{0};', + fontWeight: 'font-weight:{0};', textDecoration: 'text-decoration:{0};', cursor: 'cursor:{0};', letterSpacing: 'letter-spacing:{0};', @@ -357,7 +359,7 @@ class DecorationCSSRules { return ''; } let cssTextArr: string[] = []; - this.collectCSSText(opts, ['textDecoration', 'cursor', 'color', 'letterSpacing'], cssTextArr); + this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'cursor', 'color', 'letterSpacing'], cssTextArr); return cssTextArr.join(''); } @@ -372,10 +374,12 @@ class DecorationCSSRules { if (typeof opts !== 'undefined') { this.collectBorderSettingsCSSText(opts, cssTextArr); - if (typeof opts.contentIconPath === 'string') { - cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, URI.file(opts.contentIconPath).toString().replace(/'/g, '%27'))); - } else if (opts.contentIconPath instanceof URI) { - cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, opts.contentIconPath.toString(true).replace(/'/g, '%27'))); + if (typeof opts.contentIconPath !== 'undefined') { + if (typeof opts.contentIconPath === 'string') { + cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, URI.file(opts.contentIconPath).toString().replace(/'/g, '%27'))); + } else { + cssTextArr.push(strings.format(_CSS_MAP.contentIconPath, URI.revive(opts.contentIconPath).toString(true).replace(/'/g, '%27'))); + } } if (typeof opts.contentText === 'string') { const truncated = opts.contentText.match(/^.*$/m)[0]; // only take first line @@ -383,7 +387,7 @@ class DecorationCSSRules { cssTextArr.push(strings.format(_CSS_MAP.contentText, escaped)); } - this.collectCSSText(opts, ['textDecoration', 'color', 'backgroundColor', 'margin'], cssTextArr); + this.collectCSSText(opts, ['fontStyle', 'fontWeight', 'textDecoration', 'color', 'backgroundColor', 'margin'], cssTextArr); if (this.collectCSSText(opts, ['width', 'height'], cssTextArr)) { cssTextArr.push('display:inline-block;'); } @@ -405,7 +409,7 @@ class DecorationCSSRules { if (typeof opts.gutterIconPath === 'string') { cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.file(opts.gutterIconPath).toString())); } else { - cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, opts.gutterIconPath.toString(true).replace(/'/g, '%27'))); + cssTextArr.push(strings.format(_CSS_MAP.gutterIconPath, URI.revive(opts.gutterIconPath).toString(true).replace(/'/g, '%27'))); } if (typeof opts.gutterIconSize !== 'undefined') { cssTextArr.push(strings.format(_CSS_MAP.gutterIconSize, opts.gutterIconSize)); diff --git a/src/vs/editor/browser/view/viewController.ts b/src/vs/editor/browser/view/viewController.ts index f817cd36c73..30f1155a1a8 100644 --- a/src/vs/editor/browser/view/viewController.ts +++ b/src/vs/editor/browser/view/viewController.ts @@ -62,10 +62,11 @@ export class ViewController { this._execCoreEditorCommandFunc(editorCommand, args); } - public paste(source: string, text: string, pasteOnNewLine: boolean): void { + public paste(source: string, text: string, pasteOnNewLine: boolean, multicursorText: string[]): void { this.commandService.executeCommand(editorCommon.Handler.Paste, { text: text, pasteOnNewLine: pasteOnNewLine, + multicursorText: multicursorText }); } diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 08c9c688aed..54f9b156840 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -450,6 +450,13 @@ export class View extends ViewEventHandler { this._scrollbar.delegateVerticalScrollbarMouseDown(browserEvent); } + public restoreState(scrollPosition: { scrollLeft: number; scrollTop: number; }): void { + this._context.viewLayout.setScrollPositionNow({ scrollTop: scrollPosition.scrollTop }); + this._renderNow(); + this.viewLines.updateLineWidths(); + this._context.viewLayout.setScrollPositionNow({ scrollLeft: scrollPosition.scrollLeft }); + } + public getOffsetForColumn(modelLineNumber: number, modelColumn: number): number { let modelPosition = this._context.model.validateModelPosition({ lineNumber: modelLineNumber, @@ -472,8 +479,8 @@ export class View extends ViewEventHandler { return this.outgoingEvents; } - public createOverviewRuler(cssClassName: string, minimumHeight: number, maximumHeight: number): OverviewRuler { - return new OverviewRuler(this._context, cssClassName, minimumHeight, maximumHeight); + public createOverviewRuler(cssClassName: string): OverviewRuler { + return new OverviewRuler(this._context, cssClassName); } public change(callback: (changeAccessor: editorBrowser.IViewZoneChangeAccessor) => any): boolean { diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 096cb21474a..312129a2457 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -18,7 +18,6 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { private _lineHeight: number; private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; private _selectionIsEmpty: boolean; - private _primaryCursorIsInEditableRange: boolean; private _primaryCursorLineNumber: number; private _scrollWidth: number; private _contentWidth: number; @@ -30,7 +29,6 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight; this._selectionIsEmpty = true; - this._primaryCursorIsInEditableRange = true; this._primaryCursorLineNumber = 1; this._scrollWidth = 0; this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth; @@ -61,11 +59,6 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { let hasChanged = false; - if (this._primaryCursorIsInEditableRange !== e.isInEditableRange) { - this._primaryCursorIsInEditableRange = e.isInEditableRange; - hasChanged = true; - } - const primaryCursorLineNumber = e.selections[0].positionLineNumber; if (this._primaryCursorLineNumber !== primaryCursorLineNumber) { this._primaryCursorLineNumber = primaryCursorLineNumber; @@ -127,14 +120,12 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty - && this._primaryCursorIsInEditableRange ); } private _willRenderMarginCurrentLine(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - && this._primaryCursorIsInEditableRange ); } } diff --git a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts index 23e482db0c1..cc3fd73eb6f 100644 --- a/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineMarginHighlight/currentLineMarginHighlight.ts @@ -18,7 +18,6 @@ export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { private _lineHeight: number; private _renderLineHighlight: 'none' | 'gutter' | 'line' | 'all'; private _selectionIsEmpty: boolean; - private _primaryCursorIsInEditableRange: boolean; private _primaryCursorLineNumber: number; private _contentLeft: number; @@ -29,7 +28,6 @@ export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight; this._selectionIsEmpty = true; - this._primaryCursorIsInEditableRange = true; this._primaryCursorLineNumber = 1; this._contentLeft = this._context.configuration.editor.layoutInfo.contentLeft; @@ -59,11 +57,6 @@ export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { public onCursorStateChanged(e: viewEvents.ViewCursorStateChangedEvent): boolean { let hasChanged = false; - if (this._primaryCursorIsInEditableRange !== e.isInEditableRange) { - this._primaryCursorIsInEditableRange = e.isInEditableRange; - hasChanged = true; - } - const primaryCursorLineNumber = e.selections[0].positionLineNumber; if (this._primaryCursorLineNumber !== primaryCursorLineNumber) { this._primaryCursorLineNumber = primaryCursorLineNumber; @@ -120,7 +113,6 @@ export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { private _shouldShowCurrentLine(): boolean { return ( (this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all') - && this._primaryCursorIsInEditableRange ); } @@ -128,7 +120,6 @@ export class CurrentLineMarginHighlightOverlay extends DynamicViewOverlay { return ( (this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all') && this._selectionIsEmpty - && this._primaryCursorIsInEditableRange ); } } diff --git a/src/vs/editor/browser/viewParts/lines/viewLines.ts b/src/vs/editor/browser/viewParts/lines/viewLines.ts index 762433ff412..c42f3e90d14 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLines.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLines.ts @@ -455,6 +455,10 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost, // --- implementation + public updateLineWidths(): void { + this._updateLineWidths(false); + } + /** * Updates the max line width if it is fast to compute. * Returns true if all lines were taken into account. diff --git a/src/vs/editor/browser/viewParts/minimap/minimap.ts b/src/vs/editor/browser/viewParts/minimap/minimap.ts index 8b4cc2a9979..62c2629841d 100644 --- a/src/vs/editor/browser/viewParts/minimap/minimap.ts +++ b/src/vs/editor/browser/viewParts/minimap/minimap.ts @@ -868,10 +868,9 @@ export class Minimap extends ViewPart { let charIndex = 0; let tabsCharDelta = 0; - for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) { - const token = tokens[tokenIndex]; - const tokenEndIndex = token.endIndex; - const tokenColorId = token.getForeground(); + for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) { + const tokenEndIndex = tokens.getEndOffset(tokenIndex); + const tokenColorId = tokens.getForeground(tokenIndex); const tokenColor = colorTracker.getColor(tokenColorId); for (; charIndex < tokenEndIndex; charIndex++) { @@ -927,4 +926,4 @@ registerThemingParticipant((theme, collector) => { if (shadow) { collector.addRule(`.monaco-editor .minimap-shadow-visible { box-shadow: ${shadow} -6px 0 6px -6px inset; }`); } -}); \ No newline at end of file +}); diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index bb2b4ee58ce..1b450611c58 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -6,37 +6,41 @@ import { ViewEventHandler } from 'vs/editor/common/viewModel/viewEventHandler'; import { IOverviewRuler } from 'vs/editor/browser/editorBrowser'; -import { OverviewRulerImpl } from 'vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl'; import { ViewContext } from 'vs/editor/common/view/viewContext'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { OverviewRulerPosition } from 'vs/editor/common/config/editorOptions'; -import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; +import { OverviewRulerZone, OverviewZoneManager, ColorZone } from 'vs/editor/common/view/overviewZoneManager'; +import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { private _context: ViewContext; - private _overviewRuler: OverviewRulerImpl; + private _domNode: FastDomNode; + private _zoneManager: OverviewZoneManager; - constructor(context: ViewContext, cssClassName: string, minimumHeight: number, maximumHeight: number) { + constructor(context: ViewContext, cssClassName: string) { super(); this._context = context; - this._overviewRuler = new OverviewRulerImpl( - 0, - cssClassName, - this._context.viewLayout.getScrollHeight(), - this._context.configuration.editor.lineHeight, - this._context.configuration.editor.pixelRatio, - minimumHeight, - maximumHeight, - (lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber) - ); + + this._domNode = createFastDomNode(document.createElement('canvas')); + this._domNode.setClassName(cssClassName); + this._domNode.setPosition('absolute'); + this._domNode.setLayerHinting(true); + + this._zoneManager = new OverviewZoneManager((lineNumber: number) => this._context.viewLayout.getVerticalOffsetForLineNumber(lineNumber)); + this._zoneManager.setDOMWidth(0); + this._zoneManager.setDOMHeight(0); + this._zoneManager.setOuterHeight(this._context.viewLayout.getScrollHeight()); + this._zoneManager.setLineHeight(this._context.configuration.editor.lineHeight); + + this._zoneManager.setPixelRatio(this._context.configuration.editor.pixelRatio); this._context.addEventHandler(this); } public dispose(): void { this._context.removeEventHandler(this); - this._overviewRuler.dispose(); + this._zoneManager = null; super.dispose(); } @@ -44,40 +48,118 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { public onConfigurationChanged(e: viewEvents.ViewConfigurationChangedEvent): boolean { if (e.lineHeight) { - this._overviewRuler.setLineHeight(this._context.configuration.editor.lineHeight, true); + this._zoneManager.setLineHeight(this._context.configuration.editor.lineHeight); + this._render(); } if (e.pixelRatio) { - this._overviewRuler.setPixelRatio(this._context.configuration.editor.pixelRatio, true); + this._zoneManager.setPixelRatio(this._context.configuration.editor.pixelRatio); + this._domNode.setWidth(this._zoneManager.getDOMWidth()); + this._domNode.setHeight(this._zoneManager.getDOMHeight()); + this._domNode.domNode.width = this._zoneManager.getCanvasWidth(); + this._domNode.domNode.height = this._zoneManager.getCanvasHeight(); + this._render(); } return true; } - public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { + this._render(); return true; } - public onScrollChanged(e: viewEvents.ViewScrollChangedEvent): boolean { - this._overviewRuler.setScrollHeight(e.scrollHeight, true); - return super.onScrollChanged(e) || e.scrollHeightChanged; + if (e.scrollHeightChanged) { + this._zoneManager.setOuterHeight(e.scrollHeight); + this._render(); + } + return true; } - public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean { + this._render(); return true; } // ---- end view event handlers public getDomNode(): HTMLElement { - return this._overviewRuler.getDomNode(); + return this._domNode.domNode; } public setLayout(position: OverviewRulerPosition): void { - this._overviewRuler.setLayout(position, true); + this._domNode.setTop(position.top); + this._domNode.setRight(position.right); + + let hasChanged = false; + hasChanged = this._zoneManager.setDOMWidth(position.width) || hasChanged; + hasChanged = this._zoneManager.setDOMHeight(position.height) || hasChanged; + + if (hasChanged) { + this._domNode.setWidth(this._zoneManager.getDOMWidth()); + this._domNode.setHeight(this._zoneManager.getDOMHeight()); + this._domNode.domNode.width = this._zoneManager.getCanvasWidth(); + this._domNode.domNode.height = this._zoneManager.getCanvasHeight(); + + this._render(); + } } public setZones(zones: OverviewRulerZone[]): void { - this._overviewRuler.setZones(zones, true); + this._zoneManager.setZones(zones); + this._render(); } -} \ No newline at end of file + + private _render(): boolean { + if (this._zoneManager.getOuterHeight() === 0) { + return false; + } + + const width = this._zoneManager.getCanvasWidth(); + const height = this._zoneManager.getCanvasHeight(); + + let colorZones = this._zoneManager.resolveColorZones(); + let id2Color = this._zoneManager.getId2Color(); + + let ctx = this._domNode.domNode.getContext('2d'); + ctx.clearRect(0, 0, width, height); + if (colorZones.length > 0) { + this._renderOneLane(ctx, colorZones, id2Color, width); + } + + return true; + } + + private _renderOneLane(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], width: number): void { + + let currentColorId = 0; + let currentFrom = 0; + let currentTo = 0; + + for (let i = 0, len = colorZones.length; i < len; i++) { + const zone = colorZones[i]; + + const zoneColorId = zone.colorId; + const zoneFrom = zone.from; + const zoneTo = zone.to; + + if (zoneColorId !== currentColorId) { + ctx.fillRect(0, currentFrom, width, currentTo - currentFrom); + + currentColorId = zoneColorId; + ctx.fillStyle = id2Color[currentColorId]; + currentFrom = zoneFrom; + currentTo = zoneTo; + } else { + if (currentTo >= zoneFrom) { + currentTo = Math.max(currentTo, zoneTo); + } else { + ctx.fillRect(0, currentFrom, width, currentTo - currentFrom); + currentFrom = zoneFrom; + currentTo = zoneTo; + } + } + } + + ctx.fillRect(0, currentFrom, width, currentTo - currentFrom); + + } +} diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts deleted file mode 100644 index eec8530ef03..00000000000 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRulerImpl.ts +++ /dev/null @@ -1,250 +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 { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { OverviewRulerLane } from 'vs/editor/common/editorCommon'; -import { OverviewZoneManager, ColorZone, OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; -import { Color } from 'vs/base/common/color'; -import { OverviewRulerPosition } from 'vs/editor/common/config/editorOptions'; -import { ThemeType, LIGHT } from 'vs/platform/theme/common/themeService'; - -export class OverviewRulerImpl { - - private _canvasLeftOffset: number; - private _domNode: FastDomNode; - private _lanesCount: number; - private _zoneManager: OverviewZoneManager; - private _background: Color; - - constructor( - canvasLeftOffset: number, cssClassName: string, scrollHeight: number, lineHeight: number, - pixelRatio: number, minimumHeight: number, maximumHeight: number, - getVerticalOffsetForLine: (lineNumber: number) => number - ) { - this._canvasLeftOffset = canvasLeftOffset; - - this._domNode = createFastDomNode(document.createElement('canvas')); - - this._domNode.setClassName(cssClassName); - this._domNode.setPosition('absolute'); - this._domNode.setLayerHinting(true); - - this._lanesCount = 3; - - this._background = null; - - this._zoneManager = new OverviewZoneManager(getVerticalOffsetForLine); - this._zoneManager.setMinimumHeight(minimumHeight); - this._zoneManager.setMaximumHeight(maximumHeight); - this._zoneManager.setThemeType(LIGHT); - this._zoneManager.setDOMWidth(0); - this._zoneManager.setDOMHeight(0); - this._zoneManager.setOuterHeight(scrollHeight); - this._zoneManager.setLineHeight(lineHeight); - - this._zoneManager.setPixelRatio(pixelRatio); - } - - public dispose(): void { - this._zoneManager = null; - } - - public setLayout(position: OverviewRulerPosition, render: boolean): void { - this._domNode.setTop(position.top); - this._domNode.setRight(position.right); - - let hasChanged = false; - hasChanged = this._zoneManager.setDOMWidth(position.width) || hasChanged; - hasChanged = this._zoneManager.setDOMHeight(position.height) || hasChanged; - - if (hasChanged) { - this._domNode.setWidth(this._zoneManager.getDOMWidth()); - this._domNode.setHeight(this._zoneManager.getDOMHeight()); - this._domNode.domNode.width = this._zoneManager.getCanvasWidth(); - this._domNode.domNode.height = this._zoneManager.getCanvasHeight(); - - if (render) { - this.render(true); - } - } - } - - public getLanesCount(): number { - return this._lanesCount; - } - - public setLanesCount(newLanesCount: number, render: boolean): void { - this._lanesCount = newLanesCount; - - if (render) { - this.render(true); - } - } - - public setThemeType(themeType: ThemeType, render: boolean): void { - this._zoneManager.setThemeType(themeType); - - if (render) { - this.render(true); - } - } - - public setUseBackground(background: Color, render: boolean): void { - this._background = background; - - if (render) { - this.render(true); - } - } - - public getDomNode(): HTMLCanvasElement { - return this._domNode.domNode; - } - - public getPixelWidth(): number { - return this._zoneManager.getCanvasWidth(); - } - - public getPixelHeight(): number { - return this._zoneManager.getCanvasHeight(); - } - - public setScrollHeight(scrollHeight: number, render: boolean): void { - this._zoneManager.setOuterHeight(scrollHeight); - if (render) { - this.render(true); - } - } - - public setLineHeight(lineHeight: number, render: boolean): void { - this._zoneManager.setLineHeight(lineHeight); - if (render) { - this.render(true); - } - } - - public setPixelRatio(pixelRatio: number, render: boolean): void { - this._zoneManager.setPixelRatio(pixelRatio); - this._domNode.setWidth(this._zoneManager.getDOMWidth()); - this._domNode.setHeight(this._zoneManager.getDOMHeight()); - this._domNode.domNode.width = this._zoneManager.getCanvasWidth(); - this._domNode.domNode.height = this._zoneManager.getCanvasHeight(); - if (render) { - this.render(true); - } - } - - public setZones(zones: OverviewRulerZone[], render: boolean): void { - this._zoneManager.setZones(zones); - if (render) { - this.render(false); - } - } - - public render(forceRender: boolean): boolean { - if (this._zoneManager.getOuterHeight() === 0) { - return false; - } - - const width = this._zoneManager.getCanvasWidth(); - const height = this._zoneManager.getCanvasHeight(); - - let colorZones = this._zoneManager.resolveColorZones(); - let id2Color = this._zoneManager.getId2Color(); - - let ctx = this._domNode.domNode.getContext('2d'); - if (this._background === null) { - ctx.clearRect(0, 0, width, height); - } else { - ctx.fillStyle = Color.Format.CSS.formatHex(this._background); - ctx.fillRect(0, 0, width, height); - } - - if (colorZones.length > 0) { - let remainingWidth = width - this._canvasLeftOffset; - - if (this._lanesCount >= 3) { - this._renderThreeLanes(ctx, colorZones, id2Color, remainingWidth); - } else if (this._lanesCount === 2) { - this._renderTwoLanes(ctx, colorZones, id2Color, remainingWidth); - } else if (this._lanesCount === 1) { - this._renderOneLane(ctx, colorZones, id2Color, remainingWidth); - } - } - - return true; - } - - private _renderOneLane(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void { - - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left | OverviewRulerLane.Center | OverviewRulerLane.Right, this._canvasLeftOffset, w); - - } - - private _renderTwoLanes(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void { - - let leftWidth = Math.floor(w / 2); - let rightWidth = w - leftWidth; - let leftOffset = this._canvasLeftOffset; - let rightOffset = this._canvasLeftOffset + leftWidth; - - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left | OverviewRulerLane.Center, leftOffset, leftWidth); - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Right, rightOffset, rightWidth); - } - - private _renderThreeLanes(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], w: number): void { - - let leftWidth = Math.floor(w / 3); - let rightWidth = Math.floor(w / 3); - let centerWidth = w - leftWidth - rightWidth; - let leftOffset = this._canvasLeftOffset; - let centerOffset = this._canvasLeftOffset + leftWidth; - let rightOffset = this._canvasLeftOffset + leftWidth + centerWidth; - - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Left, leftOffset, leftWidth); - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Center, centerOffset, centerWidth); - this._renderVerticalPatch(ctx, colorZones, id2Color, OverviewRulerLane.Right, rightOffset, rightWidth); - } - - private _renderVerticalPatch(ctx: CanvasRenderingContext2D, colorZones: ColorZone[], id2Color: string[], laneMask: number, xpos: number, width: number): void { - - let currentColorId = 0; - let currentFrom = 0; - let currentTo = 0; - - for (let i = 0, len = colorZones.length; i < len; i++) { - let zone = colorZones[i]; - - if (!(zone.position & laneMask)) { - continue; - } - - let zoneColorId = zone.colorId; - let zoneFrom = zone.from; - let zoneTo = zone.to; - - if (zoneColorId !== currentColorId) { - ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom); - - currentColorId = zoneColorId; - ctx.fillStyle = id2Color[currentColorId]; - currentFrom = zoneFrom; - currentTo = zoneTo; - } else { - if (currentTo >= zoneFrom) { - currentTo = Math.max(currentTo, zoneTo); - } else { - ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom); - currentFrom = zoneFrom; - currentTo = zoneTo; - } - } - } - - ctx.fillRect(xpos, currentFrom, width, currentTo - currentFrom); - - } -} diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts index 5288f92d7ea..0ea17e25233 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursor.ts @@ -23,17 +23,13 @@ export interface IViewCursorRenderData { } class ViewCursorRenderData { - public readonly top: number; - public readonly left: number; - public readonly width: number; - public readonly textContent: string; - - constructor(top: number, left: number, width: number, textContent: string) { - this.top = top; - this.left = left; - this.width = width; - this.textContent = textContent; - } + constructor( + public readonly top: number, + public readonly left: number, + public readonly width: number, + public readonly height: number, + public readonly textContent: string + ) { } } export class ViewCursor { @@ -48,7 +44,6 @@ export class ViewCursor { private _isVisible: boolean; private _position: Position; - private _isInEditableRange: boolean; private _lastRenderedContent: string; private _renderData: ViewCursorRenderData; @@ -77,7 +72,6 @@ export class ViewCursor { this._domNode.setDisplay('none'); this.updatePosition(new Position(1, 1)); - this._isInEditableRange = true; this._lastRenderedContent = ''; this._renderData = null; @@ -87,10 +81,6 @@ export class ViewCursor { return this._domNode; } - public getIsInEditableRange(): boolean { - return this._isInEditableRange; - } - public getPosition(): Position { return this._position; } @@ -123,9 +113,8 @@ export class ViewCursor { return true; } - public onCursorPositionChanged(position: Position, isInEditableRange: boolean): boolean { + public onCursorPositionChanged(position: Position): boolean { this.updatePosition(position); - this._isInEditableRange = isInEditableRange; return true; } @@ -143,7 +132,7 @@ export class ViewCursor { width = dom.computeScreenAwareSize(1); } const top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta; - return new ViewCursorRenderData(top, visibleRange.left, width, ''); + return new ViewCursorRenderData(top, visibleRange.left, width, this._lineHeight, ''); } const visibleRangeForCharacter = ctx.linesVisibleRangesForRange(new Range(this._position.lineNumber, this._position.column, this._position.lineNumber, this._position.column + 1), false); @@ -162,8 +151,16 @@ export class ViewCursor { textContent = lineContent.charAt(this._position.column - 1); } - const top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta; - return new ViewCursorRenderData(top, range.left, width, textContent); + let top = ctx.getVerticalOffsetForLineNumber(this._position.lineNumber) - ctx.bigNumbersDelta; + let height = this._lineHeight; + + // Underline might interfere with clicking + if (this._cursorStyle === TextEditorCursorStyle.Underline || this._cursorStyle === TextEditorCursorStyle.UnderlineThin) { + top += this._lineHeight - 2; + height = 2; + } + + return new ViewCursorRenderData(top, range.left, width, height, textContent); } public prepareRender(ctx: RenderingContext): void { @@ -185,14 +182,14 @@ export class ViewCursor { this._domNode.setTop(this._renderData.top); this._domNode.setLeft(this._renderData.left); this._domNode.setWidth(this._renderData.width); - this._domNode.setLineHeight(this._lineHeight); - this._domNode.setHeight(this._lineHeight); + this._domNode.setLineHeight(this._renderData.height); + this._domNode.setHeight(this._renderData.height); return { domNode: this._domNode.domNode, position: this._position, contentLeft: this._renderData.left, - height: this._lineHeight, + height: this._renderData.height, width: 2 }; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 175a78f6464..c54b332a631 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -101,8 +101,8 @@ export class ViewCursors extends ViewPart { } return true; } - private _onCursorPositionChanged(position: Position, secondaryPositions: Position[], isInEditableRange: boolean): void { - this._primaryCursor.onCursorPositionChanged(position, isInEditableRange); + private _onCursorPositionChanged(position: Position, secondaryPositions: Position[]): void { + this._primaryCursor.onCursorPositionChanged(position); this._updateBlinking(); if (this._secondaryCursors.length < secondaryPositions.length) { @@ -123,7 +123,7 @@ export class ViewCursors extends ViewPart { } for (let i = 0; i < secondaryPositions.length; i++) { - this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i], isInEditableRange); + this._secondaryCursors[i].onCursorPositionChanged(secondaryPositions[i]); } } @@ -132,7 +132,7 @@ export class ViewCursors extends ViewPart { for (let i = 0, len = e.selections.length; i < len; i++) { positions[i] = e.selections[i].getPosition(); } - this._onCursorPositionChanged(positions[0], positions.slice(1), e.isInEditableRange); + this._onCursorPositionChanged(positions[0], positions.slice(1)); const selectionIsEmpty = e.selections[0].isEmpty(); if (this._selectionIsEmpty !== selectionIsEmpty) { @@ -198,7 +198,7 @@ export class ViewCursors extends ViewPart { if (!this._editorHasFocus) { return TextEditorCursorBlinkingStyle.Hidden; } - if (this._readOnly || !this._primaryCursor.getIsInEditableRange()) { + if (this._readOnly) { return TextEditorCursorBlinkingStyle.Solid; } return this._cursorBlinking; diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 34eb4d6e194..2cf42479c4d 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -157,8 +157,8 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito super.dispose(); } - public createOverviewRuler(cssClassName: string, minimumHeight: number, maximumHeight: number): editorBrowser.IOverviewRuler { - return this._view.createOverviewRuler(cssClassName, minimumHeight, maximumHeight); + public createOverviewRuler(cssClassName: string): editorBrowser.IOverviewRuler { + return this._view.createOverviewRuler(cssClassName); } public getDomNode(): HTMLElement { @@ -392,6 +392,16 @@ export abstract class CodeEditorWidget extends CommonCodeEditor implements edito viewEventBus.onKeyDown = (e) => this._onKeyDown.fire(e); } + public restoreViewState(s: editorCommon.ICodeEditorViewState): void { + super.restoreViewState(s); + if (!this.cursor || !this.hasView) { + return; + } + if (s && s.cursorState && s.viewState) { + this._view.restoreState(this.viewModel.viewLayout.reduceRestoreState(s.viewState)); + } + } + protected _detachModel(): editorCommon.IModel { let removeDomNode: HTMLElement = null; diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index 34bfc9c3f15..27494972896 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -24,7 +24,7 @@ import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { CodeEditor } from 'vs/editor/browser/codeEditor'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Selection, ISelection } from 'vs/editor/common/core/selection'; @@ -38,7 +38,7 @@ import { scrollbarShadow, diffInserted, diffRemoved, defaultInsertColor, default import { Color } from 'vs/base/common/color'; import { OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; import { IEditorWhitespace } from 'vs/editor/common/viewLayout/whitespaceComputer'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { DiffReview } from 'vs/editor/browser/widget/diffReview'; import URI from 'vs/base/common/uri'; import { IMessageService } from 'vs/platform/message/common/message'; @@ -361,14 +361,14 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE this._overviewDomElement.removeChild(this._originalOverviewRuler.getDomNode()); this._originalOverviewRuler.dispose(); } - this._originalOverviewRuler = this.originalEditor.createOverviewRuler('original diffOverviewRuler', 4, Number.MAX_VALUE); + this._originalOverviewRuler = this.originalEditor.createOverviewRuler('original diffOverviewRuler'); this._overviewDomElement.appendChild(this._originalOverviewRuler.getDomNode()); if (this._modifiedOverviewRuler) { this._overviewDomElement.removeChild(this._modifiedOverviewRuler.getDomNode()); this._modifiedOverviewRuler.dispose(); } - this._modifiedOverviewRuler = this.modifiedEditor.createOverviewRuler('modified diffOverviewRuler', 4, Number.MAX_VALUE); + this._modifiedOverviewRuler = this.modifiedEditor.createOverviewRuler('modified diffOverviewRuler'); this._overviewDomElement.appendChild(this._modifiedOverviewRuler.getDomNode()); this._layoutOverviewRulers(); @@ -1594,6 +1594,7 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd } _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + const overviewZoneColor = this._removeColor.toString(); let result: IEditorDiffDecorations = { decorations: [], @@ -1614,16 +1615,10 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd result.decorations.push(createDecoration(lineChange.originalStartLineNumber, 1, lineChange.originalEndLineNumber, Number.MAX_VALUE, DECORATIONS.charDeleteWholeLine)); } - let color = this._removeColor.toString(); - result.overviewZones.push(new OverviewRulerZone( lineChange.originalStartLineNumber, lineChange.originalEndLineNumber, - editorCommon.OverviewRulerLane.Full, - 0, - color, - color, - color + overviewZoneColor )); if (lineChange.charChanges) { @@ -1659,6 +1654,7 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd } _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + const overviewZoneColor = this._insertColor.toString(); let result: IEditorDiffDecorations = { decorations: [], @@ -1679,15 +1675,10 @@ class DiffEdtorWidgetSideBySide extends DiffEditorWidgetStyle implements IDiffEd if (!isChangeOrDelete(lineChange) || !lineChange.charChanges) { result.decorations.push(createDecoration(lineChange.modifiedStartLineNumber, 1, lineChange.modifiedEndLineNumber, Number.MAX_VALUE, DECORATIONS.charInsertWholeLine)); } - let color = this._insertColor.toString(); result.overviewZones.push(new OverviewRulerZone( lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber, - editorCommon.OverviewRulerLane.Full, - 0, - color, - color, - color + overviewZoneColor )); if (lineChange.charChanges) { @@ -1783,6 +1774,8 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor } _getOriginalEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + const overviewZoneColor = this._removeColor.toString(); + let result: IEditorDiffDecorations = { decorations: [], overviewZones: [] @@ -1798,15 +1791,10 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor options: DECORATIONS.lineDeleteMargin }); - let color = this._removeColor.toString(); result.overviewZones.push(new OverviewRulerZone( lineChange.originalStartLineNumber, lineChange.originalEndLineNumber, - editorCommon.OverviewRulerLane.Full, - 0, - color, - color, - color + overviewZoneColor )); } } @@ -1815,6 +1803,7 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor } _getModifiedEditorDecorations(lineChanges: editorCommon.ILineChange[], ignoreTrimWhitespace: boolean, renderIndicators: boolean, originalEditor: editorBrowser.ICodeEditor, modifiedEditor: editorBrowser.ICodeEditor): IEditorDiffDecorations { + const overviewZoneColor = this._insertColor.toString(); let result: IEditorDiffDecorations = { decorations: [], @@ -1833,15 +1822,10 @@ class DiffEdtorWidgetInline extends DiffEditorWidgetStyle implements IDiffEditor options: (renderIndicators ? DECORATIONS.lineInsertWithSign : DECORATIONS.lineInsert) }); - let color = this._insertColor.toString(); result.overviewZones.push(new OverviewRulerZone( lineChange.modifiedStartLineNumber, lineChange.modifiedEndLineNumber, - editorCommon.OverviewRulerLane.Full, - 0, - color, - color, - color + overviewZoneColor )); if (lineChange.charChanges) { @@ -1973,6 +1957,12 @@ class InlineViewZonesComputer extends ViewZonesComputer { | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) ) >>> 0; + const tokens = new Uint32Array(2); + tokens[0] = lineContent.length; + tokens[1] = defaultMetadata; + + const lineTokens = new LineTokens(tokens, lineContent); + sb.appendASCIIString('
payload.text, payload.pasteOnNewLine); + this._paste(payload.text, payload.pasteOnNewLine, payload.multicursorText); break; case H.Cut: @@ -526,8 +517,8 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors { this._executeEditOperation(TypeOperations.replacePreviousChar(this._prevEditOperationType, this.context.config, this.context.model, this.getSelections(), text, replaceCharCnt)); } - private _paste(text: string, pasteOnNewLine: boolean): void { - this._executeEditOperation(TypeOperations.paste(this.context.config, this.context.model, this.getSelections(), pasteOnNewLine, text)); + private _paste(text: string, pasteOnNewLine: boolean, multicursorText: string[]): void { + this._executeEditOperation(TypeOperations.paste(this.context.config, this.context.model, this.getSelections(), text, pasteOnNewLine, multicursorText)); } private _cut(): void { @@ -601,17 +592,6 @@ class CommandExecutor { const rawOperations = commandsData.operations; - const editableRange = ctx.model.getEditableRange(); - const editableRangeStart = editableRange.getStartPosition(); - const editableRangeEnd = editableRange.getEndPosition(); - for (let i = 0, len = rawOperations.length; i < len; i++) { - const operationRange = rawOperations[i].range; - if (!editableRangeStart.isBeforeOrEqual(operationRange.getStartPosition()) || !operationRange.getEndPosition().isBeforeOrEqual(editableRangeEnd)) { - // These commands are outside of the editable range - return null; - } - } - const loserCursorsMap = this._getLoserCursorMap(rawOperations); if (loserCursorsMap.hasOwnProperty('0')) { // These commands are very messed up diff --git a/src/vs/editor/common/controller/cursorCommon.ts b/src/vs/editor/common/controller/cursorCommon.ts index 65743dc7253..576b0020171 100644 --- a/src/vs/editor/common/controller/cursorCommon.ts +++ b/src/vs/editor/common/controller/cursorCommon.ts @@ -381,50 +381,6 @@ export class CursorState { return states; } - public static ensureInEditableRange(context: CursorContext, states: CursorState[]): CursorState[] { - const model = context.model; - if (!model.hasEditableRange()) { - return states; - } - - const modelEditableRange = model.getEditableRange(); - const viewEditableRange = context.convertModelRangeToViewRange(modelEditableRange); - - let result: CursorState[] = []; - for (let i = 0, len = states.length; i < len; i++) { - const state = states[i]; - - if (state.modelState) { - const newModelState = CursorState._ensureInEditableRange(state.modelState, modelEditableRange); - result[i] = newModelState ? CursorState.fromModelState(newModelState) : state; - } else { - const newViewState = CursorState._ensureInEditableRange(state.viewState, viewEditableRange); - result[i] = newViewState ? CursorState.fromViewState(newViewState) : state; - } - } - return result; - } - - private static _ensureInEditableRange(state: SingleCursorState, editableRange: Range): SingleCursorState { - const position = state.position; - - if (position.lineNumber < editableRange.startLineNumber || (position.lineNumber === editableRange.startLineNumber && position.column < editableRange.startColumn)) { - return new SingleCursorState( - state.selectionStart, state.selectionStartLeftoverVisibleColumns, - new Position(editableRange.startLineNumber, editableRange.startColumn), 0 - ); - } - - if (position.lineNumber > editableRange.endLineNumber || (position.lineNumber === editableRange.endLineNumber && position.column > editableRange.endColumn)) { - return new SingleCursorState( - state.selectionStart, state.selectionStartLeftoverVisibleColumns, - new Position(editableRange.endLineNumber, editableRange.endColumn), 0 - ); - } - - return null; - } - readonly modelState: SingleCursorState; readonly viewState: SingleCursorState; diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index c3320572991..a39ec7751a0 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -155,22 +155,6 @@ export class CursorMoveCommands { } public static selectAll(context: CursorContext, cursor: CursorState): CursorState { - - if (context.model.hasEditableRange()) { - // Toggle between selecting editable range and selecting the entire buffer - - const editableRange = context.model.getEditableRange(); - const selection = cursor.modelState.selection; - - if (!selection.equalsRange(editableRange)) { - // Selection is not editable range => select editable range - return CursorState.fromModelState(new SingleCursorState( - new Range(editableRange.startLineNumber, editableRange.startColumn, editableRange.startLineNumber, editableRange.startColumn), 0, - new Position(editableRange.endLineNumber, editableRange.endColumn), 0 - )); - } - } - const lineCount = context.model.getLineCount(); const maxColumn = context.model.getLineMaxColumn(lineCount); diff --git a/src/vs/editor/common/controller/cursorTypeOperations.ts b/src/vs/editor/common/controller/cursorTypeOperations.ts index f06527815ae..dd726110ed9 100644 --- a/src/vs/editor/common/controller/cursorTypeOperations.ts +++ b/src/vs/editor/common/controller/cursorTypeOperations.ts @@ -109,7 +109,7 @@ export class TypeOperations { }); } - private static _distributePasteToCursors(selections: Selection[], pasteOnNewLine: boolean, text: string): string[] { + private static _distributePasteToCursors(selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): string[] { if (pasteOnNewLine) { return null; } @@ -118,22 +118,15 @@ export class TypeOperations { return null; } - for (let i = 0; i < selections.length; i++) { - if (selections[i].startLineNumber !== selections[i].endLineNumber) { - return null; - } + if (multicursorText && multicursorText.length === selections.length) { + return multicursorText; } - let pastePieces = text.split(/\r\n|\r|\n/); - if (pastePieces.length !== selections.length) { - return null; - } - - return pastePieces; + return null; } - public static paste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], pasteOnNewLine: boolean, text: string): EditOperationResult { - const distributedPaste = this._distributePasteToCursors(selections, pasteOnNewLine, text); + public static paste(config: CursorConfiguration, model: ICursorSimpleModel, selections: Selection[], text: string, pasteOnNewLine: boolean, multicursorText: string[]): EditOperationResult { + const distributedPaste = this._distributePasteToCursors(selections, text, pasteOnNewLine, multicursorText); if (distributedPaste) { selections = selections.sort(Range.compareRangesUsingStarts); diff --git a/src/vs/editor/common/core/lineTokens.ts b/src/vs/editor/common/core/lineTokens.ts index c4c5eda5e37..cc5d88db773 100644 --- a/src/vs/editor/common/core/lineTokens.ts +++ b/src/vs/editor/common/core/lineTokens.ts @@ -5,182 +5,207 @@ 'use strict'; import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding'; -import { ViewLineTokenFactory, ViewLineToken } from 'vs/editor/common/core/viewLineToken'; -import { ColorId, FontStyle, StandardTokenType, LanguageId } from 'vs/editor/common/modes'; +import { ColorId, StandardTokenType, LanguageId } from 'vs/editor/common/modes'; -export class LineToken { - _lineTokenBrand: void; - - private readonly _source: LineTokens; - private readonly _tokenCount: number; - - private _tokenIndex: number; - private _metadata: number; - private _startOffset: number; - private _endOffset: number; - - public get startOffset(): number { - return this._startOffset; - } - public get endOffset(): number { - return this._endOffset; - } - public get hasPrev(): boolean { - return (this._tokenIndex > 0); - } - public get hasNext(): boolean { - return (this._tokenIndex + 1 < this._tokenCount); - } - public get languageId(): LanguageId { - return TokenMetadata.getLanguageId(this._metadata); - } - public get tokenType(): StandardTokenType { - return TokenMetadata.getTokenType(this._metadata); - } - public get fontStyle(): FontStyle { - return TokenMetadata.getFontStyle(this._metadata); - } - public get foregroundId(): ColorId { - return TokenMetadata.getForeground(this._metadata); - } - public get backgroundId(): ColorId { - return TokenMetadata.getBackground(this._metadata); - } - - constructor(source: LineTokens, tokenIndex: number, tokenCount: number, startOffset: number, endOffset: number, metadata: number) { - this._source = source; - this._tokenCount = tokenCount; - this._set(tokenIndex, startOffset, endOffset, metadata); - } - - public clone(): LineToken { - return new LineToken(this._source, this._tokenIndex, this._tokenCount, this._startOffset, this._endOffset, this._metadata); - } - - _set(tokenIndex: number, startOffset: number, endOffset: number, metadata: number): void { - this._tokenIndex = tokenIndex; - this._metadata = metadata; - this._startOffset = startOffset; - this._endOffset = endOffset; - } - - public prev(): LineToken { - if (!this.hasPrev) { - return null; - } - this._source.tokenAt(this._tokenIndex - 1, this); - return this; - } - - public next(): LineToken { - if (!this.hasNext) { - return null; - } - this._source.tokenAt(this._tokenIndex + 1, this); - return this; - } +export interface IViewLineTokens { + equals(other: IViewLineTokens): boolean; + getCount(): number; + getForeground(tokenIndex: number): ColorId; + getEndOffset(tokenIndex: number): number; + getClassName(tokenIndex: number): string; + getInlineStyle(tokenIndex: number, colorMap: string[]): string; } -export class LineTokens { +export class LineTokens implements IViewLineTokens { _lineTokensBrand: void; private readonly _tokens: Uint32Array; private readonly _tokensCount: number; private readonly _text: string; - private readonly _textLength: number; constructor(tokens: Uint32Array, text: string) { this._tokens = tokens; this._tokensCount = (this._tokens.length >>> 1); this._text = text; - this._textLength = this._text.length; } - public getTokenCount(): number { - return this._tokensCount; + public equals(other: IViewLineTokens): boolean { + if (other instanceof LineTokens) { + return this.slicedEquals(other, 0, this._tokensCount); + } + return false; + } + + public slicedEquals(other: LineTokens, sliceFromTokenIndex: number, sliceTokenCount: number): boolean { + if (this._text !== other._text) { + return false; + } + if (this._tokensCount !== other._tokensCount) { + return false; + } + const from = (sliceFromTokenIndex << 1); + const to = from + (sliceTokenCount << 1); + for (let i = from; i < to; i++) { + if (this._tokens[i] !== other._tokens[i]) { + return false; + } + } + return true; } public getLineContent(): string { return this._text; } - public getTokenStartOffset(tokenIndex: number): number { - return this._tokens[(tokenIndex << 1)]; + public getCount(): number { + return this._tokensCount; + } + + public getStartOffset(tokenIndex: number): number { + if (tokenIndex > 0) { + return this._tokens[(tokenIndex - 1) << 1]; + } + return 0; } public getLanguageId(tokenIndex: number): LanguageId { - let metadata = this._tokens[(tokenIndex << 1) + 1]; + const metadata = this._tokens[(tokenIndex << 1) + 1]; return TokenMetadata.getLanguageId(metadata); } public getStandardTokenType(tokenIndex: number): StandardTokenType { - let metadata = this._tokens[(tokenIndex << 1) + 1]; + const metadata = this._tokens[(tokenIndex << 1) + 1]; return TokenMetadata.getTokenType(metadata); } - public getTokenEndOffset(tokenIndex: number): number { - if (tokenIndex + 1 < this._tokensCount) { - return this._tokens[(tokenIndex + 1) << 1]; - } - return this._textLength; + public getForeground(tokenIndex: number): ColorId { + const metadata = this._tokens[(tokenIndex << 1) + 1]; + return TokenMetadata.getForeground(metadata); + } + + public getClassName(tokenIndex: number): string { + const metadata = this._tokens[(tokenIndex << 1) + 1]; + return TokenMetadata.getClassNameFromMetadata(metadata); + } + + public getInlineStyle(tokenIndex: number, colorMap: string[]): string { + const metadata = this._tokens[(tokenIndex << 1) + 1]; + return TokenMetadata.getInlineStyleFromMetadata(metadata, colorMap); + } + + public getEndOffset(tokenIndex: number): number { + return this._tokens[tokenIndex << 1]; } /** * Find the token containing offset `offset`. - * ``` - * For example, with the following tokens [0, 5), [5, 9), [9, infinity) - * Searching for 0, 1, 2, 3 or 4 will return 0. - * Searching for 5, 6, 7 or 8 will return 1. - * Searching for 9, 10, 11, ... will return 2. - * ``` * @param offset The search offset * @return The index of the token containing the offset. */ public findTokenIndexAtOffset(offset: number): number { - return ViewLineTokenFactory.findIndexInSegmentsArray(this._tokens, offset); + return LineTokens.findIndexInTokensArray(this._tokens, offset); } - public findTokenAtOffset(offset: number): LineToken { - let tokenIndex = this.findTokenIndexAtOffset(offset); - return this.tokenAt(tokenIndex); + public inflate(): IViewLineTokens { + return this; } - public tokenAt(tokenIndex: number, dest?: LineToken): LineToken { - let startOffset = this._tokens[(tokenIndex << 1)]; - let endOffset: number; - if (tokenIndex + 1 < this._tokensCount) { - endOffset = this._tokens[(tokenIndex + 1) << 1]; - } else { - endOffset = this._textLength; + public sliceAndInflate(startOffset: number, endOffset: number, deltaOffset: number): IViewLineTokens { + return new SlicedLineTokens(this, startOffset, endOffset, deltaOffset); + } + + public static convertToEndOffset(tokens: Uint32Array, lineTextLength: number): void { + const tokenCount = (tokens.length >>> 1); + const lastTokenIndex = tokenCount - 1; + for (let tokenIndex = 0; tokenIndex < lastTokenIndex; tokenIndex++) { + tokens[tokenIndex << 1] = tokens[(tokenIndex + 1) << 1]; } - let metadata = this._tokens[(tokenIndex << 1) + 1]; + tokens[lastTokenIndex << 1] = lineTextLength; + } - if (dest) { - dest._set(tokenIndex, startOffset, endOffset, metadata); - return dest; + public static findIndexInTokensArray(tokens: Uint32Array, desiredIndex: number): number { + if (tokens.length <= 2) { + return 0; } - return new LineToken(this, tokenIndex, this._tokensCount, startOffset, endOffset, metadata); - } - public firstToken(): LineToken { - if (this._textLength === 0) { - return null; + let low = 0; + let high = (tokens.length >>> 1) - 1; + + while (low < high) { + + let mid = low + Math.floor((high - low) / 2); + let endOffset = tokens[(mid << 1)]; + + if (endOffset === desiredIndex) { + return mid + 1; + } else if (endOffset < desiredIndex) { + low = mid + 1; + } else if (endOffset > desiredIndex) { + high = mid; + } } - return this.tokenAt(0); - } - public lastToken(): LineToken { - if (this._textLength === 0) { - return null; - } - return this.tokenAt(this._tokensCount - 1); - } - - public inflate(): ViewLineToken[] { - return ViewLineTokenFactory.inflateArr(this._tokens, this._textLength); - } - - public sliceAndInflate(startOffset: number, endOffset: number, deltaOffset: number): ViewLineToken[] { - return ViewLineTokenFactory.sliceAndInflate(this._tokens, startOffset, endOffset, deltaOffset, this._textLength); + return low; + } +} + +export class SlicedLineTokens implements IViewLineTokens { + + private readonly _source: LineTokens; + private readonly _startOffset: number; + private readonly _endOffset: number; + private readonly _deltaOffset: number; + + private readonly _firstTokenIndex: number; + private readonly _tokensCount: number; + + constructor(source: LineTokens, startOffset: number, endOffset: number, deltaOffset: number) { + this._source = source; + this._startOffset = startOffset; + this._endOffset = endOffset; + this._deltaOffset = deltaOffset; + this._firstTokenIndex = source.findTokenIndexAtOffset(startOffset); + + this._tokensCount = 0; + for (let i = this._firstTokenIndex, len = source.getCount(); i < len; i++) { + const tokenStartOffset = source.getStartOffset(i); + if (tokenStartOffset >= endOffset) { + break; + } + this._tokensCount++; + } + } + + public equals(other: IViewLineTokens): boolean { + if (other instanceof SlicedLineTokens) { + return ( + this._startOffset === other._startOffset + && this._endOffset === other._endOffset + && this._deltaOffset === other._deltaOffset + && this._source.slicedEquals(other._source, this._firstTokenIndex, this._tokensCount) + ); + } + return false; + } + + public getCount(): number { + return this._tokensCount; + } + + public getForeground(tokenIndex: number): ColorId { + return this._source.getForeground(this._firstTokenIndex + tokenIndex); + } + + public getEndOffset(tokenIndex: number): number { + const tokenEndOffset = this._source.getEndOffset(this._firstTokenIndex + tokenIndex); + return Math.min(this._endOffset, tokenEndOffset) - this._startOffset + this._deltaOffset; + } + + public getClassName(tokenIndex: number): string { + return this._source.getClassName(this._firstTokenIndex + tokenIndex); + } + + public getInlineStyle(tokenIndex: number, colorMap: string[]): string { + return this._source.getInlineStyle(this._firstTokenIndex + tokenIndex, colorMap); } } diff --git a/src/vs/editor/common/diff/diffComputer.ts b/src/vs/editor/common/diff/diffComputer.ts index f9c02b3ee11..a1b88e19a4b 100644 --- a/src/vs/editor/common/diff/diffComputer.ts +++ b/src/vs/editor/common/diff/diffComputer.ts @@ -34,24 +34,6 @@ class MarkerSequence implements ISequence { this.endMarkers = endMarkers; } - public equals(other: any): boolean { - if (!(other instanceof MarkerSequence)) { - return false; - } - const otherMarkerSequence = other; - if (this.getLength() !== otherMarkerSequence.getLength()) { - return false; - } - for (let i = 0, len = this.getLength(); i < len; i++) { - const myElement = this.getElementHash(i); - const otherElement = otherMarkerSequence.getElementHash(i); - if (myElement !== otherElement) { - return false; - } - } - return true; - } - public getLength(): number { return this.startMarkers.length; } @@ -321,7 +303,6 @@ class LineChange implements ILineChange { export interface IDiffComputerOpts { shouldPostProcessCharChanges: boolean; shouldIgnoreTrimWhitespace: boolean; - shouldConsiderTrimWhitespaceInEmptyCase: boolean; shouldMakePrettyDiff: boolean; } @@ -347,11 +328,6 @@ export class DiffComputer { this.modifiedLines = modifiedLines; this.original = new LineMarkerSequence(originalLines); this.modified = new LineMarkerSequence(modifiedLines); - - if (opts.shouldConsiderTrimWhitespaceInEmptyCase && this.shouldIgnoreTrimWhitespace && this.original.equals(this.modified)) { - // Diff would be empty with `shouldIgnoreTrimWhitespace` - this.shouldIgnoreTrimWhitespace = false; - } } public computeDiff(): ILineChange[] { diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 4133daa1337..2a2da3bdc4a 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -5,7 +5,7 @@ 'use strict'; import { IMarkdownString } from 'vs/base/common/htmlContent'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; @@ -1071,24 +1071,6 @@ export interface IEditableTextModel extends ITextModel { * @internal */ redo(): Selection[]; - - /** - * Set an editable range on the model. - * @internal - */ - setEditableRange(range: IRange): void; - - /** - * Check if the model has an editable range. - * @internal - */ - hasEditableRange(): boolean; - - /** - * Get the editable range on the model. - * @internal - */ - getEditableRange(): Range; } /** @@ -1578,12 +1560,14 @@ export interface IThemeDecorationRenderOptions { borderStyle?: string; borderWidth?: string; + fontStyle?: string; + fontWeight?: string; textDecoration?: string; cursor?: string; color?: string | ThemeColor; letterSpacing?: string; - gutterIconPath?: string | URI; + gutterIconPath?: string | UriComponents; gutterIconSize?: string; overviewRulerColor?: string | ThemeColor; @@ -1597,10 +1581,12 @@ export interface IThemeDecorationRenderOptions { */ export interface IContentDecorationRenderOptions { contentText?: string; - contentIconPath?: string | URI; + contentIconPath?: string | UriComponents; border?: string; borderColor?: string | ThemeColor; + fontStyle?: string; + fontWeight?: string; textDecoration?: string; color?: string | ThemeColor; backgroundColor?: string | ThemeColor; diff --git a/src/vs/editor/common/model/editableTextModel.ts b/src/vs/editor/common/model/editableTextModel.ts deleted file mode 100644 index f91f7844d95..00000000000 --- a/src/vs/editor/common/model/editableTextModel.ts +++ /dev/null @@ -1,745 +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 { Range, IRange } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { EditStack } from 'vs/editor/common/model/editStack'; -import { ILineEdit, IModelLine } from 'vs/editor/common/model/modelLine'; -import { TextModelWithDecorations, ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; -import * as strings from 'vs/base/common/strings'; -import * as arrays from 'vs/base/common/arrays'; -import { Selection } from 'vs/editor/common/core/selection'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource'; -import { ModelRawContentChangedEvent, ModelRawChange, IModelContentChange, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/model/textModelEvents'; - -export interface IValidatedEditOperation { - sortIndex: number; - identifier: editorCommon.ISingleEditOperationIdentifier; - range: Range; - rangeOffset: number; - rangeLength: number; - lines: string[]; - forceMoveMarkers: boolean; - isAutoWhitespaceEdit: boolean; -} - -interface IIdentifiedLineEdit extends ILineEdit { - lineNumber: number; -} - -export class EditableTextModel extends TextModelWithDecorations implements editorCommon.IEditableTextModel { - - private _commandManager: EditStack; - - // for extra details about change events: - private _isUndoing: boolean; - private _isRedoing: boolean; - - // editable range - private _hasEditableRange: boolean; - private _editableRangeId: string; - - private _trimAutoWhitespaceLines: number[]; - - constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) { - super(rawTextSource, creationOptions, languageIdentifier); - - this._commandManager = new EditStack(this); - - this._isUndoing = false; - this._isRedoing = false; - - this._hasEditableRange = false; - this._editableRangeId = null; - this._trimAutoWhitespaceLines = null; - } - - public dispose(): void { - this._commandManager = null; - super.dispose(); - } - - protected _resetValue(newValue: ITextSource): void { - super._resetValue(newValue); - - // Destroy my edit history and settings - this._commandManager = new EditStack(this); - this._hasEditableRange = false; - this._editableRangeId = null; - this._trimAutoWhitespaceLines = null; - } - - public pushStackElement(): void { - this._commandManager.pushStackElement(); - } - - public pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] { - try { - this._eventEmitter.beginDeferredEmit(); - this._onDidChangeDecorations.beginDeferredEmit(); - return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - this._eventEmitter.endDeferredEmit(); - } - } - - private _pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] { - if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { - // Go through each saved line number and insert a trim whitespace edit - // if it is safe to do so (no conflicts with other edits). - - let incomingEdits = editOperations.map((op) => { - return { - range: this.validateRange(op.range), - text: op.text - }; - }); - - // Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor - // We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace - let editsAreNearCursors = true; - for (let i = 0, len = beforeCursorState.length; i < len; i++) { - let sel = beforeCursorState[i]; - let foundEditNearSel = false; - for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { - let editRange = incomingEdits[j].range; - let selIsAbove = editRange.startLineNumber > sel.endLineNumber; - let selIsBelow = sel.startLineNumber > editRange.endLineNumber; - if (!selIsAbove && !selIsBelow) { - foundEditNearSel = true; - break; - } - } - if (!foundEditNearSel) { - editsAreNearCursors = false; - break; - } - } - - if (editsAreNearCursors) { - for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) { - let trimLineNumber = this._trimAutoWhitespaceLines[i]; - let maxLineColumn = this.getLineMaxColumn(trimLineNumber); - - let allowTrimLine = true; - for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { - let editRange = incomingEdits[j].range; - let editText = incomingEdits[j].text; - - if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) { - // `trimLine` is completely outside this edit - continue; - } - - // At this point: - // editRange.startLineNumber <= trimLine <= editRange.endLineNumber - - if ( - trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn - && editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n' - ) { - // This edit inserts a new line (and maybe other text) after `trimLine` - continue; - } - - // Looks like we can't trim this line as it would interfere with an incoming edit - allowTrimLine = false; - break; - } - - if (allowTrimLine) { - editOperations.push({ - identifier: null, - range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), - text: null, - forceMoveMarkers: false, - isAutoWhitespaceEdit: false - }); - } - - } - } - - this._trimAutoWhitespaceLines = null; - } - return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); - } - - /** - * Transform operations such that they represent the same logic edit, - * but that they also do not cause OOM crashes. - */ - private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] { - if (operations.length < 1000) { - // We know from empirical testing that a thousand edits work fine regardless of their shape. - return operations; - } - - // At one point, due to how events are emitted and how each operation is handled, - // some operations can trigger a high ammount of temporary string allocations, - // that will immediately get edited again. - // e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line - // Therefore, the strategy is to collapse all the operations into a huge single edit operation - return [this._toSingleEditOperation(operations)]; - } - - _toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation { - let forceMoveMarkers = false, - firstEditRange = operations[0].range, - lastEditRange = operations[operations.length - 1].range, - entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn), - lastEndLineNumber = firstEditRange.startLineNumber, - lastEndColumn = firstEditRange.startColumn, - result: string[] = []; - - for (let i = 0, len = operations.length; i < len; i++) { - let operation = operations[i], - range = operation.range; - - forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers; - - // (1) -- Push old text - for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) { - if (lineNumber === lastEndLineNumber) { - result.push(this._lines[lineNumber - 1].text.substring(lastEndColumn - 1)); - } else { - result.push('\n'); - result.push(this._lines[lineNumber - 1].text); - } - } - - if (range.startLineNumber === lastEndLineNumber) { - result.push(this._lines[range.startLineNumber - 1].text.substring(lastEndColumn - 1, range.startColumn - 1)); - } else { - result.push('\n'); - result.push(this._lines[range.startLineNumber - 1].text.substring(0, range.startColumn - 1)); - } - - // (2) -- Push new text - if (operation.lines) { - for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) { - if (j !== 0) { - result.push('\n'); - } - result.push(operation.lines[j]); - } - } - - lastEndLineNumber = operation.range.endLineNumber; - lastEndColumn = operation.range.endColumn; - } - - return { - sortIndex: 0, - identifier: operations[0].identifier, - range: entireEditRange, - rangeOffset: this.getOffsetAt(entireEditRange.getStartPosition()), - rangeLength: this.getValueLengthInRange(entireEditRange), - lines: result.join('').split('\n'), - forceMoveMarkers: forceMoveMarkers, - isAutoWhitespaceEdit: false - }; - } - - private static _sortOpsAscending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { - let r = Range.compareRangesUsingEnds(a.range, b.range); - if (r === 0) { - return a.sortIndex - b.sortIndex; - } - return r; - } - - private static _sortOpsDescending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { - let r = Range.compareRangesUsingEnds(a.range, b.range); - if (r === 0) { - return b.sortIndex - a.sortIndex; - } - return -r; - } - - public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] { - try { - this._eventEmitter.beginDeferredEmit(); - this._onDidChangeDecorations.beginDeferredEmit(); - return this._applyEdits(rawOperations); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - this._eventEmitter.endDeferredEmit(); - } - } - - private _applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] { - if (rawOperations.length === 0) { - return []; - } - - let mightContainRTL = this._mightContainRTL; - let mightContainNonBasicASCII = this._mightContainNonBasicASCII; - let canReduceOperations = true; - - let operations: IValidatedEditOperation[] = []; - for (let i = 0; i < rawOperations.length; i++) { - let op = rawOperations[i]; - if (canReduceOperations && op._isTracked) { - canReduceOperations = false; - } - let validatedRange = this.validateRange(op.range); - if (!mightContainRTL && op.text) { - // check if the new inserted text contains RTL - mightContainRTL = strings.containsRTL(op.text); - } - if (!mightContainNonBasicASCII && op.text) { - mightContainNonBasicASCII = !strings.isBasicASCII(op.text); - } - operations[i] = { - sortIndex: i, - identifier: op.identifier, - range: validatedRange, - rangeOffset: this.getOffsetAt(validatedRange.getStartPosition()), - rangeLength: this.getValueLengthInRange(validatedRange), - lines: op.text ? op.text.split(/\r\n|\r|\n/) : null, - forceMoveMarkers: op.forceMoveMarkers, - isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false - }; - } - - // Sort operations ascending - operations.sort(EditableTextModel._sortOpsAscending); - - for (let i = 0, count = operations.length - 1; i < count; i++) { - let rangeEnd = operations[i].range.getEndPosition(); - let nextRangeStart = operations[i + 1].range.getStartPosition(); - - if (nextRangeStart.isBefore(rangeEnd)) { - // overlapping ranges - throw new Error('Overlapping ranges are not allowed!'); - } - } - - if (canReduceOperations) { - operations = this._reduceOperations(operations); - } - - let editableRange = this.getEditableRange(); - let editableRangeStart = editableRange.getStartPosition(); - let editableRangeEnd = editableRange.getEndPosition(); - for (let i = 0; i < operations.length; i++) { - let operationRange = operations[i].range; - if (!editableRangeStart.isBeforeOrEqual(operationRange.getStartPosition()) || !operationRange.getEndPosition().isBeforeOrEqual(editableRangeEnd)) { - throw new Error('Editing outside of editable range not allowed!'); - } - } - - // Delta encode operations - let reverseRanges = EditableTextModel._getInverseEditRanges(operations); - let reverseOperations: editorCommon.IIdentifiedSingleEditOperation[] = []; - - let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = []; - - for (let i = 0; i < operations.length; i++) { - let op = operations[i]; - let reverseRange = reverseRanges[i]; - - reverseOperations[i] = { - identifier: op.identifier, - range: reverseRange, - text: this.getValueInRange(op.range), - forceMoveMarkers: op.forceMoveMarkers - }; - - if (this._options.trimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) { - // Record already the future line numbers that might be auto whitespace removal candidates on next edit - for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { - let currentLineContent = ''; - if (lineNumber === reverseRange.startLineNumber) { - currentLineContent = this.getLineContent(op.range.startLineNumber); - if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { - continue; - } - } - newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent }); - } - } - } - - this._mightContainRTL = mightContainRTL; - this._mightContainNonBasicASCII = mightContainNonBasicASCII; - this._doApplyEdits(operations); - - this._trimAutoWhitespaceLines = null; - if (this._options.trimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) { - // sort line numbers auto whitespace removal candidates for next edit descending - newTrimAutoWhitespaceCandidates.sort((a, b) => b.lineNumber - a.lineNumber); - - this._trimAutoWhitespaceLines = []; - for (let i = 0, len = newTrimAutoWhitespaceCandidates.length; i < len; i++) { - let lineNumber = newTrimAutoWhitespaceCandidates[i].lineNumber; - if (i > 0 && newTrimAutoWhitespaceCandidates[i - 1].lineNumber === lineNumber) { - // Do not have the same line number twice - continue; - } - - let prevContent = newTrimAutoWhitespaceCandidates[i].oldContent; - let lineContent = this.getLineContent(lineNumber); - - if (lineContent.length === 0 || lineContent === prevContent || strings.firstNonWhitespaceIndex(lineContent) !== -1) { - continue; - } - - this._trimAutoWhitespaceLines.push(lineNumber); - } - } - - return reverseOperations; - } - - /** - * Assumes `operations` are validated and sorted ascending - */ - public static _getInverseEditRanges(operations: IValidatedEditOperation[]): Range[] { - let result: Range[] = []; - - let prevOpEndLineNumber: number; - let prevOpEndColumn: number; - let prevOp: IValidatedEditOperation = null; - for (let i = 0, len = operations.length; i < len; i++) { - let op = operations[i]; - - let startLineNumber: number; - let startColumn: number; - - if (prevOp) { - if (prevOp.range.endLineNumber === op.range.startLineNumber) { - startLineNumber = prevOpEndLineNumber; - startColumn = prevOpEndColumn + (op.range.startColumn - prevOp.range.endColumn); - } else { - startLineNumber = prevOpEndLineNumber + (op.range.startLineNumber - prevOp.range.endLineNumber); - startColumn = op.range.startColumn; - } - } else { - startLineNumber = op.range.startLineNumber; - startColumn = op.range.startColumn; - } - - let resultRange: Range; - - if (op.lines && op.lines.length > 0) { - // the operation inserts something - let lineCount = op.lines.length; - let firstLine = op.lines[0]; - let lastLine = op.lines[lineCount - 1]; - - if (lineCount === 1) { - // single line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length); - } else { - // multi line insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1); - } - } else { - // There is nothing to insert - resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn); - } - - prevOpEndLineNumber = resultRange.endLineNumber; - prevOpEndColumn = resultRange.endColumn; - - result.push(resultRange); - prevOp = op; - } - - return result; - } - - private _doApplyEdits(operations: IValidatedEditOperation[]): void { - - // Sort operations descending - operations.sort(EditableTextModel._sortOpsDescending); - - let rawContentChanges: ModelRawChange[] = []; - let contentChanges: IModelContentChange[] = []; - let lineEditsQueue: IIdentifiedLineEdit[] = []; - - const queueLineEdit = (lineEdit: IIdentifiedLineEdit) => { - if (lineEdit.startColumn === lineEdit.endColumn && lineEdit.text.length === 0) { - // empty edit => ignore it - return; - } - lineEditsQueue.push(lineEdit); - }; - - const flushLineEdits = () => { - if (lineEditsQueue.length === 0) { - return; - } - - lineEditsQueue.reverse(); - - // `lineEditsQueue` now contains edits from smaller (line number,column) to larger (line number,column) - let currentLineNumber = lineEditsQueue[0].lineNumber; - let currentLineNumberStart = 0; - - for (let i = 1, len = lineEditsQueue.length; i < len; i++) { - const lineNumber = lineEditsQueue[i].lineNumber; - - if (lineNumber === currentLineNumber) { - continue; - } - - this._invalidateLine(currentLineNumber - 1); - this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, i)); - this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length); - rawContentChanges.push( - new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) - ); - - currentLineNumber = lineNumber; - currentLineNumberStart = i; - } - - this._invalidateLine(currentLineNumber - 1); - this._lines[currentLineNumber - 1].applyEdits(lineEditsQueue.slice(currentLineNumberStart, lineEditsQueue.length)); - this._lineStarts.changeValue(currentLineNumber - 1, this._lines[currentLineNumber - 1].text.length + this._EOL.length); - rawContentChanges.push( - new ModelRawLineChanged(currentLineNumber, this._lines[currentLineNumber - 1].text) - ); - - lineEditsQueue = []; - }; - - for (let i = 0, len = operations.length; i < len; i++) { - const op = operations[i]; - - // console.log(); - // console.log('-------------------'); - // console.log('OPERATION #' + (i)); - // console.log('op: ', op); - // console.log('<<<\n' + this._lines.map(l => l.text).join('\n') + '\n>>>'); - - const startLineNumber = op.range.startLineNumber; - const startColumn = op.range.startColumn; - const endLineNumber = op.range.endLineNumber; - const endColumn = op.range.endColumn; - - if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) { - // no-op - continue; - } - - const deletingLinesCnt = endLineNumber - startLineNumber; - const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0); - const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); - - // Iterating descending to overlap with previous op - // in case there are common lines being edited in both - for (let j = editingLinesCnt; j >= 0; j--) { - const editLineNumber = startLineNumber + j; - - queueLineEdit({ - lineNumber: editLineNumber, - startColumn: (editLineNumber === startLineNumber ? startColumn : 1), - endColumn: (editLineNumber === endLineNumber ? endColumn : this.getLineMaxColumn(editLineNumber)), - text: (op.lines ? op.lines[j] : '') - }); - } - - if (editingLinesCnt < deletingLinesCnt) { - // Must delete some lines - - // Flush any pending line edits - flushLineEdits(); - - const spliceStartLineNumber = startLineNumber + editingLinesCnt; - - const endLineRemains = this._lines[endLineNumber - 1].split(endColumn); - this._invalidateLine(spliceStartLineNumber - 1); - - const spliceCnt = endLineNumber - spliceStartLineNumber; - - this._lines.splice(spliceStartLineNumber, spliceCnt); - this._lineStarts.removeValues(spliceStartLineNumber, spliceCnt); - - // Reconstruct first line - this._lines[spliceStartLineNumber - 1].append(endLineRemains); - this._lineStarts.changeValue(spliceStartLineNumber - 1, this._lines[spliceStartLineNumber - 1].text.length + this._EOL.length); - - rawContentChanges.push( - new ModelRawLineChanged(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1].text) - ); - - rawContentChanges.push( - new ModelRawLinesDeleted(spliceStartLineNumber + 1, spliceStartLineNumber + spliceCnt) - ); - } - - if (editingLinesCnt < insertingLinesCnt) { - // Must insert some lines - - // Flush any pending line edits - flushLineEdits(); - - const spliceLineNumber = startLineNumber + editingLinesCnt; - let spliceColumn = (spliceLineNumber === startLineNumber ? startColumn : 1); - if (op.lines) { - spliceColumn += op.lines[editingLinesCnt].length; - } - - // Split last line - let leftoverLine = this._lines[spliceLineNumber - 1].split(spliceColumn); - this._lineStarts.changeValue(spliceLineNumber - 1, this._lines[spliceLineNumber - 1].text.length + this._EOL.length); - rawContentChanges.push( - new ModelRawLineChanged(spliceLineNumber, this._lines[spliceLineNumber - 1].text) - ); - this._invalidateLine(spliceLineNumber - 1); - - // Lines in the middle - let newLines: IModelLine[] = []; - let newLinesContent: string[] = []; - let newLinesLengths = new Uint32Array(insertingLinesCnt - editingLinesCnt); - for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) { - newLines.push(this._createModelLine(op.lines[j])); - newLinesContent.push(op.lines[j]); - newLinesLengths[j - editingLinesCnt - 1] = op.lines[j].length + this._EOL.length; - } - this._lines = arrays.arrayInsert(this._lines, startLineNumber + editingLinesCnt, newLines); - newLinesContent[newLinesContent.length - 1] += leftoverLine.text; - this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths); - - // Last line - this._lines[startLineNumber + insertingLinesCnt - 1].append(leftoverLine); - this._lineStarts.changeValue(startLineNumber + insertingLinesCnt - 1, this._lines[startLineNumber + insertingLinesCnt - 1].text.length + this._EOL.length); - rawContentChanges.push( - new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLinesContent.join('\n')) - ); - } - - const text = (op.lines ? op.lines.join(this.getEOL()) : ''); - contentChanges.push({ - range: new Range(startLineNumber, startColumn, endLineNumber, endColumn), - rangeLength: op.rangeLength, - text: text - }); - - this._adjustDecorationsForEdit(op.rangeOffset, op.rangeLength, text.length, op.forceMoveMarkers); - - // console.log('AFTER:'); - // console.log('<<<\n' + this._lines.map(l => l.text).join('\n') + '\n>>>'); - } - - flushLineEdits(); - - if (rawContentChanges.length !== 0 || contentChanges.length !== 0) { - this._increaseVersionId(); - - this._emitContentChangedEvent( - new ModelRawContentChangedEvent( - rawContentChanges, - this.getVersionId(), - this._isUndoing, - this._isRedoing - ), - { - changes: contentChanges, - eol: this._EOL, - versionId: this.getVersionId(), - isUndoing: this._isUndoing, - isRedoing: this._isRedoing, - isFlush: false - } - ); - } - } - - private _undo(): Selection[] { - this._isUndoing = true; - let r = this._commandManager.undo(); - this._isUndoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public undo(): Selection[] { - try { - this._eventEmitter.beginDeferredEmit(); - this._onDidChangeDecorations.beginDeferredEmit(); - return this._undo(); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - this._eventEmitter.endDeferredEmit(); - } - } - - private _redo(): Selection[] { - this._isRedoing = true; - let r = this._commandManager.redo(); - this._isRedoing = false; - - if (!r) { - return null; - } - - this._overwriteAlternativeVersionId(r.recordedVersionId); - - return r.selections; - } - - public redo(): Selection[] { - try { - this._eventEmitter.beginDeferredEmit(); - this._onDidChangeDecorations.beginDeferredEmit(); - return this._redo(); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - this._eventEmitter.endDeferredEmit(); - } - } - - public setEditableRange(range: IRange): void { - this._commandManager.clear(); - - if (!this._hasEditableRange && !range) { - // Nothing to do - return; - } - - this.changeDecorations((changeAccessor) => { - if (this._hasEditableRange) { - changeAccessor.removeDecoration(this._editableRangeId); - this._editableRangeId = null; - this._hasEditableRange = false; - } - - if (range) { - this._hasEditableRange = true; - this._editableRangeId = changeAccessor.addDecoration(range, EditableTextModel._DECORATION_OPTION); - } - }); - } - - private static readonly _DECORATION_OPTION = ModelDecorationOptions.register({ - stickiness: editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges - }); - - public hasEditableRange(): boolean { - return this._hasEditableRange; - } - - public getEditableRange(): Range { - if (this._hasEditableRange) { - return this.getDecorationRange(this._editableRangeId); - } else { - return this.getFullModelRange(); - } - } -} diff --git a/src/vs/editor/common/model/indentationGuesser.ts b/src/vs/editor/common/model/indentationGuesser.ts index 8648a757ba2..0ec4e0c63ef 100644 --- a/src/vs/editor/common/model/indentationGuesser.ts +++ b/src/vs/editor/common/model/indentationGuesser.ts @@ -5,6 +5,42 @@ 'use strict'; import { CharCode } from 'vs/base/common/charCode'; +import { ITextBuffer } from 'vs/editor/common/model/textBuffer'; + +export interface IIndentationGuesserTarget { + getLineCount(): number; + getLineContent(lineNumber: number): string; +} + +export class IndentationGuesserTextBufferTarget implements IIndentationGuesserTarget { + + constructor( + private readonly _buffer: ITextBuffer + ) { } + + public getLineCount(): number { + return this._buffer.getLineCount(); + } + + public getLineContent(lineNumber: number): string { + return this._buffer.getLineContent(lineNumber); + } +} + +export class IndentationGuesserStringArrayTarget implements IIndentationGuesserTarget { + + constructor( + private readonly _lines: string[] + ) { } + + public getLineCount(): number { + return this._lines.length; + } + + public getLineContent(lineNumber: number): string { + return this._lines[lineNumber - 1]; + } +} /** * Compute the diff in spaces between two line's indentation. @@ -80,9 +116,9 @@ export interface IGuessedIndentation { insertSpaces: boolean; } -export function guessIndentation(lines: string[], defaultTabSize: number, defaultInsertSpaces: boolean): IGuessedIndentation { +export function guessIndentation(target: IIndentationGuesserTarget, defaultTabSize: number, defaultInsertSpaces: boolean): IGuessedIndentation { // Look at most at the first 10k lines - const linesLen = Math.min(lines.length, 10000); + const linesCount = Math.min(target.getLineCount(), 10000); let linesIndentedWithTabsCount = 0; // number of lines that contain at least one tab in indentation let linesIndentedWithSpacesCount = 0; // number of lines that contain only spaces in indentation @@ -95,8 +131,8 @@ export function guessIndentation(lines: string[], defaultTabSize: number, defaul let spacesDiffCount = [0, 0, 0, 0, 0, 0, 0, 0, 0]; // `tabSize` scores - for (let i = 0; i < linesLen; i++) { - let currentLineText = lines[i]; + for (let lineNumber = 1; lineNumber <= linesCount; lineNumber++) { + let currentLineText = target.getLineContent(lineNumber); let currentLineHasContent = false; // does `currentLineText` contain non-whitespace chars let currentLineIndentation = 0; // index at which `currentLineText` contains the first non-whitespace char @@ -149,7 +185,7 @@ export function guessIndentation(lines: string[], defaultTabSize: number, defaul } let tabSize = defaultTabSize; - let tabSizeScore = (insertSpaces ? 0 : 0.1 * linesLen); + let tabSizeScore = (insertSpaces ? 0 : 0.1 * linesCount); // console.log("score threshold: " + tabSizeScore); diff --git a/src/vs/editor/common/model/intervalTree.ts b/src/vs/editor/common/model/intervalTree.ts index cc337f22638..530cc651900 100644 --- a/src/vs/editor/common/model/intervalTree.ts +++ b/src/vs/editor/common/model/intervalTree.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/common/model/mirrorModel.ts b/src/vs/editor/common/model/mirrorModel.ts index fae93e701c3..b29f7a6d163 100644 --- a/src/vs/editor/common/model/mirrorModel.ts +++ b/src/vs/editor/common/model/mirrorModel.ts @@ -8,7 +8,7 @@ import URI from 'vs/base/common/uri'; import { IRange } from 'vs/editor/common/core/range'; import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { IModelContentChange } from 'vs/editor/common/model/textModelEvents'; -import { IPosition } from 'vs/editor/common/core/position'; +import { Position } from 'vs/editor/common/core/position'; export interface IModelChangedEvent { /** @@ -63,10 +63,7 @@ export class MirrorModel { for (let i = 0, len = changes.length; i < len; i++) { const change = changes[i]; this._acceptDeleteRange(change.range); - this._acceptInsertText({ - lineNumber: change.range.startLineNumber, - column: change.range.startColumn - }, change.text); + this._acceptInsertText(new Position(change.range.startLineNumber, change.range.startColumn), change.text); } this._versionId = e.versionId; @@ -124,7 +121,7 @@ export class MirrorModel { } } - private _acceptInsertText(position: IPosition, insertText: string): void { + private _acceptInsertText(position: Position, insertText: string): void { if (insertText.length === 0) { // Nothing to insert return; diff --git a/src/vs/editor/common/model/model.ts b/src/vs/editor/common/model/model.ts index f21b83fa73c..e5365c5fc21 100644 --- a/src/vs/editor/common/model/model.ts +++ b/src/vs/editor/common/model/model.ts @@ -6,33 +6,142 @@ import URI from 'vs/base/common/uri'; import Event, { Emitter } from 'vs/base/common/event'; -import { IModel, ITextModelCreationOptions } from 'vs/editor/common/editorCommon'; -import { EditableTextModel } from 'vs/editor/common/model/editableTextModel'; +import * as editorCommon from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource'; +import { LanguageIdentifier, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes'; +import { IRawTextSource, RawTextSource, ITextSource } from 'vs/editor/common/model/textSource'; +import { EditStack } from 'vs/editor/common/model/editStack'; +import { Range, IRange } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; +import { ModelRawContentChangedEvent, IModelDecorationsChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; +import * as strings from 'vs/base/common/strings'; +import { CharCode } from 'vs/base/common/charCode'; +import { ThemeColor } from 'vs/platform/theme/common/themeService'; +import { IntervalNode, IntervalTree, recomputeMaxEnd, getNodeIsInOverviewRuler } from 'vs/editor/common/model/intervalTree'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { StopWatch } from 'vs/base/common/stopwatch'; +import { NULL_LANGUAGE_IDENTIFIER } from 'vs/editor/common/modes/nullMode'; +import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; +import { BracketsUtils, RichEditBrackets, RichEditBracket } from 'vs/editor/common/modes/supports/richEditBrackets'; +import { Position, IPosition } from 'vs/editor/common/core/position'; +import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; +import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { getWordAtText } from 'vs/editor/common/model/wordHelper'; +import { ModelLinesTokens, computeIndentLevel, ModelTokensChangedEventBuilder } from 'vs/editor/common/model/modelLine'; // The hierarchy is: -// Model -> EditableTextModel -> TextModelWithDecorations -> TextModelWithTokens -> TextModel +// Model -> TextModelWithTokens -> TextModel var MODEL_ID = 0; -export class Model extends EditableTextModel implements IModel { +let _INSTANCE_COUNT = 0; +/** + * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. + */ +function nextInstanceId(): string { + const LETTERS_CNT = (CharCode.Z - CharCode.A + 1); + + let result = _INSTANCE_COUNT++; + result = result % (2 * LETTERS_CNT); + + if (result < LETTERS_CNT) { + return String.fromCharCode(CharCode.a + result); + } + + return String.fromCharCode(CharCode.A + result - LETTERS_CNT); +} + +export class Model extends TextModel implements editorCommon.IModel, editorCommon.IEditableTextModel, editorCommon.ITextModelWithDecorations { + + public static createFromString(text: string, options: editorCommon.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): Model { + return new Model(RawTextSource.fromString(text), options, languageIdentifier, uri); + } private readonly _onWillDispose: Emitter = this._register(new Emitter()); public readonly onWillDispose: Event = this._onWillDispose.event; - public static createFromString(text: string, options: ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier = null, uri: URI = null): Model { - return new Model(RawTextSource.fromString(text), options, languageIdentifier, uri); - } + private readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter()); + public readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; + + private readonly _onDidChangeLanguage: Emitter = this._register(new Emitter()); + public readonly onDidChangeLanguage: Event = this._onDidChangeLanguage.event; + + private readonly _onDidChangeLanguageConfiguration: Emitter = this._register(new Emitter()); + public readonly onDidChangeLanguageConfiguration: Event = this._onDidChangeLanguageConfiguration.event; + + private readonly _onDidChangeTokens: Emitter = this._register(new Emitter()); + public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; public readonly id: string; - private readonly _associatedResource: URI; private _attachedEditorCount: number; - constructor(rawTextSource: IRawTextSource, creationOptions: ITextModelCreationOptions, languageIdentifier: LanguageIdentifier, associatedResource: URI = null) { - super(rawTextSource, creationOptions, languageIdentifier); + //#region Editing + private _commandManager: EditStack; + private _isUndoing: boolean; + private _isRedoing: boolean; + private _trimAutoWhitespaceLines: number[]; + //#endregion + + //#region Decorations + /** + * Used to workaround broken clients that might attempt using a decoration id generated by a different model. + * It is not globally unique in order to limit it to one character. + */ + private readonly _instanceId: string; + private _lastDecorationId: number; + private _decorations: { [decorationId: string]: IntervalNode; }; + private _decorationsTree: DecorationsTrees; + //#endregion + + //#region Tokenization + private _languageIdentifier: LanguageIdentifier; + private _tokenizationListener: IDisposable; + private _languageRegistryListener: IDisposable; + private _revalidateTokensTimeout: number; + /*protected*/_tokens: ModelLinesTokens; + //#endregion + + constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier, associatedResource: URI = null) { + super(rawTextSource, creationOptions); + + this._languageIdentifier = languageIdentifier || NULL_LANGUAGE_IDENTIFIER; + this._tokenizationListener = TokenizationRegistry.onDidChange((e) => { + if (e.changedLanguages.indexOf(this._languageIdentifier.language) === -1) { + return; + } + + this._resetTokenizationState(); + this.emitModelTokensChangedEvent({ + ranges: [{ + fromLineNumber: 1, + toLineNumber: this.getLineCount() + }] + }); + + if (this._shouldAutoTokenize()) { + this._warmUpTokens(); + } + }); + this._revalidateTokensTimeout = -1; + this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange((e) => { + if (e.languageIdentifier.id === this._languageIdentifier.id) { + this._onDidChangeLanguageConfiguration.fire({}); + } + }); + this._resetTokenizationState(); + + this._instanceId = nextInstanceId(); + this._lastDecorationId = 0; + this._decorations = Object.create(null); + this._decorationsTree = new DecorationsTrees(); + + this._commandManager = new EditStack(this); + this._isUndoing = false; + this._isRedoing = false; + this._trimAutoWhitespaceLines = null; // Generate a new unique model id MODEL_ID++; @@ -50,10 +159,84 @@ export class Model extends EditableTextModel implements IModel { public dispose(): void { this._isDisposing = true; this._onWillDispose.fire(); + this._commandManager = null; + this._decorations = null; + this._decorationsTree = null; + this._tokenizationListener.dispose(); + this._languageRegistryListener.dispose(); + this._clearTimers(); + this._tokens = null; super.dispose(); this._isDisposing = false; } + protected _resetValue(newValue: ITextSource): void { + super._resetValue(newValue); + + // Cancel tokenization, clear all tokens and begin tokenizing + this._resetTokenizationState(); + + // Destroy all my decorations + this._decorations = Object.create(null); + this._decorationsTree = new DecorationsTrees(); + + // Destroy my edit history and settings + this._commandManager = new EditStack(this); + this._trimAutoWhitespaceLines = null; + } + + protected _resetTokenizationState(): void { + this._clearTimers(); + let tokenizationSupport = ( + this._isTooLargeForTokenization + ? null + : TokenizationRegistry.get(this._languageIdentifier.language) + ); + this._tokens = new ModelLinesTokens(this._languageIdentifier, tokenizationSupport); + this._beginBackgroundTokenization(); + } + + private _clearTimers(): void { + if (this._revalidateTokensTimeout !== -1) { + clearTimeout(this._revalidateTokensTimeout); + this._revalidateTokensTimeout = -1; + } + } + + protected _onBeforeEOLChange(): void { + super._onBeforeEOLChange(); + + // Ensure all decorations get their `range` set. + const versionId = this.getVersionId(); + const allDecorations = this._decorationsTree.search(0, false, false, versionId); + this._ensureNodesHaveRanges(allDecorations); + } + + protected _onAfterEOLChange(): void { + super._onAfterEOLChange(); + + // Transform back `range` to offsets + const versionId = this.getVersionId(); + const allDecorations = this._decorationsTree.collectNodesPostOrder(); + for (let i = 0, len = allDecorations.length; i < len; i++) { + const node = allDecorations[i]; + + const delta = node.cachedAbsoluteStart - node.start; + + const startOffset = this._buffer.getOffsetAt(node.range.startLineNumber, node.range.startColumn); + const endOffset = this._buffer.getOffsetAt(node.range.endLineNumber, node.range.endColumn); + + node.cachedAbsoluteStart = startOffset; + node.cachedAbsoluteEnd = endOffset; + node.cachedVersionId = versionId; + + node.start = startOffset - delta; + node.end = endOffset - delta; + + recomputeMaxEnd(node); + } + } + public onBeforeAttached(): void { this._attachedEditorCount++; // Warm up tokens for the editor @@ -64,7 +247,7 @@ export class Model extends EditableTextModel implements IModel { this._attachedEditorCount--; } - protected _shouldAutoTokenize(): boolean { + private _shouldAutoTokenize(): boolean { return this.isAttachedToEditor(); } @@ -75,4 +258,1358 @@ export class Model extends EditableTextModel implements IModel { public get uri(): URI { return this._associatedResource; } + + //#region Editing + + public pushStackElement(): void { + this._commandManager.pushStackElement(); + } + + + public pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] { + try { + this._eventEmitter.beginDeferredEmit(); + this._onDidChangeDecorations.beginDeferredEmit(); + return this._pushEditOperations(beforeCursorState, editOperations, cursorStateComputer); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + this._eventEmitter.endDeferredEmit(); + } + } + + private _pushEditOperations(beforeCursorState: Selection[], editOperations: editorCommon.IIdentifiedSingleEditOperation[], cursorStateComputer: editorCommon.ICursorStateComputer): Selection[] { + if (this._options.trimAutoWhitespace && this._trimAutoWhitespaceLines) { + // Go through each saved line number and insert a trim whitespace edit + // if it is safe to do so (no conflicts with other edits). + + let incomingEdits = editOperations.map((op) => { + return { + range: this.validateRange(op.range), + text: op.text + }; + }); + + // Sometimes, auto-formatters change ranges automatically which can cause undesired auto whitespace trimming near the cursor + // We'll use the following heuristic: if the edits occur near the cursor, then it's ok to trim auto whitespace + let editsAreNearCursors = true; + for (let i = 0, len = beforeCursorState.length; i < len; i++) { + let sel = beforeCursorState[i]; + let foundEditNearSel = false; + for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { + let editRange = incomingEdits[j].range; + let selIsAbove = editRange.startLineNumber > sel.endLineNumber; + let selIsBelow = sel.startLineNumber > editRange.endLineNumber; + if (!selIsAbove && !selIsBelow) { + foundEditNearSel = true; + break; + } + } + if (!foundEditNearSel) { + editsAreNearCursors = false; + break; + } + } + + if (editsAreNearCursors) { + for (let i = 0, len = this._trimAutoWhitespaceLines.length; i < len; i++) { + let trimLineNumber = this._trimAutoWhitespaceLines[i]; + let maxLineColumn = this.getLineMaxColumn(trimLineNumber); + + let allowTrimLine = true; + for (let j = 0, lenJ = incomingEdits.length; j < lenJ; j++) { + let editRange = incomingEdits[j].range; + let editText = incomingEdits[j].text; + + if (trimLineNumber < editRange.startLineNumber || trimLineNumber > editRange.endLineNumber) { + // `trimLine` is completely outside this edit + continue; + } + + // At this point: + // editRange.startLineNumber <= trimLine <= editRange.endLineNumber + + if ( + trimLineNumber === editRange.startLineNumber && editRange.startColumn === maxLineColumn + && editRange.isEmpty() && editText && editText.length > 0 && editText.charAt(0) === '\n' + ) { + // This edit inserts a new line (and maybe other text) after `trimLine` + continue; + } + + // Looks like we can't trim this line as it would interfere with an incoming edit + allowTrimLine = false; + break; + } + + if (allowTrimLine) { + editOperations.push({ + identifier: null, + range: new Range(trimLineNumber, 1, trimLineNumber, maxLineColumn), + text: null, + forceMoveMarkers: false, + isAutoWhitespaceEdit: false + }); + } + + } + } + + this._trimAutoWhitespaceLines = null; + } + return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); + } + + public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] { + try { + this._eventEmitter.beginDeferredEmit(); + this._onDidChangeDecorations.beginDeferredEmit(); + return this._applyEdits(rawOperations); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + this._eventEmitter.endDeferredEmit(); + } + } + + private _applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[]): editorCommon.IIdentifiedSingleEditOperation[] { + for (let i = 0, len = rawOperations.length; i < len; i++) { + rawOperations[i].range = this.validateRange(rawOperations[i].range); + } + const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); + const rawContentChanges = result.rawChanges; + const contentChanges = result.changes; + this._trimAutoWhitespaceLines = result.trimAutoWhitespaceLineNumbers; + + if (rawContentChanges.length !== 0 || contentChanges.length !== 0) { + for (let i = 0, len = contentChanges.length; i < len; i++) { + const contentChange = contentChanges[i]; + this._tokens.applyEdits(contentChange.range, contentChange.lines); + this._onDidChangeDecorations.fire(); + this._decorationsTree.acceptReplace(contentChange.rangeOffset, contentChange.rangeLength, contentChange.text.length, contentChange.forceMoveMarkers); + } + + this._increaseVersionId(); + + this._emitContentChangedEvent( + new ModelRawContentChangedEvent( + rawContentChanges, + this.getVersionId(), + this._isUndoing, + this._isRedoing + ), + { + changes: contentChanges, + eol: this._buffer.getEOL(), + versionId: this.getVersionId(), + isUndoing: this._isUndoing, + isRedoing: this._isRedoing, + isFlush: false + } + ); + } + + if (this._tokens.hasLinesToTokenize(this._buffer)) { + this._beginBackgroundTokenization(); + } + + return result.reverseEdits; + } + + private _undo(): Selection[] { + this._isUndoing = true; + let r = this._commandManager.undo(); + this._isUndoing = false; + + if (!r) { + return null; + } + + this._overwriteAlternativeVersionId(r.recordedVersionId); + + return r.selections; + } + + public undo(): Selection[] { + try { + this._eventEmitter.beginDeferredEmit(); + this._onDidChangeDecorations.beginDeferredEmit(); + return this._undo(); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + this._eventEmitter.endDeferredEmit(); + } + } + + private _redo(): Selection[] { + this._isRedoing = true; + let r = this._commandManager.redo(); + this._isRedoing = false; + + if (!r) { + return null; + } + + this._overwriteAlternativeVersionId(r.recordedVersionId); + + return r.selections; + } + + public redo(): Selection[] { + try { + this._eventEmitter.beginDeferredEmit(); + this._onDidChangeDecorations.beginDeferredEmit(); + return this._redo(); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + this._eventEmitter.endDeferredEmit(); + } + } + + //#endregion + + //#region Decorations + + public changeDecorations(callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T, ownerId: number = 0): T { + this._assertNotDisposed(); + + try { + this._onDidChangeDecorations.beginDeferredEmit(); + return this._changeDecorations(ownerId, callback); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + } + } + + private _changeDecorations(ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T { + let changeAccessor: editorCommon.IModelDecorationsChangeAccessor = { + addDecoration: (range: IRange, options: editorCommon.IModelDecorationOptions): string => { + this._onDidChangeDecorations.fire(); + return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; + }, + changeDecoration: (id: string, newRange: IRange): void => { + this._onDidChangeDecorations.fire(); + this._changeDecorationImpl(id, newRange); + }, + changeDecorationOptions: (id: string, options: editorCommon.IModelDecorationOptions) => { + this._onDidChangeDecorations.fire(); + this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); + }, + removeDecoration: (id: string): void => { + this._onDidChangeDecorations.fire(); + this._deltaDecorationsImpl(ownerId, [id], []); + }, + deltaDecorations: (oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] => { + if (oldDecorations.length === 0 && newDecorations.length === 0) { + // nothing to do + return []; + } + this._onDidChangeDecorations.fire(); + return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); + } + }; + let result: T = null; + try { + result = callback(changeAccessor); + } catch (e) { + onUnexpectedError(e); + } + // Invalidate change accessor + changeAccessor.addDecoration = null; + changeAccessor.changeDecoration = null; + changeAccessor.removeDecoration = null; + changeAccessor.deltaDecorations = null; + return result; + } + + public deltaDecorations(oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[], ownerId: number = 0): string[] { + this._assertNotDisposed(); + if (!oldDecorations) { + oldDecorations = []; + } + if (oldDecorations.length === 0 && newDecorations.length === 0) { + // nothing to do + return []; + } + + try { + this._onDidChangeDecorations.beginDeferredEmit(); + this._onDidChangeDecorations.fire(); + return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); + } finally { + this._onDidChangeDecorations.endDeferredEmit(); + } + } + + _getTrackedRange(id: string): Range { + return this.getDecorationRange(id); + } + + _setTrackedRange(id: string, newRange: Range, newStickiness: editorCommon.TrackedRangeStickiness): string { + const node = (id ? this._decorations[id] : null); + + if (!node) { + if (!newRange) { + // node doesn't exist, the request is to delete => nothing to do + return null; + } + // node doesn't exist, the request is to set => add the tracked range + return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0]; + } + + if (!newRange) { + // node exists, the request is to delete => delete node + this._decorationsTree.delete(node); + delete this._decorations[node.id]; + return null; + } + + // node exists, the request is to set => change the tracked range and its options + const range = this._validateRangeRelaxedNoAllocations(newRange); + const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); + const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); + this._decorationsTree.delete(node); + node.reset(this.getVersionId(), startOffset, endOffset, range); + node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]); + this._decorationsTree.insert(node); + return node.id; + } + + public removeAllDecorationsWithOwnerId(ownerId: number): void { + if (this._isDisposed) { + return; + } + const nodes = this._decorationsTree.collectNodesFromOwner(ownerId); + for (let i = 0, len = nodes.length; i < len; i++) { + const node = nodes[i]; + + this._decorationsTree.delete(node); + delete this._decorations[node.id]; + } + } + + public getDecorationOptions(decorationId: string): editorCommon.IModelDecorationOptions { + const node = this._decorations[decorationId]; + if (!node) { + return null; + } + return node.options; + } + + public getDecorationRange(decorationId: string): Range { + const node = this._decorations[decorationId]; + if (!node) { + return null; + } + const versionId = this.getVersionId(); + if (node.cachedVersionId !== versionId) { + this._decorationsTree.resolveNode(node, versionId); + } + if (node.range === null) { + node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); + } + return node.range; + } + + public getLineDecorations(lineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { + if (lineNumber < 1 || lineNumber > this.getLineCount()) { + return []; + } + + return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation); + } + + public getLinesDecorations(_startLineNumber: number, _endLineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { + let lineCount = this.getLineCount(); + let startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber)); + let endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber)); + let endColumn = this.getLineMaxColumn(endLineNumber); + return this._getDecorationsInRange(new Range(startLineNumber, 1, endLineNumber, endColumn), ownerId, filterOutValidation); + } + + public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { + let validatedRange = this.validateRange(range); + return this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation); + } + + public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { + const versionId = this.getVersionId(); + const result = this._decorationsTree.search(ownerId, filterOutValidation, true, versionId); + return this._ensureNodesHaveRanges(result); + } + + public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { + const versionId = this.getVersionId(); + const result = this._decorationsTree.search(ownerId, filterOutValidation, false, versionId); + return this._ensureNodesHaveRanges(result); + } + + private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): IntervalNode[] { + const startOffset = this._buffer.getOffsetAt(filterRange.startLineNumber, filterRange.startColumn); + const endOffset = this._buffer.getOffsetAt(filterRange.endLineNumber, filterRange.endColumn); + + const versionId = this.getVersionId(); + const result = this._decorationsTree.intervalSearch(startOffset, endOffset, filterOwnerId, filterOutValidation, versionId); + + return this._ensureNodesHaveRanges(result); + } + + private _ensureNodesHaveRanges(nodes: IntervalNode[]): IntervalNode[] { + for (let i = 0, len = nodes.length; i < len; i++) { + const node = nodes[i]; + if (node.range === null) { + node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); + } + } + return nodes; + } + + private _getRangeAt(start: number, end: number): Range { + return this._buffer.getRangeAt(start, end - start); + } + + private _changeDecorationImpl(decorationId: string, _range: IRange): void { + const node = this._decorations[decorationId]; + if (!node) { + return; + } + const range = this._validateRangeRelaxedNoAllocations(_range); + const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); + const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); + + this._decorationsTree.delete(node); + node.reset(this.getVersionId(), startOffset, endOffset, range); + this._decorationsTree.insert(node); + } + + private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void { + const node = this._decorations[decorationId]; + if (!node) { + return; + } + + const nodeWasInOverviewRuler = (node.options.overviewRuler.color ? true : false); + const nodeIsInOverviewRuler = (options.overviewRuler.color ? true : false); + + if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { + // Delete + Insert due to an overview ruler status change + this._decorationsTree.delete(node); + node.setOptions(options); + this._decorationsTree.insert(node); + } else { + node.setOptions(options); + } + } + + private _deltaDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] { + const versionId = this.getVersionId(); + + const oldDecorationsLen = oldDecorationsIds.length; + let oldDecorationIndex = 0; + + const newDecorationsLen = newDecorations.length; + let newDecorationIndex = 0; + + let result = new Array(newDecorationsLen); + while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) { + + let node: IntervalNode = null; + + if (oldDecorationIndex < oldDecorationsLen) { + // (1) get ourselves an old node + do { + node = this._decorations[oldDecorationsIds[oldDecorationIndex++]]; + } while (!node && oldDecorationIndex < oldDecorationsLen); + + // (2) remove the node from the tree (if it exists) + if (node) { + this._decorationsTree.delete(node); + } + } + + if (newDecorationIndex < newDecorationsLen) { + // (3) create a new node if necessary + if (!node) { + const internalDecorationId = (++this._lastDecorationId); + const decorationId = `${this._instanceId};${internalDecorationId}`; + node = new IntervalNode(decorationId, 0, 0); + this._decorations[decorationId] = node; + } + + // (4) initialize node + const newDecoration = newDecorations[newDecorationIndex]; + const range = this._validateRangeRelaxedNoAllocations(newDecoration.range); + const options = _normalizeOptions(newDecoration.options); + const startOffset = this._buffer.getOffsetAt(range.startLineNumber, range.startColumn); + const endOffset = this._buffer.getOffsetAt(range.endLineNumber, range.endColumn); + + node.ownerId = ownerId; + node.reset(versionId, startOffset, endOffset, range); + node.setOptions(options); + + this._decorationsTree.insert(node); + + result[newDecorationIndex] = node.id; + + newDecorationIndex++; + } else { + if (node) { + delete this._decorations[node.id]; + } + } + } + + return result; + } + + //#endregion + + //#region Tokenization + + public forceTokenization(lineNumber: number): void { + if (lineNumber < 1 || lineNumber > this.getLineCount()) { + throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); + } + + const eventBuilder = new ModelTokensChangedEventBuilder(); + + this._tokens._updateTokensUntilLine(this._buffer, eventBuilder, lineNumber); + + const e = eventBuilder.build(); + if (e) { + this._onDidChangeTokens.fire(e); + } + } + + public isCheapToTokenize(lineNumber: number): boolean { + return this._tokens.isCheapToTokenize(lineNumber); + } + + public tokenizeIfCheap(lineNumber: number): void { + if (this.isCheapToTokenize(lineNumber)) { + this.forceTokenization(lineNumber); + } + } + + public getLineTokens(lineNumber: number): LineTokens { + if (lineNumber < 1 || lineNumber > this.getLineCount()) { + throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); + } + + return this._getLineTokens(lineNumber); + } + + private _getLineTokens(lineNumber: number): LineTokens { + const lineText = this._buffer.getLineContent(lineNumber); + return this._tokens.getTokens(this._languageIdentifier.id, lineNumber - 1, lineText); + } + + public getLanguageIdentifier(): LanguageIdentifier { + return this._languageIdentifier; + } + + public getModeId(): string { + return this._languageIdentifier.language; + } + + public setMode(languageIdentifier: LanguageIdentifier): void { + if (this._languageIdentifier.id === languageIdentifier.id) { + // There's nothing to do + return; + } + + let e: IModelLanguageChangedEvent = { + oldLanguage: this._languageIdentifier.language, + newLanguage: languageIdentifier.language + }; + + this._languageIdentifier = languageIdentifier; + + // Cancel tokenization, clear all tokens and begin tokenizing + this._resetTokenizationState(); + + this.emitModelTokensChangedEvent({ + ranges: [{ + fromLineNumber: 1, + toLineNumber: this.getLineCount() + }] + }); + this._onDidChangeLanguage.fire(e); + this._onDidChangeLanguageConfiguration.fire({}); + } + + public getLanguageIdAtPosition(_lineNumber: number, _column: number): LanguageId { + if (!this._tokens.tokenizationSupport) { + return this._languageIdentifier.id; + } + let { lineNumber, column } = this.validatePosition({ lineNumber: _lineNumber, column: _column }); + + let lineTokens = this._getLineTokens(lineNumber); + return lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(column - 1)); + } + + protected _beginBackgroundTokenization(): void { + if (this._shouldAutoTokenize() && this._revalidateTokensTimeout === -1) { + this._revalidateTokensTimeout = setTimeout(() => { + this._revalidateTokensTimeout = -1; + this._revalidateTokensNow(); + }, 0); + } + } + + _warmUpTokens(): void { + // Warm up first 100 lines (if it takes less than 50ms) + var maxLineNumber = Math.min(100, this.getLineCount()); + this._revalidateTokensNow(maxLineNumber); + + if (this._tokens.hasLinesToTokenize(this._buffer)) { + this._beginBackgroundTokenization(); + } + } + + private _revalidateTokensNow(toLineNumber: number = this._buffer.getLineCount()): void { + const MAX_ALLOWED_TIME = 20; + const eventBuilder = new ModelTokensChangedEventBuilder(); + const sw = StopWatch.create(false); + + while (this._tokens.hasLinesToTokenize(this._buffer)) { + if (sw.elapsed() > MAX_ALLOWED_TIME) { + // Stop if MAX_ALLOWED_TIME is reached + break; + } + + const tokenizedLineNumber = this._tokens._tokenizeOneLine(this._buffer, eventBuilder); + + if (tokenizedLineNumber >= toLineNumber) { + break; + } + } + + if (this._tokens.hasLinesToTokenize(this._buffer)) { + this._beginBackgroundTokenization(); + } + + const e = eventBuilder.build(); + if (e) { + this._onDidChangeTokens.fire(e); + } + } + + private emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void { + if (!this._isDisposing) { + this._onDidChangeTokens.fire(e); + } + } + + // Having tokens allows implementing additional helper methods + + public getWordAtPosition(_position: IPosition): editorCommon.IWordAtPosition { + this._assertNotDisposed(); + const position = this.validatePosition(_position); + const lineContent = this.getLineContent(position.lineNumber); + const lineTokens = this._getLineTokens(position.lineNumber); + const offset = position.column - 1; + const tokenIndex = lineTokens.findTokenIndexAtOffset(offset); + const languageId = lineTokens.getLanguageId(tokenIndex); + + // go left until a different language is hit + let startOffset: number; + for (let i = tokenIndex; i >= 0 && lineTokens.getLanguageId(i) === languageId; i--) { + startOffset = lineTokens.getStartOffset(i); + } + + // go right until a different language is hit + let endOffset: number; + for (let i = tokenIndex, tokenCount = lineTokens.getCount(); i < tokenCount && lineTokens.getLanguageId(i) === languageId; i++) { + endOffset = lineTokens.getEndOffset(i); + } + + return getWordAtText( + position.column, + LanguageConfigurationRegistry.getWordDefinition(languageId), + lineContent.substring(startOffset, endOffset), + startOffset + ); + } + + public getWordUntilPosition(position: IPosition): editorCommon.IWordAtPosition { + var wordAtPosition = this.getWordAtPosition(position); + if (!wordAtPosition) { + return { + word: '', + startColumn: position.column, + endColumn: position.column + }; + } + return { + word: wordAtPosition.word.substr(0, position.column - wordAtPosition.startColumn), + startColumn: wordAtPosition.startColumn, + endColumn: position.column + }; + } + + public findMatchingBracketUp(_bracket: string, _position: IPosition): Range { + let bracket = _bracket.toLowerCase(); + let position = this.validatePosition(_position); + + let lineTokens = this._getLineTokens(position.lineNumber); + let languageId = lineTokens.getLanguageId(lineTokens.findTokenIndexAtOffset(position.column - 1)); + let bracketsSupport = LanguageConfigurationRegistry.getBracketsSupport(languageId); + + if (!bracketsSupport) { + return null; + } + + let data = bracketsSupport.textIsBracket[bracket]; + + if (!data) { + return null; + } + + return this._findMatchingBracketUp(data, position); + } + + public matchBracket(position: IPosition): [Range, Range] { + return this._matchBracket(this.validatePosition(position)); + } + + private _matchBracket(position: Position): [Range, Range] { + const lineNumber = position.lineNumber; + const lineTokens = this._getLineTokens(lineNumber); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + if (tokenIndex < 0) { + return null; + } + const currentModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(tokenIndex)); + + // check that the token is not to be ignored + if (currentModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { + // limit search to not go before `maxBracketLength` + let searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - currentModeBrackets.maxBracketLength); + // limit search to not go after `maxBracketLength` + const searchEndOffset = Math.min(lineTokens.getEndOffset(tokenIndex), position.column - 1 + currentModeBrackets.maxBracketLength); + + // first, check if there is a bracket to the right of `position` + let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, position.column - 1, searchEndOffset); + if (foundBracket && foundBracket.startColumn === position.column) { + let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + foundBracketText = foundBracketText.toLowerCase(); + + let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); + + // check that we can actually match this bracket + if (r) { + return r; + } + } + + // it might still be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets + while (true) { + let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + if (!foundBracket) { + // there are no brackets in this text + break; + } + + // check that we didn't hit a bracket too far away from position + if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { + let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + foundBracketText = foundBracketText.toLowerCase(); + + let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); + + // check that we can actually match this bracket + if (r) { + return r; + } + } + + searchStartOffset = foundBracket.endColumn - 1; + } + } + + // If position is in between two tokens, try also looking in the previous token + if (tokenIndex > 0 && lineTokens.getStartOffset(tokenIndex) === position.column - 1) { + const searchEndOffset = lineTokens.getStartOffset(tokenIndex); + tokenIndex--; + const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(lineTokens.getLanguageId(tokenIndex)); + + // check that previous token is not to be ignored + if (prevModeBrackets && !ignoreBracketsInToken(lineTokens.getStandardTokenType(tokenIndex))) { + // limit search in case previous token is very large, there's no need to go beyond `maxBracketLength` + const searchStartOffset = Math.max(lineTokens.getStartOffset(tokenIndex), position.column - 1 - prevModeBrackets.maxBracketLength); + const foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); + + // check that we didn't hit a bracket too far away from position + if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { + let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + foundBracketText = foundBracketText.toLowerCase(); + + let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); + + // check that we can actually match this bracket + if (r) { + return r; + } + } + } + } + + return null; + } + + private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] { + if (isOpen) { + let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition()); + if (matched) { + return [foundBracket, matched]; + } + } else { + let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition()); + if (matched) { + return [foundBracket, matched]; + } + } + + return null; + } + + private _findMatchingBracketUp(bracket: RichEditBracket, position: Position): Range { + // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); + + const languageId = bracket.languageIdentifier.id; + const reversedBracketRegex = bracket.reversedRegex; + let count = -1; + + for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { + const lineTokens = this._getLineTokens(lineNumber); + const tokenCount = lineTokens.getCount(); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = tokenCount - 1; + let searchStopOffset = -1; + if (lineNumber === position.lineNumber) { + tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + searchStopOffset = position.column - 1; + } + + for (; tokenIndex >= 0; tokenIndex--) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + + if (searchStopOffset === -1) { + searchStopOffset = tokenEndOffset; + } + + if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) { + + while (true) { + let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset); + if (!r) { + break; + } + + let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + hitText = hitText.toLowerCase(); + + if (hitText === bracket.open) { + count++; + } else if (hitText === bracket.close) { + count--; + } + + if (count === 0) { + return r; + } + + searchStopOffset = r.startColumn - 1; + } + } + + searchStopOffset = -1; + } + } + + return null; + } + + private _findMatchingBracketDown(bracket: RichEditBracket, position: Position): Range { + // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); + + const languageId = bracket.languageIdentifier.id; + const bracketRegex = bracket.forwardRegex; + let count = 1; + + for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + const lineTokens = this._getLineTokens(lineNumber); + const tokenCount = lineTokens.getCount(); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = 0; + let searchStartOffset = 0; + if (lineNumber === position.lineNumber) { + tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + searchStartOffset = position.column - 1; + } + + for (; tokenIndex < tokenCount; tokenIndex++) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + + if (searchStartOffset === 0) { + searchStartOffset = tokenStartOffset; + } + + if (tokenLanguageId === languageId && !ignoreBracketsInToken(tokenType)) { + while (true) { + let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset); + if (!r) { + break; + } + + let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + hitText = hitText.toLowerCase(); + + if (hitText === bracket.open) { + count++; + } else if (hitText === bracket.close) { + count--; + } + + if (count === 0) { + return r; + } + + searchStartOffset = r.endColumn - 1; + } + } + + searchStartOffset = 0; + } + } + + return null; + } + + public findPrevBracket(_position: IPosition): editorCommon.IFoundBracket { + const position = this.validatePosition(_position); + + let languageId: LanguageId = -1; + let modeBrackets: RichEditBrackets = null; + for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { + const lineTokens = this._getLineTokens(lineNumber); + const tokenCount = lineTokens.getCount(); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = tokenCount - 1; + let searchStopOffset = -1; + if (lineNumber === position.lineNumber) { + tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + searchStopOffset = position.column - 1; + } + + for (; tokenIndex >= 0; tokenIndex--) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + + if (searchStopOffset === -1) { + searchStopOffset = tokenEndOffset; + } + if (languageId !== tokenLanguageId) { + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + } + if (modeBrackets && !ignoreBracketsInToken(tokenType)) { + let r = BracketsUtils.findPrevBracketInToken(modeBrackets.reversedRegex, lineNumber, lineText, tokenStartOffset, searchStopOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } + } + + searchStopOffset = -1; + } + } + + return null; + } + + public findNextBracket(_position: IPosition): editorCommon.IFoundBracket { + const position = this.validatePosition(_position); + + let languageId: LanguageId = -1; + let modeBrackets: RichEditBrackets = null; + for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { + const lineTokens = this._getLineTokens(lineNumber); + const tokenCount = lineTokens.getCount(); + const lineText = this._buffer.getLineContent(lineNumber); + + let tokenIndex = 0; + let searchStartOffset = 0; + if (lineNumber === position.lineNumber) { + tokenIndex = lineTokens.findTokenIndexAtOffset(position.column - 1); + searchStartOffset = position.column - 1; + } + + for (; tokenIndex < tokenCount; tokenIndex++) { + const tokenLanguageId = lineTokens.getLanguageId(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + + if (searchStartOffset === 0) { + searchStartOffset = tokenStartOffset; + } + + if (languageId !== tokenLanguageId) { + languageId = tokenLanguageId; + modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); + } + if (modeBrackets && !ignoreBracketsInToken(tokenType)) { + let r = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, tokenEndOffset); + if (r) { + return this._toFoundBracket(modeBrackets, r); + } + } + + searchStartOffset = 0; + } + } + + return null; + } + + private _toFoundBracket(modeBrackets: RichEditBrackets, r: Range): editorCommon.IFoundBracket { + if (!r) { + return null; + } + + let text = this.getValueInRange(r); + text = text.toLowerCase(); + + let data = modeBrackets.textIsBracket[text]; + if (!data) { + return null; + } + + return { + range: r, + open: data.open, + close: data.close, + isOpen: modeBrackets.textIsOpenBracket[text] + }; + } + + private _computeIndentLevel(lineIndex: number): number { + return computeIndentLevel(this._buffer.getLineContent(lineIndex + 1), this._options.tabSize); + } + + public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] { + this._assertNotDisposed(); + const lineCount = this.getLineCount(); + + if (startLineNumber < 1 || startLineNumber > lineCount) { + throw new Error('Illegal value ' + startLineNumber + ' for `startLineNumber`'); + } + if (endLineNumber < 1 || endLineNumber > lineCount) { + throw new Error('Illegal value ' + endLineNumber + ' for `endLineNumber`'); + } + + const foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id); + const offSide = foldingRules && foldingRules.offSide; + + let result: number[] = new Array(endLineNumber - startLineNumber + 1); + + let aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */ + let aboveContentLineIndent = -1; + + let belowContentLineIndex = -2; /* -2 is a marker for not having computed it */ + let belowContentLineIndent = -1; + + for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { + let resultIndex = lineNumber - startLineNumber; + + const currentIndent = this._computeIndentLevel(lineNumber - 1); + if (currentIndent >= 0) { + // This line has content (besides whitespace) + // Use the line's indent + aboveContentLineIndex = lineNumber - 1; + aboveContentLineIndent = currentIndent; + result[resultIndex] = Math.ceil(currentIndent / this._options.tabSize); + continue; + } + + if (aboveContentLineIndex === -2) { + aboveContentLineIndex = -1; + aboveContentLineIndent = -1; + + // must find previous line with content + for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) { + let indent = this._computeIndentLevel(lineIndex); + if (indent >= 0) { + aboveContentLineIndex = lineIndex; + aboveContentLineIndent = indent; + break; + } + } + } + + if (belowContentLineIndex !== -1 && (belowContentLineIndex === -2 || belowContentLineIndex < lineNumber - 1)) { + belowContentLineIndex = -1; + belowContentLineIndent = -1; + + // must find next line with content + for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) { + let indent = this._computeIndentLevel(lineIndex); + if (indent >= 0) { + belowContentLineIndex = lineIndex; + belowContentLineIndent = indent; + break; + } + } + } + + if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) { + // At the top or bottom of the file + result[resultIndex] = 0; + + } else if (aboveContentLineIndent < belowContentLineIndent) { + // we are inside the region above + result[resultIndex] = (1 + Math.floor(aboveContentLineIndent / this._options.tabSize)); + + } else if (aboveContentLineIndent === belowContentLineIndent) { + // we are in between two regions + result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize); + + } else { + + if (offSide) { + // same level as region below + result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize); + } else { + // we are inside the region that ends below + result[resultIndex] = (1 + Math.floor(belowContentLineIndent / this._options.tabSize)); + } + + } + } + return result; + } + //#endregion } + +//#region Decorations + +class DecorationsTrees { + + /** + * This tree holds decorations that do not show up in the overview ruler. + */ + private _decorationsTree0: IntervalTree; + + /** + * This tree holds decorations that show up in the overview ruler. + */ + private _decorationsTree1: IntervalTree; + + constructor() { + this._decorationsTree0 = new IntervalTree(); + this._decorationsTree1 = new IntervalTree(); + } + + public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] { + const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); + const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); + return r0.concat(r1); + } + + public search(filterOwnerId: number, filterOutValidation: boolean, overviewRulerOnly: boolean, cachedVersionId: number): IntervalNode[] { + if (overviewRulerOnly) { + return this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); + } else { + const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, cachedVersionId); + const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); + return r0.concat(r1); + } + } + + public collectNodesFromOwner(ownerId: number): IntervalNode[] { + const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId); + const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId); + return r0.concat(r1); + } + + public collectNodesPostOrder(): IntervalNode[] { + const r0 = this._decorationsTree0.collectNodesPostOrder(); + const r1 = this._decorationsTree1.collectNodesPostOrder(); + return r0.concat(r1); + } + + public insert(node: IntervalNode): void { + if (getNodeIsInOverviewRuler(node)) { + this._decorationsTree1.insert(node); + } else { + this._decorationsTree0.insert(node); + } + } + + public delete(node: IntervalNode): void { + if (getNodeIsInOverviewRuler(node)) { + this._decorationsTree1.delete(node); + } else { + this._decorationsTree0.delete(node); + } + } + + public resolveNode(node: IntervalNode, cachedVersionId: number): void { + if (getNodeIsInOverviewRuler(node)) { + this._decorationsTree1.resolveNode(node, cachedVersionId); + } else { + this._decorationsTree0.resolveNode(node, cachedVersionId); + } + } + + public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void { + this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers); + this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers); + } +} + +function cleanClassName(className: string): string { + return className.replace(/[^a-z0-9\-]/gi, ' '); +} + +export class ModelDecorationOverviewRulerOptions implements editorCommon.IModelDecorationOverviewRulerOptions { + readonly color: string | ThemeColor; + readonly darkColor: string | ThemeColor; + readonly hcColor: string | ThemeColor; + readonly position: editorCommon.OverviewRulerLane; + _resolvedColor: string; + + constructor(options: editorCommon.IModelDecorationOverviewRulerOptions) { + this.color = strings.empty; + this.darkColor = strings.empty; + this.hcColor = strings.empty; + this.position = editorCommon.OverviewRulerLane.Center; + this._resolvedColor = null; + + if (options && options.color) { + this.color = options.color; + } + if (options && options.darkColor) { + this.darkColor = options.darkColor; + this.hcColor = options.darkColor; + } + if (options && options.hcColor) { + this.hcColor = options.hcColor; + } + if (options && options.hasOwnProperty('position')) { + this.position = options.position; + } + } +} + +let lastStaticId = 0; + +export class ModelDecorationOptions implements editorCommon.IModelDecorationOptions { + + public static EMPTY: ModelDecorationOptions; + + public static register(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { + return new ModelDecorationOptions(++lastStaticId, options); + } + + public static createDynamic(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { + return new ModelDecorationOptions(0, options); + } + + readonly staticId: number; + readonly stickiness: editorCommon.TrackedRangeStickiness; + readonly className: string; + readonly hoverMessage: IMarkdownString | IMarkdownString[]; + readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[]; + readonly isWholeLine: boolean; + readonly showIfCollapsed: boolean; + readonly overviewRuler: ModelDecorationOverviewRulerOptions; + readonly glyphMarginClassName: string; + readonly linesDecorationsClassName: string; + readonly marginClassName: string; + readonly inlineClassName: string; + readonly beforeContentClassName: string; + readonly afterContentClassName: string; + + private constructor(staticId: number, options: editorCommon.IModelDecorationOptions) { + this.staticId = staticId; + this.stickiness = options.stickiness || editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; + this.className = options.className ? cleanClassName(options.className) : strings.empty; + this.hoverMessage = options.hoverMessage || []; + this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || []; + this.isWholeLine = options.isWholeLine || false; + this.showIfCollapsed = options.showIfCollapsed || false; + this.overviewRuler = new ModelDecorationOverviewRulerOptions(options.overviewRuler); + this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : strings.empty; + this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : strings.empty; + this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : strings.empty; + this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : strings.empty; + this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : strings.empty; + this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : strings.empty; + } +} +ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({}); + +/** + * The order carefully matches the values of the enum. + */ +const TRACKED_RANGE_OPTIONS = [ + ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }), + ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }), + ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }), + ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }), +]; + +function _normalizeOptions(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { + if (options instanceof ModelDecorationOptions) { + return options; + } + return ModelDecorationOptions.createDynamic(options); +} + +export class DidChangeDecorationsEmitter extends Disposable { + + private readonly _actual: Emitter = this._register(new Emitter()); + public readonly event: Event = this._actual.event; + + private _deferredCnt: number; + private _shouldFire: boolean; + + constructor() { + super(); + this._deferredCnt = 0; + this._shouldFire = false; + } + + public beginDeferredEmit(): void { + this._deferredCnt++; + } + + public endDeferredEmit(): void { + this._deferredCnt--; + if (this._deferredCnt === 0) { + if (this._shouldFire) { + this._shouldFire = false; + this._actual.fire({}); + } + } + } + + public fire(): void { + this._shouldFire = true; + } +} + +//#endregion diff --git a/src/vs/editor/common/model/modelLine.ts b/src/vs/editor/common/model/modelLine.ts index 956359c82b5..e674fe4b5f4 100644 --- a/src/vs/editor/common/model/modelLine.ts +++ b/src/vs/editor/common/model/modelLine.ts @@ -4,26 +4,17 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { IState, FontStyle, StandardTokenType, MetadataConsts, ColorId, LanguageId } from 'vs/editor/common/modes'; +import { IState, FontStyle, StandardTokenType, MetadataConsts, ColorId, LanguageId, ITokenizationSupport, LanguageIdentifier } from 'vs/editor/common/modes'; import { CharCode } from 'vs/base/common/charCode'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; -import { Constants } from 'vs/editor/common/core/uint'; - -export interface ILineEdit { - startColumn: number; - endColumn: number; - text: string; -} - -export interface ITokensAdjuster { - adjust(toColumn: number, delta: number, minimumAllowedColumn: number): void; - finish(delta: number, lineTextLength: number): void; -} - -var NO_OP_TOKENS_ADJUSTER: ITokensAdjuster = { - adjust: () => { }, - finish: () => { } -}; +import * as arrays from 'vs/base/common/arrays'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { IModelTokensChangedEvent } from 'vs/editor/common/model/textModelEvents'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { ITextBuffer } from 'vs/editor/common/model/textBuffer'; +import { TokenizationResult2 } from 'vs/editor/common/core/token'; +import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; /** * Returns: @@ -54,423 +45,6 @@ export function computeIndentLevel(line: string, tabSize: number): number { return indent; } -export interface IModelLine { - readonly text: string; - - // --- tokenization - resetTokenizationState(): void; - isInvalid(): boolean; - setIsInvalid(isInvalid: boolean): void; - getState(): IState; - setState(state: IState): void; - getTokens(topLevelLanguageId: LanguageId): LineTokens; - setTokens(topLevelLanguageId: LanguageId, tokens: Uint32Array): void; - - // --- editing - applyEdits(edits: ILineEdit[]): number; - append(other: IModelLine): void; - split(splitColumn: number): IModelLine; -} - -export abstract class AbstractModelLine { - - constructor() { - } - - /// - - public abstract get text(): string; - protected abstract _setText(text: string): void; - protected abstract _createTokensAdjuster(): ITokensAdjuster; - protected abstract _createModelLine(text: string): IModelLine; - - /// - - public applyEdits(edits: ILineEdit[]): number { - let deltaColumn = 0; - let resultText = this.text; - - let tokensAdjuster = this._createTokensAdjuster(); - - for (let i = 0, len = edits.length; i < len; i++) { - let edit = edits[i]; - - // console.log(); - // console.log('============================='); - // console.log('EDIT #' + i + ' [ ' + edit.startColumn + ' -> ' + edit.endColumn + ' ] : <<<' + edit.text + '>>>'); - // console.log('deltaColumn: ' + deltaColumn); - - let startColumn = deltaColumn + edit.startColumn; - let endColumn = deltaColumn + edit.endColumn; - let deletingCnt = endColumn - startColumn; - let insertingCnt = edit.text.length; - - // Adjust tokens before this edit - // console.log('Adjust tokens before this edit'); - tokensAdjuster.adjust(edit.startColumn - 1, deltaColumn, 1); - - // Adjust tokens for the common part of this edit - let commonLength = Math.min(deletingCnt, insertingCnt); - if (commonLength > 0) { - // console.log('Adjust tokens for the common part of this edit'); - tokensAdjuster.adjust(edit.startColumn - 1 + commonLength, deltaColumn, startColumn); - } - - // Perform the edit & update `deltaColumn` - resultText = resultText.substring(0, startColumn - 1) + edit.text + resultText.substring(endColumn - 1); - deltaColumn += insertingCnt - deletingCnt; - - // Adjust tokens inside this edit - // console.log('Adjust tokens inside this edit'); - tokensAdjuster.adjust(edit.endColumn, deltaColumn, startColumn); - } - - // Wrap up tokens; adjust remaining if needed - tokensAdjuster.finish(deltaColumn, resultText.length); - - // Save the resulting text - this._setText(resultText); - - return deltaColumn; - } - - public split(splitColumn: number): IModelLine { - const myText = this.text.substring(0, splitColumn - 1); - const otherText = this.text.substring(splitColumn - 1); - - this._setText(myText); - return this._createModelLine(otherText); - } - - public append(other: IModelLine): void { - this._setText(this.text + other.text); - } -} - -export class ModelLine extends AbstractModelLine implements IModelLine { - - private _text: string; - public get text(): string { return this._text; } - - private _isInvalid: boolean; - - public isInvalid(): boolean { - return this._isInvalid; - } - - public setIsInvalid(isInvalid: boolean): void { - this._isInvalid = isInvalid; - } - - private _state: IState; - private _lineTokens: ArrayBuffer; - - constructor(text: string) { - super(); - this._isInvalid = false; - this._setText(text); - this._state = null; - this._lineTokens = null; - } - - protected _createModelLine(text: string): IModelLine { - return new ModelLine(text); - } - - public split(splitColumn: number): IModelLine { - let result = super.split(splitColumn); - - // Mark overflowing tokens for deletion & delete marked tokens - this._deleteMarkedTokens(this._markOverflowingTokensForDeletion(0, this.text.length)); - - return result; - } - - public append(other: IModelLine): void { - let thisTextLength = this.text.length; - - super.append(other); - - if (other instanceof ModelLine) { - let otherRawTokens = other._lineTokens; - if (otherRawTokens) { - // Other has real tokens - - let otherTokens = new Uint32Array(otherRawTokens); - - // Adjust other tokens - if (thisTextLength > 0) { - for (let i = 0, len = (otherTokens.length >>> 1); i < len; i++) { - otherTokens[(i << 1)] = otherTokens[(i << 1)] + thisTextLength; - } - } - - // Append other tokens - let myRawTokens = this._lineTokens; - if (myRawTokens) { - // I have real tokens - let myTokens = new Uint32Array(myRawTokens); - let result = new Uint32Array(myTokens.length + otherTokens.length); - result.set(myTokens, 0); - result.set(otherTokens, myTokens.length); - this._lineTokens = result.buffer; - } else { - // I don't have real tokens - this._lineTokens = otherTokens.buffer; - } - } - } - } - - // --- BEGIN STATE - - public resetTokenizationState(): void { - this._state = null; - this._lineTokens = null; - } - - public setState(state: IState): void { - this._state = state; - } - - public getState(): IState { - return this._state || null; - } - - // --- END STATE - - // --- BEGIN TOKENS - - public setTokens(topLevelLanguageId: LanguageId, tokens: Uint32Array): void { - if (!tokens || tokens.length === 0) { - this._lineTokens = null; - return; - } - if (tokens.length === 2) { - // there is one token - if (tokens[0] === 0 && tokens[1] === getDefaultMetadata(topLevelLanguageId)) { - this._lineTokens = null; - return; - } - } - this._lineTokens = tokens.buffer; - } - - public getTokens(topLevelLanguageId: LanguageId): LineTokens { - let rawLineTokens = this._lineTokens; - if (rawLineTokens) { - return new LineTokens(new Uint32Array(rawLineTokens), this._text); - } - - let lineTokens = new Uint32Array(2); - lineTokens[0] = 0; - lineTokens[1] = getDefaultMetadata(topLevelLanguageId); - return new LineTokens(lineTokens, this._text); - } - - // --- END TOKENS - - protected _createTokensAdjuster(): ITokensAdjuster { - if (!this._lineTokens) { - // This line does not have real tokens, so there is nothing to adjust - return NO_OP_TOKENS_ADJUSTER; - } - - let lineTokens = new Uint32Array(this._lineTokens); - let tokensLength = (lineTokens.length >>> 1); - let tokenIndex = 0; - let tokenStartOffset = 0; - let removeTokensCount = 0; - - let adjust = (toColumn: number, delta: number, minimumAllowedColumn: number) => { - // console.log(`------------------------------------------------------------------`); - // console.log(`before call: tokenIndex: ${tokenIndex}: ${lineTokens}`); - // console.log(`adjustTokens: ${toColumn} with delta: ${delta} and [${minimumAllowedColumn}]`); - // console.log(`tokenStartOffset: ${tokenStartOffset}`); - let minimumAllowedIndex = minimumAllowedColumn - 1; - - while (tokenStartOffset < toColumn && tokenIndex < tokensLength) { - - if (tokenStartOffset > 0 && delta !== 0) { - // adjust token's `startIndex` by `delta` - let newTokenStartOffset = Math.max(minimumAllowedIndex, tokenStartOffset + delta); - lineTokens[(tokenIndex << 1)] = newTokenStartOffset; - - // console.log(` * adjusted token start offset for token at ${tokenIndex}: ${newTokenStartOffset}`); - - if (delta < 0) { - let tmpTokenIndex = tokenIndex; - while (tmpTokenIndex > 0) { - let prevTokenStartOffset = lineTokens[((tmpTokenIndex - 1) << 1)]; - if (prevTokenStartOffset >= newTokenStartOffset) { - if (prevTokenStartOffset !== Constants.MAX_UINT_32) { - // console.log(` * marking for deletion token at ${tmpTokenIndex - 1}`); - lineTokens[((tmpTokenIndex - 1) << 1)] = Constants.MAX_UINT_32; - removeTokensCount++; - } - tmpTokenIndex--; - } else { - break; - } - } - } - } - - tokenIndex++; - if (tokenIndex < tokensLength) { - tokenStartOffset = lineTokens[(tokenIndex << 1)]; - } - } - // console.log(`after call: tokenIndex: ${tokenIndex}: ${lineTokens}`); - }; - - let finish = (delta: number, lineTextLength: number) => { - adjust(Constants.MAX_SAFE_SMALL_INTEGER, delta, 1); - - // Mark overflowing tokens for deletion & delete marked tokens - this._deleteMarkedTokens(this._markOverflowingTokensForDeletion(removeTokensCount, lineTextLength)); - }; - - return { - adjust: adjust, - finish: finish - }; - } - - private _markOverflowingTokensForDeletion(removeTokensCount: number, lineTextLength: number): number { - if (!this._lineTokens) { - return removeTokensCount; - } - - let lineTokens = new Uint32Array(this._lineTokens); - let tokensLength = (lineTokens.length >>> 1); - - if (removeTokensCount + 1 === tokensLength) { - // no more removing, cannot end up without any tokens for mode transition reasons - return removeTokensCount; - } - - for (let tokenIndex = tokensLength - 1; tokenIndex > 0; tokenIndex--) { - let tokenStartOffset = lineTokens[(tokenIndex << 1)]; - if (tokenStartOffset < lineTextLength) { - // valid token => stop iterating - return removeTokensCount; - } - - // this token now overflows the text => mark it for removal - if (tokenStartOffset !== Constants.MAX_UINT_32) { - // console.log(` * marking for deletion token at ${tokenIndex}`); - lineTokens[(tokenIndex << 1)] = Constants.MAX_UINT_32; - removeTokensCount++; - - if (removeTokensCount + 1 === tokensLength) { - // no more removing, cannot end up without any tokens for mode transition reasons - return removeTokensCount; - } - } - } - - return removeTokensCount; - } - - private _deleteMarkedTokens(removeTokensCount: number): void { - if (removeTokensCount === 0) { - return; - } - - let lineTokens = new Uint32Array(this._lineTokens); - let tokensLength = (lineTokens.length >>> 1); - let newTokens = new Uint32Array(((tokensLength - removeTokensCount) << 1)), newTokenIdx = 0; - for (let i = 0; i < tokensLength; i++) { - let startOffset = lineTokens[(i << 1)]; - if (startOffset === Constants.MAX_UINT_32) { - // marked for deletion - continue; - } - let metadata = lineTokens[(i << 1) + 1]; - newTokens[newTokenIdx++] = startOffset; - newTokens[newTokenIdx++] = metadata; - } - this._lineTokens = newTokens.buffer; - } - - protected _setText(text: string): void { - this._text = text; - } - -} - -/** - * A model line that cannot store any tokenization state. - * It has no fields except the text. - */ -export class MinimalModelLine extends AbstractModelLine implements IModelLine { - - private _text: string; - public get text(): string { return this._text; } - - public isInvalid(): boolean { - return false; - } - - public setIsInvalid(isInvalid: boolean): void { - } - - constructor(text: string) { - super(); - this._setText(text); - } - - protected _createModelLine(text: string): IModelLine { - return new MinimalModelLine(text); - } - - public split(splitColumn: number): IModelLine { - return super.split(splitColumn); - } - - public append(other: IModelLine): void { - super.append(other); - } - - // --- BEGIN STATE - - public resetTokenizationState(): void { - } - - public setState(state: IState): void { - } - - public getState(): IState { - return null; - } - - // --- END STATE - - // --- BEGIN TOKENS - - public setTokens(topLevelLanguageId: LanguageId, tokens: Uint32Array): void { - } - - public getTokens(topLevelLanguageId: LanguageId): LineTokens { - let lineTokens = new Uint32Array(2); - lineTokens[0] = 0; - lineTokens[1] = getDefaultMetadata(topLevelLanguageId); - return new LineTokens(lineTokens, this._text); - } - - // --- END TOKENS - - protected _createTokensAdjuster(): ITokensAdjuster { - // This line does not have real tokens, so there is nothing to adjust - return NO_OP_TOKENS_ADJUSTER; - } - - protected _setText(text: string): void { - this._text = text; - } -} - function getDefaultMetadata(topLevelLanguageId: LanguageId): number { return ( (topLevelLanguageId << MetadataConsts.LANGUAGEID_OFFSET) @@ -480,3 +54,469 @@ function getDefaultMetadata(topLevelLanguageId: LanguageId): number { | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) ) >>> 0; } + +const EMPTY_LINE_TOKENS = new Uint32Array(0); + +class ModelLineTokens { + _state: IState; + _lineTokens: ArrayBuffer; + _invalid: boolean; + + constructor(state: IState) { + this._state = state; + this._lineTokens = null; + this._invalid = true; + } + + public deleteBeginning(toChIndex: number): void { + if (this._lineTokens === null || this._lineTokens === EMPTY_LINE_TOKENS) { + return; + } + this.delete(0, toChIndex); + } + + public deleteEnding(fromChIndex: number): void { + if (this._lineTokens === null || this._lineTokens === EMPTY_LINE_TOKENS) { + return; + } + + const tokens = new Uint32Array(this._lineTokens); + const lineTextLength = tokens[tokens.length - 2]; + this.delete(fromChIndex, lineTextLength); + } + + public delete(fromChIndex: number, toChIndex: number): void { + if (this._lineTokens === null || this._lineTokens === EMPTY_LINE_TOKENS || fromChIndex === toChIndex) { + return; + } + + const tokens = new Uint32Array(this._lineTokens); + const tokensCount = (tokens.length >>> 1); + + // special case: deleting everything + if (fromChIndex === 0 && tokens[tokens.length - 2] === toChIndex) { + this._lineTokens = EMPTY_LINE_TOKENS; + return; + } + + const fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, fromChIndex); + const fromTokenStartOffset = (fromTokenIndex > 0 ? tokens[(fromTokenIndex - 1) << 1] : 0); + const fromTokenEndOffset = tokens[fromTokenIndex << 1]; + + if (toChIndex < fromTokenEndOffset) { + // the delete range is inside a single token + const delta = (toChIndex - fromChIndex); + for (let i = fromTokenIndex; i < tokensCount; i++) { + tokens[i << 1] -= delta; + } + return; + } + + let dest: number; + let lastEnd: number; + if (fromTokenStartOffset !== fromChIndex) { + tokens[fromTokenIndex << 1] = fromChIndex; + dest = ((fromTokenIndex + 1) << 1); + lastEnd = fromChIndex; + } else { + dest = (fromTokenIndex << 1); + lastEnd = fromTokenStartOffset; + } + + const delta = (toChIndex - fromChIndex); + for (let tokenIndex = fromTokenIndex + 1; tokenIndex < tokensCount; tokenIndex++) { + const tokenEndOffset = tokens[tokenIndex << 1] - delta; + if (tokenEndOffset > lastEnd) { + tokens[dest++] = tokenEndOffset; + tokens[dest++] = tokens[(tokenIndex << 1) + 1]; + lastEnd = tokenEndOffset; + } + } + + if (dest === tokens.length) { + // nothing to trim + return; + } + + let tmp = new Uint32Array(dest); + tmp.set(tokens.subarray(0, dest), 0); + this._lineTokens = tmp.buffer; + } + + public append(_otherTokens: ArrayBuffer): void { + if (_otherTokens === EMPTY_LINE_TOKENS) { + return; + } + if (this._lineTokens === EMPTY_LINE_TOKENS) { + this._lineTokens = _otherTokens; + return; + } + if (this._lineTokens === null) { + return; + } + if (_otherTokens === null) { + // cannot determine combined line length... + this._lineTokens = null; + return; + } + const myTokens = new Uint32Array(this._lineTokens); + const otherTokens = new Uint32Array(_otherTokens); + const otherTokensCount = (otherTokens.length >>> 1); + + let result = new Uint32Array(myTokens.length + otherTokens.length); + result.set(myTokens, 0); + let dest = myTokens.length; + const delta = myTokens[myTokens.length - 2]; + for (let i = 0; i < otherTokensCount; i++) { + result[dest++] = otherTokens[(i << 1)] + delta; + result[dest++] = otherTokens[(i << 1) + 1]; + } + this._lineTokens = result.buffer; + } + + public insert(chIndex: number, textLength: number): void { + if (!this._lineTokens) { + // nothing to do + return; + } + + const tokens = new Uint32Array(this._lineTokens); + const tokensCount = (tokens.length >>> 1); + + let fromTokenIndex = LineTokens.findIndexInTokensArray(tokens, chIndex); + if (fromTokenIndex > 0) { + const fromTokenStartOffset = (fromTokenIndex > 0 ? tokens[(fromTokenIndex - 1) << 1] : 0); + if (fromTokenStartOffset === chIndex) { + fromTokenIndex--; + } + } + for (let tokenIndex = fromTokenIndex; tokenIndex < tokensCount; tokenIndex++) { + tokens[tokenIndex << 1] += textLength; + } + } +} + +export class ModelLinesTokens { + + public readonly languageIdentifier: LanguageIdentifier; + public readonly tokenizationSupport: ITokenizationSupport; + private _tokens: ModelLineTokens[]; + private _invalidLineStartIndex: number; + private _lastState: IState; + + constructor(languageIdentifier: LanguageIdentifier, tokenizationSupport: ITokenizationSupport) { + this.languageIdentifier = languageIdentifier; + this.tokenizationSupport = tokenizationSupport; + this._tokens = []; + if (this.tokenizationSupport) { + let initialState: IState = null; + try { + initialState = this.tokenizationSupport.getInitialState(); + } catch (e) { + onUnexpectedError(e); + this.tokenizationSupport = null; + } + + if (initialState) { + this._tokens[0] = new ModelLineTokens(initialState); + } + } + + this._invalidLineStartIndex = 0; + this._lastState = null; + } + + public getTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineText: string): LineTokens { + let rawLineTokens: ArrayBuffer = null; + if (lineIndex < this._tokens.length) { + rawLineTokens = this._tokens[lineIndex]._lineTokens; + } + + if (rawLineTokens !== null && rawLineTokens !== EMPTY_LINE_TOKENS) { + return new LineTokens(new Uint32Array(rawLineTokens), lineText); + } + + let lineTokens = new Uint32Array(2); + lineTokens[0] = lineText.length; + lineTokens[1] = getDefaultMetadata(topLevelLanguageId); + return new LineTokens(lineTokens, lineText); + } + + public isCheapToTokenize(lineNumber: number): boolean { + const firstInvalidLineNumber = this._invalidLineStartIndex + 1; + return (firstInvalidLineNumber >= lineNumber); + } + + public hasLinesToTokenize(buffer: ITextBuffer): boolean { + return (this._invalidLineStartIndex < buffer.getLineCount()); + } + + public invalidateLine(lineIndex: number): void { + this._setIsInvalid(lineIndex, true); + if (lineIndex < this._invalidLineStartIndex) { + this._setIsInvalid(this._invalidLineStartIndex, true); + this._invalidLineStartIndex = lineIndex; + } + } + + private _setIsInvalid(lineIndex: number, invalid: boolean): void { + if (lineIndex < this._tokens.length) { + this._tokens[lineIndex]._invalid = invalid; + } + } + + _isInvalid(lineIndex: number): boolean { + if (lineIndex < this._tokens.length) { + return this._tokens[lineIndex]._invalid; + } + return true; + } + + _getState(lineIndex: number): IState { + if (lineIndex < this._tokens.length) { + return this._tokens[lineIndex]._state; + } + return null; + } + + _setTokens(topLevelLanguageId: LanguageId, lineIndex: number, lineTextLength: number, tokens: Uint32Array): void { + let target: ModelLineTokens; + if (lineIndex < this._tokens.length) { + target = this._tokens[lineIndex]; + } else { + target = new ModelLineTokens(null); + this._tokens[lineIndex] = target; + } + + if (lineTextLength === 0) { + target._lineTokens = EMPTY_LINE_TOKENS; + return; + } + + if (!tokens || tokens.length === 0) { + tokens = new Uint32Array(2); + tokens[0] = 0; + tokens[1] = getDefaultMetadata(topLevelLanguageId); + } + + LineTokens.convertToEndOffset(tokens, lineTextLength); + + target._lineTokens = tokens.buffer; + } + + private _setState(lineIndex: number, state: IState): void { + if (lineIndex < this._tokens.length) { + this._tokens[lineIndex]._state = state; + } else { + const tmp = new ModelLineTokens(state); + this._tokens[lineIndex] = tmp; + } + } + + //#region Editing + + // TODO: simplify + public applyEdits(range: Range, lines: string[]): void { + + const deletingLinesCnt = range.endLineNumber - range.startLineNumber; + const insertingLinesCnt = (lines ? lines.length - 1 : 0); + const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); + + // Iterating descending to overlap with previous op + // in case there are common lines being edited in both + for (let j = editingLinesCnt; j >= 0; j--) { + const editLineNumber = range.startLineNumber + j; + this.invalidateLine(editLineNumber - 1); + } + + if (editingLinesCnt < deletingLinesCnt) { + // Must delete some lines + const spliceStartLineNumber = range.startLineNumber + editingLinesCnt; + this.invalidateLine(spliceStartLineNumber - 1); + } + + if (editingLinesCnt < insertingLinesCnt) { + // Must insert some lines + const spliceLineNumber = range.startLineNumber + editingLinesCnt; + this.invalidateLine(spliceLineNumber - 1); + } + + this._acceptDeleteRange(range); + this._acceptInsertText(new Position(range.startLineNumber, range.startColumn), lines); + } + + private _acceptDeleteRange(range: Range): void { + + const firstLineIndex = range.startLineNumber - 1; + if (firstLineIndex >= this._tokens.length) { + return; + } + + if (range.startLineNumber === range.endLineNumber) { + if (range.startColumn === range.endColumn) { + // Nothing to delete + return; + } + + this._tokens[firstLineIndex].delete(range.startColumn - 1, range.endColumn - 1); + return; + } + + const firstLine = this._tokens[firstLineIndex]; + firstLine.deleteEnding(range.startColumn - 1); + + const lastLineIndex = range.endLineNumber - 1; + let lastLineTokens: ArrayBuffer = null; + if (lastLineIndex < this._tokens.length) { + const lastLine = this._tokens[lastLineIndex]; + lastLine.deleteBeginning(range.endColumn - 1); + lastLineTokens = lastLine._lineTokens; + } + + // Take remaining text on last line and append it to remaining text on first line + firstLine.append(lastLineTokens); + + // Delete middle lines + this._tokens.splice(range.startLineNumber, range.endLineNumber - range.startLineNumber); + } + + private _acceptInsertText(position: Position, insertLines: string[]): void { + + if (!insertLines || insertLines.length === 0) { + // Nothing to insert + return; + } + + const lineIndex = position.lineNumber - 1; + if (lineIndex >= this._tokens.length) { + return; + } + + if (insertLines.length === 1) { + // Inserting text on one line + this._tokens[lineIndex].insert(position.column - 1, insertLines[0].length); + return; + } + + const line = this._tokens[lineIndex]; + line.deleteEnding(position.column - 1); + line.insert(position.column - 1, insertLines[0].length); + + let insert: ModelLineTokens[] = new Array(insertLines.length - 1); + for (let i = insertLines.length - 2; i >= 0; i--) { + insert[i] = new ModelLineTokens(null); + } + this._tokens = arrays.arrayInsert(this._tokens, position.lineNumber, insert); + } + + //#endregion + + //#region Tokenization + + public _tokenizeOneLine(buffer: ITextBuffer, eventBuilder: ModelTokensChangedEventBuilder): number { + if (!this.hasLinesToTokenize(buffer)) { + return buffer.getLineCount() + 1; + } + const lineNumber = this._invalidLineStartIndex + 1; + this._updateTokensUntilLine(buffer, eventBuilder, lineNumber); + return lineNumber; + } + + public _updateTokensUntilLine(buffer: ITextBuffer, eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void { + if (!this.tokenizationSupport) { + this._invalidLineStartIndex = buffer.getLineCount(); + return; + } + + const linesLength = buffer.getLineCount(); + const endLineIndex = lineNumber - 1; + + // Validate all states up to and including endLineIndex + for (let lineIndex = this._invalidLineStartIndex; lineIndex <= endLineIndex; lineIndex++) { + const endStateIndex = lineIndex + 1; + let r: TokenizationResult2 = null; + const text = buffer.getLineContent(lineIndex + 1); + + try { + // Tokenize only the first X characters + let freshState = this._getState(lineIndex).clone(); + r = this.tokenizationSupport.tokenize2(text, freshState, 0); + } catch (e) { + onUnexpectedError(e); + } + + if (!r) { + r = nullTokenize2(this.languageIdentifier.id, text, this._getState(lineIndex), 0); + } + this._setTokens(this.languageIdentifier.id, lineIndex, text.length, r.tokens); + eventBuilder.registerChangedTokens(lineIndex + 1); + this._setIsInvalid(lineIndex, false); + + if (endStateIndex < linesLength) { + if (this._getState(endStateIndex) !== null && r.endState.equals(this._getState(endStateIndex))) { + // The end state of this line remains the same + let nextInvalidLineIndex = lineIndex + 1; + while (nextInvalidLineIndex < linesLength) { + if (this._isInvalid(nextInvalidLineIndex)) { + break; + } + if (nextInvalidLineIndex + 1 < linesLength) { + if (this._getState(nextInvalidLineIndex + 1) === null) { + break; + } + } else { + if (this._lastState === null) { + break; + } + } + nextInvalidLineIndex++; + } + this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, nextInvalidLineIndex); + lineIndex = nextInvalidLineIndex - 1; // -1 because the outer loop increments it + } else { + this._setState(endStateIndex, r.endState); + } + } else { + this._lastState = r.endState; + } + } + this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, endLineIndex + 1); + } + + // #endregion +} + +export class ModelTokensChangedEventBuilder { + + private _ranges: { fromLineNumber: number; toLineNumber: number; }[]; + + constructor() { + this._ranges = []; + } + + public registerChangedTokens(lineNumber: number): void { + const ranges = this._ranges; + const rangesLength = ranges.length; + const previousRange = rangesLength > 0 ? ranges[rangesLength - 1] : null; + + if (previousRange && previousRange.toLineNumber === lineNumber - 1) { + // extend previous range + previousRange.toLineNumber++; + } else { + // insert new range + ranges[rangesLength] = { + fromLineNumber: lineNumber, + toLineNumber: lineNumber + }; + } + } + + public build(): IModelTokensChangedEvent { + if (this._ranges.length === 0) { + return null; + } + return { + ranges: this._ranges + }; + } +} diff --git a/src/vs/editor/common/model/textBuffer.ts b/src/vs/editor/common/model/textBuffer.ts new file mode 100644 index 00000000000..4b1693c5ca2 --- /dev/null +++ b/src/vs/editor/common/model/textBuffer.ts @@ -0,0 +1,636 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Range } from 'vs/editor/common/core/range'; +import { Position } from 'vs/editor/common/core/position'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import * as strings from 'vs/base/common/strings'; +import * as arrays from 'vs/base/common/arrays'; +import { ITextSource } from 'vs/editor/common/model/textSource'; +import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; +import { ModelRawChange, IModelContentChange, ModelRawLineChanged, ModelRawLinesDeleted, ModelRawLinesInserted } from 'vs/editor/common/model/textModelEvents'; + +export interface IValidatedEditOperation { + sortIndex: number; + identifier: editorCommon.ISingleEditOperationIdentifier; + range: Range; + rangeOffset: number; + rangeLength: number; + lines: string[]; + forceMoveMarkers: boolean; + isAutoWhitespaceEdit: boolean; +} + +export interface ITextBuffer { + equals(other: ITextSource): boolean; + mightContainRTL(): boolean; + mightContainNonBasicASCII(): boolean; + getBOM(): string; + getEOL(): string; + + getOffsetAt(lineNumber: number, column: number): number; + getPositionAt(offset: number): Position; + getRangeAt(offset: number, length: number): Range; + + getValueInRange(range: Range, eol: editorCommon.EndOfLinePreference): string; + getValueLengthInRange(range: Range, eol: editorCommon.EndOfLinePreference): number; + getLineCount(): number; + getLinesContent(): string[]; + getLineContent(lineNumber: number): string; + getLineCharCode(lineNumber: number, index: number): number; + getLineLength(lineNumber: number): number; + getLineFirstNonWhitespaceColumn(lineNumber: number): number; + getLineLastNonWhitespaceColumn(lineNumber: number): number; + + setEOL(newEOL: string): void; + applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult; +} + +export class ApplyEditsResult { + + constructor( + public readonly reverseEdits: editorCommon.IIdentifiedSingleEditOperation[], + public readonly rawChanges: ModelRawChange[], + public readonly changes: IInternalModelContentChange[], + public readonly trimAutoWhitespaceLineNumbers: number[] + ) { } + +} + +export interface IInternalModelContentChange extends IModelContentChange { + range: Range; + lines: string[]; + rangeOffset: number; + forceMoveMarkers: boolean; +} + +export class TextBuffer implements ITextBuffer { + + private _lines: string[]; + private _BOM: string; + private _EOL: string; + private _mightContainRTL: boolean; + private _mightContainNonBasicASCII: boolean; + private _lineStarts: PrefixSumComputer; + + constructor(textSource: ITextSource) { + this._lines = textSource.lines.slice(0); + this._BOM = textSource.BOM; + this._EOL = textSource.EOL; + this._mightContainRTL = textSource.containsRTL; + this._mightContainNonBasicASCII = !textSource.isBasicASCII; + this._constructLineStarts(); + } + + private _constructLineStarts(): void { + const eolLength = this._EOL.length; + const linesLength = this._lines.length; + const lineStartValues = new Uint32Array(linesLength); + for (let i = 0; i < linesLength; i++) { + lineStartValues[i] = this._lines[i].length + eolLength; + } + this._lineStarts = new PrefixSumComputer(lineStartValues); + } + + public equals(other: ITextSource): boolean { + if (this._BOM !== other.BOM) { + return false; + } + if (this._EOL !== other.EOL) { + return false; + } + if (this._lines.length !== other.lines.length) { + return false; + } + for (let i = 0, len = this._lines.length; i < len; i++) { + if (this._lines[i] !== other.lines[i]) { + return false; + } + } + return true; + } + + public mightContainRTL(): boolean { + return this._mightContainRTL; + } + + public mightContainNonBasicASCII(): boolean { + return this._mightContainNonBasicASCII; + } + + public getBOM(): string { + return this._BOM; + } + + public getEOL(): string { + return this._EOL; + } + + public getOffsetAt(lineNumber: number, column: number): number { + return this._lineStarts.getAccumulatedValue(lineNumber - 2) + column - 1; + } + + public getPositionAt(offset: number): Position { + offset = Math.floor(offset); + offset = Math.max(0, offset); + + let out = this._lineStarts.getIndexOf(offset); + + let lineLength = this._lines[out.index].length; + + // Ensure we return a valid position + return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1)); + } + + public getRangeAt(offset: number, length: number): Range { + const startResult = this._lineStarts.getIndexOf(offset); + const startLineLength = this._lines[startResult.index].length; + const startColumn = Math.min(startResult.remainder + 1, startLineLength + 1); + + const endResult = this._lineStarts.getIndexOf(offset + length); + const endLineLength = this._lines[endResult.index].length; + const endColumn = Math.min(endResult.remainder + 1, endLineLength + 1); + + return new Range(startResult.index + 1, startColumn, endResult.index + 1, endColumn); + } + + private _getEndOfLine(eol: editorCommon.EndOfLinePreference): string { + switch (eol) { + case editorCommon.EndOfLinePreference.LF: + return '\n'; + case editorCommon.EndOfLinePreference.CRLF: + return '\r\n'; + case editorCommon.EndOfLinePreference.TextDefined: + return this.getEOL(); + } + throw new Error('Unknown EOL preference'); + } + + public getValueInRange(range: Range, eol: editorCommon.EndOfLinePreference): string { + if (range.isEmpty()) { + return ''; + } + + if (range.startLineNumber === range.endLineNumber) { + return this._lines[range.startLineNumber - 1].substring(range.startColumn - 1, range.endColumn - 1); + } + + const lineEnding = this._getEndOfLine(eol); + const startLineIndex = range.startLineNumber - 1; + const endLineIndex = range.endLineNumber - 1; + let resultLines: string[] = []; + + resultLines.push(this._lines[startLineIndex].substring(range.startColumn - 1)); + for (let i = startLineIndex + 1; i < endLineIndex; i++) { + resultLines.push(this._lines[i]); + } + resultLines.push(this._lines[endLineIndex].substring(0, range.endColumn - 1)); + + return resultLines.join(lineEnding); + } + + public getValueLengthInRange(range: Range, eol: editorCommon.EndOfLinePreference): number { + if (range.isEmpty()) { + return 0; + } + + if (range.startLineNumber === range.endLineNumber) { + return (range.endColumn - range.startColumn); + } + + let startOffset = this.getOffsetAt(range.startLineNumber, range.startColumn); + let endOffset = this.getOffsetAt(range.endLineNumber, range.endColumn); + return endOffset - startOffset; + } + + public getLineCount(): number { + return this._lines.length; + } + + public getLinesContent(): string[] { + return this._lines.slice(0); + } + + public getLineContent(lineNumber: number): string { + return this._lines[lineNumber - 1]; + } + + public getLineCharCode(lineNumber: number, index: number): number { + return this._lines[lineNumber - 1].charCodeAt(index); + } + + public getLineLength(lineNumber: number): number { + return this._lines[lineNumber - 1].length; + } + + public getLineFirstNonWhitespaceColumn(lineNumber: number): number { + const result = strings.firstNonWhitespaceIndex(this._lines[lineNumber - 1]); + if (result === -1) { + return 0; + } + return result + 1; + } + + public getLineLastNonWhitespaceColumn(lineNumber: number): number { + const result = strings.lastNonWhitespaceIndex(this._lines[lineNumber - 1]); + if (result === -1) { + return 0; + } + return result + 2; + } + + //#region Editing + + public setEOL(newEOL: string): void { + this._EOL = newEOL; + this._constructLineStarts(); + } + + private static _sortOpsAscending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { + let r = Range.compareRangesUsingEnds(a.range, b.range); + if (r === 0) { + return a.sortIndex - b.sortIndex; + } + return r; + } + + private static _sortOpsDescending(a: IValidatedEditOperation, b: IValidatedEditOperation): number { + let r = Range.compareRangesUsingEnds(a.range, b.range); + if (r === 0) { + return b.sortIndex - a.sortIndex; + } + return -r; + } + + public applyEdits(rawOperations: editorCommon.IIdentifiedSingleEditOperation[], recordTrimAutoWhitespace: boolean): ApplyEditsResult { + if (rawOperations.length === 0) { + return new ApplyEditsResult([], [], [], []); + } + + let mightContainRTL = this._mightContainRTL; + let mightContainNonBasicASCII = this._mightContainNonBasicASCII; + let canReduceOperations = true; + + let operations: IValidatedEditOperation[] = []; + for (let i = 0; i < rawOperations.length; i++) { + let op = rawOperations[i]; + if (canReduceOperations && op._isTracked) { + canReduceOperations = false; + } + let validatedRange = op.range; + if (!mightContainRTL && op.text) { + // check if the new inserted text contains RTL + mightContainRTL = strings.containsRTL(op.text); + } + if (!mightContainNonBasicASCII && op.text) { + mightContainNonBasicASCII = !strings.isBasicASCII(op.text); + } + operations[i] = { + sortIndex: i, + identifier: op.identifier, + range: validatedRange, + rangeOffset: this.getOffsetAt(validatedRange.startLineNumber, validatedRange.startColumn), + rangeLength: this.getValueLengthInRange(validatedRange, editorCommon.EndOfLinePreference.TextDefined), + lines: op.text ? op.text.split(/\r\n|\r|\n/) : null, + forceMoveMarkers: op.forceMoveMarkers, + isAutoWhitespaceEdit: op.isAutoWhitespaceEdit || false + }; + } + + // Sort operations ascending + operations.sort(TextBuffer._sortOpsAscending); + + for (let i = 0, count = operations.length - 1; i < count; i++) { + let rangeEnd = operations[i].range.getEndPosition(); + let nextRangeStart = operations[i + 1].range.getStartPosition(); + + if (nextRangeStart.isBefore(rangeEnd)) { + // overlapping ranges + throw new Error('Overlapping ranges are not allowed!'); + } + } + + if (canReduceOperations) { + operations = this._reduceOperations(operations); + } + + // Delta encode operations + let reverseRanges = TextBuffer._getInverseEditRanges(operations); + let newTrimAutoWhitespaceCandidates: { lineNumber: number, oldContent: string }[] = []; + + for (let i = 0; i < operations.length; i++) { + let op = operations[i]; + let reverseRange = reverseRanges[i]; + + if (recordTrimAutoWhitespace && op.isAutoWhitespaceEdit && op.range.isEmpty()) { + // Record already the future line numbers that might be auto whitespace removal candidates on next edit + for (let lineNumber = reverseRange.startLineNumber; lineNumber <= reverseRange.endLineNumber; lineNumber++) { + let currentLineContent = ''; + if (lineNumber === reverseRange.startLineNumber) { + currentLineContent = this.getLineContent(op.range.startLineNumber); + if (strings.firstNonWhitespaceIndex(currentLineContent) !== -1) { + continue; + } + } + newTrimAutoWhitespaceCandidates.push({ lineNumber: lineNumber, oldContent: currentLineContent }); + } + } + } + + let reverseOperations: editorCommon.IIdentifiedSingleEditOperation[] = []; + for (let i = 0; i < operations.length; i++) { + let op = operations[i]; + let reverseRange = reverseRanges[i]; + + reverseOperations[i] = { + identifier: op.identifier, + range: reverseRange, + text: this.getValueInRange(op.range, editorCommon.EndOfLinePreference.TextDefined), + forceMoveMarkers: op.forceMoveMarkers + }; + } + + this._mightContainRTL = mightContainRTL; + this._mightContainNonBasicASCII = mightContainNonBasicASCII; + + const [rawContentChanges, contentChanges] = this._doApplyEdits(operations); + + let trimAutoWhitespaceLineNumbers: number[] = null; + if (recordTrimAutoWhitespace && newTrimAutoWhitespaceCandidates.length > 0) { + // sort line numbers auto whitespace removal candidates for next edit descending + newTrimAutoWhitespaceCandidates.sort((a, b) => b.lineNumber - a.lineNumber); + + trimAutoWhitespaceLineNumbers = []; + for (let i = 0, len = newTrimAutoWhitespaceCandidates.length; i < len; i++) { + let lineNumber = newTrimAutoWhitespaceCandidates[i].lineNumber; + if (i > 0 && newTrimAutoWhitespaceCandidates[i - 1].lineNumber === lineNumber) { + // Do not have the same line number twice + continue; + } + + let prevContent = newTrimAutoWhitespaceCandidates[i].oldContent; + let lineContent = this.getLineContent(lineNumber); + + if (lineContent.length === 0 || lineContent === prevContent || strings.firstNonWhitespaceIndex(lineContent) !== -1) { + continue; + } + + trimAutoWhitespaceLineNumbers.push(lineNumber); + } + } + + return new ApplyEditsResult( + reverseOperations, + rawContentChanges, + contentChanges, + trimAutoWhitespaceLineNumbers + ); + } + + /** + * Transform operations such that they represent the same logic edit, + * but that they also do not cause OOM crashes. + */ + private _reduceOperations(operations: IValidatedEditOperation[]): IValidatedEditOperation[] { + if (operations.length < 1000) { + // We know from empirical testing that a thousand edits work fine regardless of their shape. + return operations; + } + + // At one point, due to how events are emitted and how each operation is handled, + // some operations can trigger a high ammount of temporary string allocations, + // that will immediately get edited again. + // e.g. a formatter inserting ridiculous ammounts of \n on a model with a single line + // Therefore, the strategy is to collapse all the operations into a huge single edit operation + return [this._toSingleEditOperation(operations)]; + } + + _toSingleEditOperation(operations: IValidatedEditOperation[]): IValidatedEditOperation { + let forceMoveMarkers = false, + firstEditRange = operations[0].range, + lastEditRange = operations[operations.length - 1].range, + entireEditRange = new Range(firstEditRange.startLineNumber, firstEditRange.startColumn, lastEditRange.endLineNumber, lastEditRange.endColumn), + lastEndLineNumber = firstEditRange.startLineNumber, + lastEndColumn = firstEditRange.startColumn, + result: string[] = []; + + for (let i = 0, len = operations.length; i < len; i++) { + let operation = operations[i], + range = operation.range; + + forceMoveMarkers = forceMoveMarkers || operation.forceMoveMarkers; + + // (1) -- Push old text + for (let lineNumber = lastEndLineNumber; lineNumber < range.startLineNumber; lineNumber++) { + if (lineNumber === lastEndLineNumber) { + result.push(this._lines[lineNumber - 1].substring(lastEndColumn - 1)); + } else { + result.push('\n'); + result.push(this._lines[lineNumber - 1]); + } + } + + if (range.startLineNumber === lastEndLineNumber) { + result.push(this._lines[range.startLineNumber - 1].substring(lastEndColumn - 1, range.startColumn - 1)); + } else { + result.push('\n'); + result.push(this._lines[range.startLineNumber - 1].substring(0, range.startColumn - 1)); + } + + // (2) -- Push new text + if (operation.lines) { + for (let j = 0, lenJ = operation.lines.length; j < lenJ; j++) { + if (j !== 0) { + result.push('\n'); + } + result.push(operation.lines[j]); + } + } + + lastEndLineNumber = operation.range.endLineNumber; + lastEndColumn = operation.range.endColumn; + } + + return { + sortIndex: 0, + identifier: operations[0].identifier, + range: entireEditRange, + rangeOffset: this.getOffsetAt(entireEditRange.startLineNumber, entireEditRange.startColumn), + rangeLength: this.getValueLengthInRange(entireEditRange, editorCommon.EndOfLinePreference.TextDefined), + lines: result.join('').split('\n'), + forceMoveMarkers: forceMoveMarkers, + isAutoWhitespaceEdit: false + }; + } + + private _setLineContent(lineNumber: number, content: string, rawContentChanges: ModelRawChange[]): void { + this._lines[lineNumber - 1] = content; + this._lineStarts.changeValue(lineNumber - 1, content.length + this._EOL.length); + rawContentChanges.push(new ModelRawLineChanged(lineNumber, content)); + } + + private _doApplyEdits(operations: IValidatedEditOperation[]): [ModelRawChange[], IInternalModelContentChange[]] { + + // Sort operations descending + operations.sort(TextBuffer._sortOpsDescending); + + let rawContentChanges: ModelRawChange[] = []; + let contentChanges: IInternalModelContentChange[] = []; + + for (let i = 0, len = operations.length; i < len; i++) { + const op = operations[i]; + + const startLineNumber = op.range.startLineNumber; + const startColumn = op.range.startColumn; + const endLineNumber = op.range.endLineNumber; + const endColumn = op.range.endColumn; + + if (startLineNumber === endLineNumber && startColumn === endColumn && (!op.lines || op.lines.length === 0)) { + // no-op + continue; + } + + const deletingLinesCnt = endLineNumber - startLineNumber; + const insertingLinesCnt = (op.lines ? op.lines.length - 1 : 0); + const editingLinesCnt = Math.min(deletingLinesCnt, insertingLinesCnt); + + for (let j = editingLinesCnt; j >= 0; j--) { + const editLineNumber = startLineNumber + j; + let editText = (op.lines ? op.lines[j] : ''); + + if (editLineNumber === startLineNumber || editLineNumber === endLineNumber) { + const editStartColumn = (editLineNumber === startLineNumber ? startColumn : 1); + const editEndColumn = (editLineNumber === endLineNumber ? endColumn : this.getLineLength(editLineNumber) + 1); + + editText = ( + this._lines[editLineNumber - 1].substring(0, editStartColumn - 1) + + editText + + this._lines[editLineNumber - 1].substring(editEndColumn - 1) + ); + } + + this._setLineContent(editLineNumber, editText, rawContentChanges); + } + + if (editingLinesCnt < deletingLinesCnt) { + // Must delete some lines + + const spliceStartLineNumber = startLineNumber + editingLinesCnt; + const endLineRemains = this._lines[endLineNumber - 1].substring(endColumn - 1); + + // Reconstruct first line + this._setLineContent(spliceStartLineNumber, this._lines[spliceStartLineNumber - 1] + endLineRemains, rawContentChanges); + + this._lines.splice(spliceStartLineNumber, endLineNumber - spliceStartLineNumber); + this._lineStarts.removeValues(spliceStartLineNumber, endLineNumber - spliceStartLineNumber); + + rawContentChanges.push(new ModelRawLinesDeleted(spliceStartLineNumber + 1, endLineNumber)); + } + + if (editingLinesCnt < insertingLinesCnt) { + // Must insert some lines + + const spliceLineNumber = startLineNumber + editingLinesCnt; + let spliceColumn = (spliceLineNumber === startLineNumber ? startColumn : 1); + if (op.lines) { + spliceColumn += op.lines[editingLinesCnt].length; + } + + // Split last line + const leftoverLine = this._lines[spliceLineNumber - 1].substring(spliceColumn - 1); + + this._setLineContent(spliceLineNumber, this._lines[spliceLineNumber - 1].substring(0, spliceColumn - 1), rawContentChanges); + + // Lines in the middle + let newLines: string[] = new Array(insertingLinesCnt - editingLinesCnt); + let newLinesLengths = new Uint32Array(insertingLinesCnt - editingLinesCnt); + for (let j = editingLinesCnt + 1; j <= insertingLinesCnt; j++) { + newLines[j - editingLinesCnt - 1] = op.lines[j]; + newLinesLengths[j - editingLinesCnt - 1] = op.lines[j].length + this._EOL.length; + } + newLines[newLines.length - 1] += leftoverLine; + newLinesLengths[newLines.length - 1] += leftoverLine.length; + this._lines = arrays.arrayInsert(this._lines, startLineNumber + editingLinesCnt, newLines); + this._lineStarts.insertValues(startLineNumber + editingLinesCnt, newLinesLengths); + + rawContentChanges.push(new ModelRawLinesInserted(spliceLineNumber + 1, startLineNumber + insertingLinesCnt, newLines)); + } + + const contentChangeRange = new Range(startLineNumber, startColumn, endLineNumber, endColumn); + const text = (op.lines ? op.lines.join(this.getEOL()) : ''); + contentChanges.push({ + range: contentChangeRange, + rangeLength: op.rangeLength, + text: text, + lines: op.lines, + rangeOffset: op.rangeOffset, + forceMoveMarkers: op.forceMoveMarkers + }); + } + + return [rawContentChanges, contentChanges]; + } + + /** + * Assumes `operations` are validated and sorted ascending + */ + public static _getInverseEditRanges(operations: IValidatedEditOperation[]): Range[] { + let result: Range[] = []; + + let prevOpEndLineNumber: number; + let prevOpEndColumn: number; + let prevOp: IValidatedEditOperation = null; + for (let i = 0, len = operations.length; i < len; i++) { + let op = operations[i]; + + let startLineNumber: number; + let startColumn: number; + + if (prevOp) { + if (prevOp.range.endLineNumber === op.range.startLineNumber) { + startLineNumber = prevOpEndLineNumber; + startColumn = prevOpEndColumn + (op.range.startColumn - prevOp.range.endColumn); + } else { + startLineNumber = prevOpEndLineNumber + (op.range.startLineNumber - prevOp.range.endLineNumber); + startColumn = op.range.startColumn; + } + } else { + startLineNumber = op.range.startLineNumber; + startColumn = op.range.startColumn; + } + + let resultRange: Range; + + if (op.lines && op.lines.length > 0) { + // the operation inserts something + let lineCount = op.lines.length; + let firstLine = op.lines[0]; + let lastLine = op.lines[lineCount - 1]; + + if (lineCount === 1) { + // single line insert + resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn + firstLine.length); + } else { + // multi line insert + resultRange = new Range(startLineNumber, startColumn, startLineNumber + lineCount - 1, lastLine.length + 1); + } + } else { + // There is nothing to insert + resultRange = new Range(startLineNumber, startColumn, startLineNumber, startColumn); + } + + prevOpEndLineNumber = resultRange.endLineNumber; + prevOpEndColumn = resultRange.endColumn; + + result.push(resultRange); + prevOp = op; + } + + return result; + } + + //#endregion +} diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index b144154c927..2ea97d286a6 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -10,14 +10,13 @@ import { Position, IPosition } from 'vs/editor/common/core/position'; import { Range, IRange } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { ModelLine, IModelLine, MinimalModelLine } from 'vs/editor/common/model/modelLine'; -import { guessIndentation } from 'vs/editor/common/model/indentationGuesser'; +import { guessIndentation, IndentationGuesserTextBufferTarget, IndentationGuesserStringArrayTarget } from 'vs/editor/common/model/indentationGuesser'; import { EDITOR_MODEL_DEFAULTS } from 'vs/editor/common/config/editorOptions'; -import { PrefixSumComputer } from 'vs/editor/common/viewModel/prefixSumComputer'; import { TextModelSearch, SearchParams } from 'vs/editor/common/model/textModelSearch'; import { TextSource, ITextSource, IRawTextSource, RawTextSource } from 'vs/editor/common/model/textSource'; import { IModelContentChangedEvent, ModelRawContentChangedEvent, ModelRawFlush, ModelRawEOLChanged, IModelOptionsChangedEvent, InternalModelContentChangeEvent } from 'vs/editor/common/model/textModelEvents'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { TextBuffer, ITextBuffer } from 'vs/editor/common/model/textBuffer'; const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 10000; @@ -49,7 +48,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { let resolvedOpts: editorCommon.TextModelResolvedOptions; if (options.detectIndentation) { - const guessedIndentation = guessIndentation(textSource.lines, options.tabSize, options.insertSpaces); + const guessedIndentation = guessIndentation(new IndentationGuesserStringArrayTarget(textSource.lines), options.tabSize, options.insertSpaces); resolvedOpts = new editorCommon.TextModelResolvedOptions({ tabSize: guessedIndentation.tabSize, insertSpaces: guessedIndentation.insertSpaces, @@ -83,25 +82,21 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { return this._eventEmitter.event((e: InternalModelContentChangeEvent) => listener(e.contentChangedEvent)); } - /*protected*/ _lines: IModelLine[]; - protected _EOL: string; protected _isDisposed: boolean; protected _isDisposing: boolean; protected _options: editorCommon.TextModelResolvedOptions; - protected _lineStarts: PrefixSumComputer; private _versionId: number; /** * Unlike, versionId, this can go down (via undo) or go to previous values (via redo) */ private _alternativeVersionId: number; - private _BOM: string; - protected _mightContainRTL: boolean; - protected _mightContainNonBasicASCII: boolean; private readonly _shouldSimplifyMode: boolean; protected readonly _isTooLargeForTokenization: boolean; + protected _buffer: ITextBuffer; + constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions) { super(); @@ -127,13 +122,6 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { this._isDisposing = false; } - protected _createModelLine(text: string): IModelLine { - if (this._isTooLargeForTokenization) { - return new MinimalModelLine(text); - } - return new ModelLine(text); - } - protected _assertNotDisposed(): void { if (this._isDisposed) { throw new Error('Model is disposed!'); @@ -178,8 +166,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public detectIndentation(defaultInsertSpaces: boolean, defaultTabSize: number): void { this._assertNotDisposed(); - let lines = this._lines.map(line => line.text); - let guessedIndentation = guessIndentation(lines, defaultTabSize, defaultInsertSpaces); + let guessedIndentation = guessIndentation(new IndentationGuesserTextBufferTarget(this._buffer), defaultTabSize, defaultInsertSpaces); this.updateOptions({ insertSpaces: guessedIndentation.insertSpaces, tabSize: guessedIndentation.tabSize @@ -247,11 +234,11 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { } public mightContainRTL(): boolean { - return this._mightContainRTL; + return this._buffer.mightContainRTL(); } public mightContainNonBasicASCII(): boolean { - return this._mightContainNonBasicASCII; + return this._buffer.mightContainNonBasicASCII(); } public getAlternativeVersionId(): number { @@ -262,20 +249,12 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public getOffsetAt(rawPosition: IPosition): number { this._assertNotDisposed(); let position = this._validatePosition(rawPosition.lineNumber, rawPosition.column, false); - return this._lineStarts.getAccumulatedValue(position.lineNumber - 2) + position.column - 1; + return this._buffer.getOffsetAt(position.lineNumber, position.column); } public getPositionAt(offset: number): Position { this._assertNotDisposed(); - offset = Math.floor(offset); - offset = Math.max(0, offset); - - let out = this._lineStarts.getIndexOf(offset); - - let lineLength = this._lines[out.index].text.length; - - // Ensure we return a valid position - return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1)); + return this._buffer.getPositionAt(offset); } protected _increaseVersionId(): void { @@ -298,9 +277,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public dispose(): void { this._isDisposed = true; // Null out members, such that any use of a disposed model will throw exceptions sooner rather than later - this._lines = null; - this._EOL = null; - this._BOM = null; + this._buffer = null; super.dispose(); } @@ -312,7 +289,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { rangeLength: rangeLength, text: text, }], - eol: this._EOL, + eol: this._buffer.getEOL(), versionId: this.getVersionId(), isUndoing: isUndoing, isRedoing: isRedoing, @@ -327,21 +304,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public equals(other: ITextSource): boolean { this._assertNotDisposed(); - if (this._BOM !== other.BOM) { - return false; - } - if (this._EOL !== other.EOL) { - return false; - } - if (this._lines.length !== other.lines.length) { - return false; - } - for (let i = 0, len = this._lines.length; i < len; i++) { - if (this._lines[i].text !== other.lines[i]) { - return false; - } - } - return true; + return this._buffer.equals(other); } public setValue(value: string): void { @@ -386,7 +349,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { var fullModelValue = this.getValueInRange(fullModelRange, eol); if (preserveBOM) { - return this._BOM + fullModelValue; + return this._buffer.getBOM() + fullModelValue; } return fullModelValue; @@ -394,11 +357,11 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public getValueLength(eol?: editorCommon.EndOfLinePreference, preserveBOM: boolean = false): number { this._assertNotDisposed(); - var fullModelRange = this.getFullModelRange(); - var fullModelValue = this.getValueLengthInRange(fullModelRange, eol); + const fullModelRange = this.getFullModelRange(); + const fullModelValue = this.getValueLengthInRange(fullModelRange, eol); if (preserveBOM) { - return this._BOM.length + fullModelValue; + return this._buffer.getBOM().length + fullModelValue; } return fullModelValue; @@ -406,58 +369,23 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public getValueInRange(rawRange: IRange, eol: editorCommon.EndOfLinePreference = editorCommon.EndOfLinePreference.TextDefined): string { this._assertNotDisposed(); - var range = this.validateRange(rawRange); - - if (range.isEmpty()) { - return ''; - } - - if (range.startLineNumber === range.endLineNumber) { - return this._lines[range.startLineNumber - 1].text.substring(range.startColumn - 1, range.endColumn - 1); - } - - var lineEnding = this._getEndOfLine(eol), - startLineIndex = range.startLineNumber - 1, - endLineIndex = range.endLineNumber - 1, - resultLines: string[] = []; - - resultLines.push(this._lines[startLineIndex].text.substring(range.startColumn - 1)); - for (var i = startLineIndex + 1; i < endLineIndex; i++) { - resultLines.push(this._lines[i].text); - } - resultLines.push(this._lines[endLineIndex].text.substring(0, range.endColumn - 1)); - - return resultLines.join(lineEnding); + return this._buffer.getValueInRange(this.validateRange(rawRange), eol); } public getValueLengthInRange(rawRange: IRange, eol: editorCommon.EndOfLinePreference = editorCommon.EndOfLinePreference.TextDefined): number { this._assertNotDisposed(); var range = this.validateRange(rawRange); - - if (range.isEmpty()) { - return 0; - } - - if (range.startLineNumber === range.endLineNumber) { - return (range.endColumn - range.startColumn); - } - - let startOffset = this.getOffsetAt(new Position(range.startLineNumber, range.startColumn)); - let endOffset = this.getOffsetAt(new Position(range.endLineNumber, range.endColumn)); - return endOffset - startOffset; + return this._buffer.getValueLengthInRange(range, eol); } public isDominatedByLongLines(): boolean { this._assertNotDisposed(); - var smallLineCharCount = 0, - longLineCharCount = 0, - i: number, - len: number, - lines = this._lines, - lineLength: number; + let smallLineCharCount = 0; + let longLineCharCount = 0; - for (i = 0, len = this._lines.length; i < len; i++) { - lineLength = lines[i].text.length; + const lineCount = this._buffer.getLineCount(); + for (let lineNumber = 1; lineNumber <= lineCount; lineNumber++) { + const lineLength = this._buffer.getLineLength(lineNumber); if (lineLength >= LONG_LINE_BOUNDARY) { longLineCharCount += lineLength; } else { @@ -470,7 +398,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public getLineCount(): number { this._assertNotDisposed(); - return this._lines.length; + return this._buffer.getLineCount(); } public getLineContent(lineNumber: number): string { @@ -479,21 +407,17 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } - return this._lines[lineNumber - 1].text; + return this._buffer.getLineContent(lineNumber); } public getLinesContent(): string[] { this._assertNotDisposed(); - var r: string[] = []; - for (var i = 0, len = this._lines.length; i < len; i++) { - r[i] = this._lines[i].text; - } - return r; + return this._buffer.getLinesContent(); } public getEOL(): string { this._assertNotDisposed(); - return this._EOL; + return this._buffer.getEOL(); } protected _onBeforeEOLChange(): void { @@ -505,7 +429,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { public setEOL(eol: editorCommon.EndOfLineSequence): void { this._assertNotDisposed(); const newEOL = (eol === editorCommon.EndOfLineSequence.CRLF ? '\r\n' : '\n'); - if (this._EOL === newEOL) { + if (this._buffer.getEOL() === newEOL) { // Nothing to do return; } @@ -516,8 +440,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { const endColumn = this.getLineMaxColumn(endLineNumber); this._onBeforeEOLChange(); - this._EOL = newEOL; - this._constructLineStarts(); + this._buffer.setEOL(newEOL); this._increaseVersionId(); this._onAfterEOLChange(); @@ -544,8 +467,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } - - return this._lines[lineNumber - 1].text.length + 1; + return this._buffer.getLineLength(lineNumber) + 1; } public getLineFirstNonWhitespaceColumn(lineNumber: number): number { @@ -553,12 +475,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } - - var result = strings.firstNonWhitespaceIndex(this._lines[lineNumber - 1].text); - if (result === -1) { - return 0; - } - return result + 1; + return this._buffer.getLineFirstNonWhitespaceColumn(lineNumber); } public getLineLastNonWhitespaceColumn(lineNumber: number): number { @@ -566,12 +483,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } - - var result = strings.lastNonWhitespaceIndex(this._lines[lineNumber - 1].text); - if (result === -1) { - return 0; - } - return result + 2; + return this._buffer.getLineLastNonWhitespaceColumn(lineNumber); } /** @@ -579,7 +491,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { * Will try to not allocate if possible. */ protected _validateRangeRelaxedNoAllocations(range: IRange): Range { - const linesCount = this._lines.length; + const linesCount = this._buffer.getLineCount(); const initialStartLineNumber = range.startLineNumber; const initialStartColumn = range.startColumn; @@ -651,13 +563,14 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { private _validatePosition(_lineNumber: number, _column: number, strict: boolean): Position { const lineNumber = Math.floor(typeof _lineNumber === 'number' ? _lineNumber : 1); const column = Math.floor(typeof _column === 'number' ? _column : 1); + const lineCount = this._buffer.getLineCount(); if (lineNumber < 1) { return new Position(1, 1); } - if (lineNumber > this._lines.length) { - return new Position(this._lines.length, this.getLineMaxColumn(this._lines.length)); + if (lineNumber > lineCount) { + return new Position(lineCount, this.getLineMaxColumn(lineCount)); } if (column <= 1) { @@ -673,7 +586,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { // If the position would end up in the middle of a high-low surrogate pair, // we move it to before the pair // !!At this point, column > 1 - const charCodeBefore = this._lines[lineNumber - 1].text.charCodeAt(column - 2); + const charCodeBefore = this._buffer.getLineCharCode(lineNumber, column - 2); if (strings.isHighSurrogate(charCodeBefore)) { return new Position(lineNumber, column - 1); } @@ -697,11 +610,8 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { const endLineNumber = end.lineNumber; const endColumn = end.column; - const startLineText = this._lines[startLineNumber - 1].text; - const endLineText = this._lines[endLineNumber - 1].text; - - const charCodeBeforeStart = (startColumn > 1 ? startLineText.charCodeAt(startColumn - 2) : 0); - const charCodeBeforeEnd = (endColumn > 1 && endColumn <= endLineText.length ? endLineText.charCodeAt(endColumn - 2) : 0); + const charCodeBeforeStart = (startColumn > 1 ? this._buffer.getLineCharCode(startLineNumber, startColumn - 2) : 0); + const charCodeBeforeEnd = (endColumn > 1 && endColumn <= this._buffer.getLineLength(endLineNumber) ? this._buffer.getLineCharCode(endLineNumber, endColumn - 2) : 0); const startInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeStart); const endInsideSurrogatePair = strings.isHighSurrogate(charCodeBeforeEnd); @@ -749,40 +659,7 @@ export class TextModel extends Disposable implements editorCommon.ITextModel { } private _constructLines(textSource: ITextSource): void { - let rawLines = textSource.lines; - let modelLines: IModelLine[] = new Array(rawLines.length); - - for (let i = 0, len = rawLines.length; i < len; i++) { - modelLines[i] = this._createModelLine(rawLines[i]); - } - this._BOM = textSource.BOM; - this._mightContainRTL = textSource.containsRTL; - this._mightContainNonBasicASCII = !textSource.isBasicASCII; - this._EOL = textSource.EOL; - this._lines = modelLines; - this._constructLineStarts(); - } - - private _constructLineStarts(): void { - const eolLength = this._EOL.length; - const linesLength = this._lines.length; - const lineStartValues = new Uint32Array(linesLength); - for (let i = 0; i < linesLength; i++) { - lineStartValues[i] = this._lines[i].text.length + eolLength; - } - this._lineStarts = new PrefixSumComputer(lineStartValues); - } - - private _getEndOfLine(eol: editorCommon.EndOfLinePreference): string { - switch (eol) { - case editorCommon.EndOfLinePreference.LF: - return '\n'; - case editorCommon.EndOfLinePreference.CRLF: - return '\r\n'; - case editorCommon.EndOfLinePreference.TextDefined: - return this.getEOL(); - } - throw new Error('Unknown EOL preference'); + this._buffer = new TextBuffer(textSource); } public findMatches(searchString: string, rawSearchScope: any, isRegex: boolean, matchCase: boolean, wordSeparators: string, captureMatches: boolean, limitResultCount: number = LIMIT_FIND_COUNT): editorCommon.FindMatch[] { diff --git a/src/vs/editor/common/model/textModelEvents.ts b/src/vs/editor/common/model/textModelEvents.ts index 036ee81d6d0..50a9c701ac7 100644 --- a/src/vs/editor/common/model/textModelEvents.ts +++ b/src/vs/editor/common/model/textModelEvents.ts @@ -176,9 +176,9 @@ export class ModelRawLinesInserted { /** * The text that was inserted */ - public readonly detail: string; + public readonly detail: string[]; - constructor(fromLineNumber: number, toLineNumber: number, detail: string) { + constructor(fromLineNumber: number, toLineNumber: number, detail: string[]) { this.fromLineNumber = fromLineNumber; this.toLineNumber = toLineNumber; this.detail = detail; diff --git a/src/vs/editor/common/model/textModelWithDecorations.ts b/src/vs/editor/common/model/textModelWithDecorations.ts deleted file mode 100644 index fbd4b87e860..00000000000 --- a/src/vs/editor/common/model/textModelWithDecorations.ts +++ /dev/null @@ -1,624 +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 { onUnexpectedError } from 'vs/base/common/errors'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; -import * as strings from 'vs/base/common/strings'; -import { CharCode } from 'vs/base/common/charCode'; -import Event, { Emitter } from 'vs/base/common/event'; -import { Range, IRange } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens'; -import { LanguageIdentifier } from 'vs/editor/common/modes'; -import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource'; -import { IModelDecorationsChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { ThemeColor } from 'vs/platform/theme/common/themeService'; -import { IntervalNode, IntervalTree, recomputeMaxEnd, getNodeIsInOverviewRuler } from 'vs/editor/common/model/intervalTree'; -import { Disposable } from 'vs/base/common/lifecycle'; - -let _INSTANCE_COUNT = 0; -/** - * Produces 'a'-'z', followed by 'A'-'Z'... followed by 'a'-'z', etc. - */ -function nextInstanceId(): string { - const LETTERS_CNT = (CharCode.Z - CharCode.A + 1); - - let result = _INSTANCE_COUNT++; - result = result % (2 * LETTERS_CNT); - - if (result < LETTERS_CNT) { - return String.fromCharCode(CharCode.a + result); - } - - return String.fromCharCode(CharCode.A + result - LETTERS_CNT); -} - -export class TextModelWithDecorations extends TextModelWithTokens implements editorCommon.ITextModelWithDecorations { - - protected readonly _onDidChangeDecorations: DidChangeDecorationsEmitter = this._register(new DidChangeDecorationsEmitter()); - public readonly onDidChangeDecorations: Event = this._onDidChangeDecorations.event; - - /** - * Used to workaround broken clients that might attempt using a decoration id generated by a different model. - * It is not globally unique in order to limit it to one character. - */ - private readonly _instanceId: string; - private _lastDecorationId: number; - private _decorations: { [decorationId: string]: IntervalNode; }; - private _decorationsTree: DecorationsTrees; - - constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) { - super(rawTextSource, creationOptions, languageIdentifier); - - this._instanceId = nextInstanceId(); - this._lastDecorationId = 0; - this._decorations = Object.create(null); - this._decorationsTree = new DecorationsTrees(); - } - - public dispose(): void { - this._decorations = null; - this._decorationsTree = null; - - super.dispose(); - } - - protected _resetValue(newValue: ITextSource): void { - super._resetValue(newValue); - - // Destroy all my decorations - this._decorations = Object.create(null); - this._decorationsTree = new DecorationsTrees(); - } - - // --- END TrackedRanges - - protected _adjustDecorationsForEdit(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void { - this._onDidChangeDecorations.fire(); - this._decorationsTree.acceptReplace(offset, length, textLength, forceMoveMarkers); - } - - protected _onBeforeEOLChange(): void { - super._onBeforeEOLChange(); - - // Ensure all decorations get their `range` set. - const versionId = this.getVersionId(); - const allDecorations = this._decorationsTree.search(0, false, false, versionId); - this._ensureNodesHaveRanges(allDecorations); - } - - protected _onAfterEOLChange(): void { - super._onAfterEOLChange(); - - // Transform back `range` to offsets - const versionId = this.getVersionId(); - const allDecorations = this._decorationsTree.collectNodesPostOrder(); - for (let i = 0, len = allDecorations.length; i < len; i++) { - const node = allDecorations[i]; - - const delta = node.cachedAbsoluteStart - node.start; - - const startOffset = this._lineStarts.getAccumulatedValue(node.range.startLineNumber - 2) + node.range.startColumn - 1; - const endOffset = this._lineStarts.getAccumulatedValue(node.range.endLineNumber - 2) + node.range.endColumn - 1; - - node.cachedAbsoluteStart = startOffset; - node.cachedAbsoluteEnd = endOffset; - node.cachedVersionId = versionId; - - node.start = startOffset - delta; - node.end = endOffset - delta; - - recomputeMaxEnd(node); - } - } - - public changeDecorations(callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T, ownerId: number = 0): T { - this._assertNotDisposed(); - - try { - this._onDidChangeDecorations.beginDeferredEmit(); - return this._changeDecorations(ownerId, callback); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - } - } - - private _changeDecorations(ownerId: number, callback: (changeAccessor: editorCommon.IModelDecorationsChangeAccessor) => T): T { - let changeAccessor: editorCommon.IModelDecorationsChangeAccessor = { - addDecoration: (range: IRange, options: editorCommon.IModelDecorationOptions): string => { - this._onDidChangeDecorations.fire(); - return this._deltaDecorationsImpl(ownerId, [], [{ range: range, options: options }])[0]; - }, - changeDecoration: (id: string, newRange: IRange): void => { - this._onDidChangeDecorations.fire(); - this._changeDecorationImpl(id, newRange); - }, - changeDecorationOptions: (id: string, options: editorCommon.IModelDecorationOptions) => { - this._onDidChangeDecorations.fire(); - this._changeDecorationOptionsImpl(id, _normalizeOptions(options)); - }, - removeDecoration: (id: string): void => { - this._onDidChangeDecorations.fire(); - this._deltaDecorationsImpl(ownerId, [id], []); - }, - deltaDecorations: (oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] => { - if (oldDecorations.length === 0 && newDecorations.length === 0) { - // nothing to do - return []; - } - this._onDidChangeDecorations.fire(); - return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); - } - }; - let result: T = null; - try { - result = callback(changeAccessor); - } catch (e) { - onUnexpectedError(e); - } - // Invalidate change accessor - changeAccessor.addDecoration = null; - changeAccessor.changeDecoration = null; - changeAccessor.removeDecoration = null; - changeAccessor.deltaDecorations = null; - return result; - } - - public deltaDecorations(oldDecorations: string[], newDecorations: editorCommon.IModelDeltaDecoration[], ownerId: number = 0): string[] { - this._assertNotDisposed(); - if (!oldDecorations) { - oldDecorations = []; - } - if (oldDecorations.length === 0 && newDecorations.length === 0) { - // nothing to do - return []; - } - - try { - this._onDidChangeDecorations.beginDeferredEmit(); - this._onDidChangeDecorations.fire(); - return this._deltaDecorationsImpl(ownerId, oldDecorations, newDecorations); - } finally { - this._onDidChangeDecorations.endDeferredEmit(); - } - } - - _getTrackedRange(id: string): Range { - return this.getDecorationRange(id); - } - - _setTrackedRange(id: string, newRange: Range, newStickiness: editorCommon.TrackedRangeStickiness): string { - const node = (id ? this._decorations[id] : null); - - if (!node) { - if (!newRange) { - // node doesn't exist, the request is to delete => nothing to do - return null; - } - // node doesn't exist, the request is to set => add the tracked range - return this._deltaDecorationsImpl(0, [], [{ range: newRange, options: TRACKED_RANGE_OPTIONS[newStickiness] }])[0]; - } - - if (!newRange) { - // node exists, the request is to delete => delete node - this._decorationsTree.delete(node); - delete this._decorations[node.id]; - return null; - } - - // node exists, the request is to set => change the tracked range and its options - const range = this._validateRangeRelaxedNoAllocations(newRange); - const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1; - const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1; - this._decorationsTree.delete(node); - node.reset(this.getVersionId(), startOffset, endOffset, range); - node.setOptions(TRACKED_RANGE_OPTIONS[newStickiness]); - this._decorationsTree.insert(node); - return node.id; - } - - public removeAllDecorationsWithOwnerId(ownerId: number): void { - if (this._isDisposed) { - return; - } - const nodes = this._decorationsTree.collectNodesFromOwner(ownerId); - for (let i = 0, len = nodes.length; i < len; i++) { - const node = nodes[i]; - - this._decorationsTree.delete(node); - delete this._decorations[node.id]; - } - } - - public getDecorationOptions(decorationId: string): editorCommon.IModelDecorationOptions { - const node = this._decorations[decorationId]; - if (!node) { - return null; - } - return node.options; - } - - public getDecorationRange(decorationId: string): Range { - const node = this._decorations[decorationId]; - if (!node) { - return null; - } - const versionId = this.getVersionId(); - if (node.cachedVersionId !== versionId) { - this._decorationsTree.resolveNode(node, versionId); - } - if (node.range === null) { - node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); - } - return node.range; - } - - public getLineDecorations(lineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { - if (lineNumber < 1 || lineNumber > this.getLineCount()) { - return []; - } - - return this.getLinesDecorations(lineNumber, lineNumber, ownerId, filterOutValidation); - } - - public getLinesDecorations(_startLineNumber: number, _endLineNumber: number, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { - let lineCount = this.getLineCount(); - let startLineNumber = Math.min(lineCount, Math.max(1, _startLineNumber)); - let endLineNumber = Math.min(lineCount, Math.max(1, _endLineNumber)); - let endColumn = this.getLineMaxColumn(endLineNumber); - return this._getDecorationsInRange(new Range(startLineNumber, 1, endLineNumber, endColumn), ownerId, filterOutValidation); - } - - public getDecorationsInRange(range: IRange, ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { - let validatedRange = this.validateRange(range); - return this._getDecorationsInRange(validatedRange, ownerId, filterOutValidation); - } - - public getOverviewRulerDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { - const versionId = this.getVersionId(); - const result = this._decorationsTree.search(ownerId, filterOutValidation, true, versionId); - return this._ensureNodesHaveRanges(result); - } - - public getAllDecorations(ownerId: number = 0, filterOutValidation: boolean = false): editorCommon.IModelDecoration[] { - const versionId = this.getVersionId(); - const result = this._decorationsTree.search(ownerId, filterOutValidation, false, versionId); - return this._ensureNodesHaveRanges(result); - } - - private _getDecorationsInRange(filterRange: Range, filterOwnerId: number, filterOutValidation: boolean): IntervalNode[] { - const startOffset = this._lineStarts.getAccumulatedValue(filterRange.startLineNumber - 2) + filterRange.startColumn - 1; - const endOffset = this._lineStarts.getAccumulatedValue(filterRange.endLineNumber - 2) + filterRange.endColumn - 1; - - const versionId = this.getVersionId(); - const result = this._decorationsTree.intervalSearch(startOffset, endOffset, filterOwnerId, filterOutValidation, versionId); - - return this._ensureNodesHaveRanges(result); - } - - private _ensureNodesHaveRanges(nodes: IntervalNode[]): IntervalNode[] { - for (let i = 0, len = nodes.length; i < len; i++) { - const node = nodes[i]; - if (node.range === null) { - node.range = this._getRangeAt(node.cachedAbsoluteStart, node.cachedAbsoluteEnd); - } - } - return nodes; - } - - private _getRangeAt(start: number, end: number): Range { - const startResult = this._lineStarts.getIndexOf(start); - const startLineLength = this._lines[startResult.index].text.length; - const startColumn = Math.min(startResult.remainder + 1, startLineLength + 1); - - const endResult = this._lineStarts.getIndexOf(end); - const endLineLength = this._lines[endResult.index].text.length; - const endColumn = Math.min(endResult.remainder + 1, endLineLength + 1); - - return new Range(startResult.index + 1, startColumn, endResult.index + 1, endColumn); - } - - private _changeDecorationImpl(decorationId: string, _range: IRange): void { - const node = this._decorations[decorationId]; - if (!node) { - return; - } - const range = this._validateRangeRelaxedNoAllocations(_range); - const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1; - const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1; - - this._decorationsTree.delete(node); - node.reset(this.getVersionId(), startOffset, endOffset, range); - this._decorationsTree.insert(node); - } - - private _changeDecorationOptionsImpl(decorationId: string, options: ModelDecorationOptions): void { - const node = this._decorations[decorationId]; - if (!node) { - return; - } - - const nodeWasInOverviewRuler = (node.options.overviewRuler.color ? true : false); - const nodeIsInOverviewRuler = (options.overviewRuler.color ? true : false); - - if (nodeWasInOverviewRuler !== nodeIsInOverviewRuler) { - // Delete + Insert due to an overview ruler status change - this._decorationsTree.delete(node); - node.setOptions(options); - this._decorationsTree.insert(node); - } else { - node.setOptions(options); - } - } - - private _deltaDecorationsImpl(ownerId: number, oldDecorationsIds: string[], newDecorations: editorCommon.IModelDeltaDecoration[]): string[] { - const versionId = this.getVersionId(); - - const oldDecorationsLen = oldDecorationsIds.length; - let oldDecorationIndex = 0; - - const newDecorationsLen = newDecorations.length; - let newDecorationIndex = 0; - - let result = new Array(newDecorationsLen); - while (oldDecorationIndex < oldDecorationsLen || newDecorationIndex < newDecorationsLen) { - - let node: IntervalNode = null; - - if (oldDecorationIndex < oldDecorationsLen) { - // (1) get ourselves an old node - do { - node = this._decorations[oldDecorationsIds[oldDecorationIndex++]]; - } while (!node && oldDecorationIndex < oldDecorationsLen); - - // (2) remove the node from the tree (if it exists) - if (node) { - this._decorationsTree.delete(node); - } - } - - if (newDecorationIndex < newDecorationsLen) { - // (3) create a new node if necessary - if (!node) { - const internalDecorationId = (++this._lastDecorationId); - const decorationId = `${this._instanceId};${internalDecorationId}`; - node = new IntervalNode(decorationId, 0, 0); - this._decorations[decorationId] = node; - } - - // (4) initialize node - const newDecoration = newDecorations[newDecorationIndex]; - const range = this._validateRangeRelaxedNoAllocations(newDecoration.range); - const options = _normalizeOptions(newDecoration.options); - const startOffset = this._lineStarts.getAccumulatedValue(range.startLineNumber - 2) + range.startColumn - 1; - const endOffset = this._lineStarts.getAccumulatedValue(range.endLineNumber - 2) + range.endColumn - 1; - - node.ownerId = ownerId; - node.reset(versionId, startOffset, endOffset, range); - node.setOptions(options); - - this._decorationsTree.insert(node); - - result[newDecorationIndex] = node.id; - - newDecorationIndex++; - } else { - if (node) { - delete this._decorations[node.id]; - } - } - } - - return result; - } -} - -class DecorationsTrees { - - /** - * This tree holds decorations that do not show up in the overview ruler. - */ - private _decorationsTree0: IntervalTree; - - /** - * This tree holds decorations that show up in the overview ruler. - */ - private _decorationsTree1: IntervalTree; - - constructor() { - this._decorationsTree0 = new IntervalTree(); - this._decorationsTree1 = new IntervalTree(); - } - - public intervalSearch(start: number, end: number, filterOwnerId: number, filterOutValidation: boolean, cachedVersionId: number): IntervalNode[] { - const r0 = this._decorationsTree0.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); - const r1 = this._decorationsTree1.intervalSearch(start, end, filterOwnerId, filterOutValidation, cachedVersionId); - return r0.concat(r1); - } - - public search(filterOwnerId: number, filterOutValidation: boolean, overviewRulerOnly: boolean, cachedVersionId: number): IntervalNode[] { - if (overviewRulerOnly) { - return this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); - } else { - const r0 = this._decorationsTree0.search(filterOwnerId, filterOutValidation, cachedVersionId); - const r1 = this._decorationsTree1.search(filterOwnerId, filterOutValidation, cachedVersionId); - return r0.concat(r1); - } - } - - public collectNodesFromOwner(ownerId: number): IntervalNode[] { - const r0 = this._decorationsTree0.collectNodesFromOwner(ownerId); - const r1 = this._decorationsTree1.collectNodesFromOwner(ownerId); - return r0.concat(r1); - } - - public collectNodesPostOrder(): IntervalNode[] { - const r0 = this._decorationsTree0.collectNodesPostOrder(); - const r1 = this._decorationsTree1.collectNodesPostOrder(); - return r0.concat(r1); - } - - public insert(node: IntervalNode): void { - if (getNodeIsInOverviewRuler(node)) { - this._decorationsTree1.insert(node); - } else { - this._decorationsTree0.insert(node); - } - } - - public delete(node: IntervalNode): void { - if (getNodeIsInOverviewRuler(node)) { - this._decorationsTree1.delete(node); - } else { - this._decorationsTree0.delete(node); - } - } - - public resolveNode(node: IntervalNode, cachedVersionId: number): void { - if (getNodeIsInOverviewRuler(node)) { - this._decorationsTree1.resolveNode(node, cachedVersionId); - } else { - this._decorationsTree0.resolveNode(node, cachedVersionId); - } - } - - public acceptReplace(offset: number, length: number, textLength: number, forceMoveMarkers: boolean): void { - this._decorationsTree0.acceptReplace(offset, length, textLength, forceMoveMarkers); - this._decorationsTree1.acceptReplace(offset, length, textLength, forceMoveMarkers); - } -} - -function cleanClassName(className: string): string { - return className.replace(/[^a-z0-9\-]/gi, ' '); -} - -export class ModelDecorationOverviewRulerOptions implements editorCommon.IModelDecorationOverviewRulerOptions { - readonly color: string | ThemeColor; - readonly darkColor: string | ThemeColor; - readonly hcColor: string | ThemeColor; - readonly position: editorCommon.OverviewRulerLane; - _resolvedColor: string; - - constructor(options: editorCommon.IModelDecorationOverviewRulerOptions) { - this.color = strings.empty; - this.darkColor = strings.empty; - this.hcColor = strings.empty; - this.position = editorCommon.OverviewRulerLane.Center; - this._resolvedColor = null; - - if (options && options.color) { - this.color = options.color; - } - if (options && options.darkColor) { - this.darkColor = options.darkColor; - this.hcColor = options.darkColor; - } - if (options && options.hcColor) { - this.hcColor = options.hcColor; - } - if (options && options.hasOwnProperty('position')) { - this.position = options.position; - } - } -} - -let lastStaticId = 0; - -export class ModelDecorationOptions implements editorCommon.IModelDecorationOptions { - - public static EMPTY: ModelDecorationOptions; - - public static register(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { - return new ModelDecorationOptions(++lastStaticId, options); - } - - public static createDynamic(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { - return new ModelDecorationOptions(0, options); - } - - readonly staticId: number; - readonly stickiness: editorCommon.TrackedRangeStickiness; - readonly className: string; - readonly hoverMessage: IMarkdownString | IMarkdownString[]; - readonly glyphMarginHoverMessage: IMarkdownString | IMarkdownString[]; - readonly isWholeLine: boolean; - readonly showIfCollapsed: boolean; - readonly overviewRuler: ModelDecorationOverviewRulerOptions; - readonly glyphMarginClassName: string; - readonly linesDecorationsClassName: string; - readonly marginClassName: string; - readonly inlineClassName: string; - readonly beforeContentClassName: string; - readonly afterContentClassName: string; - - private constructor(staticId: number, options: editorCommon.IModelDecorationOptions) { - this.staticId = staticId; - this.stickiness = options.stickiness || editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges; - this.className = options.className ? cleanClassName(options.className) : strings.empty; - this.hoverMessage = options.hoverMessage || []; - this.glyphMarginHoverMessage = options.glyphMarginHoverMessage || []; - this.isWholeLine = options.isWholeLine || false; - this.showIfCollapsed = options.showIfCollapsed || false; - this.overviewRuler = new ModelDecorationOverviewRulerOptions(options.overviewRuler); - this.glyphMarginClassName = options.glyphMarginClassName ? cleanClassName(options.glyphMarginClassName) : strings.empty; - this.linesDecorationsClassName = options.linesDecorationsClassName ? cleanClassName(options.linesDecorationsClassName) : strings.empty; - this.marginClassName = options.marginClassName ? cleanClassName(options.marginClassName) : strings.empty; - this.inlineClassName = options.inlineClassName ? cleanClassName(options.inlineClassName) : strings.empty; - this.beforeContentClassName = options.beforeContentClassName ? cleanClassName(options.beforeContentClassName) : strings.empty; - this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : strings.empty; - } -} -ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({}); - -/** - * The order carefully matches the values of the enum. - */ -const TRACKED_RANGE_OPTIONS = [ - ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges }), - ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges }), - ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingBefore }), - ModelDecorationOptions.register({ stickiness: editorCommon.TrackedRangeStickiness.GrowsOnlyWhenTypingAfter }), -]; - -function _normalizeOptions(options: editorCommon.IModelDecorationOptions): ModelDecorationOptions { - if (options instanceof ModelDecorationOptions) { - return options; - } - return ModelDecorationOptions.createDynamic(options); -} - -export class DidChangeDecorationsEmitter extends Disposable { - - private readonly _actual: Emitter = this._register(new Emitter()); - public readonly event: Event = this._actual.event; - - private _deferredCnt: number; - private _shouldFire: boolean; - - constructor() { - super(); - this._deferredCnt = 0; - this._shouldFire = false; - } - - public beginDeferredEmit(): void { - this._deferredCnt++; - } - - public endDeferredEmit(): void { - this._deferredCnt--; - if (this._deferredCnt === 0) { - if (this._shouldFire) { - this._shouldFire = false; - this._actual.fire({}); - } - } - } - - public fire(): void { - this._shouldFire = true; - } -} diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts deleted file mode 100644 index 00985a04564..00000000000 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ /dev/null @@ -1,921 +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 nls from 'vs/nls'; -import { onUnexpectedError } from 'vs/base/common/errors'; -import { IDisposable } from 'vs/base/common/lifecycle'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import Event, { Emitter } from 'vs/base/common/event'; -import { Range } from 'vs/editor/common/core/range'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import { TextModel } from 'vs/editor/common/model/textModel'; -import { ITokenizationSupport, IState, TokenizationRegistry, LanguageId, LanguageIdentifier } from 'vs/editor/common/modes'; -import { NULL_LANGUAGE_IDENTIFIER, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; -import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; -import { BracketsUtils, RichEditBrackets, RichEditBracket } from 'vs/editor/common/modes/supports/richEditBrackets'; -import { Position, IPosition } from 'vs/editor/common/core/position'; -import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { LineTokens, LineToken } from 'vs/editor/common/core/lineTokens'; -import { getWordAtText } from 'vs/editor/common/model/wordHelper'; -import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { ITextSource, IRawTextSource } from 'vs/editor/common/model/textSource'; -import { IModelTokensChangedEvent, IModelLanguageChangedEvent, IModelLanguageConfigurationChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { computeIndentLevel } from 'vs/editor/common/model/modelLine'; - -class ModelTokensChangedEventBuilder { - - private _ranges: { fromLineNumber: number; toLineNumber: number; }[]; - - constructor() { - this._ranges = []; - } - - public registerChangedTokens(lineNumber: number): void { - const ranges = this._ranges; - const rangesLength = ranges.length; - const previousRange = rangesLength > 0 ? ranges[rangesLength - 1] : null; - - if (previousRange && previousRange.toLineNumber === lineNumber - 1) { - // extend previous range - previousRange.toLineNumber++; - } else { - // insert new range - ranges[rangesLength] = { - fromLineNumber: lineNumber, - toLineNumber: lineNumber - }; - } - } - - public build(): IModelTokensChangedEvent { - if (this._ranges.length === 0) { - return null; - } - return { - ranges: this._ranges - }; - } -} - -export class TextModelWithTokens extends TextModel implements editorCommon.ITokenizedModel { - - private static readonly MODE_TOKENIZATION_FAILED_MSG = nls.localize('mode.tokenizationSupportFailed', "The mode has failed while tokenizing the input."); - - private readonly _onDidChangeLanguage: Emitter = this._register(new Emitter()); - public readonly onDidChangeLanguage: Event = this._onDidChangeLanguage.event; - - private readonly _onDidChangeLanguageConfiguration: Emitter = this._register(new Emitter()); - public readonly onDidChangeLanguageConfiguration: Event = this._onDidChangeLanguageConfiguration.event; - - private readonly _onDidChangeTokens: Emitter = this._register(new Emitter()); - public readonly onDidChangeTokens: Event = this._onDidChangeTokens.event; - - private _languageIdentifier: LanguageIdentifier; - private _tokenizationListener: IDisposable; - private _tokenizationSupport: ITokenizationSupport; - - private _invalidLineStartIndex: number; - private _lastState: IState; - - private _languageRegistryListener: IDisposable; - - private _revalidateTokensTimeout: number; - - constructor(rawTextSource: IRawTextSource, creationOptions: editorCommon.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier) { - super(rawTextSource, creationOptions); - - this._languageIdentifier = languageIdentifier || NULL_LANGUAGE_IDENTIFIER; - this._tokenizationListener = TokenizationRegistry.onDidChange((e) => { - if (e.changedLanguages.indexOf(this._languageIdentifier.language) === -1) { - return; - } - - this._resetTokenizationState(); - this.emitModelTokensChangedEvent({ - ranges: [{ - fromLineNumber: 1, - toLineNumber: this.getLineCount() - }] - }); - - if (this._shouldAutoTokenize()) { - this._warmUpTokens(); - } - }); - - this._revalidateTokensTimeout = -1; - - this._languageRegistryListener = LanguageConfigurationRegistry.onDidChange((e) => { - if (e.languageIdentifier.id === this._languageIdentifier.id) { - this._onDidChangeLanguageConfiguration.fire({}); - } - }); - - this._resetTokenizationState(); - } - - public dispose(): void { - this._tokenizationListener.dispose(); - this._languageRegistryListener.dispose(); - this._clearTimers(); - this._lastState = null; - - super.dispose(); - } - - protected _shouldAutoTokenize(): boolean { - return false; - } - - protected _resetValue(newValue: ITextSource): void { - super._resetValue(newValue); - // Cancel tokenization, clear all tokens and begin tokenizing - this._resetTokenizationState(); - } - - protected _resetTokenizationState(): void { - this._clearTimers(); - for (let i = 0; i < this._lines.length; i++) { - this._lines[i].resetTokenizationState(); - } - - this._tokenizationSupport = null; - if (!this._isTooLargeForTokenization) { - this._tokenizationSupport = TokenizationRegistry.get(this._languageIdentifier.language); - } - - if (this._tokenizationSupport) { - let initialState: IState = null; - try { - initialState = this._tokenizationSupport.getInitialState(); - } catch (e) { - e.friendlyMessage = TextModelWithTokens.MODE_TOKENIZATION_FAILED_MSG; - onUnexpectedError(e); - this._tokenizationSupport = null; - } - - if (initialState) { - this._lines[0].setState(initialState); - } - } - - this._lastState = null; - this._invalidLineStartIndex = 0; - this._beginBackgroundTokenization(); - } - - private _clearTimers(): void { - if (this._revalidateTokensTimeout !== -1) { - clearTimeout(this._revalidateTokensTimeout); - this._revalidateTokensTimeout = -1; - } - } - - public forceTokenization(lineNumber: number): void { - if (lineNumber < 1 || lineNumber > this.getLineCount()) { - throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); - } - - const eventBuilder = new ModelTokensChangedEventBuilder(); - - this._updateTokensUntilLine(eventBuilder, lineNumber); - - const e = eventBuilder.build(); - if (e) { - this._onDidChangeTokens.fire(e); - } - } - - public isCheapToTokenize(lineNumber: number): boolean { - const firstInvalidLineNumber = this._invalidLineStartIndex + 1; - return (firstInvalidLineNumber >= lineNumber); - } - - public tokenizeIfCheap(lineNumber: number): void { - if (this.isCheapToTokenize(lineNumber)) { - this.forceTokenization(lineNumber); - } - } - - public getLineTokens(lineNumber: number): LineTokens { - if (lineNumber < 1 || lineNumber > this.getLineCount()) { - throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); - } - - return this._getLineTokens(lineNumber); - } - - private _getLineTokens(lineNumber: number): LineTokens { - return this._lines[lineNumber - 1].getTokens(this._languageIdentifier.id); - } - - public getLanguageIdentifier(): LanguageIdentifier { - return this._languageIdentifier; - } - - public getModeId(): string { - return this._languageIdentifier.language; - } - - public setMode(languageIdentifier: LanguageIdentifier): void { - if (this._languageIdentifier.id === languageIdentifier.id) { - // There's nothing to do - return; - } - - let e: IModelLanguageChangedEvent = { - oldLanguage: this._languageIdentifier.language, - newLanguage: languageIdentifier.language - }; - - this._languageIdentifier = languageIdentifier; - - // Cancel tokenization, clear all tokens and begin tokenizing - this._resetTokenizationState(); - - this.emitModelTokensChangedEvent({ - ranges: [{ - fromLineNumber: 1, - toLineNumber: this.getLineCount() - }] - }); - this._onDidChangeLanguage.fire(e); - this._onDidChangeLanguageConfiguration.fire({}); - } - - public getLanguageIdAtPosition(_lineNumber: number, _column: number): LanguageId { - if (!this._tokenizationSupport) { - return this._languageIdentifier.id; - } - let { lineNumber, column } = this.validatePosition({ lineNumber: _lineNumber, column: _column }); - - let lineTokens = this._getLineTokens(lineNumber); - let token = lineTokens.findTokenAtOffset(column - 1); - return token.languageId; - } - - protected _invalidateLine(lineIndex: number): void { - this._lines[lineIndex].setIsInvalid(true); - if (lineIndex < this._invalidLineStartIndex) { - if (this._invalidLineStartIndex < this._lines.length) { - this._lines[this._invalidLineStartIndex].setIsInvalid(true); - } - this._invalidLineStartIndex = lineIndex; - this._beginBackgroundTokenization(); - } - } - - private _beginBackgroundTokenization(): void { - if (this._shouldAutoTokenize() && this._revalidateTokensTimeout === -1) { - this._revalidateTokensTimeout = setTimeout(() => { - this._revalidateTokensTimeout = -1; - this._revalidateTokensNow(); - }, 0); - } - } - - _warmUpTokens(): void { - // Warm up first 100 lines (if it takes less than 50ms) - var maxLineNumber = Math.min(100, this.getLineCount()); - this._revalidateTokensNow(maxLineNumber); - - if (this._invalidLineStartIndex < this._lines.length) { - this._beginBackgroundTokenization(); - } - } - - private _revalidateTokensNow(toLineNumber: number = this._invalidLineStartIndex + 1000000): void { - - const eventBuilder = new ModelTokensChangedEventBuilder(); - - toLineNumber = Math.min(this._lines.length, toLineNumber); - - var MAX_ALLOWED_TIME = 20, - fromLineNumber = this._invalidLineStartIndex + 1, - tokenizedChars = 0, - currentCharsToTokenize = 0, - currentEstimatedTimeToTokenize = 0, - sw = StopWatch.create(false), - elapsedTime: number; - - // Tokenize at most 1000 lines. Estimate the tokenization speed per character and stop when: - // - MAX_ALLOWED_TIME is reached - // - tokenizing the next line would go above MAX_ALLOWED_TIME - - for (var lineNumber = fromLineNumber; lineNumber <= toLineNumber; lineNumber++) { - elapsedTime = sw.elapsed(); - if (elapsedTime > MAX_ALLOWED_TIME) { - // Stop if MAX_ALLOWED_TIME is reached - toLineNumber = lineNumber - 1; - break; - } - - // Compute how many characters will be tokenized for this line - currentCharsToTokenize = this._lines[lineNumber - 1].text.length; - - if (tokenizedChars > 0) { - // If we have enough history, estimate how long tokenizing this line would take - currentEstimatedTimeToTokenize = (elapsedTime / tokenizedChars) * currentCharsToTokenize; - if (elapsedTime + currentEstimatedTimeToTokenize > MAX_ALLOWED_TIME) { - // Tokenizing this line will go above MAX_ALLOWED_TIME - toLineNumber = lineNumber - 1; - break; - } - } - - this._updateTokensUntilLine(eventBuilder, lineNumber); - tokenizedChars += currentCharsToTokenize; - - // Skip the lines that got tokenized - lineNumber = Math.max(lineNumber, this._invalidLineStartIndex + 1); - } - - elapsedTime = sw.elapsed(); - - if (this._invalidLineStartIndex < this._lines.length) { - this._beginBackgroundTokenization(); - } - - const e = eventBuilder.build(); - if (e) { - this._onDidChangeTokens.fire(e); - } - } - - private _updateTokensUntilLine(eventBuilder: ModelTokensChangedEventBuilder, lineNumber: number): void { - if (!this._tokenizationSupport) { - this._invalidLineStartIndex = this._lines.length; - return; - } - - const linesLength = this._lines.length; - const endLineIndex = lineNumber - 1; - - // Validate all states up to and including endLineIndex - for (let lineIndex = this._invalidLineStartIndex; lineIndex <= endLineIndex; lineIndex++) { - const endStateIndex = lineIndex + 1; - let r: TokenizationResult2 = null; - const text = this._lines[lineIndex].text; - - try { - // Tokenize only the first X characters - let freshState = this._lines[lineIndex].getState().clone(); - r = this._tokenizationSupport.tokenize2(this._lines[lineIndex].text, freshState, 0); - } catch (e) { - e.friendlyMessage = TextModelWithTokens.MODE_TOKENIZATION_FAILED_MSG; - onUnexpectedError(e); - } - - if (!r) { - r = nullTokenize2(this._languageIdentifier.id, text, this._lines[lineIndex].getState(), 0); - } - this._lines[lineIndex].setTokens(this._languageIdentifier.id, r.tokens); - eventBuilder.registerChangedTokens(lineIndex + 1); - this._lines[lineIndex].setIsInvalid(false); - - if (endStateIndex < linesLength) { - if (this._lines[endStateIndex].getState() !== null && r.endState.equals(this._lines[endStateIndex].getState())) { - // The end state of this line remains the same - let nextInvalidLineIndex = lineIndex + 1; - while (nextInvalidLineIndex < linesLength) { - if (this._lines[nextInvalidLineIndex].isInvalid()) { - break; - } - if (nextInvalidLineIndex + 1 < linesLength) { - if (this._lines[nextInvalidLineIndex + 1].getState() === null) { - break; - } - } else { - if (this._lastState === null) { - break; - } - } - nextInvalidLineIndex++; - } - this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, nextInvalidLineIndex); - lineIndex = nextInvalidLineIndex - 1; // -1 because the outer loop increments it - } else { - this._lines[endStateIndex].setState(r.endState); - } - } else { - this._lastState = r.endState; - } - } - this._invalidLineStartIndex = Math.max(this._invalidLineStartIndex, endLineIndex + 1); - } - - private emitModelTokensChangedEvent(e: IModelTokensChangedEvent): void { - if (!this._isDisposing) { - this._onDidChangeTokens.fire(e); - } - } - - // Having tokens allows implementing additional helper methods - - public getWordAtPosition(_position: IPosition): editorCommon.IWordAtPosition { - this._assertNotDisposed(); - const position = this.validatePosition(_position); - const lineContent = this.getLineContent(position.lineNumber); - - if (this._invalidLineStartIndex <= position.lineNumber - 1) { - // this line is not tokenized - return getWordAtText( - position.column, - LanguageConfigurationRegistry.getWordDefinition(this._languageIdentifier.id), - lineContent, - 0 - ); - } - - const lineTokens = this._getLineTokens(position.lineNumber); - const offset = position.column - 1; - const token = lineTokens.findTokenAtOffset(offset); - const languageId = token.languageId; - - // go left until a different language is hit - let startOffset: number; - for (let leftToken = token.clone(); leftToken !== null && leftToken.languageId === languageId; leftToken = leftToken.prev()) { - startOffset = leftToken.startOffset; - } - - // go right until a different language is hit - let endOffset: number; - for (let rightToken = token.clone(); rightToken !== null && rightToken.languageId === languageId; rightToken = rightToken.next()) { - endOffset = rightToken.endOffset; - } - - return getWordAtText( - position.column, - LanguageConfigurationRegistry.getWordDefinition(languageId), - lineContent.substring(startOffset, endOffset), - startOffset - ); - } - - public getWordUntilPosition(position: IPosition): editorCommon.IWordAtPosition { - var wordAtPosition = this.getWordAtPosition(position); - if (!wordAtPosition) { - return { - word: '', - startColumn: position.column, - endColumn: position.column - }; - } - return { - word: wordAtPosition.word.substr(0, position.column - wordAtPosition.startColumn), - startColumn: wordAtPosition.startColumn, - endColumn: position.column - }; - } - - public findMatchingBracketUp(_bracket: string, _position: IPosition): Range { - let bracket = _bracket.toLowerCase(); - let position = this.validatePosition(_position); - - let lineTokens = this._getLineTokens(position.lineNumber); - let token = lineTokens.findTokenAtOffset(position.column - 1); - let bracketsSupport = LanguageConfigurationRegistry.getBracketsSupport(token.languageId); - - if (!bracketsSupport) { - return null; - } - - let data = bracketsSupport.textIsBracket[bracket]; - - if (!data) { - return null; - } - - return this._findMatchingBracketUp(data, position); - } - - public matchBracket(position: IPosition): [Range, Range] { - return this._matchBracket(this.validatePosition(position)); - } - - private _matchBracket(position: Position): [Range, Range] { - const lineNumber = position.lineNumber; - let lineTokens = this._getLineTokens(lineNumber); - const lineText = this._lines[lineNumber - 1].text; - - let currentToken = lineTokens.findTokenAtOffset(position.column - 1); - if (!currentToken) { - return null; - } - const currentModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(currentToken.languageId); - - // check that the token is not to be ignored - if (currentModeBrackets && !ignoreBracketsInToken(currentToken.tokenType)) { - // limit search to not go before `maxBracketLength` - let searchStartOffset = Math.max(currentToken.startOffset, position.column - 1 - currentModeBrackets.maxBracketLength); - // limit search to not go after `maxBracketLength` - const searchEndOffset = Math.min(currentToken.endOffset, position.column - 1 + currentModeBrackets.maxBracketLength); - - // first, check if there is a bracket to the right of `position` - let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, position.column - 1, searchEndOffset); - if (foundBracket && foundBracket.startColumn === position.column) { - let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); - foundBracketText = foundBracketText.toLowerCase(); - - let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); - - // check that we can actually match this bracket - if (r) { - return r; - } - } - - // it might still be the case that [currentTokenStart -> currentTokenEnd] contains multiple brackets - while (true) { - let foundBracket = BracketsUtils.findNextBracketInToken(currentModeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); - if (!foundBracket) { - // there are no brackets in this text - break; - } - - // check that we didn't hit a bracket too far away from position - if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { - let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); - foundBracketText = foundBracketText.toLowerCase(); - - let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); - - // check that we can actually match this bracket - if (r) { - return r; - } - } - - searchStartOffset = foundBracket.endColumn - 1; - } - } - - // If position is in between two tokens, try also looking in the previous token - if (currentToken.hasPrev && currentToken.startOffset === position.column - 1) { - const searchEndOffset = currentToken.startOffset; - currentToken = currentToken.prev(); - const prevModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(currentToken.languageId); - - // check that previous token is not to be ignored - if (prevModeBrackets && !ignoreBracketsInToken(currentToken.tokenType)) { - // limit search in case previous token is very large, there's no need to go beyond `maxBracketLength` - const searchStartOffset = Math.max(currentToken.startOffset, position.column - 1 - prevModeBrackets.maxBracketLength); - const foundBracket = BracketsUtils.findPrevBracketInToken(prevModeBrackets.reversedRegex, lineNumber, lineText, searchStartOffset, searchEndOffset); - - // check that we didn't hit a bracket too far away from position - if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { - let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); - foundBracketText = foundBracketText.toLowerCase(); - - let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); - - // check that we can actually match this bracket - if (r) { - return r; - } - } - } - } - - return null; - } - - private _matchFoundBracket(foundBracket: Range, data: RichEditBracket, isOpen: boolean): [Range, Range] { - if (isOpen) { - let matched = this._findMatchingBracketDown(data, foundBracket.getEndPosition()); - if (matched) { - return [foundBracket, matched]; - } - } else { - let matched = this._findMatchingBracketUp(data, foundBracket.getStartPosition()); - if (matched) { - return [foundBracket, matched]; - } - } - - return null; - } - - private _findMatchingBracketUp(bracket: RichEditBracket, position: Position): Range { - // console.log('_findMatchingBracketUp: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); - - const languageId = bracket.languageIdentifier.id; - const reversedBracketRegex = bracket.reversedRegex; - let count = -1; - - for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { - const lineTokens = this._getLineTokens(lineNumber); - const lineText = this._lines[lineNumber - 1].text; - - let currentToken: LineToken; - let searchStopOffset: number; - if (lineNumber === position.lineNumber) { - currentToken = lineTokens.findTokenAtOffset(position.column - 1); - searchStopOffset = position.column - 1; - } else { - currentToken = lineTokens.lastToken(); - if (currentToken) { - searchStopOffset = currentToken.endOffset; - } - } - - while (currentToken) { - if (currentToken.languageId === languageId && !ignoreBracketsInToken(currentToken.tokenType)) { - - while (true) { - let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, lineNumber, lineText, currentToken.startOffset, searchStopOffset); - if (!r) { - break; - } - - let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); - hitText = hitText.toLowerCase(); - - if (hitText === bracket.open) { - count++; - } else if (hitText === bracket.close) { - count--; - } - - if (count === 0) { - return r; - } - - searchStopOffset = r.startColumn - 1; - } - } - - currentToken = currentToken.prev(); - if (currentToken) { - searchStopOffset = currentToken.endOffset; - } - } - } - - return null; - } - - private _findMatchingBracketDown(bracket: RichEditBracket, position: Position): Range { - // console.log('_findMatchingBracketDown: ', 'bracket: ', JSON.stringify(bracket), 'startPosition: ', String(position)); - - const languageId = bracket.languageIdentifier.id; - const bracketRegex = bracket.forwardRegex; - let count = 1; - - for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { - const lineTokens = this._getLineTokens(lineNumber); - const lineText = this._lines[lineNumber - 1].text; - - let currentToken: LineToken; - let searchStartOffset: number; - if (lineNumber === position.lineNumber) { - currentToken = lineTokens.findTokenAtOffset(position.column - 1); - searchStartOffset = position.column - 1; - } else { - currentToken = lineTokens.firstToken(); - if (currentToken) { - searchStartOffset = currentToken.startOffset; - } - } - - while (currentToken) { - if (currentToken.languageId === languageId && !ignoreBracketsInToken(currentToken.tokenType)) { - while (true) { - let r = BracketsUtils.findNextBracketInToken(bracketRegex, lineNumber, lineText, searchStartOffset, currentToken.endOffset); - if (!r) { - break; - } - - let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); - hitText = hitText.toLowerCase(); - - if (hitText === bracket.open) { - count++; - } else if (hitText === bracket.close) { - count--; - } - - if (count === 0) { - return r; - } - - searchStartOffset = r.endColumn - 1; - } - } - - currentToken = currentToken.next(); - if (currentToken) { - searchStartOffset = currentToken.startOffset; - } - } - } - - return null; - } - - public findPrevBracket(_position: IPosition): editorCommon.IFoundBracket { - const position = this.validatePosition(_position); - - let languageId: LanguageId = -1; - let modeBrackets: RichEditBrackets = null; - for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { - const lineTokens = this._getLineTokens(lineNumber); - const lineText = this._lines[lineNumber - 1].text; - - let currentToken: LineToken; - let searchStopOffset: number; - if (lineNumber === position.lineNumber) { - currentToken = lineTokens.findTokenAtOffset(position.column - 1); - searchStopOffset = position.column - 1; - } else { - currentToken = lineTokens.lastToken(); - if (currentToken) { - searchStopOffset = currentToken.endOffset; - } - } - - while (currentToken) { - if (languageId !== currentToken.languageId) { - languageId = currentToken.languageId; - modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - } - if (modeBrackets && !ignoreBracketsInToken(currentToken.tokenType)) { - let r = BracketsUtils.findPrevBracketInToken(modeBrackets.reversedRegex, lineNumber, lineText, currentToken.startOffset, searchStopOffset); - if (r) { - return this._toFoundBracket(modeBrackets, r); - } - } - - currentToken = currentToken.prev(); - if (currentToken) { - searchStopOffset = currentToken.endOffset; - } - } - } - - return null; - } - - public findNextBracket(_position: IPosition): editorCommon.IFoundBracket { - const position = this.validatePosition(_position); - - let languageId: LanguageId = -1; - let modeBrackets: RichEditBrackets = null; - for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { - const lineTokens = this._getLineTokens(lineNumber); - const lineText = this._lines[lineNumber - 1].text; - - let currentToken: LineToken; - let searchStartOffset: number; - if (lineNumber === position.lineNumber) { - currentToken = lineTokens.findTokenAtOffset(position.column - 1); - searchStartOffset = position.column - 1; - } else { - currentToken = lineTokens.firstToken(); - if (currentToken) { - searchStartOffset = currentToken.startOffset; - } - } - - while (currentToken) { - if (languageId !== currentToken.languageId) { - languageId = currentToken.languageId; - modeBrackets = LanguageConfigurationRegistry.getBracketsSupport(languageId); - } - if (modeBrackets && !ignoreBracketsInToken(currentToken.tokenType)) { - let r = BracketsUtils.findNextBracketInToken(modeBrackets.forwardRegex, lineNumber, lineText, searchStartOffset, currentToken.endOffset); - if (r) { - return this._toFoundBracket(modeBrackets, r); - } - } - - currentToken = currentToken.next(); - if (currentToken) { - searchStartOffset = currentToken.startOffset; - } - } - } - - return null; - } - - private _toFoundBracket(modeBrackets: RichEditBrackets, r: Range): editorCommon.IFoundBracket { - if (!r) { - return null; - } - - let text = this.getValueInRange(r); - text = text.toLowerCase(); - - let data = modeBrackets.textIsBracket[text]; - if (!data) { - return null; - } - - return { - range: r, - open: data.open, - close: data.close, - isOpen: modeBrackets.textIsOpenBracket[text] - }; - } - - private _computeIndentLevel(lineIndex: number): number { - return computeIndentLevel(this._lines[lineIndex].text, this._options.tabSize); - } - - public getLinesIndentGuides(startLineNumber: number, endLineNumber: number): number[] { - this._assertNotDisposed(); - const lineCount = this.getLineCount(); - - if (startLineNumber < 1 || startLineNumber > lineCount) { - throw new Error('Illegal value ' + startLineNumber + ' for `startLineNumber`'); - } - if (endLineNumber < 1 || endLineNumber > lineCount) { - throw new Error('Illegal value ' + endLineNumber + ' for `endLineNumber`'); - } - - const foldingRules = LanguageConfigurationRegistry.getFoldingRules(this._languageIdentifier.id); - const offSide = foldingRules && foldingRules.offSide; - - let result: number[] = new Array(endLineNumber - startLineNumber + 1); - - let aboveContentLineIndex = -2; /* -2 is a marker for not having computed it */ - let aboveContentLineIndent = -1; - - let belowContentLineIndex = -2; /* -2 is a marker for not having computed it */ - let belowContentLineIndent = -1; - - for (let lineNumber = startLineNumber; lineNumber <= endLineNumber; lineNumber++) { - let resultIndex = lineNumber - startLineNumber; - - const currentIndent = this._computeIndentLevel(lineNumber - 1); - if (currentIndent >= 0) { - // This line has content (besides whitespace) - // Use the line's indent - aboveContentLineIndex = lineNumber - 1; - aboveContentLineIndent = currentIndent; - result[resultIndex] = Math.ceil(currentIndent / this._options.tabSize); - continue; - } - - if (aboveContentLineIndex === -2) { - aboveContentLineIndex = -1; - aboveContentLineIndent = -1; - - // must find previous line with content - for (let lineIndex = lineNumber - 2; lineIndex >= 0; lineIndex--) { - let indent = this._computeIndentLevel(lineIndex); - if (indent >= 0) { - aboveContentLineIndex = lineIndex; - aboveContentLineIndent = indent; - break; - } - } - } - - if (belowContentLineIndex !== -1 && (belowContentLineIndex === -2 || belowContentLineIndex < lineNumber - 1)) { - belowContentLineIndex = -1; - belowContentLineIndent = -1; - - // must find next line with content - for (let lineIndex = lineNumber; lineIndex < lineCount; lineIndex++) { - let indent = this._computeIndentLevel(lineIndex); - if (indent >= 0) { - belowContentLineIndex = lineIndex; - belowContentLineIndent = indent; - break; - } - } - } - - if (aboveContentLineIndent === -1 || belowContentLineIndent === -1) { - // At the top or bottom of the file - result[resultIndex] = 0; - - } else if (aboveContentLineIndent < belowContentLineIndent) { - // we are inside the region above - result[resultIndex] = (1 + Math.floor(aboveContentLineIndent / this._options.tabSize)); - - } else if (aboveContentLineIndent === belowContentLineIndent) { - // we are in between two regions - result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize); - - } else { - - if (offSide) { - // same level as region below - result[resultIndex] = Math.ceil(belowContentLineIndent / this._options.tabSize); - } else { - // we are inside the region that ends below - result[resultIndex] = (1 + Math.floor(belowContentLineIndent / this._options.tabSize)); - } - - } - } - return result; - } -} diff --git a/src/vs/editor/common/modes/supports.ts b/src/vs/editor/common/modes/supports.ts index dad9860810f..0ecec39a1b5 100644 --- a/src/vs/editor/common/modes/supports.ts +++ b/src/vs/editor/common/modes/supports.ts @@ -8,7 +8,7 @@ import * as modes from 'vs/editor/common/modes'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; export function createScopedLineTokens(context: LineTokens, offset: number): ScopedLineTokens { - let tokenCount = context.getTokenCount(); + let tokenCount = context.getCount(); let tokenIndex = context.findTokenIndexAtOffset(offset); let desiredLanguageId = context.getLanguageId(tokenIndex); @@ -27,8 +27,8 @@ export function createScopedLineTokens(context: LineTokens, offset: number): Sco desiredLanguageId, firstTokenIndex, lastTokenIndex + 1, - context.getTokenStartOffset(firstTokenIndex), - context.getTokenEndOffset(lastTokenIndex) + context.getStartOffset(firstTokenIndex), + context.getEndOffset(lastTokenIndex) ); } diff --git a/src/vs/editor/common/modes/textToHtmlTokenizer.ts b/src/vs/editor/common/modes/textToHtmlTokenizer.ts index ec741759baa..3c2bd218343 100644 --- a/src/vs/editor/common/modes/textToHtmlTokenizer.ts +++ b/src/vs/editor/common/modes/textToHtmlTokenizer.ts @@ -7,24 +7,22 @@ import * as strings from 'vs/base/common/strings'; import { IState, ITokenizationSupport, TokenizationRegistry, LanguageId } from 'vs/editor/common/modes'; import { NULL_STATE, nullTokenize2 } from 'vs/editor/common/modes/nullMode'; -import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { LineTokens, IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { CharCode } from 'vs/base/common/charCode'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; export function tokenizeToString(text: string, languageId: string): string { return _tokenizeToString(text, _getSafeTokenizationSupport(languageId)); } -export function tokenizeLineToHTML(text: string, viewLineTokens: ViewLineToken[], colorMap: string[], startOffset: number, endOffset: number, tabSize: number): string { +export function tokenizeLineToHTML(text: string, viewLineTokens: IViewLineTokens, colorMap: string[], startOffset: number, endOffset: number, tabSize: number): string { let result = `
`; let charIndex = startOffset; let tabsCharDelta = 0; - for (let tokenIndex = 0, lenJ = viewLineTokens.length; tokenIndex < lenJ; tokenIndex++) { - const token = viewLineTokens[tokenIndex]; - const tokenEndIndex = token.endIndex; + for (let tokenIndex = 0, tokenCount = viewLineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) { + const tokenEndIndex = viewLineTokens.getEndOffset(tokenIndex); - if (token.endIndex <= startOffset) { + if (tokenEndIndex <= startOffset) { continue; } @@ -74,9 +72,9 @@ export function tokenizeLineToHTML(text: string, viewLineTokens: ViewLineToken[] } } - result += `${partContent}`; + result += `${partContent}`; - if (token.endIndex > endOffset || charIndex >= endOffset) { + if (tokenEndIndex > endOffset || charIndex >= endOffset) { break; } } @@ -109,14 +107,16 @@ function _tokenizeToString(text: string, tokenizationSupport: ITokenizationSuppo } let tokenizationResult = tokenizationSupport.tokenize2(line, currentState, 0); + LineTokens.convertToEndOffset(tokenizationResult.tokens, line.length); let lineTokens = new LineTokens(tokenizationResult.tokens, line); let viewLineTokens = lineTokens.inflate(); let startOffset = 0; - for (let j = 0, lenJ = viewLineTokens.length; j < lenJ; j++) { - const viewLineToken = viewLineTokens[j]; - result += `${strings.escape(line.substring(startOffset, viewLineToken.endIndex))}`; - startOffset = viewLineToken.endIndex; + for (let j = 0, lenJ = viewLineTokens.getCount(); j < lenJ; j++) { + const type = viewLineTokens.getClassName(j); + const endIndex = viewLineTokens.getEndOffset(j); + result += `${strings.escape(line.substring(startOffset, endIndex))}`; + startOffset = endIndex; } currentState = tokenizationResult.endState; diff --git a/src/vs/editor/common/services/editorSimpleWorker.ts b/src/vs/editor/common/services/editorSimpleWorker.ts index 169df1f0de1..155f97c34cf 100644 --- a/src/vs/editor/common/services/editorSimpleWorker.ts +++ b/src/vs/editor/common/services/editorSimpleWorker.ts @@ -313,7 +313,6 @@ export abstract class BaseEditorSimpleWorker { let diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldPostProcessCharChanges: true, shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, - shouldConsiderTrimWhitespaceInEmptyCase: true, shouldMakePrettyDiff: true }); return TPromise.as(diffComputer.computeDiff()); @@ -331,7 +330,6 @@ export abstract class BaseEditorSimpleWorker { let diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldPostProcessCharChanges: false, shouldIgnoreTrimWhitespace: ignoreTrimWhitespace, - shouldConsiderTrimWhitespaceInEmptyCase: false, shouldMakePrettyDiff: true }); return TPromise.as(diffComputer.computeDiff()); diff --git a/src/vs/editor/common/view/overviewZoneManager.ts b/src/vs/editor/common/view/overviewZoneManager.ts index 8bbf6e5e685..bb624485513 100644 --- a/src/vs/editor/common/view/overviewZoneManager.ts +++ b/src/vs/editor/common/view/overviewZoneManager.ts @@ -4,22 +4,31 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { OverviewRulerLane } from 'vs/editor/common/editorCommon'; -import { ThemeType, DARK, HIGH_CONTRAST, LIGHT } from 'vs/platform/theme/common/themeService'; +const enum Constants { + MINIMUM_HEIGHT = 4 +} export class ColorZone { _colorZoneBrand: void; - from: number; - to: number; - colorId: number; - position: OverviewRulerLane; + public readonly from: number; + public readonly to: number; + public readonly colorId: number; - constructor(from: number, to: number, colorId: number, position: OverviewRulerLane) { + constructor(from: number, to: number, colorId: number) { this.from = from | 0; this.to = to | 0; this.colorId = colorId | 0; - this.position = position | 0; + } + + public static compare(a: ColorZone, b: ColorZone): number { + if (a.colorId === b.colorId) { + if (a.from === b.from) { + return a.to - b.to; + } + return a.from - b.from; + } + return a.colorId - b.colorId; } } @@ -29,74 +38,39 @@ export class ColorZone { export class OverviewRulerZone { _overviewRulerZoneBrand: void; - startLineNumber: number; - endLineNumber: number; - position: OverviewRulerLane; - forceHeight: number; + public readonly startLineNumber: number; + public readonly endLineNumber: number; + public readonly color: string; - private _color: string; - private _darkColor: string; - private _hcColor: string; - - private _colorZones: ColorZone[]; + private _colorZone: ColorZone; constructor( - startLineNumber: number, endLineNumber: number, - position: OverviewRulerLane, - forceHeight: number, - color: string, darkColor: string, hcColor: string + startLineNumber: number, + endLineNumber: number, + color: string ) { this.startLineNumber = startLineNumber; this.endLineNumber = endLineNumber; - this.position = position; - this.forceHeight = forceHeight; - this._color = color; - this._darkColor = darkColor; - this._hcColor = hcColor; - this._colorZones = null; + this.color = color; + this._colorZone = null; } - public getColor(themeType: ThemeType): string { - switch (themeType) { - case HIGH_CONTRAST: - return this._hcColor; - case DARK: - return this._darkColor; - } - return this._color; - } - - public compareTo(other: OverviewRulerZone): number { - if (this.startLineNumber === other.startLineNumber) { - if (this.endLineNumber === other.endLineNumber) { - if (this.forceHeight === other.forceHeight) { - if (this.position === other.position) { - if (this._darkColor === other._darkColor) { - if (this._color === other._color) { - if (this._hcColor === other._hcColor) { - return 0; - } - return this._hcColor < other._hcColor ? -1 : 1; - } - return this._color < other._color ? -1 : 1; - } - return this._darkColor < other._darkColor ? -1 : 1; - } - return this.position - other.position; - } - return this.forceHeight - other.forceHeight; + public static compare(a: OverviewRulerZone, b: OverviewRulerZone): number { + if (a.color === b.color) { + if (a.startLineNumber === b.startLineNumber) { + return a.endLineNumber - b.endLineNumber; } - return this.endLineNumber - other.endLineNumber; + return a.startLineNumber - b.startLineNumber; } - return this.startLineNumber - other.startLineNumber; + return a.color < b.color ? -1 : 1; } - public setColorZones(colorZones: ColorZone[]): void { - this._colorZones = colorZones; + public setColorZone(colorZone: ColorZone): void { + this._colorZone = colorZone; } - public getColorZones(): ColorZone[] { - return this._colorZones; + public getColorZones(): ColorZone { + return this._colorZone; } } @@ -109,9 +83,6 @@ export class OverviewZoneManager { private _domWidth: number; private _domHeight: number; private _outerHeight: number; - private _maximumHeight: number; - private _minimumHeight: number; - private _themeType: ThemeType; private _pixelRatio: number; private _lastAssignedId: number; @@ -126,9 +97,6 @@ export class OverviewZoneManager { this._domWidth = 0; this._domHeight = 0; this._outerHeight = 0; - this._maximumHeight = 0; - this._minimumHeight = 0; - this._themeType = LIGHT; this._pixelRatio = 1; this._lastAssignedId = 0; @@ -141,39 +109,8 @@ export class OverviewZoneManager { } public setZones(newZones: OverviewRulerZone[]): void { - newZones.sort((a, b) => a.compareTo(b)); - - let oldZones = this._zones; - let oldIndex = 0; - let oldLength = this._zones.length; - let newIndex = 0; - let newLength = newZones.length; - - let result: OverviewRulerZone[] = []; - while (newIndex < newLength) { - let newZone = newZones[newIndex]; - - if (oldIndex >= oldLength) { - result.push(newZone); - newIndex++; - } else { - let oldZone = oldZones[oldIndex]; - let cmp = oldZone.compareTo(newZone); - if (cmp < 0) { - oldIndex++; - } else if (cmp > 0) { - result.push(newZone); - newIndex++; - } else { - // cmp === 0 - result.push(oldZone); - oldIndex++; - newIndex++; - } - } - } - - this._zones = result; + this._zones = newZones; + this._zones.sort(OverviewRulerZone.compare); } public setLineHeight(lineHeight: number): boolean { @@ -237,146 +174,59 @@ export class OverviewZoneManager { return true; } - public setMaximumHeight(maximumHeight: number): boolean { - if (this._maximumHeight === maximumHeight) { - return false; - } - this._maximumHeight = maximumHeight; - this._colorZonesInvalid = true; - return true; - } - - public setMinimumHeight(minimumHeight: number): boolean { - if (this._minimumHeight === minimumHeight) { - return false; - } - this._minimumHeight = minimumHeight; - this._colorZonesInvalid = true; - return true; - } - - public setThemeType(themeType: ThemeType): boolean { - if (this._themeType === themeType) { - return false; - } - this._themeType = themeType; - this._colorZonesInvalid = true; - return true; - } - public resolveColorZones(): ColorZone[] { const colorZonesInvalid = this._colorZonesInvalid; const lineHeight = Math.floor(this._lineHeight); // @perf const totalHeight = Math.floor(this.getCanvasHeight()); // @perf - const maximumHeight = Math.floor(this._maximumHeight * this._pixelRatio); // @perf - const minimumHeight = Math.floor(this._minimumHeight * this._pixelRatio); // @perf - const themeType = this._themeType; // @perf const outerHeight = Math.floor(this._outerHeight); // @perf const heightRatio = totalHeight / outerHeight; + const halfMinimumHeight = Math.floor(Constants.MINIMUM_HEIGHT * this._pixelRatio / 2); let allColorZones: ColorZone[] = []; for (let i = 0, len = this._zones.length; i < len; i++) { - let zone = this._zones[i]; + const zone = this._zones[i]; if (!colorZonesInvalid) { - let colorZones = zone.getColorZones(); - if (colorZones) { - for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { - allColorZones.push(colorZones[j]); - } + const colorZone = zone.getColorZones(); + if (colorZone) { + allColorZones.push(colorZone); continue; } } - let colorZones: ColorZone[] = []; - if (zone.forceHeight) { - let forcedHeight = Math.floor(zone.forceHeight * this._pixelRatio); + const y1 = Math.floor(heightRatio * (this._getVerticalOffsetForLine(zone.startLineNumber))); + const y2 = Math.floor(heightRatio * (this._getVerticalOffsetForLine(zone.endLineNumber) + lineHeight)); - let y1 = Math.floor(this._getVerticalOffsetForLine(zone.startLineNumber)); - y1 = Math.floor(y1 * heightRatio); + let ycenter = Math.floor((y1 + y2) / 2); + let halfHeight = (y2 - ycenter); - let y2 = y1 + forcedHeight; - colorZones.push(this.createZone(totalHeight, y1, y2, forcedHeight, forcedHeight, zone.getColor(themeType), zone.position)); - } else { - let y1 = Math.floor(this._getVerticalOffsetForLine(zone.startLineNumber)); - let y2 = Math.floor(this._getVerticalOffsetForLine(zone.endLineNumber)) + lineHeight; - - y1 = Math.floor(y1 * heightRatio); - y2 = Math.floor(y2 * heightRatio); - - // Figure out if we can render this in one continuous zone - let zoneLineNumbers = zone.endLineNumber - zone.startLineNumber + 1; - let zoneMaximumHeight = zoneLineNumbers * maximumHeight; - - if (y2 - y1 > zoneMaximumHeight) { - // We need to draw one zone per line - for (let lineNumber = zone.startLineNumber; lineNumber <= zone.endLineNumber; lineNumber++) { - y1 = Math.floor(this._getVerticalOffsetForLine(lineNumber)); - y2 = y1 + lineHeight; - - y1 = Math.floor(y1 * heightRatio); - y2 = Math.floor(y2 * heightRatio); - - colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, maximumHeight, zone.getColor(themeType), zone.position)); - } - } else { - colorZones.push(this.createZone(totalHeight, y1, y2, minimumHeight, zoneMaximumHeight, zone.getColor(themeType), zone.position)); - } + if (halfHeight < halfMinimumHeight) { + halfHeight = halfMinimumHeight; } - zone.setColorZones(colorZones); - for (let j = 0, lenJ = colorZones.length; j < lenJ; j++) { - allColorZones.push(colorZones[j]); + if (ycenter - halfHeight < 0) { + ycenter = halfHeight; } + if (ycenter + halfHeight > totalHeight) { + ycenter = totalHeight - halfHeight; + } + + const color = zone.color; + let colorId = this._color2Id[color]; + if (!colorId) { + colorId = (++this._lastAssignedId); + this._color2Id[color] = colorId; + this._id2Color[colorId] = color; + } + const colorZone = new ColorZone(ycenter - halfHeight, ycenter + halfHeight, colorId); + + zone.setColorZone(colorZone); + allColorZones.push(colorZone); } this._colorZonesInvalid = false; - let sortFunc = (a: ColorZone, b: ColorZone) => { - if (a.colorId === b.colorId) { - if (a.from === b.from) { - return a.to - b.to; - } - return a.from - b.from; - } - return a.colorId - b.colorId; - }; - - allColorZones.sort(sortFunc); + allColorZones.sort(ColorZone.compare); return allColorZones; } - - public createZone(totalHeight: number, y1: number, y2: number, minimumHeight: number, maximumHeight: number, color: string, position: OverviewRulerLane): ColorZone { - totalHeight = Math.floor(totalHeight); // @perf - y1 = Math.floor(y1); // @perf - y2 = Math.floor(y2); // @perf - minimumHeight = Math.floor(minimumHeight); // @perf - maximumHeight = Math.floor(maximumHeight); // @perf - - let ycenter = Math.floor((y1 + y2) / 2); - let halfHeight = (y2 - ycenter); - - - if (halfHeight > maximumHeight / 2) { - halfHeight = maximumHeight / 2; - } - if (halfHeight < minimumHeight / 2) { - halfHeight = minimumHeight / 2; - } - - if (ycenter - halfHeight < 0) { - ycenter = halfHeight; - } - if (ycenter + halfHeight > totalHeight) { - ycenter = totalHeight - halfHeight; - } - - let colorId = this._color2Id[color]; - if (!colorId) { - colorId = (++this._lastAssignedId); - this._color2Id[color] = colorId; - this._id2Color[colorId] = color; - } - return new ColorZone(ycenter - halfHeight, ycenter + halfHeight, colorId, position); - } } diff --git a/src/vs/editor/common/view/viewEvents.ts b/src/vs/editor/common/view/viewEvents.ts index 46244956a30..c850e4249e9 100644 --- a/src/vs/editor/common/view/viewEvents.ts +++ b/src/vs/editor/common/view/viewEvents.ts @@ -70,14 +70,9 @@ export class ViewCursorStateChangedEvent { * The primary selection is always at index 0. */ public readonly selections: Selection[]; - /** - * Is the primary cursor in the editable range? - */ - public readonly isInEditableRange: boolean; - constructor(selections: Selection[], isInEditableRange: boolean) { + constructor(selections: Selection[]) { this.selections = selections; - this.isInEditableRange = isInEditableRange; } } diff --git a/src/vs/editor/common/viewLayout/viewLayout.ts b/src/vs/editor/common/viewLayout/viewLayout.ts index a0802b952e2..86b9a615782 100644 --- a/src/vs/editor/common/viewLayout/viewLayout.ts +++ b/src/vs/editor/common/viewLayout/viewLayout.ts @@ -175,15 +175,15 @@ export class ViewLayout extends Disposable implements IViewLayout { }; } - public restoreState(state: editorCommon.IViewState): void { + public reduceRestoreState(state: editorCommon.IViewState): { scrollLeft: number; scrollTop: number; } { let restoreScrollTop = state.scrollTop; if (typeof state.scrollTopWithoutViewZones === 'number' && !this._linesLayout.hasWhitespace()) { restoreScrollTop = state.scrollTopWithoutViewZones; } - this.scrollable.setScrollPositionNow({ + return { scrollLeft: state.scrollLeft, scrollTop: restoreScrollTop - }); + }; } // ---- IVerticalLayoutProvider diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 8f1696f969b..c9994d1baaf 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { CharCode } from 'vs/base/common/charCode'; import { LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations'; import * as strings from 'vs/base/common/strings'; @@ -38,7 +38,7 @@ export class RenderLineInput { public readonly lineContent: string; public readonly mightContainRTL: boolean; public readonly fauxIndentLength: number; - public readonly lineTokens: ViewLineToken[]; + public readonly lineTokens: IViewLineTokens; public readonly lineDecorations: LineDecoration[]; public readonly tabSize: number; public readonly spaceWidth: number; @@ -52,7 +52,7 @@ export class RenderLineInput { lineContent: string, mightContainRTL: boolean, fauxIndentLength: number, - lineTokens: ViewLineToken[], + lineTokens: IViewLineTokens, lineDecorations: LineDecoration[], tabSize: number, spaceWidth: number, @@ -94,7 +94,7 @@ export class RenderLineInput { && this.renderControlCharacters === other.renderControlCharacters && this.fontLigatures === other.fontLigatures && LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations) - && ViewLineToken.equalsArr(this.lineTokens, other.lineTokens) + && this.lineTokens.equals(other.lineTokens) ); } } @@ -357,7 +357,7 @@ function resolveRenderLineInput(input: RenderLineInput): ResolvedRenderLineInput * In the rendering phase, characters are always looped until token.endIndex. * Ensure that all tokens end before `len` and the last one ends precisely at `len`. */ -function transformAndRemoveOverflowing(tokens: ViewLineToken[], fauxIndentLength: number, len: number): LinePart[] { +function transformAndRemoveOverflowing(tokens: IViewLineTokens, fauxIndentLength: number, len: number): LinePart[] { let result: LinePart[] = [], resultLen = 0; // The faux indent part of the line should have no token type @@ -365,14 +365,13 @@ function transformAndRemoveOverflowing(tokens: ViewLineToken[], fauxIndentLength result[resultLen++] = new LinePart(fauxIndentLength, ''); } - for (let tokenIndex = 0, tokensLen = tokens.length; tokenIndex < tokensLen; tokenIndex++) { - const token = tokens[tokenIndex]; - const endIndex = token.endIndex; + for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) { + const endIndex = tokens.getEndOffset(tokenIndex); if (endIndex <= fauxIndentLength) { // The faux indent part of the line should have no token type continue; } - const type = token.getType(); + const type = tokens.getClassName(tokenIndex); if (endIndex >= len) { result[resultLen++] = new LinePart(len, type); break; diff --git a/src/vs/editor/common/viewModel/splitLinesCollection.ts b/src/vs/editor/common/viewModel/splitLinesCollection.ts index 5fe97ffeb40..553527a2b5d 100644 --- a/src/vs/editor/common/viewModel/splitLinesCollection.ts +++ b/src/vs/editor/common/viewModel/splitLinesCollection.ts @@ -12,7 +12,7 @@ import { PrefixSumComputerWithCache } from 'vs/editor/common/viewModel/prefixSum import { ViewLineData, ICoordinatesConverter, IOverviewRulerDecorations } from 'vs/editor/common/viewModel/viewModel'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; -import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions, ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/model'; import { ThemeColor, ITheme } from 'vs/platform/theme/common/themeService'; import { Color } from 'vs/base/common/color'; diff --git a/src/vs/editor/common/viewModel/viewModel.ts b/src/vs/editor/common/viewModel/viewModel.ts index 379460cebe9..52dc31739f2 100644 --- a/src/vs/editor/common/viewModel/viewModel.ts +++ b/src/vs/editor/common/viewModel/viewModel.ts @@ -5,7 +5,7 @@ 'use strict'; import { INewScrollPosition, EndOfLinePreference, IViewState, IModelDecorationOptions } from 'vs/editor/common/editorCommon'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { Position, IPosition } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { ViewEvent, IViewEventListener } from 'vs/editor/common/view/viewEvents'; @@ -63,7 +63,7 @@ export interface IViewLayout { getWhitespaces(): IEditorWhitespace[]; saveState(): IViewState; - restoreState(state: IViewState): void; + reduceRestoreState(state: IViewState): { scrollLeft: number; scrollTop: number; }; isAfterLines(verticalOffset: number): boolean; getLineNumberAtVerticalOffset(verticalOffset: number): number; @@ -144,7 +144,8 @@ export interface IViewModel { validateModelPosition(modelPosition: IPosition): Position; deduceModelPositionRelativeToViewPosition(viewAnchorPosition: Position, deltaOffset: number, lineFeedCnt: number): Position; - getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string; + getEOL(): string; + getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[]; getHTMLToCopy(ranges: Range[], emptySelectionClipboard: boolean): string; } @@ -179,13 +180,13 @@ export class ViewLineData { /** * The tokens at this view line. */ - public readonly tokens: ViewLineToken[]; + public readonly tokens: IViewLineTokens; constructor( content: string, minColumn: number, maxColumn: number, - tokens: ViewLineToken[] + tokens: IViewLineTokens ) { this.content = content; this.minColumn = minColumn; @@ -218,7 +219,7 @@ export class ViewLineRenderingData { /** * The tokens at this view line. */ - public readonly tokens: ViewLineToken[]; + public readonly tokens: IViewLineTokens; /** * Inline decorations at this view line. */ @@ -234,7 +235,7 @@ export class ViewLineRenderingData { content: string, mightContainRTL: boolean, mightContainNonBasicASCII: boolean, - tokens: ViewLineToken[], + tokens: IViewLineTokens, inlineDecorations: InlineDecoration[], tabSize: number ) { diff --git a/src/vs/editor/common/viewModel/viewModelImpl.ts b/src/vs/editor/common/viewModel/viewModelImpl.ts index ec78471f926..cb1e06b31c9 100644 --- a/src/vs/editor/common/viewModel/viewModelImpl.ts +++ b/src/vs/editor/common/viewModel/viewModelImpl.ts @@ -22,7 +22,7 @@ import { ViewLayout } from 'vs/editor/common/viewLayout/viewLayout'; import { Color } from 'vs/base/common/color'; import { IDisposable } from 'vs/base/common/lifecycle'; import { ITheme } from 'vs/platform/theme/common/themeService'; -import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOverviewRulerOptions } from 'vs/editor/common/model/model'; const USE_IDENTITY_LINES_COLLECTION = true; @@ -183,7 +183,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel break; } case textModelEvents.RawContentChangedType.LinesInserted: { - const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail.split('\n')); + const linesInsertedEvent = this.lines.onModelLinesInserted(versionId, change.fromLineNumber, change.toLineNumber, change.detail); if (linesInsertedEvent !== null) { eventsCollector.emit(linesInsertedEvent); this.viewLayout.onLinesInserted(linesInsertedEvent.fromLineNumber, linesInsertedEvent.toLineNumber); @@ -428,7 +428,11 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel return this.model.getPositionAt(resultOffset); } - public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string { + public getEOL(): string { + return this.model.getEOL(); + } + + public getPlainTextToCopy(ranges: Range[], emptySelectionClipboard: boolean): string | string[] { const newLineCharacter = this.model.getEOL(); ranges = ranges.slice(0); @@ -459,7 +463,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel for (let i = 0; i < nonEmptyRanges.length; i++) { result.push(this.getValueInRange(nonEmptyRanges[i], editorCommon.EndOfLinePreference.TextDefined)); } - return result.join(newLineCharacter); + return result.length === 1 ? result[0] : result; } public getHTMLToCopy(viewRanges: Range[], emptySelectionClipboard: boolean): string { diff --git a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts index fbd1a37d39c..e59526a6b4a 100644 --- a/src/vs/editor/contrib/bracketMatching/bracketMatching.ts +++ b/src/vs/editor/contrib/bracketMatching/bracketMatching.ts @@ -18,7 +18,7 @@ import { registerEditorAction, registerEditorContribution, ServicesAccessor, Edi import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorBracketMatchBackground, editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; class SelectBracketAction extends EditorAction { diff --git a/src/vs/editor/contrib/codelens/codelensController.ts b/src/vs/editor/contrib/codelens/codelensController.ts index e38293a1d12..45fc1d5d584 100644 --- a/src/vs/editor/contrib/codelens/codelensController.ts +++ b/src/vs/editor/contrib/codelens/codelensController.ts @@ -129,12 +129,18 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { this._localToDispose.push(this._editor.onDidChangeModelContent((e) => { this._editor.changeDecorations((changeAccessor) => { this._editor.changeViewZones((viewAccessor) => { - const toDispose: CodeLens[] = []; + let toDispose: CodeLens[] = []; + let lastLensLineNumber: number = -1; + this._lenses.forEach((lens) => { - if (lens.isValid()) { - lens.update(viewAccessor); - } else { + if (!lens.isValid() || lastLensLineNumber === lens.getLineNumber()) { + // invalid -> lens collapsed, attach range doesn't exist anymore + // line_number -> lenses should never be on the same line toDispose.push(lens); + + } else { + lens.update(viewAccessor); + lastLensLineNumber = lens.getLineNumber(); } }); diff --git a/src/vs/editor/contrib/codelens/codelensWidget.ts b/src/vs/editor/contrib/codelens/codelensWidget.ts index 7bccc850046..5e85cb5fff2 100644 --- a/src/vs/editor/contrib/codelens/codelensWidget.ts +++ b/src/vs/editor/contrib/codelens/codelensWidget.ts @@ -17,7 +17,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { ICodeLensSymbol, Command } from 'vs/editor/common/modes'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import { ICodeLensData } from './codelens'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { editorCodeLensForeground } from 'vs/editor/common/view/editorColorRegistry'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; diff --git a/src/vs/editor/contrib/dnd/dnd.ts b/src/vs/editor/contrib/dnd/dnd.ts index dcbc3023c76..ff7a7ba3479 100644 --- a/src/vs/editor/contrib/dnd/dnd.ts +++ b/src/vs/editor/contrib/dnd/dnd.ts @@ -17,7 +17,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { DragAndDropCommand } from 'vs/editor/contrib/dnd/dragAndDropCommand'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; export class DragAndDropController implements editorCommon.IEditorContribution { diff --git a/src/vs/editor/contrib/find/findDecorations.ts b/src/vs/editor/contrib/find/findDecorations.ts index 8c9d2dd5f30..c7912573755 100644 --- a/src/vs/editor/contrib/find/findDecorations.ts +++ b/src/vs/editor/contrib/find/findDecorations.ts @@ -8,7 +8,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { editorFindMatchHighlight, editorFindMatch } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/src/vs/editor/contrib/find/findModel.ts b/src/vs/editor/contrib/find/findModel.ts index 22012b05cb7..c84eb165267 100644 --- a/src/vs/editor/contrib/find/findModel.ts +++ b/src/vs/editor/contrib/find/findModel.ts @@ -147,14 +147,8 @@ export class FindModelBoundToEditorModel { } } - private static _getSearchRange(model: editorCommon.IModel, searchOnlyEditableRange: boolean, findScope: Range): Range { - let searchRange: Range; - - if (searchOnlyEditableRange) { - searchRange = model.getEditableRange(); - } else { - searchRange = model.getFullModelRange(); - } + private static _getSearchRange(model: editorCommon.IModel, findScope: Range): Range { + let searchRange = model.getFullModelRange(); // If we have set now or before a find scope, use it for computing the search range if (findScope) { @@ -226,7 +220,7 @@ export class FindModelBoundToEditorModel { } let findScope = this._decorations.getFindScope(); - let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), this._state.isReplaceRevealed, findScope); + let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope); // ...(----)...|... if (searchRange.getEndPosition().isBefore(before)) { @@ -297,7 +291,7 @@ export class FindModelBoundToEditorModel { } let findScope = this._decorations.getFindScope(); - let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), this._state.isReplaceRevealed, findScope); + let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope); // ...(----)...|... if (searchRange.getEndPosition().isBefore(after)) { @@ -389,7 +383,7 @@ export class FindModelBoundToEditorModel { } private _findMatches(findScope: Range, captureMatches: boolean, limitResultCount: number): editorCommon.FindMatch[] { - let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), this._state.isReplaceRevealed, findScope); + let searchRange = FindModelBoundToEditorModel._getSearchRange(this._editor.getModel(), findScope); return this._editor.getModel().findMatches(this._state.searchString, searchRange, this._state.isRegex, this._state.matchCase, this._state.wholeWord ? this._editor.getConfiguration().wordSeparators : null, captureMatches, limitResultCount); } diff --git a/src/vs/editor/contrib/find/test/findModel.test.ts b/src/vs/editor/contrib/find/test/findModel.test.ts index 31c3f449e11..41c52e122cb 100644 --- a/src/vs/editor/contrib/find/test/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/findModel.test.ts @@ -1475,46 +1475,6 @@ suite('FindModel', () => { findState.dispose(); }); - findTest('finds only in editable range if replace is shown', (editor, cursor) => { - editor.getModel().setEditableRange({ - startLineNumber: 6, - startColumn: 1, - endLineNumber: 8, - endColumn: 1 - }); - - let findState = new FindReplaceState(); - findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); - let findModel = new FindModelBoundToEditorModel(editor, findState); - - assertFindState( - editor, - [1, 1, 1, 1], - null, - [ - [6, 14, 6, 19], - [6, 27, 6, 32], - [7, 14, 7, 19], - [8, 14, 8, 19] - ] - ); - - findState.change({ isReplaceRevealed: true }, false); - assertFindState( - editor, - [1, 1, 1, 1], - null, - [ - [6, 14, 6, 19], - [6, 27, 6, 32], - [7, 14, 7, 19] - ] - ); - - findModel.dispose(); - findState.dispose(); - }); - findTest('listens to model content changes', (editor, cursor) => { let findState = new FindReplaceState(); findState.change({ searchString: 'hello', replaceString: 'hi', wholeWord: true }, false); diff --git a/src/vs/editor/contrib/folding/folding.ts b/src/vs/editor/contrib/folding/folding.ts index b7c02090042..4acca4efbcf 100644 --- a/src/vs/editor/contrib/folding/folding.ts +++ b/src/vs/editor/contrib/folding/folding.ts @@ -332,12 +332,15 @@ abstract class FoldingAction extends EditorAction { if (!foldingController) { return; } - this.reportTelemetry(accessor, editor); - return foldingController.getFoldingModel().then(foldingModel => { - if (foldingModel) { - this.invoke(foldingController, foldingModel, editor, args); - } - }); + let foldingModelPromise = foldingController.getFoldingModel(); + if (foldingModelPromise) { + this.reportTelemetry(accessor, editor); + return foldingModelPromise.then(foldingModel => { + if (foldingModel) { + this.invoke(foldingController, foldingModel, editor, args); + } + }); + } } protected getSelectedLines(editor: ICodeEditor) { diff --git a/src/vs/editor/contrib/folding/foldingDecorations.ts b/src/vs/editor/contrib/folding/foldingDecorations.ts index b549fcac3c5..44de87f0526 100644 --- a/src/vs/editor/contrib/folding/foldingDecorations.ts +++ b/src/vs/editor/contrib/folding/foldingDecorations.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationsChangeAccessor } from 'vs/editor/common/editorCommon'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { IDecorationProvider } from 'vs/editor/contrib/folding/foldingModel'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; diff --git a/src/vs/editor/contrib/folding/test/foldingModel.test.ts b/src/vs/editor/contrib/folding/test/foldingModel.test.ts index 287baab9c2d..dd0a9444957 100644 --- a/src/vs/editor/contrib/folding/test/foldingModel.test.ts +++ b/src/vs/editor/contrib/folding/test/foldingModel.test.ts @@ -6,9 +6,8 @@ import * as assert from 'assert'; import { FoldingModel, setCollapseStateAtLevel, setCollapseStateLevelsDown, setCollapseStateLevelsUp, setCollapseStateForMatchingLines } from 'vs/editor/contrib/folding/foldingModel'; -import { Model } from 'vs/editor/common/model/model'; +import { Model, ModelDecorationOptions } from 'vs/editor/common/model/model'; import { computeRanges } from 'vs/editor/contrib/folding/indentRangeProvider'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModel, IModelDecorationsChangeAccessor } from 'vs/editor/common/editorCommon'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Position } from 'vs/editor/common/core/position'; @@ -630,4 +629,4 @@ suite('Folding Model', () => { }); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/hover/hoverWidgets.ts b/src/vs/editor/contrib/hover/hoverWidgets.ts index d11c154e171..7af3834a560 100644 --- a/src/vs/editor/contrib/hover/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/hoverWidgets.ts @@ -140,6 +140,10 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this.updateFont(); this._editor.layoutContentWidget(this); + this.onContentsChange(); + } + + protected onContentsChange(): void { this.scrollbar.scanDomNode(); } diff --git a/src/vs/editor/contrib/hover/modesContentHover.ts b/src/vs/editor/contrib/hover/modesContentHover.ts index 4e93efdcb96..4b9a0048a73 100644 --- a/src/vs/editor/contrib/hover/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/modesContentHover.ts @@ -16,7 +16,7 @@ import { HoverOperation, IHoverComputer } from './hoverOperation'; import { ContentHoverWidget } from './hoverWidgets'; import { IMarkdownString, MarkdownString, isEmptyMarkdownString, markedStringsEquals } from 'vs/base/common/htmlContent'; import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel'; import { ColorPickerWidget } from 'vs/editor/contrib/colorPicker/colorPickerWidget'; import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector'; @@ -154,7 +154,7 @@ class ModesContentComputer implements IHoverComputer { export class ModesContentHoverWidget extends ContentHoverWidget { - static ID = 'editor.contrib.modesContentHoverWidget'; + static readonly ID = 'editor.contrib.modesContentHoverWidget'; private _messages: HoverPart[]; private _lastRange: Range; @@ -167,7 +167,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { private _colorPicker: ColorPickerWidget; private renderDisposable: IDisposable = EmptyDisposable; - private toDispose: IDisposable[]; + private toDispose: IDisposable[] = []; constructor(editor: ICodeEditor, markdownRenderner: MarkdownRenderer) { super(ModesContentHoverWidget.ID, editor); @@ -175,7 +175,9 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._computer = new ModesContentComputer(this._editor); this._highlightDecorations = []; this._isChangingDecorations = false; + this._markdownRenderer = markdownRenderner; + markdownRenderner.onDidRenderCodeBlock(this.onContentsChange, this, this.toDispose); this._hoverOperation = new HoverOperation( this._computer, @@ -184,7 +186,6 @@ export class ModesContentHoverWidget extends ContentHoverWidget { result => this._withResult(result, false) ); - this.toDispose = []; this.toDispose.push(dom.addStandardDisposableListener(this.getDomNode(), dom.EventType.FOCUS, () => { if (this._colorPicker) { dom.addClass(this.getDomNode(), 'colorpicker-hover'); diff --git a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts index 988586c8339..f25ab5b640c 100644 --- a/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/inPlaceReplace.ts @@ -18,7 +18,7 @@ import { InPlaceReplaceCommand } from './inPlaceReplaceCommand'; import { EditorState, CodeEditorStateFlag } from 'vs/editor/browser/core/editorState'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorBracketMatchBorder } from 'vs/editor/common/view/editorColorRegistry'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; class InPlaceReplaceController implements IEditorContribution { diff --git a/src/vs/editor/contrib/indentation/indentation.ts b/src/vs/editor/contrib/indentation/indentation.ts index 427ef548b30..8b400af8d29 100644 --- a/src/vs/editor/contrib/indentation/indentation.ts +++ b/src/vs/editor/contrib/indentation/indentation.ts @@ -554,9 +554,9 @@ export class AutoIndentOnPaste implements IEditorContribution { return true; } let tokens = model.getLineTokens(lineNumber); - if (tokens.getTokenCount() > 0) { - let firstNonWhiteSpaceToken = tokens.findTokenAtOffset(nonWhiteSpaceColumn); - if (firstNonWhiteSpaceToken && firstNonWhiteSpaceToken.tokenType === StandardTokenType.Comment) { + if (tokens.getCount() > 0) { + let firstNonWhitespaceTokenIndex = tokens.findTokenIndexAtOffset(nonWhiteSpaceColumn); + if (firstNonWhitespaceTokenIndex >= 0 && tokens.getStandardTokenType(firstNonWhitespaceTokenIndex) === StandardTokenType.Comment) { return true; } } diff --git a/src/vs/editor/contrib/links/getLinks.ts b/src/vs/editor/contrib/links/getLinks.ts index 047e094293c..86f3513e88b 100644 --- a/src/vs/editor/contrib/links/getLinks.ts +++ b/src/vs/editor/contrib/links/getLinks.ts @@ -25,6 +25,13 @@ export class Link implements ILink { this._provider = provider; } + toJSON(): ILink { + return { + range: this.range, + url: this.url + }; + } + get range(): IRange { return this._link.range; } @@ -134,4 +141,4 @@ CommandsRegistry.registerCommand('_executeLinkProvider', (accessor, ...args) => } return getLinks(model); -}); \ No newline at end of file +}); diff --git a/src/vs/editor/contrib/links/links.ts b/src/vs/editor/contrib/links/links.ts index 897e8f66f83..b6aab6a6690 100644 --- a/src/vs/editor/contrib/links/links.ts +++ b/src/vs/editor/contrib/links/links.ts @@ -22,7 +22,7 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { editorActiveLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { Position } from 'vs/editor/common/core/position'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'vs/editor/contrib/goToDeclaration/clickLinkGesture'; import { MarkdownString } from 'vs/base/common/htmlContent'; diff --git a/src/vs/editor/contrib/markdown/markdownRenderer.ts b/src/vs/editor/contrib/markdown/markdownRenderer.ts index 29ce9afe931..462a3917918 100644 --- a/src/vs/editor/contrib/markdown/markdownRenderer.ts +++ b/src/vs/editor/contrib/markdown/markdownRenderer.ts @@ -15,9 +15,13 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { tokenizeToString } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { optional } from 'vs/platform/instantiation/common/instantiation'; +import Event, { Emitter } from 'vs/base/common/event'; export class MarkdownRenderer { + private _onDidRenderCodeBlock = new Emitter(); + readonly onDidRenderCodeBlock: Event = this._onDidRenderCodeBlock.event; + private readonly _options: RenderOptions; constructor( @@ -29,7 +33,7 @@ export class MarkdownRenderer { actionCallback: (content) => { this._openerService.open(URI.parse(content)).then(void 0, onUnexpectedError); }, - codeBlockRenderer: (languageAlias, value): string | TPromise => { + codeBlockRenderer: (languageAlias, value): TPromise => { // In markdown, // it is possible that we stumble upon language aliases (e.g.js instead of javascript) // it is possible no alias is given in which case we fall back to the current editor lang @@ -42,7 +46,8 @@ export class MarkdownRenderer { }).then(code => { return `${code}`; }); - } + }, + codeBlockRenderCallback: () => this._onDidRenderCodeBlock.fire() }; } diff --git a/src/vs/editor/contrib/multicursor/multicursor.ts b/src/vs/editor/contrib/multicursor/multicursor.ts index 2366fbb8968..1c3523aa025 100644 --- a/src/vs/editor/contrib/multicursor/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/multicursor.ts @@ -15,11 +15,11 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { CursorChangeReason, ICursorSelectionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; import { CursorMoveCommands } from 'vs/editor/common/controller/cursorMoveCommands'; -import { CursorState, RevealTarget } from 'vs/editor/common/controller/cursorCommon'; +import { RevealTarget } from 'vs/editor/common/controller/cursorCommon'; import { Constants } from 'vs/editor/common/core/uint'; import { DocumentHighlightProviderRegistry } from 'vs/editor/common/modes'; import { CommonFindController } from 'vs/editor/contrib/find/findController'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { INewFindReplaceState, FindOptionOverride } from 'vs/editor/contrib/find/findState'; @@ -55,10 +55,7 @@ export class InsertCursorAbove extends EditorAction { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - context, - CursorMoveCommands.addCursorUp(context, cursors.getAll()) - ) + CursorMoveCommands.addCursorUp(context, cursors.getAll()) ); cursors.reveal(true, RevealTarget.TopMost, ScrollType.Smooth); } @@ -94,10 +91,7 @@ export class InsertCursorBelow extends EditorAction { cursors.setStates( args.source, CursorChangeReason.Explicit, - CursorState.ensureInEditableRange( - context, - CursorMoveCommands.addCursorDown(context, cursors.getAll()) - ) + CursorMoveCommands.addCursorDown(context, cursors.getAll()) ); cursors.reveal(true, RevealTarget.BottomMost, ScrollType.Smooth); } diff --git a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts index 3aac48899b2..7b79df1ed7a 100644 --- a/src/vs/editor/contrib/multicursor/test/multicursor.test.ts +++ b/src/vs/editor/contrib/multicursor/test/multicursor.test.ts @@ -27,7 +27,13 @@ suite('Multicursor', () => { addCursorUpAction.run(null, editor, {}); assert.equal(cursor.getSelections().length, 2); - editor.trigger('test', Handler.Paste, { text: '1\n2' }); + editor.trigger('test', Handler.Paste, { + text: '1\n2', + multicursorText: [ + '1', + '2' + ] + }); // cursorCommand(cursor, H.Paste, { text: '1\n2' }); assert.equal(editor.getModel().getLineContent(1), '1abc'); assert.equal(editor.getModel().getLineContent(2), '2def'); diff --git a/src/vs/editor/contrib/quickFix/quickFix.ts b/src/vs/editor/contrib/quickFix/quickFix.ts index 33a0b7d8411..6c9de668262 100644 --- a/src/vs/editor/contrib/quickFix/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/quickFix.ts @@ -13,6 +13,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { onUnexpectedExternalError, illegalArgument } from 'vs/base/common/errors'; import { IModelService } from 'vs/editor/common/services/modelService'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; export function getCodeActions(model: IReadOnlyModel, range: Range): TPromise { @@ -31,7 +32,26 @@ export function getCodeActions(model: IReadOnlyModel, range: Range): TPromise allResults); + return TPromise.join(promises).then( + () => allResults.sort(codeActionsComparator) + ); +} + +function codeActionsComparator(a: CodeAction, b: CodeAction): number { + + const aHasDiags = !isFalsyOrEmpty(a.diagnostics); + const bHasDiags = !isFalsyOrEmpty(b.diagnostics); + if (aHasDiags) { + if (bHasDiags) { + return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message); + } else { + return -1; + } + } else if (bHasDiags) { + return 1; + } else { + return 0; // both have no diagnostics + } } registerLanguageCommand('_executeCodeActionProvider', function (accessor, args) { diff --git a/src/vs/editor/contrib/quickFix/quickFixCommands.ts b/src/vs/editor/contrib/quickFix/quickFixCommands.ts index 7de263a3863..ca1467b88ea 100644 --- a/src/vs/editor/contrib/quickFix/quickFixCommands.ts +++ b/src/vs/editor/contrib/quickFix/quickFixCommands.ts @@ -125,7 +125,7 @@ export class QuickFixController implements IEditorContribution { export class QuickFixAction extends EditorAction { - static Id = 'editor.action.quickFix'; + static readonly Id = 'editor.action.quickFix'; constructor() { super({ diff --git a/src/vs/editor/contrib/quickFix/test/quickFix.test.ts b/src/vs/editor/contrib/quickFix/test/quickFix.test.ts index 315d5b56e31..4bd0a52c4f2 100644 --- a/src/vs/editor/contrib/quickFix/test/quickFix.test.ts +++ b/src/vs/editor/contrib/quickFix/test/quickFix.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { Model } from 'vs/editor/common/model/model'; -import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider } from 'vs/editor/common/modes'; +import { CodeActionProviderRegistry, LanguageIdentifier, CodeActionProvider, Command, WorkspaceEdit, IResourceEdit } from 'vs/editor/common/modes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Range } from 'vs/editor/common/core/range'; import { getCodeActions } from 'vs/editor/contrib/quickFix/quickFix'; @@ -19,6 +19,64 @@ suite('QuickFix', () => { let uri = URI.parse('untitled:path'); let model: Model; let disposables: IDisposable[] = []; + let testData = { + diagnostics: { + abc: { + title: 'bTitle', + diagnostics: [{ + startLineNumber: 1, + startColumn: 1, + endLineNumber: 2, + endColumn: 1, + severity: Severity.Error, + message: 'abc' + }] + }, + bcd: { + title: 'aTitle', + diagnostics: [{ + startLineNumber: 1, + startColumn: 1, + endLineNumber: 2, + endColumn: 1, + severity: Severity.Error, + message: 'bcd' + }] + } + }, + command: { + abc: { + command: new class implements Command { + id: '1'; + title: 'abc'; + }, + title: 'Extract to inner function in function "test"' + } + }, + spelling: { + bcd: { + diagnostics: [], + edits: new class implements WorkspaceEdit { + edits: IResourceEdit[]; + }, + title: 'abc' + } + }, + tsLint: { + abc: { + $ident: 57, + arguments: [], + id: '_internal_command_delegation', + title: 'abc' + }, + bcd: { + $ident: 47, + arguments: [], + id: '_internal_command_delegation', + title: 'bcd' + } + } + }; setup(function () { model = Model.createFromString('test1\ntest2\ntest3', undefined, langId, uri); @@ -29,30 +87,37 @@ suite('QuickFix', () => { dispose(disposables); }); - test('basics', async function () { + test('CodeActions are sorted by type, #38623', async function () { const provider = new class implements CodeActionProvider { provideCodeActions() { - return [{ - title: 'Testing1', - diagnostics: [{ - startLineNumber: 1, - startColumn: 1, - endLineNumber: 2, - endColumn: 1, - severity: Severity.Error, - message: 'some error' - }] - }, { - title: 'Testing2' - }]; + return [ + testData.command.abc, + testData.diagnostics.bcd, + testData.spelling.bcd, + testData.tsLint.bcd, + testData.tsLint.abc, + testData.diagnostics.abc + ]; } }; disposables.push(CodeActionProviderRegistry.register('fooLang', provider)); - const actions = await getCodeActions(model, new Range(1, 1, 2, 1)); - assert.equal(actions.length, 2); - }); + const expected = [ + // CodeActions with a diagnostics array are shown first ordered by diagnostics.message + testData.diagnostics.abc, + testData.diagnostics.bcd, + // CodeActions without diagnostics are shown in the given order without any further sorting + testData.command.abc, + testData.spelling.bcd, // empty diagnostics array + testData.tsLint.bcd, + testData.tsLint.abc + ]; + + const actions = await getCodeActions(model, new Range(1, 1, 2, 1)); + assert.equal(actions.length, 6); + assert.deepEqual(actions, expected); + }); }); diff --git a/src/vs/editor/contrib/quickFix/test/quickFixModel.test.ts b/src/vs/editor/contrib/quickFix/test/quickFixModel.test.ts index 0631075c94a..f161a0f47f9 100644 --- a/src/vs/editor/contrib/quickFix/test/quickFixModel.test.ts +++ b/src/vs/editor/contrib/quickFix/test/quickFixModel.test.ts @@ -80,7 +80,7 @@ suite('QuickFix', () => { editor.setPosition({ lineNumber: 2, column: 1 }); - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { const oracle = new QuickFixOracle(editor, markerService, e => { assert.equal(e.type, 'auto'); @@ -157,7 +157,7 @@ suite('QuickFix', () => { }]); // case 1 - drag selection over multiple lines -> range of enclosed marker, position or marker - await new TPromise(resolve => { + await new Promise(resolve => { let oracle = new QuickFixOracle(editor, markerService, e => { assert.equal(e.type, 'auto'); diff --git a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts index a1b758e7844..83f8022a4f6 100644 --- a/src/vs/editor/contrib/referenceSearch/referencesWidget.ts +++ b/src/vs/editor/contrib/referenceSearch/referencesWidget.ts @@ -29,7 +29,7 @@ import { IInstantiationService, optional } from 'vs/platform/instantiation/commo import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { Range, IRange } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { Model } from 'vs/editor/common/model/model'; +import { Model, ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { PeekViewWidget } from './peekViewWidget'; @@ -40,7 +40,6 @@ import { registerThemingParticipant, ITheme, IThemeService } from 'vs/platform/t import { attachListStyler, attachBadgeStyler } from 'vs/platform/theme/common/styler'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; import URI from 'vs/base/common/uri'; class DecorationsManager implements IDisposable { diff --git a/src/vs/editor/contrib/smartSelect/tokenTree.ts b/src/vs/editor/contrib/smartSelect/tokenTree.ts index 2f59884b34c..de793890ce0 100644 --- a/src/vs/editor/contrib/smartSelect/tokenTree.ts +++ b/src/vs/editor/contrib/smartSelect/tokenTree.ts @@ -7,7 +7,7 @@ import { Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IModel } from 'vs/editor/common/editorCommon'; -import { LineToken } from 'vs/editor/common/core/lineTokens'; +import { LineTokens } from 'vs/editor/common/core/lineTokens'; import { ignoreBracketsInToken } from 'vs/editor/common/modes/supports'; import { BracketsUtils, RichEditBrackets } from 'vs/editor/common/modes/supports/richEditBrackets'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; @@ -132,13 +132,13 @@ class RawToken { public type: StandardTokenType; public languageId: LanguageId; - constructor(source: LineToken, lineNumber: number, lineText: string) { + constructor(source: LineTokens, tokenIndex: number, lineNumber: number) { this.lineNumber = lineNumber; - this.lineText = lineText; - this.startOffset = source.startOffset; - this.endOffset = source.endOffset; - this.type = source.tokenType; - this.languageId = source.languageId; + this.lineText = source.getLineContent(); + this.startOffset = source.getStartOffset(tokenIndex); + this.endOffset = source.getEndOffset(tokenIndex); + this.type = source.getStandardTokenType(tokenIndex); + this.languageId = source.getLanguageId(tokenIndex); } } @@ -148,38 +148,48 @@ class ModelRawTokenScanner { private _lineCount: number; private _versionId: number; private _lineNumber: number; - private _lineText: string; - private _next: LineToken; + private _tokenIndex: number; + private _lineTokens: LineTokens; constructor(model: IModel) { this._model = model; this._lineCount = this._model.getLineCount(); this._versionId = this._model.getVersionId(); this._lineNumber = 0; - this._lineText = null; + this._tokenIndex = 0; + this._lineTokens = null; this._advance(); } private _advance(): void { - this._next = (this._next ? this._next.next() : null); - while (!this._next && this._lineNumber < this._lineCount) { + if (this._lineTokens) { + this._tokenIndex++; + if (this._tokenIndex >= this._lineTokens.getCount()) { + this._lineTokens = null; + } + } + + while (this._lineNumber < this._lineCount && !this._lineTokens) { this._lineNumber++; - this._lineText = this._model.getLineContent(this._lineNumber); this._model.forceTokenization(this._lineNumber); - let currentLineTokens = this._model.getLineTokens(this._lineNumber); - this._next = currentLineTokens.firstToken(); + this._lineTokens = this._model.getLineTokens(this._lineNumber); + this._tokenIndex = 0; + if (this._lineTokens.getCount() === 0) { + // Skip empty lines + this._lineTokens = null; + } } } public next(): RawToken { - if (!this._next) { + if (!this._lineTokens) { return null; } if (this._model.getVersionId() !== this._versionId) { return null; } - let result = new RawToken(this._next, this._lineNumber, this._lineText); + let result = new RawToken(this._lineTokens, this._tokenIndex, this._lineNumber); this._advance(); return result; } diff --git a/src/vs/editor/contrib/snippet/snippetSession.ts b/src/vs/editor/contrib/snippet/snippetSession.ts index a588b76930d..987c10c4278 100644 --- a/src/vs/editor/contrib/snippet/snippetSession.ts +++ b/src/vs/editor/contrib/snippet/snippetSession.ts @@ -15,9 +15,11 @@ import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; import { groupBy } from 'vs/base/common/arrays'; import { dispose } from 'vs/base/common/lifecycle'; -import { EditorSnippetVariableResolver } from './snippetVariables'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver } from './snippetVariables'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { optional } from 'vs/platform/instantiation/common/instantiation'; export class OneSnippet { @@ -269,6 +271,9 @@ export class SnippetSession { const edits: IIdentifiedSingleEditOperation[] = []; const snippets: OneSnippet[] = []; + const modelBasedVariableResolver = new ModelBasedVariableResolver(model); + const clipboardService = editor.invokeWithinContext(accessor => accessor.get(IClipboardService, optional)); + let delta = 0; // know what text the overwrite[Before|After] extensions @@ -281,11 +286,11 @@ export class SnippetSession { // the original index. that allows you to create correct // offset-based selection logic without changing the // primary selection - const indexedSelection = editor.getSelections() + const indexedSelections = editor.getSelections() .map((selection, idx) => ({ selection, idx })) .sort((a, b) => Range.compareRangesUsingStarts(a.selection, b.selection)); - for (const { selection, idx } of indexedSelection) { + for (const { selection, idx } of indexedSelections) { // extend selection with the `overwriteBefore` and `overwriteAfter` and then // compare if this matches the extensions of the primary selection @@ -310,7 +315,11 @@ export class SnippetSession { const snippet = new SnippetParser() .parse(adjustedTemplate, true, enforceFinalTabstop) - .resolveVariables(new EditorSnippetVariableResolver(model, selection)); + .resolveVariables(new CompositeSnippetVariableResolver([ + modelBasedVariableResolver, + new ClipboardBasedVariableResolver(clipboardService, idx, indexedSelections.length), + new SelectionBasedVariableResolver(model, selection) + ])); const offset = model.getOffsetAt(start) + delta; delta += snippet.toString().length - model.getValueLengthInRange(snippetSelection); @@ -386,11 +395,13 @@ export class SnippetSession { next(): void { const newSelections = this._move(true); this._editor.setSelections(newSelections); + this._editor.revealPositionInCenterIfOutsideViewport(newSelections[0].getPosition()); } prev(): void { const newSelections = this._move(false); this._editor.setSelections(newSelections); + this._editor.revealPositionInCenterIfOutsideViewport(newSelections[0].getPosition()); } private _move(fwd: boolean | undefined): Selection[] { diff --git a/src/vs/editor/contrib/snippet/snippetVariables.ts b/src/vs/editor/contrib/snippet/snippetVariables.ts index d9b0703d07b..eac5593f75a 100644 --- a/src/vs/editor/contrib/snippet/snippetVariables.ts +++ b/src/vs/editor/contrib/snippet/snippetVariables.ts @@ -9,22 +9,41 @@ import { basename, dirname } from 'vs/base/common/paths'; import { IModel } from 'vs/editor/common/editorCommon'; import { Selection } from 'vs/editor/common/core/selection'; import { VariableResolver, Variable, Text } from 'vs/editor/contrib/snippet/snippetParser'; -import { getLeadingWhitespace, commonPrefixLength } from 'vs/base/common/strings'; +import { getLeadingWhitespace, commonPrefixLength, isFalsyOrWhitespace } from 'vs/base/common/strings'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; -export class EditorSnippetVariableResolver implements VariableResolver { +export const KnownSnippetVariableNames = Object.freeze({ + 'SELECTION': true, + 'CLIPBOARD': true, + 'TM_SELECTED_TEXT': true, + 'TM_CURRENT_LINE': true, + 'TM_CURRENT_WORD': true, + 'TM_LINE_INDEX': true, + 'TM_LINE_NUMBER': true, + 'TM_FILENAME': true, + 'TM_FILENAME_BASE': true, + 'TM_DIRECTORY': true, + 'TM_FILEPATH': true, +}); - static readonly VariableNames = Object.freeze({ - 'SELECTION': true, - 'TM_SELECTED_TEXT': true, - 'TM_CURRENT_LINE': true, - 'TM_CURRENT_WORD': true, - 'TM_LINE_INDEX': true, - 'TM_LINE_NUMBER': true, - 'TM_FILENAME': true, - 'TM_FILENAME_BASE': true, - 'TM_DIRECTORY': true, - 'TM_FILEPATH': true, - }); +export class CompositeSnippetVariableResolver implements VariableResolver { + + constructor(private readonly _delegates: VariableResolver[]) { + // + } + + resolve(variable: Variable): string { + for (const delegate of this._delegates) { + let value = delegate.resolve(variable); + if (value !== void 0) { + return value; + } + } + return undefined; + } +} + +export class SelectionBasedVariableResolver implements VariableResolver { constructor( private readonly _model: IModel, @@ -82,8 +101,24 @@ export class EditorSnippetVariableResolver implements VariableResolver { } else if (name === 'TM_LINE_NUMBER') { return String(this._selection.positionLineNumber); + } + return undefined; + } +} - } else if (name === 'TM_FILENAME') { +export class ModelBasedVariableResolver implements VariableResolver { + + constructor( + private readonly _model: IModel + ) { + // + } + + resolve(variable: Variable): string { + + const { name } = variable; + + if (name === 'TM_FILENAME') { return basename(this._model.uri.fsPath); } else if (name === 'TM_FILENAME_BASE') { @@ -101,9 +136,37 @@ export class EditorSnippetVariableResolver implements VariableResolver { } else if (name === 'TM_FILEPATH') { return this._model.uri.fsPath; + } - } else { + return undefined; + } +} + +export class ClipboardBasedVariableResolver implements VariableResolver { + + constructor( + private readonly _clipboardService: IClipboardService, + private readonly _selectionIdx: number, + private readonly _selectionCount: number + ) { + // + } + + resolve(variable: Variable): string { + if (variable.name !== 'CLIPBOARD' || !this._clipboardService) { return undefined; } + + const text = this._clipboardService.readText(); + if (!text) { + return undefined; + } + + const lines = text.split(/\r\n|\n|\r/).filter(s => !isFalsyOrWhitespace(s)); + if (lines.length === this._selectionCount) { + return lines[this._selectionIdx]; + } else { + return text; + } } } diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts index 4ad7e0dec05..6979ff09551 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.old.test.ts @@ -12,7 +12,7 @@ import { TestCodeEditor, withTestCodeEditor } from 'vs/editor/test/browser/testC import { Cursor } from 'vs/editor/common/controller/cursor'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; class TestSnippetController extends SnippetController2 { @@ -20,7 +20,7 @@ class TestSnippetController extends SnippetController2 { editor: ICodeEditor, @IContextKeyService private _contextKeyService: IContextKeyService ) { - super(editor, new NoopLogService(), _contextKeyService); + super(editor, new NullLogService(), _contextKeyService); } isInSnippetMode(): boolean { diff --git a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts index 638047b4f7c..2145e74537e 100644 --- a/src/vs/editor/contrib/snippet/test/snippetController2.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetController2.test.ts @@ -11,7 +11,7 @@ import { createTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { Model } from 'vs/editor/common/model/model'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('SnippetController2', function () { @@ -32,7 +32,7 @@ suite('SnippetController2', function () { let editor: ICodeEditor; let model: Model; let contextKeys: MockContextKeyService; - let logService = new NoopLogService(); + let logService = new NullLogService(); setup(function () { contextKeys = new MockContextKeyService(); diff --git a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts index 991b5a97e28..f98043d0c20 100644 --- a/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts +++ b/src/vs/editor/contrib/snippet/test/snippetVariables.test.ts @@ -8,14 +8,15 @@ import * as assert from 'assert'; import { isWindows } from 'vs/base/common/platform'; import URI from 'vs/base/common/uri'; import { Selection } from 'vs/editor/common/core/selection'; -import { EditorSnippetVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; -import { SnippetParser, Variable } from 'vs/editor/contrib/snippet/snippetParser'; +import { SelectionBasedVariableResolver, CompositeSnippetVariableResolver, ModelBasedVariableResolver, ClipboardBasedVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; +import { SnippetParser, Variable, VariableResolver } from 'vs/editor/contrib/snippet/snippetParser'; import { Model } from 'vs/editor/common/model/model'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; suite('Snippet Variables Resolver', function () { let model: Model; - let resolver: EditorSnippetVariableResolver; + let resolver: VariableResolver; setup(function () { model = Model.createFromString([ @@ -24,14 +25,17 @@ suite('Snippet Variables Resolver', function () { ' this is line three' ].join('\n'), undefined, undefined, URI.parse('file:///foo/files/text.txt')); - resolver = new EditorSnippetVariableResolver(model, new Selection(1, 1, 1, 1)); + resolver = new CompositeSnippetVariableResolver([ + new ModelBasedVariableResolver(model), + new SelectionBasedVariableResolver(model, new Selection(1, 1, 1, 1)), + ]); }); teardown(function () { model.dispose(); }); - function assertVariableResolve(resolver: EditorSnippetVariableResolver, varName: string, expected: string) { + function assertVariableResolve(resolver: VariableResolver, varName: string, expected: string) { const snippet = new SnippetParser().parse(`$${varName}`); const variable = snippet.children[0]; variable.resolve(resolver); @@ -55,9 +59,8 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'TM_FILEPATH', '/foo/files/text.txt'); } - resolver = new EditorSnippetVariableResolver( - Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')), - new Selection(1, 1, 1, 1) + resolver = new ModelBasedVariableResolver( + Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME', 'ghi'); if (!isWindows) { @@ -65,9 +68,8 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'TM_FILEPATH', '/abc/def/ghi'); } - resolver = new EditorSnippetVariableResolver( - Model.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')), - new Selection(1, 1, 1, 1) + resolver = new ModelBasedVariableResolver( + Model.createFromString('', undefined, undefined, URI.parse('mem:fff.ts')) ); assertVariableResolve(resolver, 'TM_DIRECTORY', ''); assertVariableResolve(resolver, 'TM_FILEPATH', 'fff.ts'); @@ -76,24 +78,24 @@ suite('Snippet Variables Resolver', function () { test('editor variables, selection', function () { - resolver = new EditorSnippetVariableResolver(model, new Selection(1, 2, 2, 3)); + resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 2, 3)); assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth'); assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line two'); assertVariableResolve(resolver, 'TM_LINE_INDEX', '1'); assertVariableResolve(resolver, 'TM_LINE_NUMBER', '2'); - resolver = new EditorSnippetVariableResolver(model, new Selection(2, 3, 1, 2)); + resolver = new SelectionBasedVariableResolver(model, new Selection(2, 3, 1, 2)); assertVariableResolve(resolver, 'TM_SELECTED_TEXT', 'his is line one\nth'); assertVariableResolve(resolver, 'TM_CURRENT_LINE', 'this is line one'); assertVariableResolve(resolver, 'TM_LINE_INDEX', '0'); assertVariableResolve(resolver, 'TM_LINE_NUMBER', '1'); - resolver = new EditorSnippetVariableResolver(model, new Selection(1, 2, 1, 2)); + resolver = new SelectionBasedVariableResolver(model, new Selection(1, 2, 1, 2)); assertVariableResolve(resolver, 'TM_SELECTED_TEXT', undefined); assertVariableResolve(resolver, 'TM_CURRENT_WORD', 'this'); - resolver = new EditorSnippetVariableResolver(model, new Selection(3, 1, 3, 1)); + resolver = new SelectionBasedVariableResolver(model, new Selection(3, 1, 3, 1)); assertVariableResolve(resolver, 'TM_CURRENT_WORD', undefined); }); @@ -117,21 +119,18 @@ suite('Snippet Variables Resolver', function () { assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'text'); - resolver = new EditorSnippetVariableResolver( - Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')), - new Selection(1, 1, 1, 1) + resolver = new ModelBasedVariableResolver( + Model.createFromString('', undefined, undefined, URI.parse('http://www.pb.o/abc/def/ghi')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'ghi'); - resolver = new EditorSnippetVariableResolver( - Model.createFromString('', undefined, undefined, URI.parse('mem:.git')), - new Selection(1, 1, 1, 1) + resolver = new ModelBasedVariableResolver( + Model.createFromString('', undefined, undefined, URI.parse('mem:.git')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', '.git'); - resolver = new EditorSnippetVariableResolver( - Model.createFromString('', undefined, undefined, URI.parse('mem:foo.')), - new Selection(1, 1, 1, 1) + resolver = new ModelBasedVariableResolver( + Model.createFromString('', undefined, undefined, URI.parse('mem:foo.')) ); assertVariableResolve(resolver, 'TM_FILENAME_BASE', 'foo'); }); @@ -208,4 +207,58 @@ suite('Snippet Variables Resolver', function () { ); }); + test('Add variable to insert value from clipboard to a snippet #40153', function () { + + let readTextResult: string; + const clipboardService = new class implements IClipboardService { + _serviceBrand: any; + readText(): string { return readTextResult; } + _throw = () => { throw new Error(); }; + writeText = this._throw; + readFindText = this._throw; + writeFindText = this._throw; + }; + let resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 0); + + readTextResult = undefined; + assertVariableResolve(resolver, 'CLIPBOARD', undefined); + + readTextResult = null; + assertVariableResolve(resolver, 'CLIPBOARD', undefined); + + readTextResult = ''; + assertVariableResolve(resolver, 'CLIPBOARD', undefined); + + readTextResult = 'foo'; + assertVariableResolve(resolver, 'CLIPBOARD', 'foo'); + + assertVariableResolve(resolver, 'foo', undefined); + assertVariableResolve(resolver, 'cLIPBOARD', undefined); + }); + + test('Add variable to insert value from clipboard to a snippet #40153', function () { + + let readTextResult: string; + let resolver: VariableResolver; + const clipboardService = new class implements IClipboardService { + _serviceBrand: any; + readText(): string { return readTextResult; } + _throw = () => { throw new Error(); }; + writeText = this._throw; + readFindText = this._throw; + writeFindText = this._throw; + }; + + resolver = new ClipboardBasedVariableResolver(clipboardService, 1, 2); + readTextResult = 'line1'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line1'); + readTextResult = 'line1\nline2\nline3'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line1\nline2\nline3'); + + readTextResult = 'line1\nline2'; + assertVariableResolve(resolver, 'CLIPBOARD', 'line2'); + readTextResult = 'line1\nline2'; + resolver = new ClipboardBasedVariableResolver(clipboardService, 0, 2); + assertVariableResolve(resolver, 'CLIPBOARD', 'line1'); + }); }); diff --git a/src/vs/editor/contrib/suggest/suggest.ts b/src/vs/editor/contrib/suggest/suggest.ts index 8723f674e84..042c3762292 100644 --- a/src/vs/editor/contrib/suggest/suggest.ts +++ b/src/vs/editor/contrib/suggest/suggest.ts @@ -233,11 +233,6 @@ let _provider = new class implements ISuggestSupport { SuggestRegistry.register('*', _provider); -/** - * - * @param editor - * @param suggestions - */ export function showSimpleSuggestions(editor: ICodeEditor, suggestions: ISuggestion[]) { setTimeout(() => { _suggestions = suggestions; diff --git a/src/vs/editor/contrib/suggest/suggestController.ts b/src/vs/editor/contrib/suggest/suggestController.ts index ac65ff1abd4..7d7013aaa1a 100644 --- a/src/vs/editor/contrib/suggest/suggestController.ts +++ b/src/vs/editor/contrib/suggest/suggestController.ts @@ -193,7 +193,7 @@ export class SuggestController implements IEditorContribution { } } - private _onDidSelectItem(item: ICompletionItem): void { + protected _onDidSelectItem(item: ICompletionItem): void { if (!item) { this._model.cancel(); return; diff --git a/src/vs/editor/contrib/suggest/suggestModel.ts b/src/vs/editor/contrib/suggest/suggestModel.ts index fd804919392..18afdacfcc1 100644 --- a/src/vs/editor/contrib/suggest/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/suggestModel.ts @@ -59,18 +59,6 @@ export class LineContext { return true; } - static isInEditableRange(editor: ICodeEditor): boolean { - const model = editor.getModel(); - const position = editor.getPosition(); - if (model.hasEditableRange()) { - const editableRange = model.getEditableRange(); - if (!editableRange.containsPosition(position)) { - return false; - } - } - return true; - } - readonly lineNumber: number; readonly column: number; readonly leadingLineContent: string; @@ -289,9 +277,9 @@ export class SuggestModel implements IDisposable { this.cancel(); - if (LineContext.shouldAutoTrigger(this._editor)) { - this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay); - this._triggerAutoSuggestPromise.then(() => { + this._triggerAutoSuggestPromise = TPromise.timeout(this._quickSuggestDelay); + this._triggerAutoSuggestPromise.then(() => { + if (LineContext.shouldAutoTrigger(this._editor)) { const model = this._editor.getModel(); const pos = this._editor.getPosition(); @@ -307,9 +295,8 @@ export class SuggestModel implements IDisposable { } else { // Check the type of the token that triggered this model.tokenizeIfCheap(pos.lineNumber); - const { tokenType } = model - .getLineTokens(pos.lineNumber) - .findTokenAtOffset(Math.max(pos.column - 1 - 1, 0)); + const lineTokens = model.getLineTokens(pos.lineNumber); + const tokenType = lineTokens.getStandardTokenType(lineTokens.findTokenIndexAtOffset(Math.max(pos.column - 1 - 1, 0))); const inValidScope = quickSuggestions.other && tokenType === StandardTokenType.Other || quickSuggestions.comments && tokenType === StandardTokenType.Comment || quickSuggestions.strings && tokenType === StandardTokenType.String; @@ -319,10 +306,10 @@ export class SuggestModel implements IDisposable { } } - this._triggerAutoSuggestPromise = null; this.trigger({ auto: true }); - }); - } + } + this._triggerAutoSuggestPromise = null; + }); } } } @@ -352,10 +339,6 @@ export class SuggestModel implements IDisposable { const auto = context.auto; const ctx = new LineContext(model, this._editor.getPosition(), auto); - if (!LineContext.isInEditableRange(this._editor)) { - return; - } - // Cancel previous requests, change state & update UI this.cancel(retrigger); this._state = auto ? State.Auto : State.Manual; diff --git a/src/vs/editor/contrib/suggest/suggestWidget.ts b/src/vs/editor/contrib/suggest/suggestWidget.ts index 8755293fa81..1742ab97781 100644 --- a/src/vs/editor/contrib/suggest/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/suggestWidget.ts @@ -34,7 +34,7 @@ import { MarkdownRenderer } from 'vs/editor/contrib/markdown/markdownRenderer'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IOpenerService } from 'vs/platform/opener/common/opener'; -const sticky = true; // for development purposes +const sticky = false; // for development purposes const expandSuggestionDocsByDefault = false; const maxSuggestionsToShow = 12; diff --git a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts index 264040790bb..b8a9b9d0079 100644 --- a/src/vs/editor/contrib/suggest/test/suggestModel.test.ts +++ b/src/vs/editor/contrib/suggest/test/suggestModel.test.ts @@ -8,7 +8,6 @@ import * as assert from 'assert'; import Event from 'vs/base/common/event'; import URI from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { TPromise } from 'vs/base/common/winjs.base'; import { Model } from 'vs/editor/common/model/model'; import { Handler } from 'vs/editor/common/editorCommon'; import { ISuggestSupport, ISuggestResult, SuggestRegistry, SuggestTriggerKind } from 'vs/editor/common/modes'; @@ -22,15 +21,18 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { Range } from 'vs/editor/common/core/range'; -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; +import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; +import { IStorageService, NullStorageService } from 'vs/platform/storage/common/storage'; +import { SnippetController2 } from 'vs/editor/contrib/snippet/snippetController2'; function createMockEditor(model: Model): TestCodeEditor { const contextKeyService = new MockContextKeyService(); const telemetryService = NullTelemetryService; const instantiationService = new InstantiationService(new ServiceCollection( [IContextKeyService, contextKeyService], - [ITelemetryService, telemetryService] + [ITelemetryService, telemetryService], + [IStorageService, NullStorageService] )); const editor = new TestCodeEditor(new MockScopeLocation(), {}, instantiationService, contextKeyService); @@ -102,9 +104,9 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { disposables.push(model); }); - function withOracle(callback: (model: SuggestModel, editor: ICodeEditor) => any): TPromise { + function withOracle(callback: (model: SuggestModel, editor: TestCodeEditor) => any): Promise { - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { const editor = createMockEditor(model); const oracle = new SuggestModel(editor); disposables.push(oracle, editor); @@ -118,7 +120,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { } function assertEvent(event: Event, action: () => any, assert: (e: E) => any) { - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { const sub = event(e => { sub.dispose(); try { @@ -138,7 +140,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { test('events - cancel/trigger', function () { return withOracle(model => { - return TPromise.join([ + return Promise.all([ assertEvent(model.onDidCancel, function () { model.cancel(); }, function (event) { @@ -185,7 +187,7 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { disposables.push(SuggestRegistry.register({ scheme: 'test' }, alwaysEmptySupport)); return withOracle(model => { - return TPromise.join([ + return Promise.all([ assertEvent(model.onDidCancel, function () { model.trigger({ auto: true }); }, function (event) { @@ -564,4 +566,53 @@ suite('SuggestModel - TriggerAndCancelOracle', function () { }); }); }); + + test('Text changes for completion CodeAction are affected by the completion #39893', function () { + disposables.push(SuggestRegistry.register({ scheme: 'test' }, { + provideCompletionItems(doc, pos): ISuggestResult { + return { + incomplete: true, + suggestions: [{ + label: 'bar', + type: 'property', + insertText: 'bar', + overwriteBefore: 2, + additionalTextEdits: [{ + text: ', bar', + range: { startLineNumber: 1, endLineNumber: 1, startColumn: 17, endColumn: 17 } + }] + }] + }; + } + })); + + model.setValue('ba; import { foo } from "./b"'); + + return withOracle(async (sugget, editor) => { + class TestCtrl extends SuggestController { + _onDidSelectItem(item) { + super._onDidSelectItem(item); + } + } + const ctrl = editor.registerAndInstantiateContribution(TestCtrl); + editor.registerAndInstantiateContribution(SnippetController2); + + await assertEvent(sugget.onDidSuggest, () => { + editor.setPosition({ lineNumber: 1, column: 3 }); + sugget.trigger({ auto: false }); + }, event => { + + assert.equal(event.completionModel.items.length, 1); + const [first] = event.completionModel.items; + assert.equal(first.suggestion.label, 'bar'); + + ctrl._onDidSelectItem(first); + }); + + assert.equal( + model.getValue(), + 'bar; import { foo, bar } from "./b"' + ); + }); + }); }); diff --git a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts index 9f31985a37e..507c87ec242 100644 --- a/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/wordHighlighter.ts @@ -18,7 +18,7 @@ import { Position } from 'vs/editor/common/core/position'; import { registerColor, editorSelectionHighlight, activeContrastBorder, overviewRulerSelectionHighlightForeground } from 'vs/platform/theme/common/colorRegistry'; import { registerThemingParticipant, themeColorFromId } from 'vs/platform/theme/common/themeService'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; diff --git a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts index a2a990f46e8..f044224c653 100644 --- a/src/vs/editor/contrib/zoneWidget/zoneWidget.ts +++ b/src/vs/editor/contrib/zoneWidget/zoneWidget.ts @@ -15,7 +15,7 @@ import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, IViewZo import { Color, RGBA } from 'vs/base/common/color'; import { EditorLayoutInfo } from 'vs/editor/common/config/editorOptions'; import { Position, IPosition } from 'vs/editor/common/core/position'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { IdGenerator } from 'vs/base/common/idGenerator'; import { ScrollType, TrackedRangeStickiness } from 'vs/editor/common/editorCommon'; diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index c8fa5477679..02d37470343 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -10,8 +10,7 @@ import { IModel } from 'vs/editor/common/editorCommon'; import { ColorId, MetadataConsts, FontStyle, TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; import { renderViewLine2 as renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; -import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { LineTokens, IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import * as strings from 'vs/base/common/strings'; import { IStandaloneThemeService } from 'vs/editor/standalone/common/standaloneThemeService'; @@ -94,7 +93,7 @@ export class Colorizer { }); } - public static colorizeLine(line: string, mightContainRTL: boolean, tokens: ViewLineToken[], tabSize: number = 4): string { + public static colorizeLine(line: string, mightContainRTL: boolean, tokens: IViewLineTokens, tabSize: number = 4): string { let renderResult = renderViewLine(new RenderLineInput( false, line, @@ -134,15 +133,22 @@ function _fakeColorize(lines: string[], tabSize: number): string { | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) ) >>> 0; + const tokens = new Uint32Array(2); + tokens[0] = 0; + tokens[1] = defaultMetadata; + for (let i = 0, length = lines.length; i < length; i++) { let line = lines[i]; + tokens[0] = line.length; + const lineTokens = new LineTokens(tokens, line); + let renderResult = renderViewLine(new RenderLineInput( false, line, false, 0, - [new ViewLineToken(line.length, defaultMetadata)], + lineTokens, [], tabSize, 0, @@ -166,6 +172,7 @@ function _actualColorize(lines: string[], tabSize: number, tokenizationSupport: for (let i = 0, length = lines.length; i < length; i++) { let line = lines[i]; let tokenizeResult = tokenizationSupport.tokenize2(line, state, 0); + LineTokens.convertToEndOffset(tokenizeResult.tokens, line.length); let lineTokens = new LineTokens(tokenizeResult.tokens, line); let renderResult = renderViewLine(new RenderLineInput( false, diff --git a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts index 34154c4a710..39fe16f538d 100644 --- a/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts +++ b/src/vs/editor/standalone/browser/quickOpen/editorQuickOpen.ts @@ -13,7 +13,7 @@ import { Selection } from 'vs/editor/common/core/selection'; import { registerEditorContribution, IActionOptions, EditorAction } from 'vs/editor/browser/editorExtensions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Range } from 'vs/editor/common/core/range'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; export interface IQuickOpenControllerOpts { inputAriaLabel: string; diff --git a/src/vs/editor/standalone/browser/simpleServices.ts b/src/vs/editor/standalone/browser/simpleServices.ts index 44dd3f9f46f..45c83bd1d5c 100644 --- a/src/vs/editor/standalone/browser/simpleServices.ts +++ b/src/vs/editor/standalone/browser/simpleServices.ts @@ -260,17 +260,22 @@ export class SimpleMessageService implements IMessageService { // No-op } - public confirm(confirmation: IConfirmation): boolean { + public confirm(confirmation: IConfirmation): TPromise { let messageText = confirmation.message; if (confirmation.detail) { messageText = messageText + '\n\n' + confirmation.detail; } - return window.confirm(messageText); + return TPromise.wrap(window.confirm(messageText)); } public confirmWithCheckbox(confirmation: IConfirmation): TPromise { - return TPromise.as({ confirmed: this.confirm(confirmation), checkboxChecked: false /* unsupported */ } as IConfirmationResult); + return this.confirm(confirmation).then(confirmed => { + return { + confirmed, + checkboxChecked: false // unsupported + } as IConfirmationResult; + }); } } diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts index d33f5cf7360..53bec7bd137 100644 --- a/src/vs/editor/test/browser/controller/cursor.test.ts +++ b/src/vs/editor/test/browser/controller/cursor.test.ts @@ -1418,6 +1418,78 @@ suite('Editor Controller - Regression tests', () => { }); }); + test('issue #4996: Multiple cursor paste pastes contents of all cursors', () => { + usingCursor({ + text: [ + 'line1', + 'line2', + 'line3' + ], + }, (model, cursor) => { + cursor.setSelections('test', [new Selection(1, 1, 1, 1), new Selection(2, 1, 2, 1)]); + + cursorCommand(cursor, H.Paste, { + text: 'a\nb\nc\nd', + pasteOnNewLine: false, + multicursorText: [ + 'a\nb', + 'c\nd' + ] + }); + + assert.equal(model.getValue(), [ + 'a', + 'bline1', + 'c', + 'dline2', + 'line3' + ].join('\n')); + }); + }); + + test('issue #16155: Paste into multiple cursors has edge case when number of lines equals number of cursors - 1', () => { + usingCursor({ + text: [ + 'test', + 'test', + 'test', + 'test' + ], + }, (model, cursor) => { + cursor.setSelections('test', [ + new Selection(1, 1, 1, 5), + new Selection(2, 1, 2, 5), + new Selection(3, 1, 3, 5), + new Selection(4, 1, 4, 5), + ]); + + cursorCommand(cursor, H.Paste, { + text: 'aaa\nbbb\nccc\n', + pasteOnNewLine: false, + multicursorText: null + }); + + assert.equal(model.getValue(), [ + 'aaa', + 'bbb', + 'ccc', + '', + 'aaa', + 'bbb', + 'ccc', + '', + 'aaa', + 'bbb', + 'ccc', + '', + 'aaa', + 'bbb', + 'ccc', + '', + ].join('\n')); + }); + }); + test('issue #3071: Investigate why undo stack gets corrupted', () => { let model = Model.createFromString( [ diff --git a/src/vs/editor/test/common/core/lineTokens.test.ts b/src/vs/editor/test/common/core/lineTokens.test.ts index c18b585a953..b4f6b651677 100644 --- a/src/vs/editor/test/common/core/lineTokens.test.ts +++ b/src/vs/editor/test/common/core/lineTokens.test.ts @@ -6,9 +6,8 @@ 'use strict'; import * as assert from 'assert'; -import { LineTokens } from 'vs/editor/common/core/lineTokens'; +import { LineTokens, IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { MetadataConsts } from 'vs/editor/common/modes'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; suite('LineTokens', () => { @@ -21,10 +20,9 @@ suite('LineTokens', () => { let binTokens = new Uint32Array(tokens.length << 1); for (let i = 0, len = tokens.length; i < len; i++) { - let token = tokens[i]; - binTokens[(i << 1)] = token.startIndex; + binTokens[(i << 1)] = (i + 1 < len ? tokens[i + 1].startIndex : text.length); binTokens[(i << 1) + 1] = ( - token.foreground << MetadataConsts.FOREGROUND_OFFSET + tokens[i].foreground << MetadataConsts.FOREGROUND_OFFSET ) >>> 0; } @@ -51,22 +49,22 @@ suite('LineTokens', () => { assert.equal(lineTokens.getLineContent(), 'Hello world, this is a lovely day'); assert.equal(lineTokens.getLineContent().length, 33); - assert.equal(lineTokens.getTokenCount(), 7); + assert.equal(lineTokens.getCount(), 7); - assert.equal(lineTokens.getTokenStartOffset(0), 0); - assert.equal(lineTokens.getTokenEndOffset(0), 6); - assert.equal(lineTokens.getTokenStartOffset(1), 6); - assert.equal(lineTokens.getTokenEndOffset(1), 13); - assert.equal(lineTokens.getTokenStartOffset(2), 13); - assert.equal(lineTokens.getTokenEndOffset(2), 18); - assert.equal(lineTokens.getTokenStartOffset(3), 18); - assert.equal(lineTokens.getTokenEndOffset(3), 21); - assert.equal(lineTokens.getTokenStartOffset(4), 21); - assert.equal(lineTokens.getTokenEndOffset(4), 23); - assert.equal(lineTokens.getTokenStartOffset(5), 23); - assert.equal(lineTokens.getTokenEndOffset(5), 30); - assert.equal(lineTokens.getTokenStartOffset(6), 30); - assert.equal(lineTokens.getTokenEndOffset(6), 33); + assert.equal(lineTokens.getStartOffset(0), 0); + assert.equal(lineTokens.getEndOffset(0), 6); + assert.equal(lineTokens.getStartOffset(1), 6); + assert.equal(lineTokens.getEndOffset(1), 13); + assert.equal(lineTokens.getStartOffset(2), 13); + assert.equal(lineTokens.getEndOffset(2), 18); + assert.equal(lineTokens.getStartOffset(3), 18); + assert.equal(lineTokens.getEndOffset(3), 21); + assert.equal(lineTokens.getStartOffset(4), 21); + assert.equal(lineTokens.getEndOffset(4), 23); + assert.equal(lineTokens.getStartOffset(5), 23); + assert.equal(lineTokens.getEndOffset(5), 30); + assert.equal(lineTokens.getStartOffset(6), 30); + assert.equal(lineTokens.getEndOffset(6), 33); }); test('findToken', () => { @@ -107,98 +105,6 @@ suite('LineTokens', () => { assert.equal(lineTokens.findTokenIndexAtOffset(32), 6); assert.equal(lineTokens.findTokenIndexAtOffset(33), 6); assert.equal(lineTokens.findTokenIndexAtOffset(34), 6); - - assert.equal(lineTokens.findTokenAtOffset(7).startOffset, 6); - assert.equal(lineTokens.findTokenAtOffset(7).endOffset, 13); - assert.equal(lineTokens.findTokenAtOffset(7).foregroundId, 2); - - assert.equal(lineTokens.findTokenAtOffset(30).startOffset, 30); - assert.equal(lineTokens.findTokenAtOffset(30).endOffset, 33); - assert.equal(lineTokens.findTokenAtOffset(30).foregroundId, 7); - }); - - test('iterate forward', () => { - const lineTokens = createTestLineTokens(); - - let token = lineTokens.firstToken(); - assert.equal(token.startOffset, 0); - assert.equal(token.endOffset, 6); - assert.equal(token.foregroundId, 1); - - token = token.next(); - assert.equal(token.startOffset, 6); - assert.equal(token.endOffset, 13); - assert.equal(token.foregroundId, 2); - - token = token.next(); - assert.equal(token.startOffset, 13); - assert.equal(token.endOffset, 18); - assert.equal(token.foregroundId, 3); - - token = token.next(); - assert.equal(token.startOffset, 18); - assert.equal(token.endOffset, 21); - assert.equal(token.foregroundId, 4); - - token = token.next(); - assert.equal(token.startOffset, 21); - assert.equal(token.endOffset, 23); - assert.equal(token.foregroundId, 5); - - token = token.next(); - assert.equal(token.startOffset, 23); - assert.equal(token.endOffset, 30); - assert.equal(token.foregroundId, 6); - - token = token.next(); - assert.equal(token.startOffset, 30); - assert.equal(token.endOffset, 33); - assert.equal(token.foregroundId, 7); - - token = token.next(); - assert.equal(token, null); - }); - - test('iterate backward', () => { - const lineTokens = createTestLineTokens(); - - let token = lineTokens.lastToken(); - assert.equal(token.startOffset, 30); - assert.equal(token.endOffset, 33); - assert.equal(token.foregroundId, 7); - - token = token.prev(); - assert.equal(token.startOffset, 23); - assert.equal(token.endOffset, 30); - assert.equal(token.foregroundId, 6); - - token = token.prev(); - assert.equal(token.startOffset, 21); - assert.equal(token.endOffset, 23); - assert.equal(token.foregroundId, 5); - - token = token.prev(); - assert.equal(token.startOffset, 18); - assert.equal(token.endOffset, 21); - assert.equal(token.foregroundId, 4); - - token = token.prev(); - assert.equal(token.startOffset, 13); - assert.equal(token.endOffset, 18); - assert.equal(token.foregroundId, 3); - - token = token.prev(); - assert.equal(token.startOffset, 6); - assert.equal(token.endOffset, 13); - assert.equal(token.foregroundId, 2); - - token = token.prev(); - assert.equal(token.startOffset, 0); - assert.equal(token.endOffset, 6); - assert.equal(token.foregroundId, 1); - - token = token.prev(); - assert.equal(token, null); }); interface ITestViewLineToken { @@ -206,13 +112,15 @@ suite('LineTokens', () => { foreground: number; } - function assertViewLineTokens(actual: ViewLineToken[], expected: ITestViewLineToken[]): void { - assert.deepEqual(actual.map(token => { - return { - endIndex: token.endIndex, - foreground: token.getForeground() + function assertViewLineTokens(_actual: IViewLineTokens, expected: ITestViewLineToken[]): void { + let actual: ITestViewLineToken[] = []; + for (let i = 0, len = _actual.getCount(); i < len; i++) { + actual[i] = { + endIndex: _actual.getEndOffset(i), + foreground: _actual.getForeground(i) }; - }), expected); + } + assert.deepEqual(actual, expected); } test('inflate', () => { diff --git a/src/vs/editor/common/core/viewLineToken.ts b/src/vs/editor/test/common/core/viewLineToken.ts similarity index 54% rename from src/vs/editor/common/core/viewLineToken.ts rename to src/vs/editor/test/common/core/viewLineToken.ts index 9dc11ad465e..3ca2afaa82f 100644 --- a/src/vs/editor/common/core/viewLineToken.ts +++ b/src/vs/editor/test/common/core/viewLineToken.ts @@ -6,6 +6,7 @@ import { ColorId } from 'vs/editor/common/modes'; import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; /** * A token on a line. @@ -58,14 +59,51 @@ export class ViewLineToken { } } +export class ViewLineTokens implements IViewLineTokens { + + private readonly _actual: ViewLineToken[]; + + constructor(actual: ViewLineToken[]) { + this._actual = actual; + } + + public equals(other: IViewLineTokens): boolean { + if (other instanceof ViewLineTokens) { + return ViewLineToken.equalsArr(this._actual, other._actual); + } + return false; + } + + public getCount(): number { + return this._actual.length; + } + + public getForeground(tokenIndex: number): ColorId { + return this._actual[tokenIndex].getForeground(); + } + + public getEndOffset(tokenIndex: number): number { + return this._actual[tokenIndex].endIndex; + } + + public getClassName(tokenIndex: number): string { + return this._actual[tokenIndex].getType(); + } + + public getInlineStyle(tokenIndex: number, colorMap: string[]): string { + return this._actual[tokenIndex].getInlineStyle(colorMap); + } +} + export class ViewLineTokenFactory { - public static inflateArr(tokens: Uint32Array, lineLength: number): ViewLineToken[] { - let result: ViewLineToken[] = []; + public static inflateArr(tokens: Uint32Array): ViewLineToken[] { + const tokensCount = (tokens.length >>> 1); - for (let i = 0, len = (tokens.length >>> 1); i < len; i++) { - let endOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength); - let metadata = tokens[(i << 1) + 1]; + let result: ViewLineToken[] = new Array(tokensCount); + for (let i = 0; i < tokensCount; i++) { + const endOffset = tokens[i << 1]; + const metadata = tokens[(i << 1) + 1]; result[i] = new ViewLineToken(endOffset, metadata); } @@ -73,46 +111,4 @@ export class ViewLineTokenFactory { return result; } - public static sliceAndInflate(tokens: Uint32Array, startOffset: number, endOffset: number, deltaOffset: number, lineLength: number): ViewLineToken[] { - const tokenIndex = this.findIndexInSegmentsArray(tokens, startOffset); - const maxEndOffset = (endOffset - startOffset + deltaOffset); - let result: ViewLineToken[] = [], resultLen = 0; - - for (let i = tokenIndex, len = (tokens.length >>> 1); i < len; i++) { - let tokenStartOffset = tokens[(i << 1)]; - - if (tokenStartOffset >= endOffset) { - break; - } - - let tokenEndOffset = (i + 1 < len ? tokens[((i + 1) << 1)] : lineLength); - let newEndOffset = Math.min(maxEndOffset, tokenEndOffset - startOffset + deltaOffset); - let metadata = tokens[(i << 1) + 1]; - - result[resultLen++] = new ViewLineToken(newEndOffset, metadata); - } - - return result; - } - - public static findIndexInSegmentsArray(tokens: Uint32Array, desiredIndex: number): number { - - let low = 0; - let high = (tokens.length >>> 1) - 1; - - while (low < high) { - - let mid = low + Math.ceil((high - low) / 2); - - let value = tokens[(mid << 1)]; - - if (value > desiredIndex) { - high = mid - 1; - } else { - low = mid; - } - } - - return low; - } } diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index 54ef2f3a86e..0d521de7eb8 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -55,7 +55,6 @@ function assertDiff(originalLines: string[], modifiedLines: string[], expectedCh var diffComputer = new DiffComputer(originalLines, modifiedLines, { shouldPostProcessCharChanges: shouldPostProcessCharChanges || false, shouldIgnoreTrimWhitespace: shouldIgnoreTrimWhitespace || false, - shouldConsiderTrimWhitespaceInEmptyCase: true, shouldMakePrettyDiff: true }); var changes = diffComputer.computeDiff(); diff --git a/src/vs/editor/test/common/model/editableTextModel.test.ts b/src/vs/editor/test/common/model/editableTextModel.test.ts index 80f08739bda..2c489954f6e 100644 --- a/src/vs/editor/test/common/model/editableTextModel.test.ts +++ b/src/vs/editor/test/common/model/editableTextModel.test.ts @@ -7,16 +7,17 @@ import * as assert from 'assert'; import { Range } from 'vs/editor/common/core/range'; -import { EndOfLineSequence, IIdentifiedSingleEditOperation } from 'vs/editor/common/editorCommon'; -import { EditableTextModel, IValidatedEditOperation } from 'vs/editor/common/model/editableTextModel'; +import { EndOfLineSequence, IIdentifiedSingleEditOperation, DefaultEndOfLine } from 'vs/editor/common/editorCommon'; +import { Model } from 'vs/editor/common/model/model'; import { MirrorModel } from 'vs/editor/common/model/mirrorModel'; import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { RawTextSource } from 'vs/editor/common/model/textSource'; +import { RawTextSource, TextSource } from 'vs/editor/common/model/textSource'; +import { TextBuffer, IValidatedEditOperation } from 'vs/editor/common/model/textBuffer'; -function createEditableTextModelFromString(text: string): EditableTextModel { - return new EditableTextModel(RawTextSource.fromString(text), TextModel.DEFAULT_CREATION_OPTIONS, null); +function createEditableTextModelFromString(text: string): Model { + return new Model(RawTextSource.fromString(text), TextModel.DEFAULT_CREATION_OPTIONS, null); } suite('EditorModel - EditableTextModel._getInverseEdits', () => { @@ -39,7 +40,7 @@ suite('EditorModel - EditableTextModel._getInverseEdits', () => { } function assertInverseEdits(ops: IValidatedEditOperation[], expected: Range[]): void { - var actual = EditableTextModel._getInverseEditRanges(ops); + var actual = TextBuffer._getInverseEditRanges(ops); assert.deepEqual(actual, expected); } @@ -286,13 +287,11 @@ suite('EditorModel - EditableTextModel._toSingleEditOperation', () => { } function testSimpleApplyEdits(original: string[], edits: IValidatedEditOperation[], expected: IValidatedEditOperation): void { - let model = createEditableTextModelFromString(original.join('\n')); - model.setEOL(EndOfLineSequence.LF); + const textSource = TextSource.fromString(original.join('\n'), DefaultEndOfLine.LF); + const textBuffer = new TextBuffer(textSource); - let actual = model._toSingleEditOperation(edits); + const actual = textBuffer._toSingleEditOperation(edits); assert.deepEqual(actual, expected); - - model.dispose(); } test('one edit op is unchanged', () => { diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index b0b18723062..3ec11ae617b 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { EditableTextModel } from 'vs/editor/common/model/editableTextModel'; +import { Model } from 'vs/editor/common/model/model'; import { MirrorModel } from 'vs/editor/common/model/mirrorModel'; import { TextModel } from 'vs/editor/common/model/textModel'; import { Position } from 'vs/editor/common/core/position'; @@ -81,8 +81,8 @@ function assertLineMapping(model: TextModel, msg: string): void { } -export function assertSyncedModels(text: string, callback: (model: EditableTextModel, assertMirrorModels: () => void) => void, setup: (model: EditableTextModel) => void = null): void { - var model = new EditableTextModel(RawTextSource.fromString(text), TextModel.DEFAULT_CREATION_OPTIONS, null); +export function assertSyncedModels(text: string, callback: (model: Model, assertMirrorModels: () => void) => void, setup: (model: Model) => void = null): void { + var model = new Model(RawTextSource.fromString(text), TextModel.DEFAULT_CREATION_OPTIONS, null); model.setEOL(editorCommon.EndOfLineSequence.LF); assertLineMapping(model, 'model'); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index 0cf4d426b33..093b2cf5bae 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -6,20 +6,43 @@ import * as assert from 'assert'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; -import { ModelLine, ILineEdit, computeIndentLevel } from 'vs/editor/common/model/modelLine'; -import { MetadataConsts } from 'vs/editor/common/modes'; -import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/common/core/viewLineToken'; +import { computeIndentLevel } from 'vs/editor/common/model/modelLine'; +import { LanguageIdentifier, MetadataConsts } from 'vs/editor/common/modes'; +import { Range } from 'vs/editor/common/core/range'; +import { ViewLineToken, ViewLineTokenFactory } from 'vs/editor/test/common/core/viewLineToken'; +import { Model } from 'vs/editor/common/model/model'; +import { TextModel } from 'vs/editor/common/model/textModel'; +import { RawTextSource } from 'vs/editor/common/model/textSource'; -function assertLineTokens(_actual: LineTokens, _expected: TestToken[]): void { - let expected = ViewLineTokenFactory.inflateArr(TestToken.toTokens(_expected), _actual.getLineContent().length); - let actual = _actual.inflate(); +interface ILineEdit { + startColumn: number; + endColumn: number; + text: string; +} + +function assertLineTokens(__actual: LineTokens, _expected: TestToken[]): void { + let tmp = TestToken.toTokens(_expected); + LineTokens.convertToEndOffset(tmp, __actual.getLineContent().length); + let expected = ViewLineTokenFactory.inflateArr(tmp); + let _actual = __actual.inflate(); + interface ITestToken { + endIndex: number; + type: string; + } + let actual: ITestToken[] = []; + for (let i = 0, len = _actual.getCount(); i < len; i++) { + actual[i] = { + endIndex: _actual.getEndOffset(i), + type: _actual.getClassName(i) + }; + } let decode = (token: ViewLineToken) => { return { endIndex: token.endIndex, type: token.getType() }; }; - assert.deepEqual(actual.map(decode), expected.map(decode)); + assert.deepEqual(actual, expected.map(decode)); } suite('ModelLine - getIndentLevel', () => { @@ -46,223 +69,6 @@ suite('ModelLine - getIndentLevel', () => { }); }); -suite('Editor Model - modelLine.applyEdits text', () => { - - function testEdits(initial: string, edits: ILineEdit[], expected: string): void { - var line = new ModelLine(initial); - line.applyEdits(edits); - assert.equal(line.text, expected); - } - - function editOp(startColumn: number, endColumn: number, text: string): ILineEdit { - return { - startColumn: startColumn, - endColumn: endColumn, - text: text - }; - } - - test('single insert 1', () => { - testEdits( - '', - [ - editOp(1, 1, 'Hello world') - ], - 'Hello world' - ); - }); - - test('single insert 2', () => { - testEdits( - 'Hworld', - [ - editOp(2, 2, 'ello ') - ], - 'Hello world' - ); - }); - - test('multiple inserts 1', () => { - testEdits( - 'Hw', - [ - editOp(2, 2, 'ello '), - editOp(3, 3, 'orld') - ], - 'Hello world' - ); - }); - - test('multiple inserts 2', () => { - testEdits( - 'Hw,', - [ - editOp(2, 2, 'ello '), - editOp(3, 3, 'orld'), - editOp(4, 4, ' this is H.A.L.') - ], - 'Hello world, this is H.A.L.' - ); - }); - - test('single delete 1', () => { - testEdits( - 'Hello world', - [ - editOp(1, 12, '') - ], - '' - ); - }); - - test('single delete 2', () => { - testEdits( - 'Hello world', - [ - editOp(2, 7, '') - ], - 'Hworld' - ); - }); - - test('multiple deletes 1', () => { - testEdits( - 'Hello world', - [ - editOp(2, 7, ''), - editOp(8, 12, '') - ], - 'Hw' - ); - }); - - test('multiple deletes 2', () => { - testEdits( - 'Hello world, this is H.A.L.', - [ - editOp(2, 7, ''), - editOp(8, 12, ''), - editOp(13, 28, '') - ], - 'Hw,' - ); - }); - - test('single replace 1', () => { - testEdits( - '', - [ - editOp(1, 1, 'Hello world') - ], - 'Hello world' - ); - }); - - test('single replace 2', () => { - testEdits( - 'H1234world', - [ - editOp(2, 6, 'ello ') - ], - 'Hello world' - ); - }); - - test('multiple replace 1', () => { - testEdits( - 'H123w321', - [ - editOp(2, 5, 'ello '), - editOp(6, 9, 'orld') - ], - 'Hello world' - ); - }); - - test('multiple replace 2', () => { - testEdits( - 'H1w12,123', - [ - editOp(2, 3, 'ello '), - editOp(4, 6, 'orld'), - editOp(7, 10, ' this is H.A.L.') - ], - 'Hello world, this is H.A.L.' - ); - }); -}); - -suite('Editor Model - modelLine.split text', () => { - - function testLineSplit(initial: string, splitColumn: number, expected1: string, expected2: string): void { - var line = new ModelLine(initial); - var newLine = line.split(splitColumn); - assert.equal(line.text, expected1); - assert.equal(newLine.text, expected2); - } - - test('split at the beginning', () => { - testLineSplit( - 'qwerty', - 1, - '', - 'qwerty' - ); - }); - - test('split at the end', () => { - testLineSplit( - 'qwerty', - 7, - 'qwerty', - '' - ); - }); - - test('split in the middle', () => { - testLineSplit( - 'qwerty', - 3, - 'qw', - 'erty' - ); - }); -}); - -suite('Editor Model - modelLine.append text', () => { - - function testLineAppend(a: string, b: string, expected: string): void { - var line1 = new ModelLine(a); - var line2 = new ModelLine(b); - line1.append(line2); - assert.equal(line1.text, expected); - } - - test('append at the beginning', () => { - testLineAppend( - '', - 'qwerty', - 'qwerty' - ); - }); - - test('append at the end', () => { - testLineAppend( - 'qwerty', - '', - 'qwerty' - ); - }); - - test('append in the middle', () => { - testLineAppend( - 'qw', - 'erty', - 'qwerty' - ); - }); -}); - class TestToken { public readonly startOffset: number; public readonly color: number; @@ -289,28 +95,375 @@ class TestToken { } } -suite('Editor Model - modelLine.applyEdits text & tokens', () => { +suite('ModelLinesTokens', () => { + interface IBufferLineState { + text: string; + tokens: TestToken[]; + } + + interface IEdit { + range: Range; + text: string; + } + + function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void { + const initialText = initial.map(el => el.text).join('\n'); + const model = new Model(RawTextSource.fromString(initialText), TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) { + const lineTokens = initial[lineIndex].tokens; + const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1; + model._tokens._setTokens(0, lineIndex, lineTextLength, TestToken.toTokens(lineTokens)); + } + + model.applyEdits(edits.map((ed) => ({ + identifier: null, + range: ed.range, + text: ed.text, + forceMoveMarkers: false + }))); + + for (let lineIndex = 0; lineIndex < expected.length; lineIndex++) { + const actualLine = model.getLineContent(lineIndex + 1); + const actualTokens = model.getLineTokens(lineIndex + 1); + assert.equal(actualLine, expected[lineIndex].text); + assertLineTokens(actualTokens, expected[lineIndex].tokens); + } + } + + test('single delete 1', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 1, 1, 2), text: '' }], + [{ + text: 'ello world', + tokens: [new TestToken(0, 1), new TestToken(4, 2), new TestToken(5, 3)] + }] + ); + }); + + test('single delete 2', () => { + testApplyEdits( + [{ + text: 'helloworld', + tokens: [new TestToken(0, 1), new TestToken(5, 2)] + }], + [{ range: new Range(1, 3, 1, 8), text: '' }], + [{ + text: 'herld', + tokens: [new TestToken(0, 1), new TestToken(2, 2)] + }] + ); + }); + + test('single delete 3', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 1, 1, 6), text: '' }], + [{ + text: ' world', + tokens: [new TestToken(0, 2), new TestToken(1, 3)] + }] + ); + }); + + test('single delete 4', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 2, 1, 7), text: '' }], + [{ + text: 'hworld', + tokens: [new TestToken(0, 1), new TestToken(1, 3)] + }] + ); + }); + + test('single delete 5', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 1, 1, 12), text: '' }], + [{ + text: '', + tokens: [new TestToken(0, 1)] + }] + ); + }); + + test('multi delete 6', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 7), new TestToken(5, 8), new TestToken(6, 9)] + }], + [{ range: new Range(1, 6, 3, 6), text: '' }], + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 8), new TestToken(6, 9)] + }] + ); + }); + + test('multi delete 7', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 7), new TestToken(5, 8), new TestToken(6, 9)] + }], + [{ range: new Range(1, 12, 3, 12), text: '' }], + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }] + ); + }); + + test('multi delete 8', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 7), new TestToken(5, 8), new TestToken(6, 9)] + }], + [{ range: new Range(1, 1, 3, 1), text: '' }], + [{ + text: 'hello world', + tokens: [new TestToken(0, 7), new TestToken(5, 8), new TestToken(6, 9)] + }] + ); + }); + + test('multi delete 9', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 7), new TestToken(5, 8), new TestToken(6, 9)] + }], + [{ range: new Range(1, 12, 3, 1), text: '' }], + [{ + text: 'hello worldhello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3), new TestToken(11, 7), new TestToken(16, 8), new TestToken(17, 9)] + }] + ); + }); + + test('single insert 1', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 1, 1, 1), text: 'xx' }], + [{ + text: 'xxhello world', + tokens: [new TestToken(0, 1), new TestToken(7, 2), new TestToken(8, 3)] + }] + ); + }); + + test('single insert 2', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 2, 1, 2), text: 'xx' }], + [{ + text: 'hxxello world', + tokens: [new TestToken(0, 1), new TestToken(7, 2), new TestToken(8, 3)] + }] + ); + }); + + test('single insert 3', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 6, 1, 6), text: 'xx' }], + [{ + text: 'helloxx world', + tokens: [new TestToken(0, 1), new TestToken(7, 2), new TestToken(8, 3)] + }] + ); + }); + + test('single insert 4', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 7, 1, 7), text: 'xx' }], + [{ + text: 'hello xxworld', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(8, 3)] + }] + ); + }); + + test('single insert 5', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 12, 1, 12), text: 'xx' }], + [{ + text: 'hello worldxx', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }] + ); + }); + + test('multi insert 6', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 1, 1, 1), text: '\n' }], + [{ + text: '', + tokens: [new TestToken(0, 1)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 1)] + }] + ); + }); + + test('multi insert 7', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 12, 1, 12), text: '\n' }], + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: '', + tokens: [new TestToken(0, 1)] + }] + ); + }); + + test('multi insert 8', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }], + [{ range: new Range(1, 7, 1, 7), text: '\n' }], + [{ + text: 'hello ', + tokens: [new TestToken(0, 1), new TestToken(5, 2)] + }, { + text: 'world', + tokens: [new TestToken(0, 1)] + }] + ); + }); + + test('multi insert 9', () => { + testApplyEdits( + [{ + text: 'hello world', + tokens: [new TestToken(0, 1), new TestToken(5, 2), new TestToken(6, 3)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }], + [{ range: new Range(1, 7, 1, 7), text: 'xx\nyy' }], + [{ + text: 'hello xx', + tokens: [new TestToken(0, 1), new TestToken(5, 2)] + }, { + text: 'yyworld', + tokens: [new TestToken(0, 1)] + }, { + text: 'hello world', + tokens: [new TestToken(0, 4), new TestToken(5, 5), new TestToken(6, 6)] + }] + ); + }); function testLineEditTokens(initialText: string, initialTokens: TestToken[], edits: ILineEdit[], expectedText: string, expectedTokens: TestToken[]): void { - let line = new ModelLine(initialText); - line.setTokens(0, TestToken.toTokens(initialTokens)); - - line.applyEdits(edits); - - assert.equal(line.text, expectedText); - assertLineTokens(line.getTokens(0), expectedTokens); + testApplyEdits( + [{ + text: initialText, + tokens: initialTokens + }], + edits.map((ed) => ({ + range: new Range(1, ed.startColumn, 1, ed.endColumn), + text: ed.text + })), + [{ + text: expectedText, + tokens: expectedTokens + }] + ); } test('insertion on empty line', () => { - let line = new ModelLine('some text'); - line.setTokens(0, TestToken.toTokens([new TestToken(0, 1)])); + const model = new Model(RawTextSource.fromString('some text'), TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0)); + model._tokens._setTokens(0, 0, model.getLineMaxColumn(1) - 1, TestToken.toTokens([new TestToken(0, 1)])); - line.applyEdits([{ startColumn: 1, endColumn: 10, text: '' }]); - line.setTokens(0, new Uint32Array(0)); + model.applyEdits([{ + identifier: null, + range: new Range(1, 1, 1, 10), + text: '', + forceMoveMarkers: false + }]); - line.applyEdits([{ startColumn: 1, endColumn: 1, text: 'a' }]); - assertLineTokens(line.getTokens(0), [new TestToken(0, 1)]); + model._tokens._setTokens(0, 0, model.getLineMaxColumn(1) - 1, new Uint32Array(0)); + + model.applyEdits([{ + identifier: null, + range: new Range(1, 1, 1, 1), + text: 'a', + forceMoveMarkers: false + }]); + + const actualTokens = model.getLineTokens(1); + assertLineTokens(actualTokens, [new TestToken(0, 1)]); }); test('updates tokens on insertion 1', () => { @@ -712,7 +865,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], '', [ - new TestToken(0, 3) + new TestToken(0, 1) ] ); }); @@ -798,8 +951,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'Hi world, ciao', [ - new TestToken(0, 1), - new TestToken(2, 0), + new TestToken(0, 0), new TestToken(3, 2), new TestToken(8, 0), new TestToken(10, 0), @@ -828,26 +980,32 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'Hi wmy friends, ciao', [ - new TestToken(0, 1), - new TestToken(2, 0), + new TestToken(0, 0), new TestToken(3, 2), new TestToken(14, 0), new TestToken(16, 0), ] ); }); -}); -suite('Editor Model - modelLine.split text & tokens', () => { function testLineSplitTokens(initialText: string, initialTokens: TestToken[], splitColumn: number, expectedText1: string, expectedText2: string, expectedTokens: TestToken[]): void { - let line = new ModelLine(initialText); - line.setTokens(0, TestToken.toTokens(initialTokens)); - - let other = line.split(splitColumn); - - assert.equal(line.text, expectedText1); - assert.equal(other.text, expectedText2); - assertLineTokens(line.getTokens(0), expectedTokens); + testApplyEdits( + [{ + text: initialText, + tokens: initialTokens + }], + [{ + range: new Range(1, splitColumn, 1, splitColumn), + text: '\n' + }], + [{ + text: expectedText1, + tokens: expectedTokens + }, { + text: expectedText2, + tokens: [new TestToken(0, 1)] + }] + ); } test('split at the beginning', () => { @@ -920,20 +1078,25 @@ suite('Editor Model - modelLine.split text & tokens', () => { ] ); }); -}); -suite('Editor Model - modelLine.append text & tokens', () => { function testLineAppendTokens(aText: string, aTokens: TestToken[], bText: string, bTokens: TestToken[], expectedText: string, expectedTokens: TestToken[]): void { - let a = new ModelLine(aText); - a.setTokens(0, TestToken.toTokens(aTokens)); - - let b = new ModelLine(bText); - b.setTokens(0, TestToken.toTokens(bTokens)); - - a.append(b); - - assert.equal(a.text, expectedText); - assertLineTokens(a.getTokens(0), expectedTokens); + testApplyEdits( + [{ + text: aText, + tokens: aTokens + }, { + text: bText, + tokens: bTokens + }], + [{ + range: new Range(1, aText.length + 1, 2, 1), + text: '' + }], + [{ + text: expectedText, + tokens: expectedTokens + }] + ); } test('append empty 1', () => { @@ -1040,484 +1203,3 @@ suite('Editor Model - modelLine.append text & tokens', () => { ); }); }); - -suite('Editor Model - modelLine.applyEdits', () => { - - function testLineEdit(initialText: string, edits: ILineEdit[], expectedText: string): void { - let line = new ModelLine(initialText); - - line.applyEdits(edits); - - assert.equal(line.text, expectedText, 'text'); - } - - test('insertion: updates markers 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'abc', - }], - 'abcabcd efgh', - ); - }); - - test('insertion: updates markers 2', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 2, - endColumn: 2, - text: 'abc', - }], - 'aabcbcd efgh', - ); - }); - - test('insertion: updates markers 3', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 3, - endColumn: 3, - text: 'abc', - }], - 'ababccd efgh', - ); - }); - - test('insertion: updates markers 4', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 5, - endColumn: 5, - text: 'abc', - }], - 'abcdabc efgh', - ); - }); - - test('insertion: updates markers 5', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 10, - endColumn: 10, - text: 'abc', - }], - 'abcd efghabc', - ); - }); - - test('insertion bis: updates markers 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('insertion bis: updates markers 2', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 2, - endColumn: 2, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('insertion bis: updates markers 3', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 3, - endColumn: 3, - text: 'a', - }], - 'abacd efgh', - ); - }); - - test('insertion bis: updates markers 4', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 5, - endColumn: 5, - text: 'a', - }], - 'abcda efgh', - ); - }); - - test('insertion bis: updates markers 5', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 10, - endColumn: 10, - text: 'a', - }], - 'abcd efgha', - ); - }); - - test('insertion: does not move marker at column 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('insertion: does move marker at column 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('insertion: two markers at column 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('insertion: two markers at column 1 unsorted', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }], - 'aabcd efgh', - ); - }); - - test('deletion: updates markers 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 2, - text: '', - }], - 'bcd efgh', - ); - }); - - test('deletion: updates markers 2', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 4, - text: '', - }], - 'd efgh', - ); - }); - - test('deletion: updates markers 3', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 5, - endColumn: 6, - text: '', - }], - 'abcdefgh', - ); - }); - - test('replace: updates markers 1', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 1, - endColumn: 1, - text: 'a', - }, { - startColumn: 2, - endColumn: 3, - text: '', - }], - 'aacd efgh', - ); - }); - - test('delete near markers', () => { - testLineEdit( - 'abcd', - [{ - startColumn: 3, - endColumn: 4, - text: '', - }], - 'abd', - ); - }); - - test('replace: updates markers 2', () => { - testLineEdit( - 'Hello world, how are you', - [{ - startColumn: 1, - endColumn: 1, - text: ' - ', - }, { - startColumn: 6, - endColumn: 12, - text: '', - }, { - startColumn: 22, - endColumn: 25, - text: 'things', - }], - ' - Hello, how are things', - ); - }); - - test('sorts markers', () => { - testLineEdit( - 'Hello world, how are you', - [{ - startColumn: 1, - endColumn: 1, - text: ' - ', - }, { - startColumn: 6, - endColumn: 12, - text: '', - }, { - startColumn: 22, - endColumn: 25, - text: 'things', - }], - ' - Hello, how are things', - ); - }); - - test('change text inside markers', () => { - testLineEdit( - 'abcd efgh', - [{ - startColumn: 6, - endColumn: 10, - text: '1234567', - }], - 'abcd 1234567', - ); - }); - - test('inserting is different than replacing for markers part 1', () => { - testLineEdit( - 'abcd', - [{ - startColumn: 2, - endColumn: 2, - text: 'INSERT', - }], - 'aINSERTbcd', - ); - }); - - test('inserting is different than replacing for markers part 2', () => { - testLineEdit( - 'abcd', - [{ - startColumn: 2, - endColumn: 3, - text: 'REPLACED', - }], - 'aREPLACEDcd', - ); - }); - - test('replacing the entire line with more text', () => { - testLineEdit( - 'this is a short text', - [{ - startColumn: 1, - endColumn: 21, - text: 'Some new text here', - }], - 'Some new text here', - ); - }); - - test('replacing the entire line with less text', () => { - testLineEdit( - 'this is a short text', - [{ - startColumn: 1, - endColumn: 21, - text: 'ttt', - }], - 'ttt', - ); - }); - - test('replace selection', () => { - testLineEdit( - 'first', - [{ - startColumn: 1, - endColumn: 6, - text: 'something', - }], - 'something', - ); - }); -}); - -suite('Editor Model - modelLine.split', () => { - - function testLineSplit(initialText: string, splitColumn: number, forceMoveMarkers: boolean, expectedText1: string, expectedText2: string): void { - let line = new ModelLine(initialText); - - let otherLine = line.split(splitColumn); - - assert.equal(line.text, expectedText1, 'text'); - assert.equal(otherLine.text, expectedText2, 'text'); - } - - test('split at the beginning', () => { - testLineSplit( - 'abcd efgh', - 1, - false, - '', - 'abcd efgh', - ); - }); - - test('split at the beginning 2', () => { - testLineSplit( - 'abcd efgh', - 1, - true, - '', - 'abcd efgh', - ); - }); - - test('split at the end', () => { - testLineSplit( - 'abcd efgh', - 10, - false, - 'abcd efgh', - '', - ); - }); - - test('split it the middle 1', () => { - testLineSplit( - 'abcd efgh', - 2, - false, - 'a', - 'bcd efgh', - ); - }); - - test('split it the middle 2', () => { - testLineSplit( - 'abcd efgh', - 3, - false, - 'ab', - 'cd efgh', - ); - }); - - test('split it the middle 3', () => { - testLineSplit( - 'abcd efgh', - 5, - false, - 'abcd', - ' efgh', - ); - }); - - test('split it the middle 4', () => { - testLineSplit( - 'abcd efgh', - 6, - false, - 'abcd ', - 'efgh', - ); - }); -}); - -suite('Editor Model - modelLine.append', () => { - - function testLinePrependMarkers(aText: string, bText: string, expectedText: string): void { - let a = new ModelLine(aText); - let b = new ModelLine(bText); - - a.append(b); - - assert.equal(a.text, expectedText, 'text'); - } - - test('append to an empty', () => { - testLinePrependMarkers( - 'abcd efgh', - '', - 'abcd efgh', - ); - }); - - test('append an empty', () => { - testLinePrependMarkers( - '', - 'abcd efgh', - 'abcd efgh', - ); - }); - - test('append 1', () => { - testLinePrependMarkers( - 'abcd', - ' efgh', - 'abcd efgh', - ); - }); - - test('append 2', () => { - testLinePrependMarkers( - 'abcd e', - 'fgh', - 'abcd efgh', - ); - }); -}); diff --git a/src/vs/editor/test/common/model/model.modes.test.ts b/src/vs/editor/test/common/model/model.modes.test.ts index 5d970ea6c69..13ce7639174 100644 --- a/src/vs/editor/test/common/model/model.modes.test.ts +++ b/src/vs/editor/test/common/model/model.modes.test.ts @@ -185,7 +185,7 @@ suite('Editor Model - Model Modes 2', () => { function invalidEqual(model: Model, expected: number[]): void { let actual: number[] = []; for (let i = 0, len = model.getLineCount(); i < len; i++) { - if (model._lines[i].isInvalid()) { + if (model._tokens._isInvalid(i)) { actual.push(i); } } @@ -199,9 +199,9 @@ suite('Editor Model - Model Modes 2', () => { function statesEqual(model: Model, states: string[]): void { var i, len = states.length - 1; for (i = 0; i < len; i++) { - stateEqual(model._lines[i].getState(), states[i]); + stateEqual(model._tokens._getState(i), states[i]); } - stateEqual((model)._lastState, states[len]); + stateEqual((model)._tokens._lastState, states[len]); } let thisModel: Model = null; @@ -253,8 +253,9 @@ suite('Editor Model - Model Modes 2', () => { thisModel.forceTokenization(5); statesEqual(thisModel, ['', 'Line1', 'Line2', 'Line3', 'Line4', 'Line5']); thisModel.applyEdits([EditOperation.insert(new Position(1, 6), '\nNew line\nAnother new line')]); + invalidEqual(thisModel, [0, 1, 2]); thisModel.applyEdits([EditOperation.insert(new Position(5, 6), '-')]); - invalidEqual(thisModel, [0, 4]); + invalidEqual(thisModel, [0, 1, 2, 4]); thisModel.forceTokenization(7); statesEqual(thisModel, ['', 'Line1', 'New line', 'Another new line', 'Line2', 'Line3-', 'Line4', 'Line5']); }); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index f23ad58d685..436cfc245e4 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -131,7 +131,7 @@ suite('Editor Model - Model', () => { [ new ModelRawLineChanged(1, 'My new line First Line'), new ModelRawLineChanged(1, 'My new line'), - new ModelRawLinesInserted(2, 2, 'No longer First Line'), + new ModelRawLinesInserted(2, 2, ['No longer First Line']), ], 2, false, diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts index 1ee24bc97c7..ba0e31d80fe 100644 --- a/src/vs/editor/test/common/model/textModelWithTokens.test.ts +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -7,14 +7,13 @@ import * as assert from 'assert'; import { Model } from 'vs/editor/common/model/model'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken } from 'vs/editor/test/common/core/viewLineToken'; import { ITokenizationSupport, TokenizationRegistry, LanguageId, LanguageIdentifier, MetadataConsts } from 'vs/editor/common/modes'; import { CharacterPair } from 'vs/editor/common/modes/languageConfiguration'; import { Range } from 'vs/editor/common/core/range'; import { Position } from 'vs/editor/common/core/position'; import { IFoundBracket } from 'vs/editor/common/editorCommon'; import { TextModel } from 'vs/editor/common/model/textModel'; -import { TextModelWithTokens } from 'vs/editor/common/model/textModelWithTokens'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; @@ -76,7 +75,7 @@ suite('TextModelWithTokens', () => { brackets: brackets }); - let model = new TextModelWithTokens( + let model = new Model( RawTextSource.fromString(contents.join('\n')), TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier @@ -268,14 +267,25 @@ suite('TextModelWithTokens regression tests', () => { if (forceTokenization) { model.forceTokenization(lineNumber); } - let actual = model.getLineTokens(lineNumber).inflate(); + let _actual = model.getLineTokens(lineNumber).inflate(); + interface ISimpleViewToken { + endIndex: number; + foreground: number; + } + let actual: ISimpleViewToken[] = []; + for (let i = 0, len = _actual.getCount(); i < len; i++) { + actual[i] = { + endIndex: _actual.getEndOffset(i), + foreground: _actual.getForeground(i) + }; + } let decode = (token: ViewLineToken) => { return { endIndex: token.endIndex, foreground: token.getForeground() }; }; - assert.deepEqual(actual.map(decode), expected.map(decode)); + assert.deepEqual(actual, expected.map(decode)); } let _tokenId = 10; diff --git a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts index 7bc91ada333..7449d71b00d 100644 --- a/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts +++ b/src/vs/editor/test/common/modes/textToHtmlTokenizer.test.ts @@ -9,7 +9,7 @@ import { TokenizationRegistry, IState, LanguageIdentifier, ColorId, FontStyle, M import { tokenizeToString, tokenizeLineToHTML } from 'vs/editor/common/modes/textToHtmlTokenizer'; import { MockMode } from 'vs/editor/test/common/mocks/mockMode'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken, ViewLineTokens } from 'vs/editor/test/common/core/viewLineToken'; suite('Editor Modes - textToHtmlTokenizer', () => { function toStr(pieces: { className: string; text: string }[]): string { @@ -67,7 +67,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { test('tokenizeLineToHTML', () => { const text = 'Ciao hello world!'; - const lineTokens = [ + const lineTokens = new ViewLineTokens([ new ViewLineToken( 4, ( @@ -100,7 +100,7 @@ suite('Editor Modes - textToHtmlTokenizer', () => { | ((FontStyle.Underline) << MetadataConsts.FONT_STYLE_OFFSET) ) >>> 0 ) - ]; + ]); const colorMap = [null, '#000000', '#ffffff', '#ff0000', '#00ff00', '#0000ff']; assert.equal( diff --git a/src/vs/editor/test/common/modesTestUtils.ts b/src/vs/editor/test/common/modesTestUtils.ts index 211362c220a..8dceda3b0a0 100644 --- a/src/vs/editor/test/common/modesTestUtils.ts +++ b/src/vs/editor/test/common/modesTestUtils.ts @@ -30,5 +30,6 @@ export function createFakeScopedLineTokens(rawTokens: TokenText[]): ScopedLineTo line += rawToken.text; } + LineTokens.convertToEndOffset(tokens, line.length); return createScopedLineTokens(new LineTokens(tokens, line), 0); } diff --git a/src/vs/editor/test/common/view/overviewZoneManager.test.ts b/src/vs/editor/test/common/view/overviewZoneManager.test.ts index 1cfc1a4a2b1..1781b29855b 100644 --- a/src/vs/editor/test/common/view/overviewZoneManager.test.ts +++ b/src/vs/editor/test/common/view/overviewZoneManager.test.ts @@ -5,9 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { OverviewRulerLane } from 'vs/editor/common/editorCommon'; import { OverviewZoneManager, ColorZone, OverviewRulerZone } from 'vs/editor/common/view/overviewZoneManager'; -import { LIGHT } from 'vs/platform/theme/common/themeService'; suite('Editor View - OverviewZoneManager', () => { @@ -15,9 +13,6 @@ suite('Editor View - OverviewZoneManager', () => { const LINE_COUNT = 50; const LINE_HEIGHT = 20; let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); - manager.setMinimumHeight(6); - manager.setMaximumHeight(6); - manager.setThemeType(LIGHT); manager.setDOMWidth(30); manager.setDOMHeight(600); manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); @@ -25,19 +20,18 @@ suite('Editor View - OverviewZoneManager', () => { manager.setPixelRatio(1); manager.setZones([ - new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1', '1'), - new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2', '2'), - new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3', '3'), - new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4', '4'), + new OverviewRulerZone(1, 1, '1'), + new OverviewRulerZone(10, 10, '2'), + new OverviewRulerZone(30, 31, '3'), + new OverviewRulerZone(50, 50, '4'), ]); // one line = 12, but cap is at 6 assert.deepEqual(manager.resolveColorZones(), [ - new ColorZone(12, 22, 1, OverviewRulerLane.Full), // forced height of 10 - new ColorZone(123, 129, 2, OverviewRulerLane.Full), // 120 -> 132 - new ColorZone(363, 369, 3, OverviewRulerLane.Full), // 360 -> 372 [360 -> 384] - new ColorZone(375, 381, 3, OverviewRulerLane.Full), // 372 -> 384 [360 -> 384] - new ColorZone(594, 600, 4, OverviewRulerLane.Full), // 588 -> 600 + new ColorZone(12, 24, 1), // + new ColorZone(120, 132, 2), // 120 -> 132 + new ColorZone(360, 384, 3), // 360 -> 372 [360 -> 384] + new ColorZone(588, 600, 4), // 588 -> 600 ]); }); @@ -45,9 +39,6 @@ suite('Editor View - OverviewZoneManager', () => { const LINE_COUNT = 50; const LINE_HEIGHT = 20; let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); - manager.setMinimumHeight(6); - manager.setMaximumHeight(6); - manager.setThemeType(LIGHT); manager.setDOMWidth(30); manager.setDOMHeight(300); manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); @@ -55,18 +46,18 @@ suite('Editor View - OverviewZoneManager', () => { manager.setPixelRatio(1); manager.setZones([ - new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1', '1'), - new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2', '2'), - new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3', '3'), - new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4', '4'), + new OverviewRulerZone(1, 1, '1'), + new OverviewRulerZone(10, 10, '2'), + new OverviewRulerZone(30, 31, '3'), + new OverviewRulerZone(50, 50, '4'), ]); // one line = 6, cap is at 6 assert.deepEqual(manager.resolveColorZones(), [ - new ColorZone(6, 16, 1, OverviewRulerLane.Full), // forced height of 10 - new ColorZone(60, 66, 2, OverviewRulerLane.Full), // 60 -> 66 - new ColorZone(180, 192, 3, OverviewRulerLane.Full), // 180 -> 192 - new ColorZone(294, 300, 4, OverviewRulerLane.Full), // 294 -> 300 + new ColorZone(6, 12, 1), // + new ColorZone(60, 66, 2), // 60 -> 66 + new ColorZone(180, 192, 3), // 180 -> 192 + new ColorZone(294, 300, 4), // 294 -> 300 ]); }); @@ -74,9 +65,6 @@ suite('Editor View - OverviewZoneManager', () => { const LINE_COUNT = 50; const LINE_HEIGHT = 20; let manager = new OverviewZoneManager((lineNumber) => LINE_HEIGHT * lineNumber); - manager.setMinimumHeight(6); - manager.setMaximumHeight(6); - manager.setThemeType(LIGHT); manager.setDOMWidth(30); manager.setDOMHeight(300); manager.setOuterHeight(LINE_COUNT * LINE_HEIGHT); @@ -84,18 +72,18 @@ suite('Editor View - OverviewZoneManager', () => { manager.setPixelRatio(2); manager.setZones([ - new OverviewRulerZone(1, 1, OverviewRulerLane.Full, 10, '1', '1', '1'), - new OverviewRulerZone(10, 10, OverviewRulerLane.Full, 0, '2', '2', '2'), - new OverviewRulerZone(30, 31, OverviewRulerLane.Full, 0, '3', '3', '3'), - new OverviewRulerZone(50, 50, OverviewRulerLane.Full, 0, '4', '4', '4'), + new OverviewRulerZone(1, 1, '1'), + new OverviewRulerZone(10, 10, '2'), + new OverviewRulerZone(30, 31, '3'), + new OverviewRulerZone(50, 50, '4'), ]); // one line = 6, cap is at 12 assert.deepEqual(manager.resolveColorZones(), [ - new ColorZone(12, 32, 1, OverviewRulerLane.Full), // forced height of 10 => forced height of 20 - new ColorZone(120, 132, 2, OverviewRulerLane.Full), // 120 -> 132 - new ColorZone(360, 384, 3, OverviewRulerLane.Full), // 360 -> 384 - new ColorZone(588, 600, 4, OverviewRulerLane.Full), // 588 -> 600 + new ColorZone(12, 24, 1), // + new ColorZone(120, 132, 2), // 120 -> 132 + new ColorZone(360, 384, 3), // 360 -> 384 + new ColorZone(588, 600, 4), // 588 -> 600 ]); }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index bd45bf98e97..7312c7560ac 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -6,27 +6,32 @@ import * as assert from 'assert'; import { renderViewLine2 as renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; +import { ViewLineToken, ViewLineTokens } from 'vs/editor/test/common/core/viewLineToken'; import { CharCode } from 'vs/base/common/charCode'; import { MetadataConsts } from 'vs/editor/common/modes'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; + +function createViewLineTokens(viewLineTokens: ViewLineToken[]): IViewLineTokens { + return new ViewLineTokens(viewLineTokens); +} + +function createPart(endIndex: number, foreground: number): ViewLineToken { + return new ViewLineToken(endIndex, ( + foreground << MetadataConsts.FOREGROUND_OFFSET + ) >>> 0); +} suite('viewLineRenderer.renderLine', () => { - function createPart(endIndex: number, foreground: number): ViewLineToken { - return new ViewLineToken(endIndex, ( - foreground << MetadataConsts.FOREGROUND_OFFSET - ) >>> 0); - } - function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][], expectedPartLengts: number[]): void { let _actual = renderViewLine(new RenderLineInput( false, lineContent, false, 0, - [new ViewLineToken(lineContent.length, 0)], + createViewLineTokens([new ViewLineToken(lineContent.length, 0)]), [], tabSize, 0, @@ -72,7 +77,7 @@ suite('viewLineRenderer.renderLine', () => { lineContent, false, 0, - parts, + createViewLineTokens(parts), [], tabSize, 0, @@ -108,7 +113,7 @@ suite('viewLineRenderer.renderLine', () => { 'Hello world!', false, 0, - [ + createViewLineTokens([ createPart(1, 0), createPart(2, 1), createPart(3, 2), @@ -121,7 +126,7 @@ suite('viewLineRenderer.renderLine', () => { createPart(10, 9), createPart(11, 10), createPart(12, 11), - ], + ]), [], 4, 10, @@ -157,7 +162,7 @@ suite('viewLineRenderer.renderLine', () => { test('typical line', () => { let lineText = '\t export class Game { // http://test.com '; - let lineParts = [ + let lineParts = createViewLineTokens([ createPart(5, 1), createPart(11, 2), createPart(12, 3), @@ -170,7 +175,7 @@ suite('viewLineRenderer.renderLine', () => { createPart(28, 10), createPart(43, 11), createPart(48, 12), - ]; + ]); let expectedOutput = [ '\u2192\u00a0\u00a0\u00a0', '\u00b7\u00b7\u00b7\u00b7', @@ -226,7 +231,7 @@ suite('viewLineRenderer.renderLine', () => { test('issue #2255: Weird line rendering part 1', () => { let lineText = '\t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; - let lineParts = [ + let lineParts = createViewLineTokens([ createPart(3, 1), // 3 chars createPart(15, 2), // 12 chars createPart(21, 3), // 6 chars @@ -237,7 +242,7 @@ suite('viewLineRenderer.renderLine', () => { createPart(66, 8), // 20 chars createPart(67, 9), // 1 char createPart(68, 10), // 2 chars - ]; + ]); let expectedOutput = [ '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', 'cursorStyle:', @@ -285,7 +290,7 @@ suite('viewLineRenderer.renderLine', () => { test('issue #2255: Weird line rendering part 2', () => { let lineText = ' \t\t\tcursorStyle:\t\t\t\t\t\t(prevOpts.cursorStyle !== newOpts.cursorStyle),'; - let lineParts = [ + let lineParts = createViewLineTokens([ createPart(4, 1), // 4 chars createPart(16, 2), // 12 chars createPart(22, 3), // 6 chars @@ -296,7 +301,7 @@ suite('viewLineRenderer.renderLine', () => { createPart(67, 8), // 20 chars createPart(68, 9), // 1 char createPart(69, 10), // 2 chars - ]; + ]); let expectedOutput = [ '\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0', 'cursorStyle:', @@ -344,12 +349,12 @@ suite('viewLineRenderer.renderLine', () => { test('issue Microsoft/monaco-editor#280: Improved source code rendering for RTL languages', () => { let lineText = 'var קודמות = \"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם\";'; - let lineParts = [ + let lineParts = createViewLineTokens([ createPart(3, 6), createPart(13, 1), createPart(66, 20), createPart(67, 1), - ]; + ]); let expectedOutput = [ 'var', @@ -384,7 +389,7 @@ suite('viewLineRenderer.renderLine', () => { let _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.'; function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { - let lineParts = [createPart(lineText.length, 1)]; + let lineParts = createViewLineTokens([createPart(lineText.length, 1)]); let actual = renderViewLine(new RenderLineInput( false, lineText, @@ -481,7 +486,7 @@ suite('viewLineRenderer.renderLine', () => { let _lineText = 'This is just a long line that contains very interesting text. This is just a long line that contains very interesting text.'; function assertSplitsTokens(message: string, lineText: string, expectedOutput: string[]): void { - let lineParts = [createPart(lineText.length, 1)]; + let lineParts = createViewLineTokens([createPart(lineText.length, 1)]); let actual = renderViewLine(new RenderLineInput( false, lineText, @@ -514,7 +519,7 @@ suite('viewLineRenderer.renderLine', () => { test('issue #20624: Unaligned surrogate pairs are corrupted at multiples of 50 columns', () => { let lineText = 'a𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷𠮷'; - let lineParts = [createPart(lineText.length, 1)]; + let lineParts = createViewLineTokens([createPart(lineText.length, 1)]); let actual = renderViewLine(new RenderLineInput( false, lineText, @@ -541,7 +546,7 @@ suite('viewLineRenderer.renderLine', () => { test('issue #6885: Does not split large tokens in RTL text', () => { let lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; - let lineParts = [createPart(lineText.length, 1)]; + let lineParts = createViewLineTokens([createPart(lineText.length, 1)]); let expectedOutput = [ 'את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.' ]; @@ -566,7 +571,7 @@ suite('viewLineRenderer.renderLine', () => { test('issue #19673: Monokai Theme bad-highlighting in line wrap', () => { let lineText = ' MongoCallback): void {'; - let lineParts = [ + let lineParts = createViewLineTokens([ createPart(17, 1), createPart(18, 2), createPart(24, 3), @@ -575,7 +580,7 @@ suite('viewLineRenderer.renderLine', () => { createPart(28, 6), createPart(32, 7), createPart(34, 8), - ]; + ]); let expectedOutput = [ '\u00a0\u00a0\u00a0\u00a0', 'MongoCallback', @@ -666,11 +671,6 @@ suite('viewLineRenderer.renderLine', () => { }); suite('viewLineRenderer.renderLine 2', () => { - function createPart(endIndex: number, foreground: number): ViewLineToken { - return new ViewLineToken(endIndex, ( - foreground << MetadataConsts.FOREGROUND_OFFSET - ) >>> 0); - } function testCreateLineParts(fontIsMonospace: boolean, lineContent: string, tokens: ViewLineToken[], fauxIndentLength: number, renderWhitespace: 'none' | 'boundary' | 'all', expected: string): void { let actual = renderViewLine(new RenderLineInput( @@ -678,7 +678,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, fauxIndentLength, - tokens, + createViewLineTokens(tokens), [], 4, 10, @@ -700,7 +700,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - [createPart(21, 3)], + createViewLineTokens([createPart(21, 3)]), [new LineDecoration(1, 22, 'link', InlineDecorationType.Regular)], 4, 10, @@ -728,13 +728,13 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - [ + createViewLineTokens([ createPart(49, 6), createPart(51, 4), createPart(72, 6), createPart(74, 4), createPart(84, 6), - ], + ]), [ new LineDecoration(13, 51, 'detected-link', InlineDecorationType.Regular) ], @@ -992,7 +992,7 @@ suite('viewLineRenderer.renderLine 2', () => { 'Hello world', false, 0, - [createPart(11, 0)], + createViewLineTokens([createPart(11, 0)]), [ new LineDecoration(5, 7, 'a', InlineDecorationType.Regular), new LineDecoration(1, 3, 'b', InlineDecorationType.Regular), @@ -1033,7 +1033,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - [createPart(4, 3)], + createViewLineTokens([createPart(4, 3)]), [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], 4, 10, @@ -1062,7 +1062,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - [createPart(4, 3)], + createViewLineTokens([createPart(4, 3)]), [new LineDecoration(2, 3, 'before', InlineDecorationType.Before)], 4, 10, @@ -1092,7 +1092,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - [createPart(0, 3)], + createViewLineTokens([createPart(0, 3)]), [new LineDecoration(1, 2, 'before', InlineDecorationType.Before)], 4, 10, @@ -1118,7 +1118,7 @@ suite('viewLineRenderer.renderLine 2', () => { ' 1. 🙏', false, 0, - [createPart(7, 3)], + createViewLineTokens([createPart(7, 3)]), [new LineDecoration(7, 8, 'inline-folded', InlineDecorationType.After)], 2, 10, @@ -1145,7 +1145,7 @@ suite('viewLineRenderer.renderLine 2', () => { '', false, 0, - [createPart(0, 3)], + createViewLineTokens([createPart(0, 3)]), [ new LineDecoration(1, 2, 'before', InlineDecorationType.Before), new LineDecoration(0, 1, 'after', InlineDecorationType.After), @@ -1174,7 +1174,7 @@ suite('viewLineRenderer.renderLine 2', () => { '\t}', false, 0, - [createPart(2, 3)], + createViewLineTokens([createPart(2, 3)]), [ new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-3 ced-TextEditorDecorationType2-3', InlineDecorationType.Before), new LineDecoration(3, 3, 'ced-TextEditorDecorationType2-5e9b9b3f-4 ced-TextEditorDecorationType2-4', InlineDecorationType.After), @@ -1203,7 +1203,7 @@ suite('viewLineRenderer.renderLine 2', () => { lineContent, false, 0, - parts, + createViewLineTokens(parts), [], tabSize, 10, diff --git a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts index cc43e9c1d34..df6663b0016 100644 --- a/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts +++ b/src/vs/editor/test/common/viewModel/splitLinesCollection.test.ts @@ -16,9 +16,9 @@ import * as modes from 'vs/editor/common/modes'; import { NULL_STATE } from 'vs/editor/common/modes/nullMode'; import { TokenizationResult2 } from 'vs/editor/common/core/token'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { ViewLineData } from 'vs/editor/common/viewModel/viewModel'; import { Range } from 'vs/editor/common/core/range'; +import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; suite('Editor ViewModel - SplitLinesCollection', () => { test('SplitLine', () => { @@ -363,14 +363,15 @@ suite('SplitLinesCollection', () => { value: number; } - function assertViewLineTokens(actual: ViewLineToken[], expected: ITestViewLineToken[]): void { - let _actual = actual.map((token) => { - return { - endIndex: token.endIndex, - value: token.getForeground() + function assertViewLineTokens(_actual: IViewLineTokens, expected: ITestViewLineToken[]): void { + let actual: ITestViewLineToken[] = []; + for (let i = 0, len = _actual.getCount(); i < len; i++) { + actual[i] = { + endIndex: _actual.getEndOffset(i), + value: _actual.getForeground(i) }; - }); - assert.deepEqual(_actual, expected); + } + assert.deepEqual(actual, expected); } interface ITestMinimapLineRenderingData { @@ -785,4 +786,4 @@ function createModel(text: string): IModel { return text.length + 1; } }; -} \ No newline at end of file +} diff --git a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts index 665f07f72ef..637efcb87ab 100644 --- a/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts +++ b/src/vs/editor/test/common/viewModel/viewModelImpl.test.ts @@ -42,10 +42,10 @@ suite('ViewModel', () => { }); }); - function assertGetPlainTextToCopy(text: string[], ranges: Range[], emptySelectionClipboard: boolean, expected: string): void { + function assertGetPlainTextToCopy(text: string[], ranges: Range[], emptySelectionClipboard: boolean, expected: string | string[]): void { testViewModel(text, {}, (viewModel, model) => { let actual = viewModel.getPlainTextToCopy(ranges, emptySelectionClipboard); - assert.equal(actual, expected); + assert.deepEqual(actual, expected); }); } @@ -157,7 +157,7 @@ suite('ViewModel', () => { new Range(3, 2, 3, 6), ], false, - 'ine2\nine3' + ['ine2', 'ine3'] ); }); @@ -169,7 +169,7 @@ suite('ViewModel', () => { new Range(2, 2, 2, 6), ], false, - 'ine2\nine3' + ['ine2', 'ine3'] ); }); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 4f7f950ec97..e028366042a 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -43,6 +43,7 @@ export class MenuId { static readonly EditorTitleContext = new MenuId(); static readonly EditorContext = new MenuId(); static readonly ExplorerContext = new MenuId(); + static readonly OpenEditorsContext = new MenuId(); static readonly ProblemsPanelContext = new MenuId(); static readonly DebugVariablesContext = new MenuId(); static readonly DebugWatchContext = new MenuId(); diff --git a/src/vs/platform/actions/test/common/menuService.test.ts b/src/vs/platform/actions/test/common/menuService.test.ts index 40ecb80f1ff..996fd93c2b8 100644 --- a/src/vs/platform/actions/test/common/menuService.test.ts +++ b/src/vs/platform/actions/test/common/menuService.test.ts @@ -151,8 +151,8 @@ suite('MenuService', function () { const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions(); - assert.equal(groups.length, 1); - const [[, actions]] = groups; + assert.equal(groups.length, 2); + const [, actions] = groups[1]; assert.equal(actions.length, 3); const [one, two, three] = actions; @@ -188,8 +188,8 @@ suite('MenuService', function () { const groups = menuService.createMenu(MenuId.ExplorerContext, contextKeyService).getActions(); - assert.equal(groups.length, 1); - const [[, actions]] = groups; + assert.equal(groups.length, 2); + const [, actions] = groups[1]; assert.equal(actions.length, 4); const [one, two, three, four] = actions; @@ -225,7 +225,7 @@ suite('MenuService', function () { assert.equal(groups.length, 1); const [[, actions]] = groups; - assert.equal(actions.length, 3); + assert.equal(actions.length, 5); const [one, two, three] = actions; assert.equal(one.id, 'c'); assert.equal(two.id, 'b'); diff --git a/src/vs/platform/commands/test/commandService.test.ts b/src/vs/platform/commands/test/commandService.test.ts index 78207993cb0..9815e83aaf8 100644 --- a/src/vs/platform/commands/test/commandService.test.ts +++ b/src/vs/platform/commands/test/commandService.test.ts @@ -16,7 +16,7 @@ import { ContextKeyService } from 'vs/platform/contextkey/browser/contextKeyServ import { SimpleConfigurationService } from 'vs/editor/standalone/browser/simpleServices'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import Event, { Emitter } from 'vs/base/common/event'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; class SimpleExtensionService implements IExtensionService { _serviceBrand: any; @@ -75,7 +75,7 @@ suite('CommandService', function () { lastEvent = activationEvent; return super.activateByEvent(activationEvent); } - }, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService()); + }, new ContextKeyService(new SimpleConfigurationService()), new NullLogService()); return service.executeCommand('foo').then(() => { assert.ok(lastEvent, 'onCommand:foo'); @@ -93,7 +93,7 @@ suite('CommandService', function () { activateByEvent(activationEvent: string): TPromise { return TPromise.wrapError(new Error('bad_activate')); } - }, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService()); + }, new ContextKeyService(new SimpleConfigurationService()), new NullLogService()); return service.executeCommand('foo').then(() => assert.ok(false), err => { assert.equal(err.message, 'bad_activate'); @@ -109,7 +109,7 @@ suite('CommandService', function () { whenInstalledExtensionsRegistered() { return new TPromise(_resolve => { /*ignore*/ }); } - }, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService()); + }, new ContextKeyService(new SimpleConfigurationService()), new NullLogService()); service.executeCommand('bar'); assert.equal(callCounter, 1); @@ -126,7 +126,7 @@ suite('CommandService', function () { whenInstalledExtensionsRegistered() { return new TPromise(_resolve => { resolveFunc = _resolve; }); } - }, new ContextKeyService(new SimpleConfigurationService()), new NoopLogService()); + }, new ContextKeyService(new SimpleConfigurationService()), new NullLogService()); let r = service.executeCommand('bar'); assert.equal(callCounter, 0); @@ -146,7 +146,7 @@ suite('CommandService', function () { new InstantiationService(), new SimpleExtensionService(), contextKeyService, - new NoopLogService() + new NullLogService() ); let counter = 0; diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index 108eabbba6f..69381904e0a 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -439,6 +439,10 @@ export class RawContextKey extends ContextKeyDefinedExpr { public isEqualTo(value: string): ContextKeyExpr { return ContextKeyExpr.equals(this.key, value); } + + public notEqualsTo(value: string): ContextKeyExpr { + return ContextKeyExpr.notEquals(this.key, value); + } } export interface IContext { diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index be03bb99677..98d63a1977b 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -192,6 +192,19 @@ export enum Verbosity { LONG } +export interface IRevertOptions { + + /** + * Forces to load the contents of the editor again even if the editor is not dirty. + */ + force?: boolean; + + /** + * A soft revert will clear dirty state of an editor but will not attempt to load it. + */ + soft?: boolean; +} + export interface IEditorInput extends IDisposable { /** @@ -232,7 +245,7 @@ export interface IEditorInput extends IDisposable { /** * Reverts this input. */ - revert(): TPromise; + revert(options?: IRevertOptions): TPromise; /** * Returns if the other object matches this input. diff --git a/src/vs/platform/environment/common/environment.ts b/src/vs/platform/environment/common/environment.ts index 932af88fa1e..7c6ed230677 100644 --- a/src/vs/platform/environment/common/environment.ts +++ b/src/vs/platform/environment/common/environment.ts @@ -52,6 +52,8 @@ export interface ParsedArgs { 'disable-updates'?: string; 'disable-crash-reporter'?: string; 'skip-add-to-recently-opened'?: boolean; + 'file-write'?: boolean; + 'file-chmod'?: boolean; } export const IEnvironmentService = createDecorator('environmentService'); @@ -71,6 +73,7 @@ export interface IEnvironmentService { args: ParsedArgs; execPath: string; + cliPath: string; appRoot: string; userHome: string; diff --git a/src/vs/platform/environment/node/argv.ts b/src/vs/platform/environment/node/argv.ts index 5de73213eff..627e7cdc920 100644 --- a/src/vs/platform/environment/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -9,6 +9,8 @@ import * as assert from 'assert'; import { firstIndex } from 'vs/base/common/arrays'; import { localize } from 'vs/nls'; import { ParsedArgs } from '../common/environment'; +import { isWindows } from 'vs/base/common/platform'; +import product from 'vs/platform/node/product'; const options: minimist.Opts = { string: [ @@ -54,7 +56,9 @@ const options: minimist.Opts = { 'disable-updates', 'disable-crash-reporter', 'skip-add-to-recently-opened', - 'status' + 'status', + 'file-write', + 'file-chmod' ], alias: { add: 'a', @@ -127,32 +131,38 @@ export function parseArgs(args: string[]): ParsedArgs { return minimist(args, options) as ParsedArgs; } -export const optionsHelp: { [name: string]: string; } = { +const optionsHelp: { [name: string]: string; } = { '-d, --diff ': localize('diff', "Compare two files with each other."), '-a, --add ': localize('add', "Add folder(s) to the last active window."), '-g, --goto ': localize('goto', "Open a file at the path on the specified line and character position."), - '--locale ': localize('locale', "The locale to use (e.g. en-US or zh-TW)."), '-n, --new-window': localize('newWindow', "Force a new instance of Code."), - '-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."), - '--prof-startup': localize('prof-startup', "Run CPU profiler during startup"), - '--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection uri."), - '--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection uri."), '-r, --reuse-window': localize('reuseWindow', "Force opening a file or folder in the last active window."), - '--user-data-dir ': localize('userDataDir', "Specifies the directory that user data is kept in, useful when running as root."), - '--log ': localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'."), - '--verbose': localize('verbose', "Print verbose output (implies --wait)."), '-w, --wait': localize('wait', "Wait for the files to be closed before returning."), + '--locale ': localize('locale', "The locale to use (e.g. en-US or zh-TW)."), + '--user-data-dir ': localize('userDataDir', "Specifies the directory that user data is kept in, useful when running as root."), + '-v, --version': localize('version', "Print version."), + '-h, --help': localize('help', "Print usage.") +}; + +const extensionsHelp: { [name: string]: string; } = { '--extensions-dir ': localize('extensionHomePath', "Set the root path for extensions."), '--list-extensions': localize('listExtensions', "List the installed extensions."), '--show-versions': localize('showVersions', "Show versions of installed extensions, when using --list-extension."), '--install-extension ( | )': localize('installExtension', "Installs an extension."), '--uninstall-extension ( | )': localize('uninstallExtension', "Uninstalls an extension."), - '--enable-proposed-api ': localize('experimentalApis', "Enables proposed api features for an extension."), - '--disable-extensions': localize('disableExtensions', "Disable all installed extensions."), - '--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration."), + '--enable-proposed-api ': localize('experimentalApis', "Enables proposed api features for an extension.") +}; + +const troubleshootingHelp: { [name: string]: string; } = { + '--verbose': localize('verbose', "Print verbose output (implies --wait)."), + '--log ': localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'."), '-s, --status': localize('status', "Print process usage and diagnostics information."), - '-v, --version': localize('version', "Print version."), - '-h, --help': localize('help', "Print usage.") + '-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."), + '--prof-startup': localize('prof-startup', "Run CPU profiler during startup"), + '--disable-extensions': localize('disableExtensions', "Disable all installed extensions."), + '--inspect-extensions': localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection uri."), + '--inspect-brk-extensions': localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection uri."), + '--disable-gpu': localize('disableGPU', "Disable GPU hardware acceleration.") }; export function formatOptions(options: { [name: string]: string; }, columns: number): string { @@ -197,6 +207,14 @@ export function buildHelpMessage(fullName: string, name: string, version: string ${ localize('usage', "Usage")}: ${executable} [${localize('options', "options")}] [${localize('paths', 'paths')}...] +${ isWindows ? localize('stdinWindows', "To read output from another program, append '-' (e.g. 'echo Hello World | {0} -')", product.applicationName) : localize('stdinUnix', "To read from stdin, append '-' (e.g. 'ps aux | grep code | {0} -')", product.applicationName)} + ${ localize('optionsUpperCase', "Options")}: -${formatOptions(optionsHelp, columns)}`; +${formatOptions(optionsHelp, columns)} + +${ localize('extensionsManagement', "Extensions Management")}: +${formatOptions(extensionsHelp, columns)} + +${ localize('troubleshooting', "Troubleshooting")}: +${formatOptions(troubleshootingHelp, columns)}`; } diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 4e7728abac0..b5e01b791cc 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -14,6 +14,7 @@ import pkg from 'vs/platform/node/package'; import product from 'vs/platform/node/product'; import { LogLevel } from 'vs/platform/log/common/log'; import { toLocalISOString } from 'vs/base/common/date'; +import { isWindows, isLinux } from 'vs/base/common/platform'; // Read this before there's any chance it is overwritten // Related to https://github.com/Microsoft/vscode/issues/30624 @@ -29,15 +30,44 @@ function getNixIPCHandle(userDataPath: string, type: string): string { function getWin32IPCHandle(userDataPath: string, type: string): string { const scope = crypto.createHash('md5').update(userDataPath).digest('hex'); + return `\\\\.\\pipe\\${scope}-${pkg.version}-${type}-sock`; } function getIPCHandle(userDataPath: string, type: string): string { - if (process.platform === 'win32') { + if (isWindows) { return getWin32IPCHandle(userDataPath, type); - } else { - return getNixIPCHandle(userDataPath, type); } + + return getNixIPCHandle(userDataPath, type); +} + +function getCLIPath(execPath: string, appRoot: string, isBuilt: boolean): string { + + // Windows + if (isWindows) { + if (isBuilt) { + return path.join(path.dirname(execPath), 'bin', `${product.applicationName}.cmd`); + } + + return path.join(appRoot, 'scripts', 'code-cli.bat'); + } + + // Linux + if (isLinux) { + if (isBuilt) { + return path.join(path.dirname(execPath), 'bin', `${product.applicationName}`); + } + + return path.join(appRoot, 'scripts', 'code-cli.sh'); + } + + // macOS + if (isBuilt) { + return path.join(appRoot, 'bin', 'code'); + } + + return path.join(appRoot, 'scripts', 'code-cli.sh'); } export class EnvironmentService implements IEnvironmentService { @@ -51,6 +81,9 @@ export class EnvironmentService implements IEnvironmentService { get execPath(): string { return this._execPath; } + @memoize + get cliPath(): string { return getCLIPath(this.execPath, this.appRoot, this.isBuilt); } + readonly logsPath: string; @memoize diff --git a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts index 8fd3621d647..3b60c27a367 100644 --- a/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts +++ b/src/vs/platform/extensionManagement/common/extensionManagementUtil.ts @@ -39,6 +39,27 @@ export function adoptToGalleryExtensionId(id: string): string { return id.replace(EXTENSION_IDENTIFIER_REGEX, (match, publisher: string, name: string) => getGalleryExtensionId(publisher, name)); } +export function groupByExtension(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] { + const byExtension: T[][] = []; + const findGroup = extension => { + for (const group of byExtension) { + if (group.some(e => areSameExtensions(getExtensionIdentifier(e), getExtensionIdentifier(extension)))) { + return group; + } + } + return null; + }; + for (const extension of extensions) { + const group = findGroup(extension); + if (group) { + group.push(extension); + } else { + byExtension.push([extension]); + } + } + return byExtension; +} + export function getLocalExtensionTelemetryData(extension: ILocalExtension): any { return { id: getGalleryExtensionIdFromLocal(extension), diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index c447fb77b99..7043baa881e 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -21,31 +21,33 @@ import { StatisticType, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; -import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; -import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; +import { getGalleryExtensionIdFromLocal, adoptToGalleryExtensionId, areSameExtensions, getGalleryExtensionId, groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { localizeManifest } from '../common/extensionNls'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { Limiter } from 'vs/base/common/async'; import Event, { Emitter } from 'vs/base/common/event'; import * as semver from 'semver'; -import { groupBy, values } from 'vs/base/common/collections'; import URI from 'vs/base/common/uri'; import { IChoiceService, Severity } from 'vs/platform/message/common/message'; import pkg from 'vs/platform/node/package'; import { isMacintosh } from 'vs/base/common/platform'; import { MANIFEST_CACHE_FOLDER, USER_MANIFEST_CACHE_FILE } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); -const INSTALL_ERROR_OBSOLETE = 'obsolete'; +const ERROR_SCANNING_SYS_EXTENSIONS = 'scanningSystem'; +const ERROR_SCANNING_USER_EXTENSIONS = 'scanningUser'; +const INSTALL_ERROR_UNSET_UNINSTALLED = 'unsetUninstalled'; const INSTALL_ERROR_INCOMPATIBLE = 'incompatible'; const INSTALL_ERROR_DOWNLOADING = 'downloading'; const INSTALL_ERROR_VALIDATING = 'validating'; const INSTALL_ERROR_GALLERY = 'gallery'; const INSTALL_ERROR_LOCAL = 'local'; const INSTALL_ERROR_EXTRACTING = 'extracting'; +const INSTALL_ERROR_DELETING = 'deleting'; const INSTALL_ERROR_UNKNOWN = 'unknown'; -export class InstallationError extends Error { +export class ExtensionManagementError extends Error { constructor(message: string, readonly code: string) { super(message); } @@ -91,7 +93,6 @@ interface InstallableExtension { zipPath: string; id: string; metadata?: IGalleryMetadata; - current?: ILocalExtension; } export class ExtensionManagementService implements IExtensionManagementService { @@ -99,9 +100,9 @@ export class ExtensionManagementService implements IExtensionManagementService { _serviceBrand: any; private extensionsPath: string; - private obsoletePath: string; + private uninstalledPath: string; private userDataPath: string; - private obsoleteFileLimiter: Limiter; + private uninstalledFileLimiter: Limiter; private disposables: IDisposable[] = []; private _onInstallExtension = new Emitter(); @@ -119,12 +120,13 @@ export class ExtensionManagementService implements IExtensionManagementService { constructor( @IEnvironmentService environmentService: IEnvironmentService, @IChoiceService private choiceService: IChoiceService, - @IExtensionGalleryService private galleryService: IExtensionGalleryService + @IExtensionGalleryService private galleryService: IExtensionGalleryService, + @ILogService private logService: ILogService, ) { this.extensionsPath = environmentService.extensionsPath; - this.obsoletePath = path.join(this.extensionsPath, '.obsolete'); + this.uninstalledPath = path.join(this.extensionsPath, '.obsolete'); this.userDataPath = environmentService.userDataPath; - this.obsoleteFileLimiter = new Limiter(1); + this.uninstalledFileLimiter = new Limiter(1); } private deleteExtensionsManifestCache(): void { @@ -142,29 +144,42 @@ export class ExtensionManagementService implements IExtensionManagementService { return validateLocalExtension(zipPath) .then(manifest => { const identifier = { id: getLocalExtensionIdFromManifest(manifest) }; - return this.isObsolete(identifier.id) - .then(isObsolete => { - if (isObsolete) { - return TPromise.wrapError(new Error(nls.localize('restartCodeLocal', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name))); - } - return this.checkOutdated(manifest) - .then(validated => { - if (validated) { - this._onInstallExtension.fire({ identifier, zipPath }); - return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) - .then( - metadata => this.installFromZipPath(identifier, zipPath, metadata, manifest), - error => this.installFromZipPath(identifier, zipPath, null, manifest)); - } - return null; - }); - }); + return this.unsetUninstalledAndRemove(identifier.id) + .then( + () => this.checkOutdated(manifest) + .then(validated => { + if (validated) { + this.logService.info('Installing the extension:', identifier.id); + this._onInstallExtension.fire({ identifier, zipPath }); + return this.getMetadata(getGalleryExtensionId(manifest.publisher, manifest.name)) + .then( + metadata => this.installFromZipPath(identifier, zipPath, metadata, manifest), + error => this.installFromZipPath(identifier, zipPath, null, manifest)) + .then(() => this.logService.info('Successfully installed the extension:', identifier.id), e => this.logService.error('Failed to install the extension:', identifier.id, e.message)); + } + return null; + }), + e => TPromise.wrapError(new Error(nls.localize('restartCode', "Please restart Code before reinstalling {0}.", manifest.displayName || manifest.name)))); + }); + } + + private unsetUninstalledAndRemove(id: string): TPromise { + return this.isUninstalled(id) + .then(isUninstalled => { + if (isUninstalled) { + this.logService.trace('Removing the extension:', id); + const extensionPath = path.join(this.extensionsPath, id); + return pfs.rimraf(extensionPath) + .then(() => this.unsetUninstalled(id)) + .then(() => this.logService.info('Removed the extension:', id)); + } + return null; }); } private checkOutdated(manifest: IExtensionManifest): TPromise { const extensionIdentifier = { id: getGalleryExtensionId(manifest.publisher, manifest.name) }; - return this.getInstalled() + return this.getInstalled(LocalExtensionType.User) .then(installedExtensions => { const newer = installedExtensions.filter(local => areSameExtensions(extensionIdentifier, { id: getGalleryExtensionIdFromLocal(local) }) && semver.gt(local.manifest.version, manifest.version))[0]; if (newer) { @@ -192,7 +207,7 @@ export class ExtensionManagementService implements IExtensionManagementService { return this.getDependenciesToInstall(local.manifest.extensionDependencies) .then(dependenciesToInstall => this.downloadAndInstallExtensions(metadata ? dependenciesToInstall.filter(d => d.identifier.uuid !== metadata.id) : dependenciesToInstall)) .then(() => local, error => { - this.uninstallExtension(local.identifier); + this.uninstallExtension(local); return TPromise.wrapError(error); }); } @@ -226,48 +241,23 @@ export class ExtensionManagementService implements IExtensionManagementService { return this.galleryService.loadCompatibleVersion(extension) .then(compatible => { if (!compatible) { - return TPromise.wrapError(new InstallationError(nls.localize('notFoundCompatible', "Unable to install because, the extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); + return TPromise.wrapError(new ExtensionManagementError(nls.localize('notFoundCompatible', "Unable to install '{0}'; there is no available version compatible with VS Code '{1}'.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); } return this.getDependenciesToInstall(compatible.properties.dependencies) .then( - dependenciesToInstall => { - const extensionsToInstall = [compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]; - return this.checkForObsolete(extensionsToInstall) - .then( - extensionsToInstall => extensionsToInstall, - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_OBSOLETE)) - ); - }, - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); + dependenciesToInstall => ([compatible, ...dependenciesToInstall.filter(d => d.identifier.uuid !== compatible.identifier.uuid)]), + error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); }, - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); + error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); } private downloadAndInstallExtensions(extensions: IGalleryExtension[]): TPromise { - return this.getInstalled(LocalExtensionType.User) - .then( - installed => TPromise.join(extensions.map(extensionToInstall => - this.downloadInstallableExtension(extensionToInstall, installed) - .then(installableExtension => this.installExtension(installableExtension).then(null, e => TPromise.wrapError(new InstallationError(this.joinErrors(e).message, INSTALL_ERROR_EXTRACTING)))) - )).then(null, errors => this.rollback(extensions).then(() => TPromise.wrapError(errors), () => TPromise.wrapError(errors))), - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_LOCAL))); + return TPromise.join(extensions.map(extensionToInstall => this.downloadInstallableExtension(extensionToInstall) + .then(installableExtension => this.installExtension(installableExtension)) + )).then(null, errors => this.rollback(extensions).then(() => TPromise.wrapError(errors), () => TPromise.wrapError(errors))); } - private checkForObsolete(extensionsToInstall: IGalleryExtension[]): TPromise { - return this.filterObsolete(...extensionsToInstall.map(i => getLocalExtensionIdFromGallery(i, i.version))) - .then(obsolete => { - if (obsolete.length) { - if (isMacintosh) { - return TPromise.wrapError(new Error(nls.localize('quitCode', "Unable to install because an obsolete instance of the extension is still running. Please Quit and Start VS Code before reinstalling."))); - } - return TPromise.wrapError(new Error(nls.localize('exitCode', "Unable to install because an obsolete instance of the extension is still running. Please Exit and Start VS Code before reinstalling."))); - } - return extensionsToInstall; - }); - } - - private downloadInstallableExtension(extension: IGalleryExtension, installed: ILocalExtension[]): TPromise { - const current = installed.filter(i => i.identifier.uuid === extension.identifier.uuid)[0]; + private downloadInstallableExtension(extension: IGalleryExtension): TPromise { const id = getLocalExtensionIdFromGallery(extension, extension.version); const metadata = { id: extension.identifier.uuid, @@ -279,29 +269,34 @@ export class ExtensionManagementService implements IExtensionManagementService { .then( compatible => { if (compatible) { + this.logService.trace('Started downloading extension:', extension.name); return this.galleryService.download(extension) .then( - zipPath => validateLocalExtension(zipPath) - .then( - () => ({ zipPath, id, metadata, current }), - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING)) - ), - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING))); + zipPath => { + this.logService.info('Downloaded extension:', extension.name); + return validateLocalExtension(zipPath) + .then( + () => ({ zipPath, id, metadata }), + error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_VALIDATING)) + ); + }, + error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_DOWNLOADING))); } else { - return TPromise.wrapError(new InstallationError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); + return TPromise.wrapError(new ExtensionManagementError(nls.localize('notFoundCompatibleDependency', "Unable to install because, the depending extension '{0}' compatible with current version '{1}' of VS Code is not found.", extension.identifier.id, pkg.version), INSTALL_ERROR_INCOMPATIBLE)); } }, - error => TPromise.wrapError(new InstallationError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); + error => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(error).message, INSTALL_ERROR_GALLERY))); } private rollback(extensions: IGalleryExtension[]): TPromise { return this.filterOutUninstalled(extensions) - .then(installed => TPromise.join(installed.map(local => this.uninstallExtension(local.identifier)))) + .then(installed => TPromise.join(installed.map(local => this.uninstallExtension(local)))) .then(() => null, () => null); } private onInstallExtensions(extensions: IGalleryExtension[]): void { for (const extension of extensions) { + this.logService.info('Installing extension:', extension.name); const id = getLocalExtensionIdFromGallery(extension, extension.version); this._onInstallExtension.fire({ identifier: { id, uuid: extension.identifier.uuid }, gallery: extension }); } @@ -313,9 +308,11 @@ export class ExtensionManagementService implements IExtensionManagementService { const local = locals[index]; const error = errors[index]; if (local) { + this.logService.info(`Extensions installed successfully:`, gallery.identifier.id); this._onDidInstallExtension.fire({ identifier, gallery, local }); } else { - const errorCode = error && (error).code ? (error).code : INSTALL_ERROR_UNKNOWN; + const errorCode = error && (error).code ? (error).code : INSTALL_ERROR_UNKNOWN; + this.logService.error(`Failed to install extension:`, gallery.identifier.id, error ? error.message : errorCode); this._onDidInstallExtension.fire({ identifier, gallery, error: errorCode }); } }); @@ -346,14 +343,54 @@ export class ExtensionManagementService implements IExtensionManagementService { return filtered.length ? filtered[0] : null; } - private installExtension({ zipPath, id, metadata, current }: InstallableExtension): TPromise { - const extensionPath = path.join(this.extensionsPath, id); + private installExtension(installableExtension: InstallableExtension): TPromise { + return this.unsetUninstalledAndGetLocal(installableExtension.id) + .then( + local => { + if (local) { + return local; + } + return this.extractAndInstall(installableExtension); + }, + e => { + if (isMacintosh) { + return TPromise.wrapError(new ExtensionManagementError(nls.localize('quitCode', "Unable to install the extension. Please Quit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED)); + } + return TPromise.wrapError(new ExtensionManagementError(nls.localize('exitCode', "Unable to install the extension. Please Exit and Start VS Code before reinstalling."), INSTALL_ERROR_UNSET_UNINSTALLED)); + }); + } - return pfs.rimraf(extensionPath).then(() => { - return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }) - .then(() => readManifest(extensionPath)) - .then(({ manifest }) => { - return pfs.readdir(extensionPath).then(children => { + private unsetUninstalledAndGetLocal(id: string): TPromise { + return this.isUninstalled(id) + .then(isUninstalled => { + if (isUninstalled) { + this.logService.trace('Removing the extension from uninstalled list:', id); + // If the same version of extension is marked as uninstalled, remove it from there and return the local. + return this.unsetUninstalled(id) + .then(() => { + this.logService.info('Removed the extension from uninstalled list:', id); + return this.getInstalled(LocalExtensionType.User); + }) + .then(installed => installed.filter(i => i.identifier.id === id)[0]); + } + return null; + }); + } + + private extractAndInstall({ zipPath, id, metadata }: InstallableExtension): TPromise { + const extensionPath = path.join(this.extensionsPath, id); + return pfs.rimraf(extensionPath) + .then(() => { + this.logService.trace(`Started extracting the extension from ${zipPath} to ${extensionPath}`); + return extract(zipPath, extensionPath, { sourcePath: 'extension', overwrite: true }) + .then( + () => { + this.logService.info(`Extracted extension to ${extensionPath}:`, id); + return TPromise.join([readManifest(extensionPath), pfs.readdir(extensionPath)]) + .then(null, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_LOCAL))); + }, + e => TPromise.wrapError(new ExtensionManagementError(e.message, INSTALL_ERROR_EXTRACTING))) + .then(([{ manifest }, children]) => { const readme = children.filter(child => /^readme(\.txt|\.md|)$/i.test(child))[0]; const readmeUrl = readme ? URI.file(path.join(extensionPath, readme)).toString() : null; const changelog = children.filter(child => /^changelog(\.txt|\.md|)$/i.test(child))[0]; @@ -363,27 +400,26 @@ export class ExtensionManagementService implements IExtensionManagementService { const local: ILocalExtension = { type, identifier, manifest, metadata, path: extensionPath, readmeUrl, changelogUrl }; + this.logService.trace(`Updating metadata of the extension:`, id); return this.saveMetadataForLocalExtension(local) - .then(() => this.checkForRename(current, local)) - .then(() => local); + .then(() => { + this.logService.info(`Updated metadata of the extension:`, id); + return local; + }, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_LOCAL))); }); - }); - }); + }, e => TPromise.wrapError(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING))); } uninstall(extension: ILocalExtension, force = false): TPromise { this.deleteExtensionsManifestCache(); - return this.removeOutdatedExtensions() - .then(() => - this.scanUserExtensions() - .then(installed => { - const promises = installed - .filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name) - .map(e => this.checkForDependenciesAndUninstall(e, installed, force)); - return TPromise.join(promises).then(null, error => TPromise.wrapError(this.joinErrors(error))); - })) - .then(() => { /* drop resolved value */ }); + return this.getInstalled(LocalExtensionType.User) + .then(installed => { + const promises = installed + .filter(e => e.manifest.publisher === extension.manifest.publisher && e.manifest.name === extension.manifest.name) + .map(e => this.checkForDependenciesAndUninstall(e, installed, force)); + return TPromise.join(promises).then(() => null, error => TPromise.wrapError(this.joinErrors(error))); + }); } updateMetadata(local: ILocalExtension, metadata: IGalleryMetadata): TPromise { @@ -413,14 +449,6 @@ export class ExtensionManagementService implements IExtensionManagementService { }); } - private checkForRename(currentExtension: ILocalExtension, newExtension: ILocalExtension): TPromise { - // Check if the gallery id for current and new exensions are same, if not, remove the current one. - if (currentExtension && getGalleryExtensionIdFromLocal(currentExtension) !== getGalleryExtensionIdFromLocal(newExtension)) { - return this.setObsolete(currentExtension.identifier.id); - } - return TPromise.as(null); - } - private joinErrors(errorOrErrors: (Error | string) | ((Error | string)[])): Error { const errors = Array.isArray(errorOrErrors) ? errorOrErrors : [errorOrErrors]; if (errors.length === 1) { @@ -460,6 +488,7 @@ export class ExtensionManagementService implements IExtensionManagementService { nls.localize('uninstallAll', "All"), nls.localize('cancel', "Cancel") ]; + this.logService.info('Requesting for confirmation to uninstall extension with dependencies', extension.identifier.id); return this.choiceService.choose(Severity.Info, message, options, 2, true) .then(value => { if (value === 0) { @@ -469,6 +498,7 @@ export class ExtensionManagementService implements IExtensionManagementService { const dependencies = distinct(this.getDependenciesToUninstallRecursively(extension, installed, [])).filter(e => e !== extension); return this.uninstallWithDependencies(extension, dependencies, installed); } + this.logService.info('Cancelled uninstalling extension:', extension.identifier.id); return TPromise.wrapError(errors.canceled()); }, error => TPromise.wrapError(errors.canceled())); } @@ -483,11 +513,13 @@ export class ExtensionManagementService implements IExtensionManagementService { nls.localize('ok', "OK"), nls.localize('cancel', "Cancel") ]; + this.logService.info('Requesting for confirmation to uninstall extension', extension.identifier.id); return this.choiceService.choose(Severity.Info, message, options, 1, true) .then(value => { if (value === 0) { return this.uninstallWithDependencies(extension, [], installed); } + this.logService.info('Cancelled uninstalling extension:', extension.identifier.id); return TPromise.wrapError(errors.canceled()); }, error => TPromise.wrapError(errors.canceled())); } @@ -498,7 +530,7 @@ export class ExtensionManagementService implements IExtensionManagementService { if (dependents.length) { return TPromise.wrapError(new Error(this.getDependentsErrorMessage(extension, dependents))); } - return TPromise.join([this.uninstallExtension(extension.identifier), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null); + return TPromise.join([this.uninstallExtension(extension), ...dependenciesToUninstall.map(d => this.doUninstall(d))]).then(() => null); } private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string { @@ -549,7 +581,7 @@ export class ExtensionManagementService implements IExtensionManagementService { private doUninstall(extension: ILocalExtension): TPromise { return this.preUninstallExtension(extension) - .then(() => this.uninstallExtension(extension.identifier)) + .then(() => this.uninstallExtension(extension)) .then(() => this.postUninstallExtension(extension), error => { this.postUninstallExtension(extension, INSTALL_ERROR_LOCAL); @@ -561,24 +593,29 @@ export class ExtensionManagementService implements IExtensionManagementService { const extensionPath = path.join(this.extensionsPath, extension.identifier.id); return pfs.exists(extensionPath) .then(exists => exists ? null : TPromise.wrapError(new Error(nls.localize('notExists', "Could not find extension")))) - .then(() => this._onUninstallExtension.fire(extension.identifier)); + .then(() => { + this.logService.info('Uninstalling extension:', extension.identifier.id); + this._onUninstallExtension.fire(extension.identifier); + }); } - private uninstallExtension({ id }: IExtensionIdentifier): TPromise { - const extensionPath = path.join(this.extensionsPath, id); - return this.setObsolete(id) - .then(() => pfs.rimraf(extensionPath)) - .then(() => this.unsetObsolete(id)); + private uninstallExtension(local: ILocalExtension): TPromise { + const identifier = { id: getGalleryExtensionIdFromLocal(local), uuid: local.identifier.uuid }; + return this.scanUserExtensions(false) // Uninstall all extensions which are same as requested + .then(extensions => extensions.filter(i => areSameExtensions({ id: getGalleryExtensionIdFromLocal(i), uuid: i.identifier.uuid }, identifier))) + .then(uninstalled => this.setUninstalled(...uninstalled.map(u => u.identifier.id))); } private async postUninstallExtension(extension: ILocalExtension, error?: string): TPromise { - if (!error) { + if (error) { + this.logService.error('Failed to uninstall extension:', extension.identifier.id, error); + } else { + this.logService.info('Successfully uninstalled extension:', extension.identifier.id); // only report if extension has a mapped gallery extension. UUID identifies the gallery extension. if (extension.identifier.uuid) { await this.galleryService.reportStatistic(extension.manifest.publisher, extension.manifest.name, extension.manifest.version, StatisticType.Uninstall); } } - this._onDidUninstallExtension.fire({ identifier: extension.identifier, error }); } @@ -586,25 +623,36 @@ export class ExtensionManagementService implements IExtensionManagementService { const promises = []; if (type === null || type === LocalExtensionType.System) { - promises.push(this.scanSystemExtensions()); + promises.push(this.scanSystemExtensions().then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_SYS_EXTENSIONS))); } if (type === null || type === LocalExtensionType.User) { - promises.push(this.scanUserExtensions()); + promises.push(this.scanUserExtensions(true).then(null, e => new ExtensionManagementError(this.joinErrors(e).message, ERROR_SCANNING_USER_EXTENSIONS))); } - return TPromise.join(promises).then(flatten); + return TPromise.join(promises).then(flatten, errors => TPromise.wrapError(this.joinErrors(errors))); } private scanSystemExtensions(): TPromise { - return this.scanExtensions(SystemExtensionsRoot, LocalExtensionType.System); + this.logService.trace('Started scanning system extensions'); + return this.scanExtensions(SystemExtensionsRoot, LocalExtensionType.System) + .then(result => { + this.logService.info('Scanned system extensions:', result.length); + return result; + }); } - private scanUserExtensions(): TPromise { - return this.scanExtensions(this.extensionsPath, LocalExtensionType.User).then(extensions => { - const byId = values(groupBy(extensions, p => getGalleryExtensionIdFromLocal(p))); - return byId.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]); - }); + private scanUserExtensions(excludeOutdated: boolean): TPromise { + this.logService.trace('Started scanning user extensions'); + return this.scanExtensions(this.extensionsPath, LocalExtensionType.User) + .then(extensions => { + this.logService.info('Scanned user extensions:', extensions.length); + if (excludeOutdated) { + const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid })); + return byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version))[0]); + } + return extensions; + }); } private scanExtensions(root: string, type: LocalExtensionType): TPromise { @@ -636,91 +684,69 @@ export class ExtensionManagementService implements IExtensionManagementService { } private scanExtensionFolders(root: string): TPromise { - return this.getObsoleteExtensions() - .then(obsolete => pfs.readdir(root).then(extensions => extensions.filter(id => !obsolete[id]))); + return this.getUninstalledExtensions() + .then(uninstalled => pfs.readdir(root).then(extensions => extensions.filter(id => !uninstalled[id]))); } removeDeprecatedExtensions(): TPromise { - return TPromise.join([ - // Remove obsolte extensions first to avoid removing installed older extension. See #38609. - this.removeObsoleteExtensions(), - this.removeOutdatedExtensions() - ]); - } + return this.getUninstalledExtensions() + .then(uninstalled => { + const unInstalledExtensionIds = Object.keys(uninstalled); + return this.scanUserExtensions(false) + .then(extensions => { + const byExtension: ILocalExtension[][] = groupByExtension(extensions, e => ({ id: getGalleryExtensionIdFromLocal(e), uuid: e.identifier.uuid })); + const outDatedExtensionIds = flatten(byExtension.map(p => p.sort((a, b) => semver.rcompare(a.manifest.version, b.manifest.version)).slice(1))) + .map(a => a.identifier.id); - private removeOutdatedExtensions(): TPromise { - return this.getOutdatedExtensionIds() - .then(extensionIds => this.removeExtensions(extensionIds)); - } - - private removeObsoleteExtensions(): TPromise { - return this.getObsoleteExtensions() - .then(obsolete => Object.keys(obsolete)) - .then(extensionIds => this.removeExtensions(extensionIds)); - } - - private removeExtensions(extensionsIds: string[]): TPromise { - return TPromise.join(extensionsIds.map(id => { - return pfs.rimraf(path.join(this.extensionsPath, id)) - .then(() => this.withObsoleteExtensions(obsolete => delete obsolete[id])); - })); - } - - private getOutdatedExtensionIds(): TPromise { - return this.scanExtensionFolders(this.extensionsPath) - .then(folders => { - const galleryFolders = folders - .map(folder => (assign({ folder }, getIdAndVersionFromLocalExtensionId(folder)))) - .filter(({ id, version }) => !!id && !!version); - - const byId = values(groupBy(galleryFolders, p => p.id)); - - return flatten(byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version)).slice(1))) - .map(a => a.folder); + return TPromise.join([...unInstalledExtensionIds, ...outDatedExtensionIds].map(id => { + return pfs.rimraf(path.join(this.extensionsPath, id)) + .then(() => this.withUninstalledExtensions(uninstalled => delete uninstalled[id])); + })); + }); }); } - private isObsolete(id: string): TPromise { - return this.filterObsolete(id).then(obsolete => obsolete.length === 1); + private isUninstalled(id: string): TPromise { + return this.filterUninstalled(id).then(uninstalled => uninstalled.length === 1); } - private filterObsolete(...ids: string[]): TPromise { - return this.withObsoleteExtensions(allObsolete => { - const obsolete = []; + private filterUninstalled(...ids: string[]): TPromise { + return this.withUninstalledExtensions(allUninstalled => { + const uninstalled = []; for (const id of ids) { - if (!!allObsolete[id]) { - obsolete.push(id); + if (!!allUninstalled[id]) { + uninstalled.push(id); } } - return obsolete; + return uninstalled; }); } - private setObsolete(id: string): TPromise { - return this.withObsoleteExtensions(obsolete => assign(obsolete, { [id]: true })); + private setUninstalled(...ids: string[]): TPromise { + return this.withUninstalledExtensions(uninstalled => assign(uninstalled, ids.reduce((result, id) => { result[id] = true; return result; }, {}))); } - private unsetObsolete(id: string): TPromise { - return this.withObsoleteExtensions(obsolete => delete obsolete[id]); + private unsetUninstalled(id: string): TPromise { + return this.withUninstalledExtensions(uninstalled => delete uninstalled[id]); } - private getObsoleteExtensions(): TPromise<{ [id: string]: boolean; }> { - return this.withObsoleteExtensions(obsolete => obsolete); + private getUninstalledExtensions(): TPromise<{ [id: string]: boolean; }> { + return this.withUninstalledExtensions(uninstalled => uninstalled); } - private withObsoleteExtensions(fn: (obsolete: { [id: string]: boolean; }) => T): TPromise { - return this.obsoleteFileLimiter.queue(() => { + private withUninstalledExtensions(fn: (uninstalled: { [id: string]: boolean; }) => T): TPromise { + return this.uninstalledFileLimiter.queue(() => { let result: T = null; - return pfs.readFile(this.obsoletePath, 'utf8') + return pfs.readFile(this.uninstalledPath, 'utf8') .then(null, err => err.code === 'ENOENT' ? TPromise.as('{}') : TPromise.wrapError(err)) .then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; } }) - .then(obsolete => { result = fn(obsolete); return obsolete; }) - .then(obsolete => { - if (Object.keys(obsolete).length === 0) { - return pfs.rimraf(this.obsoletePath); + .then(uninstalled => { result = fn(uninstalled); return uninstalled; }) + .then(uninstalled => { + if (Object.keys(uninstalled).length === 0) { + return pfs.rimraf(this.uninstalledPath); } else { - const raw = JSON.stringify(obsolete); - return pfs.writeFile(this.obsoletePath, raw); + const raw = JSON.stringify(uninstalled); + return pfs.writeFile(this.uninstalledPath, raw); } }) .then(() => result); diff --git a/src/vs/platform/extensions/common/extensions.ts b/src/vs/platform/extensions/common/extensions.ts index 7c78e26544b..fe757879b07 100644 --- a/src/vs/platform/extensions/common/extensions.ts +++ b/src/vs/platform/extensions/common/extensions.ts @@ -127,7 +127,6 @@ export interface IExtensionService { _serviceBrand: any; /** - * TODO@Ben: Delete this and use `whenInstalledExtensionsRegistered` * An event emitted when extensions are registered after their extension points got handled. * * This event will also fire on startup to signal the installed extensions. diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 262916172b6..ab62f52ccbe 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -506,6 +506,12 @@ export interface IResolveContentOptions { * The optional guessEncoding parameter allows to guess encoding from content of the file. */ autoGuessEncoding?: boolean; + + /** + * Is an integer specifying where to begin reading from in the file. If position is null, + * data will be read from the current file position. + */ + position?: number; } export interface IUpdateContentOptions { @@ -525,6 +531,12 @@ export interface IUpdateContentOptions { */ overwriteReadonly?: boolean; + /** + * Wether to write to the file as elevated (admin) user. When setting this option a prompt will + * ask the user to authenticate as super user. + */ + writeElevated?: boolean; + /** * The last known modification time of the file. This can be used to prevent dirty writes. */ @@ -556,7 +568,7 @@ export interface IImportResult { } export class FileOperationError extends Error { - constructor(message: string, public fileOperationResult: FileOperationResult) { + constructor(message: string, public fileOperationResult: FileOperationResult, public options?: IResolveContentOptions & IUpdateContentOptions & ICreateFileOptions) { super(message); } } @@ -569,6 +581,7 @@ export enum FileOperationResult { FILE_MODIFIED_SINCE, FILE_MOVE_CONFLICT, FILE_READ_ONLY, + FILE_PERMISSION_DENIED, FILE_TOO_LARGE, FILE_INVALID_PATH } diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index 7e20f516ecf..761b5c05265 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -22,10 +22,6 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { isEqual } from 'vs/base/common/paths'; import { RunOnceScheduler } from 'vs/base/common/async'; -export interface ILegacyRecentlyOpened extends IRecentlyOpened { - folders: string[]; // TODO@Ben migration -} - export class HistoryMainService implements IHistoryMainService { private static readonly MAX_TOTAL_RECENT_ENTRIES = 100; @@ -179,9 +175,9 @@ export class HistoryMainService implements IHistoryMainService { let files: string[]; // Get from storage - const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey) as ILegacyRecentlyOpened; + const storedRecents = this.stateService.getItem(HistoryMainService.recentlyOpenedStorageKey); if (storedRecents) { - workspaces = storedRecents.workspaces || storedRecents.folders || []; + workspaces = storedRecents.workspaces || []; files = storedRecents.files || []; } else { workspaces = []; diff --git a/src/vs/platform/integrity/common/integrity.ts b/src/vs/platform/integrity/common/integrity.ts index 747c07ca50d..6984b0fa8e7 100644 --- a/src/vs/platform/integrity/common/integrity.ts +++ b/src/vs/platform/integrity/common/integrity.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import URI from 'vs/base/common/uri'; @@ -25,5 +24,5 @@ export interface IntegrityTestResult { export interface IIntegrityService { _serviceBrand: any; - isPure(): TPromise; + isPure(): Thenable; } diff --git a/src/vs/platform/integrity/node/integrityServiceImpl.ts b/src/vs/platform/integrity/node/integrityServiceImpl.ts index 33ce7582d89..96798846b0b 100644 --- a/src/vs/platform/integrity/node/integrityServiceImpl.ts +++ b/src/vs/platform/integrity/node/integrityServiceImpl.ts @@ -15,6 +15,7 @@ import URI from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { Action } from 'vs/base/common/actions'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; interface IStorageData { dontShowPrompt: boolean; @@ -60,11 +61,12 @@ export class IntegrityServiceImpl implements IIntegrityService { private _messageService: IMessageService; private _storage: IntegrityStorage; - private _isPurePromise: TPromise; + private _isPurePromise: Thenable; constructor( @IMessageService messageService: IMessageService, - @IStorageService storageService: IStorageService + @IStorageService storageService: IStorageService, + @ILifecycleService private lifecycleService: ILifecycleService ) { this._messageService = messageService; this._storage = new IntegrityStorage(storageService); @@ -124,14 +126,14 @@ export class IntegrityServiceImpl implements IIntegrityService { }); } - public isPure(): TPromise { + public isPure(): Thenable { return this._isPurePromise; } - private _isPure(): TPromise { + private _isPure(): Thenable { const expectedChecksums = product.checksums || {}; - return TPromise.timeout(10000).then(() => { + return this.lifecycleService.when(LifecyclePhase.Eventually).then(() => { let asyncResults: TPromise[] = Object.keys(expectedChecksums).map((filename) => { return this._resolve(filename, expectedChecksums[filename]); }); diff --git a/src/vs/platform/list/browser/listService.ts b/src/vs/platform/list/browser/listService.ts index b4d344e30ac..5ae623175f4 100644 --- a/src/vs/platform/list/browser/listService.ts +++ b/src/vs/platform/list/browser/listService.ts @@ -73,12 +73,18 @@ export class ListService implements IListService { } const RawWorkbenchListFocusContextKey = new RawContextKey('listFocus', true); +export const WorkbenchListSupportsMultiSelectContextKey = new RawContextKey('listSupportsMultiselect', true); export const WorkbenchListFocusContextKey = ContextKeyExpr.and(RawWorkbenchListFocusContextKey, ContextKeyExpr.not(InputFocusedContextKey)); export type Widget = List | PagedList | ITree; function createScopedContextKeyService(contextKeyService: IContextKeyService, widget: Widget): IContextKeyService { const result = contextKeyService.createScoped(widget.getHTMLElement()); + + if (widget instanceof List || widget instanceof PagedList) { + WorkbenchListSupportsMultiSelectContextKey.bindTo(result); + } + RawWorkbenchListFocusContextKey.bindTo(result); return result; } diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 95bd211f019..823ec1cd2e0 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -236,7 +236,7 @@ export class MultiplexLogService implements ILogService { } } -export class NoopLogService implements ILogService { +export class NullLogService implements ILogService { _serviceBrand: any; setLevel(level: LogLevel): void { } getLevel(): LogLevel { return LogLevel.Info; } diff --git a/src/vs/platform/log/node/spdlogService.ts b/src/vs/platform/log/node/spdlogService.ts index ffdd39e2d23..d39af82182d 100644 --- a/src/vs/platform/log/node/spdlogService.ts +++ b/src/vs/platform/log/node/spdlogService.ts @@ -6,14 +6,15 @@ 'use strict'; import * as path from 'path'; -import { ILogService, LogLevel, NoopLogService } from 'vs/platform/log/common/log'; +import { ILogService, LogLevel, NullLogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { RotatingLogger, setAsyncMode } from 'spdlog'; -export function createLogService(processName: string, environmentService: IEnvironmentService): ILogService { +export function createLogService(processName: string, environmentService: IEnvironmentService, logsSubfolder?: string): ILogService { try { setAsyncMode(8192, 2000); - const logfilePath = path.join(environmentService.logsPath, `${processName}.log`); + const logsDirPath = logsSubfolder ? path.join(environmentService.logsPath, logsSubfolder) : environmentService.logsPath; + const logfilePath = path.join(logsDirPath, `${processName}.log`); const logger = new RotatingLogger(processName, logfilePath, 1024 * 1024 * 5, 6); logger.setLevel(0); @@ -21,7 +22,7 @@ export function createLogService(processName: string, environmentService: IEnvir } catch (e) { console.error(e); } - return new NoopLogService(); + return new NullLogService(); } class SpdLogService implements ILogService { diff --git a/src/vs/platform/markers/common/problemMatcher.ts b/src/vs/platform/markers/common/problemMatcher.ts index b518a234e3f..7268756d769 100644 --- a/src/vs/platform/markers/common/problemMatcher.ts +++ b/src/vs/platform/markers/common/problemMatcher.ts @@ -12,6 +12,7 @@ import * as Assert from 'vs/base/common/assert'; import * as Paths from 'vs/base/common/paths'; import * as Types from 'vs/base/common/types'; import * as UUID from 'vs/base/common/uuid'; +import * as Platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -189,6 +190,8 @@ export function createLineMatcher(matcher: ProblemMatcher): ILineMatcher { } } +const endOfLine: string = Platform.OS === Platform.OperatingSystem.Windows ? '\r\n' : '\n'; + abstract class AbstractLineMatcher implements ILineMatcher { private matcher: ProblemMatcher; @@ -208,7 +211,7 @@ abstract class AbstractLineMatcher implements ILineMatcher { protected fillProblemData(data: ProblemData, pattern: ProblemPattern, matches: RegExpExecArray): void { this.fillProperty(data, 'file', pattern, matches, true); - this.fillProperty(data, 'message', pattern, matches, true); + this.appendProperty(data, 'message', pattern, matches, true); this.fillProperty(data, 'code', pattern, matches, true); this.fillProperty(data, 'severity', pattern, matches, true); this.fillProperty(data, 'location', pattern, matches, true); @@ -218,6 +221,19 @@ abstract class AbstractLineMatcher implements ILineMatcher { this.fillProperty(data, 'endCharacter', pattern, matches); } + private appendProperty(data: ProblemData, property: keyof ProblemData, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void { + if (Types.isUndefined(data[property])) { + this.fillProperty(data, property, pattern, matches, trim); + } + else if (!Types.isUndefined(pattern[property]) && pattern[property] < matches.length) { + let value = matches[pattern[property]]; + if (trim) { + value = Strings.trim(value); + } + data[property] += endOfLine + value; + } + } + private fillProperty(data: ProblemData, property: keyof ProblemData, pattern: ProblemPattern, matches: RegExpExecArray, trim: boolean = false): void { if (Types.isUndefined(data[property]) && !Types.isUndefined(pattern[property]) && pattern[property] < matches.length) { let value = matches[pattern[property]]; @@ -1598,7 +1614,7 @@ class ProblemMatcherRegistryImpl implements IProblemMatcherRegistry { label: localize('eslint-compact', 'ESLint compact problems'), owner: 'eslint', applyTo: ApplyToKind.allDocuments, - fileLocation: FileLocationKind.Relative, + fileLocation: FileLocationKind.Absolute, filePrefix: '${workspaceFolder}', pattern: ProblemPatternRegistry.get('eslint-compact') }); diff --git a/src/vs/platform/message/common/message.ts b/src/vs/platform/message/common/message.ts index 63ca9fe894b..61ed328a213 100644 --- a/src/vs/platform/message/common/message.ts +++ b/src/vs/platform/message/common/message.ts @@ -62,7 +62,7 @@ export interface IMessageService { /** * Ask the user for confirmation. */ - confirm(confirmation: IConfirmation): boolean; + confirm(confirmation: IConfirmation): TPromise; /** * Ask the user for confirmation with a checkbox. diff --git a/src/vs/platform/progress/common/progress.ts b/src/vs/platform/progress/common/progress.ts index cd966c7e193..75addbff74d 100644 --- a/src/vs/platform/progress/common/progress.ts +++ b/src/vs/platform/progress/common/progress.ts @@ -78,5 +78,5 @@ export interface IProgressService2 { _serviceBrand: any; - withProgress(options: IProgressOptions, task: (progress: IProgress) => TPromise): void; + withProgress

, R=any>(options: IProgressOptions, task: (progress: IProgress) => P): P; } diff --git a/src/vs/platform/telemetry/common/telemetryUtils.ts b/src/vs/platform/telemetry/common/telemetryUtils.ts index 4890d1d609a..41c3db8d63f 100644 --- a/src/vs/platform/telemetry/common/telemetryUtils.ts +++ b/src/vs/platform/telemetry/common/telemetryUtils.ts @@ -134,7 +134,6 @@ const configurationValueWhitelist = [ 'workbench.sideBar.location', 'window.openFilesInNewWindow', 'javascript.validate.enable', - 'window.reopenFolders', 'window.restoreWindows', 'extensions.autoUpdate', 'files.eol', diff --git a/src/vs/platform/theme/common/styler.ts b/src/vs/platform/theme/common/styler.ts index 0e7ca7a5177..5b67b103f2d 100644 --- a/src/vs/platform/theme/common/styler.ts +++ b/src/vs/platform/theme/common/styler.ts @@ -6,7 +6,7 @@ 'use strict'; import { ITheme, IThemeService } from 'vs/platform/theme/common/themeService'; -import { inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusForeground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground } from 'vs/platform/theme/common/colorRegistry'; +import { focusBorder, inputBackground, inputForeground, ColorIdentifier, selectForeground, selectBackground, selectBorder, inputBorder, foreground, editorBackground, contrastBorder, inputActiveOptionBorder, listFocusBackground, listFocusForeground, listActiveSelectionBackground, listActiveSelectionForeground, listInactiveSelectionForeground, listInactiveSelectionBackground, listInactiveFocusForeground, listInactiveFocusBackground, listHoverBackground, listHoverForeground, listDropBackground, pickerGroupBorder, pickerGroupForeground, widgetShadow, inputValidationInfoBorder, inputValidationInfoBackground, inputValidationWarningBorder, inputValidationWarningBackground, inputValidationErrorBorder, inputValidationErrorBackground, activeContrastBorder, buttonForeground, buttonBackground, buttonHoverBackground, ColorFunction, lighten, badgeBackground, badgeForeground, progressBarBackground } from 'vs/platform/theme/common/colorRegistry'; import { IDisposable } from 'vs/base/common/lifecycle'; export type styleFn = (colors: { [name: string]: ColorIdentifier }) => void; @@ -97,17 +97,25 @@ export function attachInputBoxStyler(widget: IThemable, themeService: IThemeServ } as IInputBoxStyleOverrides, widget); } -export interface ISelectBoxStyleOverrides extends IStyleOverrides { +export interface ISelectBoxStyleOverrides extends IStyleOverrides, IListStyleOverrides { selectBackground?: ColorIdentifier; selectForeground?: ColorIdentifier; selectBorder?: ColorIdentifier; + focusBorder?: ColorIdentifier; } export function attachSelectBoxStyler(widget: IThemable, themeService: IThemeService, style?: ISelectBoxStyleOverrides): IDisposable { return attachStyler(themeService, { selectBackground: (style && style.selectBackground) || selectBackground, selectForeground: (style && style.selectForeground) || selectForeground, - selectBorder: (style && style.selectBorder) || selectBorder + selectBorder: (style && style.selectBorder) || selectBorder, + focusBorder: (style && style.focusBorder) || focusBorder, + listFocusBackground: (style && style.listFocusBackground) || listFocusBackground, + listFocusForeground: (style && style.listFocusForeground) || listFocusForeground, + listFocusOutline: (style && style.listFocusOutline) || activeContrastBorder, + listHoverBackground: (style && style.listHoverBackground) || listHoverBackground, + listHoverForeground: (style && style.listHoverForeground) || listHoverForeground, + listHoverOutline: (style && style.listFocusOutline) || activeContrastBorder } as ISelectBoxStyleOverrides, widget); } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 29af4d3a449..c20b1900988 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -98,10 +98,15 @@ export interface IWindowsService { onWindowFocus: Event; onWindowBlur: Event; + // Dialogs pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise; pickFileAndOpen(options: INativeOpenDialogOptions): TPromise; pickFolderAndOpen(options: INativeOpenDialogOptions): TPromise; pickWorkspaceAndOpen(options: INativeOpenDialogOptions): TPromise; + showMessageBox(windowId: number, options: MessageBoxOptions): TPromise; + showSaveDialog(windowId: number, options: SaveDialogOptions): TPromise; + showOpenDialog(windowId: number, options: OpenDialogOptions): TPromise; + reloadWindow(windowId: number): TPromise; openDevTools(windowId: number): TPromise; toggleDevTools(windowId: number): TPromise; @@ -191,10 +196,9 @@ export interface IWindowService { setDocumentEdited(flag: boolean): TPromise; onWindowTitleDoubleClick(): TPromise; show(): TPromise; - showMessageBox(options: MessageBoxOptions): number; - showSaveDialog(options: SaveDialogOptions): string; - showOpenDialog(options: OpenDialogOptions): string[]; - showMessageBoxWithCheckbox(options: MessageBoxOptions): TPromise; + showMessageBox(options: MessageBoxOptions): TPromise; + showSaveDialog(options: SaveDialogOptions): TPromise; + showOpenDialog(options: OpenDialogOptions): TPromise; } export type MenuBarVisibility = 'default' | 'visible' | 'toggle' | 'hidden'; @@ -207,7 +211,6 @@ export interface IWindowSettings { openFilesInNewWindow: 'on' | 'off' | 'default'; openFoldersInNewWindow: 'on' | 'off' | 'default'; restoreWindows: 'all' | 'folders' | 'one' | 'none'; - reopenFolders: 'all' | 'one' | 'none'; // TODO@Ben deprecated restoreFullscreen: boolean; zoomLevel: number; titleBarStyle: 'native' | 'custom'; @@ -293,6 +296,7 @@ export interface IAddFoldersRequest { export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest { machineId: string; + windowId: number; appRoot: string; execPath: string; diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 2000965fadf..c4fedca111d 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import Event, { buffer } from 'vs/base/common/event'; import { IChannel, eventToCall, eventFromCall } from 'vs/base/parts/ipc/common/ipc'; -import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions } from 'vs/platform/windows/common/windows'; +import { IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, CrashReporterStartOptions, IMessageBoxResult, MessageBoxOptions, SaveDialogOptions, OpenDialogOptions } from 'vs/platform/windows/common/windows'; import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ICommandAction } from 'vs/platform/actions/common/actions'; @@ -22,6 +22,9 @@ export interface IWindowsChannel extends IChannel { call(command: 'pickFileAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickFolderAndOpen', arg: INativeOpenDialogOptions): TPromise; call(command: 'pickWorkspaceAndOpen', arg: INativeOpenDialogOptions): TPromise; + call(command: 'showMessageBox', arg: [number, MessageBoxOptions]): TPromise; + call(command: 'showSaveDialog', arg: [number, SaveDialogOptions]): TPromise; + call(command: 'showOpenDialog', arg: [number, OpenDialogOptions]): TPromise; call(command: 'reloadWindow', arg: number): TPromise; call(command: 'toggleDevTools', arg: number): TPromise; call(command: 'closeWorkspace', arg: number): TPromise; @@ -84,6 +87,9 @@ export class WindowsChannel implements IWindowsChannel { case 'pickFileAndOpen': return this.service.pickFileAndOpen(arg); case 'pickFolderAndOpen': return this.service.pickFolderAndOpen(arg); case 'pickWorkspaceAndOpen': return this.service.pickWorkspaceAndOpen(arg); + case 'showMessageBox': return this.service.showMessageBox(arg[0], arg[1]); + case 'showSaveDialog': return this.service.showSaveDialog(arg[0], arg[1]); + case 'showOpenDialog': return this.service.showOpenDialog(arg[0], arg[1]); case 'reloadWindow': return this.service.reloadWindow(arg); case 'openDevTools': return this.service.openDevTools(arg); case 'toggleDevTools': return this.service.toggleDevTools(arg); @@ -172,6 +178,18 @@ export class WindowsChannelClient implements IWindowsService { return this.channel.call('pickWorkspaceAndOpen', options); } + showMessageBox(windowId: number, options: MessageBoxOptions): TPromise { + return this.channel.call('showMessageBox', [windowId, options]); + } + + showSaveDialog(windowId: number, options: SaveDialogOptions): TPromise { + return this.channel.call('showSaveDialog', [windowId, options]); + } + + showOpenDialog(windowId: number, options: OpenDialogOptions): TPromise { + return this.channel.call('showOpenDialog', [windowId, options]); + } + reloadWindow(windowId: number): TPromise { return this.channel.call('reloadWindow', windowId); } diff --git a/src/vs/platform/windows/electron-browser/windowService.ts b/src/vs/platform/windows/electron-browser/windowService.ts index 3e8a9ee4555..677d4603464 100644 --- a/src/vs/platform/windows/electron-browser/windowService.ts +++ b/src/vs/platform/windows/electron-browser/windowService.ts @@ -8,12 +8,10 @@ import Event, { filterEvent, mapEvent, anyEvent } from 'vs/base/common/event'; import { TPromise } from 'vs/base/common/winjs.base'; import { IWindowService, IWindowsService, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult, IWindowConfiguration } from 'vs/platform/windows/common/windows'; -import { remote } from 'electron'; import { IRecentlyOpened } from 'vs/platform/history/common/history'; import { ICommandAction } from 'vs/platform/actions/common/actions'; -import { isMacintosh } from 'vs/base/common/platform'; -import { normalizeNFC } from 'vs/base/common/strings'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { ILogService } from 'vs/platform/log/common/log'; export class WindowService implements IWindowService { @@ -24,7 +22,8 @@ export class WindowService implements IWindowService { constructor( private windowId: number, private configuration: IWindowConfiguration, - @IWindowsService private windowsService: IWindowsService + @IWindowsService private windowsService: IWindowsService, + @ILogService private logService: ILogService // TODO@Ben remove logging when no longer needed ) { const onThisWindowFocus = mapEvent(filterEvent(windowsService.onWindowFocus, id => id === windowId), _ => true); const onThisWindowBlur = mapEvent(filterEvent(windowsService.onWindowBlur, id => id === windowId), _ => false); @@ -42,24 +41,32 @@ export class WindowService implements IWindowService { pickFileFolderAndOpen(options: INativeOpenDialogOptions): TPromise { options.windowId = this.windowId; + this.logService.info('pickFileFolderAndOpen: begin'); + return this.windowsService.pickFileFolderAndOpen(options); } pickFileAndOpen(options: INativeOpenDialogOptions): TPromise { options.windowId = this.windowId; + this.logService.info('pickFileAndOpen: begin'); + return this.windowsService.pickFileAndOpen(options); } pickFolderAndOpen(options: INativeOpenDialogOptions): TPromise { options.windowId = this.windowId; + this.logService.info('pickFolderAndOpen: begin'); + return this.windowsService.pickFolderAndOpen(options); } pickWorkspaceAndOpen(options: INativeOpenDialogOptions): TPromise { options.windowId = this.windowId; + this.logService.info('pickWorkspaceAndOpen: begin'); + return this.windowsService.pickWorkspaceAndOpen(options); } @@ -123,42 +130,28 @@ export class WindowService implements IWindowService { return this.windowsService.showWindow(this.windowId); } - showMessageBox(options: Electron.MessageBoxOptions): number { - return remote.dialog.showMessageBox(remote.getCurrentWindow(), options); - } - - showMessageBoxWithCheckbox(options: Electron.MessageBoxOptions): TPromise { - return new TPromise((c, e) => { - return remote.dialog.showMessageBox(remote.getCurrentWindow(), options, (response: number, checkboxChecked: boolean) => { - c({ button: response, checkboxChecked }); - }); + showMessageBox(options: Electron.MessageBoxOptions): TPromise { + this.logService.info('showMessageBox begin: ', options); + return this.windowsService.showMessageBox(this.windowId, options).then(result => { + this.logService.info('showMessageBox closed, response: ', result); + return result; }); } - showSaveDialog(options: Electron.SaveDialogOptions): string { - - function normalizePath(path: string): string { - if (path && isMacintosh) { - path = normalizeNFC(path); // normalize paths returned from the OS - } - - return path; - } - - return normalizePath(remote.dialog.showSaveDialog(remote.getCurrentWindow(), options)); // https://github.com/electron/electron/issues/4936 + showSaveDialog(options: Electron.SaveDialogOptions): TPromise { + this.logService.info('showSaveDialog begin: ', options); + return this.windowsService.showSaveDialog(this.windowId, options).then(result => { + this.logService.info('showSaveDialog begin: ', result); + return result; + }); } - showOpenDialog(options: Electron.OpenDialogOptions): string[] { - - function normalizePaths(paths: string[]): string[] { - if (paths && paths.length > 0 && isMacintosh) { - paths = paths.map(path => normalizeNFC(path)); // normalize paths returned from the OS - } - - return paths; - } - - return normalizePaths(remote.dialog.showOpenDialog(remote.getCurrentWindow(), options)); // https://github.com/electron/electron/issues/4936 + showOpenDialog(options: Electron.OpenDialogOptions): TPromise { + this.logService.info('showOpenDialog begin: ', options); + return this.windowsService.showOpenDialog(this.windowId, options).then(result => { + this.logService.info('showOpenDialog closed: ', result); + return result; + }); } updateTouchBar(items: ICommandAction[][]): TPromise { diff --git a/src/vs/platform/windows/electron-main/windows.ts b/src/vs/platform/windows/electron-main/windows.ts index f6c8b4d712b..c105c89f2d2 100644 --- a/src/vs/platform/windows/electron-main/windows.ts +++ b/src/vs/platform/windows/electron-main/windows.ts @@ -6,7 +6,7 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import { OpenContext, IWindowConfiguration, ReadyState, INativeOpenDialogOptions, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; +import { OpenContext, IWindowConfiguration, ReadyState, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; import Event from 'vs/base/common/event'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -69,6 +69,9 @@ export interface IWindowsMainService { pickFolderAndOpen(options: INativeOpenDialogOptions): void; pickFileAndOpen(options: INativeOpenDialogOptions): void; pickWorkspaceAndOpen(options: INativeOpenDialogOptions): void; + showMessageBox(options: Electron.MessageBoxOptions, win?: ICodeWindow): TPromise; + showSaveDialog(options: Electron.SaveDialogOptions, win?: ICodeWindow): TPromise; + showOpenDialog(options: Electron.OpenDialogOptions, win?: ICodeWindow): TPromise; focusLastActive(cli: ParsedArgs, context: OpenContext): ICodeWindow; getLastActiveWindow(): ICodeWindow; waitForWindowCloseOrLoad(windowId: number): TPromise; diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index 56fe2279713..25ac113b9fd 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { assign } from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; -import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult } from 'vs/platform/windows/common/windows'; +import { IWindowsService, OpenContext, INativeOpenDialogOptions, IEnterWorkspaceResult, IMessageBoxResult } from 'vs/platform/windows/common/windows'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { shell, crashReporter, app, Menu } from 'electron'; import Event, { chain, fromNodeEventEmitter } from 'vs/base/common/event'; @@ -75,6 +75,24 @@ export class WindowsService implements IWindowsService, IDisposable { return TPromise.as(null); } + showMessageBox(windowId: number, options: Electron.MessageBoxOptions): TPromise { + const codeWindow = this.windowsMainService.getWindowById(windowId); + + return this.windowsMainService.showMessageBox(options, codeWindow); + } + + showSaveDialog(windowId: number, options: Electron.SaveDialogOptions): TPromise { + const codeWindow = this.windowsMainService.getWindowById(windowId); + + return this.windowsMainService.showSaveDialog(options, codeWindow); + } + + showOpenDialog(windowId: number, options: Electron.OpenDialogOptions): TPromise { + const codeWindow = this.windowsMainService.getWindowById(windowId); + + return this.windowsMainService.showOpenDialog(options, codeWindow); + } + reloadWindow(windowId: number): TPromise { const codeWindow = this.windowsMainService.getWindowById(windowId); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index b02773f70b9..515a5e62cfc 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -836,6 +836,16 @@ declare module 'vscode' { */ borderWidth?: string; + /** + * CSS styling property that will be applied to text enclosed by a decoration. + */ + fontStyle?: string; + + /** + * CSS styling property that will be applied to text enclosed by a decoration. + */ + fontWeight?: string; + /** * CSS styling property that will be applied to text enclosed by a decoration. */ @@ -902,6 +912,14 @@ declare module 'vscode' { * CSS styling property that will be applied to text enclosed by a decoration. */ borderColor?: string | ThemeColor; + /** + * CSS styling property that will be applied to the decoration attachment. + */ + fontStyle?: string; + /** + * CSS styling property that will be applied to the decoration attachment. + */ + fontWeight?: string; /** * CSS styling property that will be applied to the decoration attachment. */ @@ -1805,6 +1823,48 @@ declare module 'vscode' { readonly diagnostics: Diagnostic[]; } + /** + * A code action represents a change that can be performed in code, e.g. to fix a problem or + * to refactor code. + */ + export class CodeAction { + + /** + * A short, human-readanle, title for this code action. + */ + title: string; + + /** + * A workspace edit this code action performs. + * + * *Note* that either an [`edit`](CodeAction#edit) or a [`command`](CodeAction#command) must be supplied. + */ + edit?: WorkspaceEdit; + + /** + * Diagnostics that this code action resolves. + */ + diagnostics?: Diagnostic[]; + + /** + * A command this code action performs. + * + * *Note* that either an [`edit`](CodeAction#edit) or a [`command`](CodeAction#command) must be supplied. + */ + command?: Command; + + /** + * Creates a new code action. + * + * A code action must have at least a [title](#CodeAction.title) and either [edits](#CodeAction.edits) + * or a [command](#CodeAction.command). + * + * @param title The title of the code action. + * @param edits The edit of the code action. + */ + constructor(title: string, edit?: WorkspaceEdit); + } + /** * The code action interface defines the contract between extensions and * the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) feature. @@ -1820,10 +1880,10 @@ declare module 'vscode' { * @param range The range for which the command was invoked. * @param context Context carrying additional information. * @param token A cancellation token. - * @return An array of commands or a thenable of such. The lack of a result can be + * @return An array of commands, quick fixes, or refactorings or a thenable of such. The lack of a result can be * signaled by returning `undefined`, `null`, or an empty array. */ - provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult; + provideCodeActions(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]>; } /** @@ -4985,6 +5045,12 @@ declare module 'vscode' { * Args for the custom shell executable, this does not work on Windows (see #8429) */ shellArgs?: string[]; + + /** + * A path for the current working directory to be used for the terminal. + */ + cwd?: string; + /** * Object with environment variables that will be added to the VS Code process. */ @@ -5782,6 +5848,11 @@ declare module 'vscode' { * A string to show as place holder in the input box to guide the user. */ placeholder: string; + + /** + * The warning threshold for lines in the input box. + */ + lineWarningLength: number | undefined; } interface QuickDiffProvider { diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index cc732a7adfa..9683e1171a1 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -86,9 +86,9 @@ declare module 'vscode' { // todo@joh discover files etc export interface FileSystemProvider { - onDidChange?: Event; + readonly onDidChange?: Event; - root: Uri; + readonly root: Uri; // more... // @@ -198,56 +198,6 @@ declare module 'vscode' { //#endregion - /** - * Represents an action that can be performed in code. - * - * Shown using the [light bulb](https://code.visualstudio.com/docs/editor/editingevolved#_code-action) - */ - export class CodeAction { - /** - * Label used to identify the code action in UI. - */ - title: string; - - /** - * Optional command that performs the code action. - * - * Executed after `edits` if any edits are provided. Either `command` or `edits` must be provided for a `CodeAction`. - */ - command?: Command; - - /** - * Optional edit that performs the code action. - * - * Either `command` or `edits` must be provided for a `CodeAction`. - */ - edits?: TextEdit[] | WorkspaceEdit; - - /** - * Diagnostics that this code action resolves. - */ - diagnostics?: Diagnostic[]; - - constructor(title: string, edits?: TextEdit[] | WorkspaceEdit); - } - - export interface CodeActionProvider { - - /** - * Provide commands for the given document and range. - * - * If implemented, overrides `provideCodeActions` - * - * @param document The document in which the command was invoked. - * @param range The range for which the command was invoked. - * @param context Context carrying additional information. - * @param token A cancellation token. - * @return An array of commands, quick fixes, or refactorings or a thenable of such. The lack of a result can be - * signaled by returning `undefined`, `null`, or an empty array. - */ - provideCodeActions2?(document: TextDocument, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<(Command | CodeAction)[]>; - } - export namespace debug { /** @@ -326,4 +276,40 @@ declare module 'vscode' { private constructor(enabled: boolean, condition: string, hitCondition: string, functionName: string); } + + /** + * The severity level of a log message + */ + export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 + } + + /** + * A logger for writing to an extension's log file, and accessing its dedicated log directory. + */ + export interface Logger { + readonly onDidChangeLogLevel: Event; + readonly currentLevel: LogLevel; + readonly logDirectory: Thenable; + + trace(message: string, ...args: any[]): void; + debug(message: string, ...args: any[]): void; + info(message: string, ...args: any[]): void; + warn(message: string, ...args: any[]): void; + error(message: string | Error, ...args: any[]): void; + critical(message: string | Error, ...args: any[]): void; + } + + export interface ExtensionContext { + /** + * This extension's logger + */ + logger: Logger; + } } diff --git a/src/vs/workbench/api/electron-browser/extHostCustomers.ts b/src/vs/workbench/api/electron-browser/extHostCustomers.ts index b3a700850d4..6ce6d843443 100644 --- a/src/vs/workbench/api/electron-browser/extHostCustomers.ts +++ b/src/vs/workbench/api/electron-browser/extHostCustomers.ts @@ -6,7 +6,7 @@ 'use strict'; import { IDisposable } from 'vs/base/common/lifecycle'; -import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; +import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadCommands.ts b/src/vs/workbench/api/electron-browser/mainThreadCommands.ts index f0a84773a51..ad4bfc1165a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadCommands.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadCommands.ts @@ -9,6 +9,7 @@ import { IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { revive } from 'vs/base/common/marshalling'; @extHostNamedCustomer(MainContext.MainThreadCommands) export class MainThreadCommands implements MainThreadCommandsShape { @@ -21,7 +22,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { extHostContext: IExtHostContext, @ICommandService private readonly _commandService: ICommandService, ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostCommands); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostCommands); this._generateCommandsDocumentationRegistration = CommandsRegistry.registerCommand('_generateCommandsDocumentation', () => this._generateCommandsDocumentation()); } @@ -33,7 +34,7 @@ export class MainThreadCommands implements MainThreadCommandsShape { this._generateCommandsDocumentationRegistration.dispose(); } - private _generateCommandsDocumentation(): TPromise { + private _generateCommandsDocumentation(): Thenable { return this._proxy.$getContributedCommandHandlerDescriptions().then(result => { // add local commands const commands = CommandsRegistry.getCommands(); @@ -53,23 +54,28 @@ export class MainThreadCommands implements MainThreadCommandsShape { }); } - $registerCommand(id: string): TPromise { + $registerCommand(id: string): void { this._disposables.set( id, - CommandsRegistry.registerCommand(id, (accessor, ...args) => this._proxy.$executeContributedCommand(id, ...args)) + CommandsRegistry.registerCommand(id, (accessor, ...args) => { + return this._proxy.$executeContributedCommand(id, ...args).then(result => { + return revive(result, 0); + }); + }) ); - return undefined; } - $unregisterCommand(id: string): TPromise { + $unregisterCommand(id: string): void { if (this._disposables.has(id)) { this._disposables.get(id).dispose(); this._disposables.delete(id); } - return undefined; } $executeCommand(id: string, args: any[]): Thenable { + for (let i = 0; i < args.length; i++) { + args[i] = revive(args[i], 0); + } return this._commandService.executeCommand(id, ...args); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts index 5133d7125a9..2b307d2d019 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadConfiguration.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -13,7 +13,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; -import { ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; +import { ConfigurationTarget, IConfigurationChangeEvent, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; @extHostNamedCustomer(MainContext.MainThreadConfiguration) export class MainThreadConfiguration implements MainThreadConfigurationShape { @@ -25,7 +25,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { @IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService, @IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService ) { - const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostConfiguration); this._configurationListener = configurationService.onDidChangeConfiguration(e => { proxy.$acceptConfigurationChanged(configurationService.getConfigurationData(), this.toConfigurationChangeEventData(e)); @@ -36,11 +36,13 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { this._configurationListener.dispose(); } - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise { + $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resourceUriComponenets: UriComponents): TPromise { + const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, value, resource); } - $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: URI): TPromise { + $removeConfigurationOption(target: ConfigurationTarget, key: string, resourceUriComponenets: UriComponents): TPromise { + const resource = resourceUriComponenets ? URI.revive(resourceUriComponenets) : null; return this.writeConfiguration(target, key, undefined, resource); } @@ -61,11 +63,19 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape { private toConfigurationChangeEventData(event: IConfigurationChangeEvent): IWorkspaceConfigurationChangeEventData { return { - changedConfiguration: event.changedConfiguration, + changedConfiguration: this.toJSONConfiguration(event.changedConfiguration), changedConfigurationByResource: event.changedConfigurationByResource.keys().reduce((result, resource) => { - result[resource.toString()] = event.changedConfigurationByResource.get(resource); + result[resource.toString()] = this.toJSONConfiguration(event.changedConfigurationByResource.get(resource)); return result; }, Object.create({})) }; } + + private toJSONConfiguration({ contents, keys, overrides }: IConfigurationModel = { contents: {}, keys: [], overrides: [] }): IConfigurationModel { + return { + contents, + keys, + overrides + }; + } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts index 28026490146..f43810a894f 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDebugService.ts @@ -25,7 +25,7 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { @IDebugService private debugService: IDebugService, @IWorkspaceContextService private contextService: IWorkspaceContextService, ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostDebugService); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDebugService); this._toDispose = []; this._toDispose.push(debugService.onDidNewProcess(proc => this._proxy.$acceptDebugSessionStarted(proc.getId(), proc.configuration.type, proc.getName(false)))); this._toDispose.push(debugService.onDidEndProcess(proc => this._proxy.$acceptDebugSessionTerminated(proc.getId(), proc.configuration.type, proc.getName(false)))); @@ -143,8 +143,9 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { return TPromise.wrap(undefined); } - public $startDebugging(folderUri: uri | undefined, nameOrConfiguration: string | IConfig): TPromise { - const folder = folderUri ? this.contextService.getWorkspace().folders.filter(wf => wf.uri.toString() === folderUri.toString()).pop() : undefined; + public $startDebugging(_folderUri: uri | undefined, nameOrConfiguration: string | IConfig): TPromise { + const folderUriString = _folderUri ? uri.revive(_folderUri).toString() : undefined; + const folder = folderUriString ? this.contextService.getWorkspace().folders.filter(wf => wf.uri.toString() === folderUriString).pop() : undefined; return this.debugService.startDebugging(folder, nameOrConfiguration).then(x => { return true; }, err => { @@ -156,10 +157,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape { const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === sessionId).pop(); if (process) { return process.session.custom(request, args).then(response => { - if (response.success) { + if (response && response.success) { return response.body; } else { - return TPromise.wrapError(new Error(response.message)); + return TPromise.wrapError(new Error(response ? response.message : 'custom request failed')); } }); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts index b7eb0fc9ea9..d0ab516ec47 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDecorations.ts @@ -4,24 +4,72 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape } from '../node/extHost.protocol'; +import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape, DecorationData, DecorationRequest } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations'; +class DecorationRequestsQueue { + + private _idPool = 0; + private _requests: DecorationRequest[] = []; + private _resolver: { [id: number]: Function } = Object.create(null); + + private _timer: number; + + constructor( + private _proxy: ExtHostDecorationsShape + ) { + // + } + + enqueue(handle: number, uri: URI): Thenable { + return new Promise((resolve, reject) => { + const id = ++this._idPool; + this._requests.push({ id, handle, uri }); + this._resolver[id] = resolve; + this._processQueue(); + }); + } + + private _processQueue(): void { + if (typeof this._timer === 'number') { + // already queued + return; + } + this._timer = setTimeout(() => { + // make request + const requests = this._requests; + const resolver = this._resolver; + this._proxy.$provideDecorations(requests).then(data => { + for (const id in resolver) { + resolver[id](data[id]); + } + }); + + // reset + this._requests = []; + this._resolver = []; + this._timer = void 0; + }, 0); + } +} + @extHostNamedCustomer(MainContext.MainThreadDecorations) export class MainThreadDecorations implements MainThreadDecorationsShape { private readonly _provider = new Map, IDisposable]>(); private readonly _proxy: ExtHostDecorationsShape; + private readonly _requestQueue: DecorationRequestsQueue; constructor( context: IExtHostContext, @IDecorationsService private readonly _decorationsService: IDecorationsService ) { - this._proxy = context.get(ExtHostContext.ExtHostDecorations); + this._proxy = context.getProxy(ExtHostContext.ExtHostDecorations); + this._requestQueue = new DecorationRequestsQueue(this._proxy); } dispose() { @@ -30,12 +78,12 @@ export class MainThreadDecorations implements MainThreadDecorationsShape { } $registerDecorationProvider(handle: number, label: string): void { - let emitter = new Emitter(); - let registration = this._decorationsService.registerDecorationsProvider({ + const emitter = new Emitter(); + const registration = this._decorationsService.registerDecorationsProvider({ label, onDidChange: emitter.event, provideDecorations: (uri) => { - return this._proxy.$providerDecorations(handle, uri).then(data => { + return this._requestQueue.enqueue(handle, uri).then(data => { if (!data) { return undefined; } @@ -54,9 +102,9 @@ export class MainThreadDecorations implements MainThreadDecorationsShape { this._provider.set(handle, [emitter, registration]); } - $onDidChange(handle: number, resources: URI[]): void { + $onDidChange(handle: number, resources: UriComponents[]): void { const [emitter] = this._provider.get(handle); - emitter.fire(resources); + emitter.fire(resources && resources.map(URI.revive)); } $unregisterDecorationProvider(handle: number): void { diff --git a/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts b/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts index 66f23f0ed40..5051aabba45 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDiagnostics.ts @@ -5,8 +5,7 @@ 'use strict'; import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers'; -import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { MainThreadDiagnosticsShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -27,18 +26,16 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape { this._activeOwners.forEach(owner => this._markerService.changeAll(owner, undefined)); } - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { + $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { for (let entry of entries) { let [uri, markers] = entry; - this._markerService.changeOne(owner, uri, markers); + this._markerService.changeOne(owner, URI.revive(uri), markers); } this._activeOwners.add(owner); - return undefined; } - $clear(owner: string): TPromise { + $clear(owner: string): void { this._markerService.changeAll(owner, undefined); this._activeOwners.delete(owner); - return undefined; } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts b/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts index 6a4e7b7c164..e43fa20ef2e 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDialogs.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { MainThreadDiaglogsShape, MainContext, IExtHostContext, MainThreadDialogOpenOptions, MainThreadDialogSaveOptions } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -25,30 +25,27 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { // } - $showOpenDialog(options: MainThreadDialogOpenOptions): TPromise { + $showOpenDialog(options: MainThreadDialogOpenOptions): Promise { // TODO@joh what about remote dev setup? if (options.defaultUri && options.defaultUri.scheme !== 'file') { - return TPromise.wrapError(new Error('Not supported - Open-dialogs can only be opened on `file`-uris.')); + return Promise.reject(new Error('Not supported - Open-dialogs can only be opened on `file`-uris.')); } - return new TPromise(resolve => { - const filenames = this._windowService.showOpenDialog( + return new Promise(resolve => { + this._windowService.showOpenDialog( MainThreadDialogs._convertOpenOptions(options) - ); - - resolve(isFalsyOrEmpty(filenames) ? undefined : filenames); + ).then(filenames => resolve(isFalsyOrEmpty(filenames) ? undefined : filenames)); }); } - $showSaveDialog(options: MainThreadDialogSaveOptions): TPromise { + $showSaveDialog(options: MainThreadDialogSaveOptions): Promise { // TODO@joh what about remote dev setup? if (options.defaultUri && options.defaultUri.scheme !== 'file') { - return TPromise.wrapError(new Error('Not supported - Save-dialogs can only be opened on `file`-uris.')); + return Promise.reject(new Error('Not supported - Save-dialogs can only be opened on `file`-uris.')); } - return new TPromise(resolve => { - const filename = this._windowService.showSaveDialog( + return new Promise(resolve => { + this._windowService.showSaveDialog( MainThreadDialogs._convertSaveOptions(options) - ); - resolve(!filename ? undefined : filename); + ).then(filename => resolve(!filename ? undefined : filename)); }); } @@ -60,7 +57,7 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { result.buttonLabel = options.openLabel; } if (options.defaultUri) { - result.defaultPath = options.defaultUri.fsPath; + result.defaultPath = URI.revive(options.defaultUri).fsPath; } if (!options.canSelectFiles && !options.canSelectFolders) { options.canSelectFiles = true; @@ -86,7 +83,7 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape { }; if (options.defaultUri) { - result.defaultPath = options.defaultUri.fsPath; + result.defaultPath = URI.revive(options.defaultUri).fsPath; } if (options.saveLabel) { result.buttonLabel = options.saveLabel; diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts index ffc9644d266..74cd3e9b559 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocumentContentProviders.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; @@ -31,7 +31,7 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon @ICodeEditorService codeEditorService: ICodeEditorService, @IEditorGroupService editorGroupService: IEditorGroupService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentContentProviders); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentContentProviders); } public dispose(): void { @@ -63,8 +63,8 @@ export class MainThreadDocumentContentProviders implements MainThreadDocumentCon } } - $onVirtualDocumentChange(uri: URI, value: ITextSource): void { - const model = this._modelService.getModel(uri); + $onVirtualDocumentChange(uri: UriComponents, value: ITextSource): void { + const model = this._modelService.getModel(URI.revive(uri)); if (!model) { return; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts index 9293b819709..c6dffaa74c9 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocuments.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; @@ -93,7 +93,7 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { this._fileService = fileService; this._untitledEditorService = untitledEditorService; - this._proxy = extHostContext.get(ExtHostContext.ExtHostDocuments); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocuments); this._modelIsSynced = {}; this._toDispose = []; @@ -168,12 +168,12 @@ export class MainThreadDocuments implements MainThreadDocumentsShape { // --- from extension host process - $trySaveDocument(uri: URI): TPromise { - return this._textFileService.save(uri); + $trySaveDocument(uri: UriComponents): TPromise { + return this._textFileService.save(URI.revive(uri)); } - $tryOpenDocument(uri: URI): TPromise { - + $tryOpenDocument(_uri: UriComponents): TPromise { + const uri = URI.revive(_uri); if (!uri.scheme || !(uri.fsPath || uri.authority)) { return TPromise.wrapError(new Error(`Invalid uri. Scheme and authority or path must be set.`)); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts index 026be3a7145..fc6880d302c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors.ts @@ -308,7 +308,7 @@ export class MainThreadDocumentsAndEditors { @IUntitledEditorService untitledEditorService: IUntitledEditorService, @IEditorGroupService editorGroupService: IEditorGroupService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentsAndEditors); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentsAndEditors); const mainThreadDocuments = new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService); extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments); @@ -398,7 +398,7 @@ export class MainThreadDocumentsAndEditors { private _toModelAddData(model: IModel): IModelAddedData { return { - url: model.uri, + uri: model.uri, versionId: model.getVersionId(), lines: model.getLinesContent(), EOL: model.getEOL(), @@ -410,7 +410,7 @@ export class MainThreadDocumentsAndEditors { private _toTextEditorAddData(textEditor: MainThreadTextEditor): ITextEditorAddData { return { id: textEditor.getId(), - document: textEditor.getModel().uri, + documentUri: textEditor.getModel().uri, options: textEditor.getConfiguration(), selections: textEditor.getSelections(), editorPosition: this._findEditorPosition(textEditor) diff --git a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts index 9870316763a..01660eb6a4d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadEditors.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadEditors.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { disposed } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -46,7 +46,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { @IFileService private readonly _fileService: IFileService, @IModelService private readonly _modelService: IModelService, ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostEditors); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors); this._documentsAndEditors = documentsAndEditors; this._workbenchEditorService = workbenchEditorService; this._toDispose = []; @@ -115,7 +115,9 @@ export class MainThreadEditors implements MainThreadEditorsShape { // --- from extension host process - $tryShowTextDocument(resource: URI, options: ITextDocumentShowOptions): TPromise { + $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): TPromise { + const uri = URI.revive(resource); + const editorOptions: ITextEditorOptions = { preserveFocus: options.preserveFocus, pinned: options.pinned, @@ -123,7 +125,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { }; const input = { - resource, + resource: uri, options: editorOptions }; @@ -160,7 +162,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { return undefined; } - $trySetSelections(id: string, selections: ISelection[]): TPromise { + $trySetSelections(id: string, selections: ISelection[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -168,7 +170,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { return TPromise.as(null); } - $trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { + $trySetDecorations(id: string, key: string, ranges: IDecorationOptions[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -176,15 +178,15 @@ export class MainThreadEditors implements MainThreadEditorsShape { return TPromise.as(null); } - $trySetDecorationsFast(id: string, key: string, ranges: string): TPromise { + $trySetDecorationsFast(id: string, key: string, ranges: number[]): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } - this._documentsAndEditors.getEditor(id).setDecorationsFast(key, /*TODO: marshaller is too slow*/JSON.parse(ranges)); + this._documentsAndEditors.getEditor(id).setDecorationsFast(key, ranges); return TPromise.as(null); } - $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise { + $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -192,7 +194,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { return undefined; } - $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise { + $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise { if (!this._documentsAndEditors.getEditor(id)) { return TPromise.wrapError(disposed(`TextEditor(${id})`)); } @@ -213,7 +215,8 @@ export class MainThreadEditors implements MainThreadEditorsShape { for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) { const workspaceResourceEdit = workspaceResourceEdits[i]; if (workspaceResourceEdit.modelVersionId) { - let model = this._modelService.getModel(workspaceResourceEdit.resource); + const uri = URI.revive(workspaceResourceEdit.resource); + let model = this._modelService.getModel(uri); if (model && model.getVersionId() !== workspaceResourceEdit.modelVersionId) { // model changed in the meantime return TPromise.as(false); @@ -225,7 +228,7 @@ export class MainThreadEditors implements MainThreadEditorsShape { let resourceEdits: IResourceEdit[] = []; for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) { const workspaceResourceEdit = workspaceResourceEdits[i]; - const uri = workspaceResourceEdit.resource; + const uri = URI.revive(workspaceResourceEdit.resource); const edits = workspaceResourceEdit.edits; for (let j = 0, lenJ = edits.length; j < lenJ; j++) { diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts index a874c9121fe..acc5872827c 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise, PPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape } from '../node/extHost.protocol'; import { IFileService, IFileSystemProvider, IStat, IFileChange } from 'vs/platform/files/common/files'; @@ -29,7 +29,7 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { @ISearchService private readonly _searchService: ISearchService, @IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostFileSystem); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostFileSystem); } dispose(): void { @@ -45,22 +45,22 @@ export class MainThreadFileSystem implements MainThreadFileSystemShape { this._provider.delete(handle); } - $onDidAddFileSystemRoot(uri: URI): void { - this._workspaceEditingService.addFolders([{ uri }], true).done(null, onUnexpectedError); + $onDidAddFileSystemRoot(data: UriComponents): void { + this._workspaceEditingService.addFolders([{ uri: URI.revive(data) }], true).done(null, onUnexpectedError); } $onFileSystemChange(handle: number, changes: IFileChange[]): void { this._provider.get(handle).$onFileSystemChange(changes); } - $reportFileChunk(handle: number, resource: URI, chunk: number[]): void { - this._provider.get(handle).reportFileChunk(resource, chunk); + $reportFileChunk(handle: number, data: UriComponents, chunk: number[]): void { + this._provider.get(handle).reportFileChunk(URI.revive(data), chunk); } // --- search - $handleSearchProgress(handle: number, session: number, resource: URI): void { - this._provider.get(handle).handleSearchProgress(session, resource); + $handleSearchProgress(handle: number, session: number, data: UriComponents): void { + this._provider.get(handle).handleSearchProgress(session, URI.revive(data)); } } @@ -123,7 +123,9 @@ class RemoteFileSystemProvider implements IFileSystemProvider, ISearchResultProv return this._proxy.$mkdir(this._handle, resource); } readdir(resource: URI): TPromise<[URI, IStat][], any> { - return this._proxy.$readdir(this._handle, resource); + return this._proxy.$readdir(this._handle, resource).then(data => { + return data.map(tuple => <[URI, IStat]>[URI.revive(tuple[0]), tuple[1]]); + }); } rmdir(resource: URI): TPromise { return this._proxy.$rmdir(this._handle, resource); diff --git a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts index b3e0045dc48..cda620bf166 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadFileSystemEventService.ts @@ -19,7 +19,7 @@ export class MainThreadFileSystemEventService { @IFileService fileService: IFileService ) { - const proxy: ExtHostFileSystemEventServiceShape = extHostContext.get(ExtHostContext.ExtHostFileSystemEventService); + const proxy: ExtHostFileSystemEventServiceShape = extHostContext.getProxy(ExtHostContext.ExtHostFileSystemEventService); const events: FileSystemEvents = { created: [], changed: [], diff --git a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts index e962c85b7ac..0c699210c82 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadHeapService.ts @@ -5,14 +5,14 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostContext, ObjectIdentifier, IExtHostContext } from '../node/extHost.protocol'; -import { consumeSignals, GCSignal } from 'gc-signals'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable } from 'vs/base/common/lifecycle'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; +import { isThenable } from 'vs/base/common/async'; +import { isNullOrUndefined } from 'util'; export const IHeapService = createDecorator('heapService'); @@ -25,16 +25,9 @@ export interface IHeapService { * Track gc-collection for all new objects that * have the $ident-value set. */ - trackRecursive(p: TPromise): TPromise; - - /** - * Track gc-collection for all new objects that - * have the $ident-value set. - */ - trackRecursive(obj: T): T; + trackRecursive(obj: T | Thenable): Thenable; } - export class HeapService implements IHeapService { _serviceBrand: any; @@ -42,75 +35,85 @@ export class HeapService implements IHeapService { private _onGarbageCollection: Emitter = new Emitter(); public readonly onGarbageCollection: Event = this._onGarbageCollection.event; - private _activeSignals = new WeakMap(); + private _activeSignals = new WeakMap(); private _activeIds = new Set(); private _consumeHandle: number; constructor() { - this._consumeHandle = setInterval(() => { - const ids = consumeSignals(); - - if (ids.length > 0) { - // local book-keeping - for (const id of ids) { - this._activeIds.delete(id); - } - - // fire event - this._onGarbageCollection.fire(ids); - } - - }, 15 * 1000); + // } dispose() { clearInterval(this._consumeHandle); } - trackRecursive(p: TPromise): TPromise; - trackRecursive(obj: T): T; - trackRecursive(obj: any): any { - if (TPromise.is(obj)) { + trackRecursive(obj: T | Thenable): Thenable { + if (isThenable(obj)) { return obj.then(result => this.trackRecursive(result)); } else { return this._doTrackRecursive(obj); } } - private _doTrackRecursive(obj: any): any { + private _doTrackRecursive(obj: any): Promise { - const stack = [obj]; - while (stack.length > 0) { + if (isNullOrUndefined(obj)) { + return Promise.resolve(obj); + } - // remove first element - let obj = stack.shift(); + return import('gc-signals').then(({ GCSignal, consumeSignals }) => { - if (!obj || typeof obj !== 'object') { - continue; + if (this._consumeHandle === void 0) { + // ensure that there is one consumer of signals + this._consumeHandle = setInterval(() => { + const ids = consumeSignals(); + + if (ids.length > 0) { + // local book-keeping + for (const id of ids) { + this._activeIds.delete(id); + } + + // fire event + this._onGarbageCollection.fire(ids); + } + + }, 15 * 1000); } - for (let key in obj) { - if (!Object.prototype.hasOwnProperty.call(obj, key)) { + const stack = [obj]; + while (stack.length > 0) { + + // remove first element + let obj = stack.shift(); + + if (!obj || typeof obj !== 'object') { continue; } - const value = obj[key]; - // recurse -> object/array - if (typeof value === 'object') { - stack.push(value); + for (let key in obj) { + if (!Object.prototype.hasOwnProperty.call(obj, key)) { + continue; + } - } else if (key === ObjectIdentifier.name) { - // track new $ident-objects + const value = obj[key]; + // recurse -> object/array + if (typeof value === 'object') { + stack.push(value); - if (typeof value === 'number' && !this._activeIds.has(value)) { - this._activeIds.add(value); - this._activeSignals.set(obj, new GCSignal(value)); + } else if (key === ObjectIdentifier.name) { + // track new $ident-objects + + if (typeof value === 'number' && !this._activeIds.has(value)) { + this._activeIds.add(value); + this._activeSignals.set(obj, new GCSignal(value)); + } } } } - } - return obj; + return obj; + }); } } @@ -123,7 +126,7 @@ export class MainThreadHeapService { extHostContext: IExtHostContext, @IHeapService heapService: IHeapService, ) { - const proxy = extHostContext.get(ExtHostContext.ExtHostHeapService); + const proxy = extHostContext.getProxy(ExtHostContext.ExtHostHeapService); this._toDispose = heapService.onGarbageCollection((ids) => { // send to ext host proxy.$onGarbageCollection(ids); diff --git a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts index 7466f66d835..e1bc5da5f5b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadLanguageFeatures.ts @@ -15,13 +15,14 @@ import { wireCancellationToken } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; +import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext, ISerializedLanguageConfiguration, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, LocationDto, SymbolInformationDto, WorkspaceEditDto, ResourceEditDto, CodeActionDto } from '../node/extHost.protocol'; import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry'; -import { LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration'; +import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/modes/languageConfiguration'; import { IHeapService } from './mainThreadHeapService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { toLanguageSelector } from 'vs/workbench/api/node/extHostTypeConverters'; +import URI from 'vs/base/common/uri'; @extHostNamedCustomer(MainContext.MainThreadLanguageFeatures) export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape { @@ -36,7 +37,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha @IHeapService heapService: IHeapService, @IModeService modeService: IModeService, ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostLanguageFeatures); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures); this._heapService = heapService; this._modeService = modeService; } @@ -47,29 +48,78 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } } - $unregister(handle: number): TPromise { + $unregister(handle: number): void { let registration = this._registrations[handle]; if (registration) { registration.dispose(); delete this._registrations[handle]; } - return undefined; } + //#region --- revive functions + + private static _reviveLocationDto(data: LocationDto): modes.Location; + private static _reviveLocationDto(data: LocationDto[]): modes.Location[]; + private static _reviveLocationDto(data: LocationDto | LocationDto[]): modes.Location | modes.Location[] { + if (!data) { + return data; + } else if (Array.isArray(data)) { + data.forEach(l => MainThreadLanguageFeatures._reviveLocationDto(l)); + return data; + } else { + data.uri = URI.revive(data.uri); + return data; + } + } + + private static _reviveSymbolInformationDto(data: SymbolInformationDto): modes.SymbolInformation; + private static _reviveSymbolInformationDto(data: SymbolInformationDto[]): modes.SymbolInformation[]; + private static _reviveSymbolInformationDto(data: SymbolInformationDto | SymbolInformationDto[]): modes.SymbolInformation | modes.SymbolInformation[] { + if (!data) { + return data; + } else if (Array.isArray(data)) { + data.forEach(MainThreadLanguageFeatures._reviveSymbolInformationDto); + return data; + } else { + data.location = MainThreadLanguageFeatures._reviveLocationDto(data.location); + return data; + } + } + + private static _reviveResourceEditDto(data: ResourceEditDto): modes.IResourceEdit { + data.resource = URI.revive(data.resource); + return data; + } + + private static _reviveWorkspaceEditDto(data: WorkspaceEditDto): modes.WorkspaceEdit { + if (data && data.edits) { + data.edits.forEach(MainThreadLanguageFeatures._reviveResourceEditDto); + } + return data; + } + + private static _reviveCodeActionDto(data: CodeActionDto[]): modes.CodeAction[] { + if (data) { + data.forEach(code => MainThreadLanguageFeatures._reviveWorkspaceEditDto(code.edits)); + } + return data; + } + + //#endregion + // --- outline - $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.DocumentSymbolProviderRegistry.register(toLanguageSelector(selector), { provideDocumentSymbols: (model: IReadOnlyModel, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri)); + return wireCancellationToken(token, this._proxy.$provideDocumentSymbols(handle, model.uri)).then(MainThreadLanguageFeatures._reviveSymbolInformationDto); } }); - return undefined; } // --- code lens - $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise { + $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): void { const provider = { provideCodeLenses: (model: IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable => { @@ -87,111 +137,100 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } this._registrations[handle] = modes.CodeLensProviderRegistry.register(toLanguageSelector(selector), provider); - return undefined; } - $emitCodeLensEvent(eventHandle: number, event?: any): TPromise { + $emitCodeLensEvent(eventHandle: number, event?: any): void { const obj = this._registrations[eventHandle]; if (obj instanceof Emitter) { obj.fire(event); } - return undefined; } // --- declaration - $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.DefinitionProviderRegistry.register(toLanguageSelector(selector), { provideDefinition: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)); + return wireCancellationToken(token, this._proxy.$provideDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); } }); - return undefined; } - $registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.ImplementationProviderRegistry.register(toLanguageSelector(selector), { provideImplementation: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)); + return wireCancellationToken(token, this._proxy.$provideImplementation(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); } }); - return undefined; } - $registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.TypeDefinitionProviderRegistry.register(toLanguageSelector(selector), { provideTypeDefinition: (model, position, token): Thenable => { - return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)); + return wireCancellationToken(token, this._proxy.$provideTypeDefinition(handle, model.uri, position)).then(MainThreadLanguageFeatures._reviveLocationDto); } }); - return undefined; } // --- extra info - $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.HoverProviderRegistry.register(toLanguageSelector(selector), { provideHover: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$provideHover(handle, model.uri, position)); } }); - return undefined; } // --- occurrences - $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.DocumentHighlightProviderRegistry.register(toLanguageSelector(selector), { provideDocumentHighlights: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$provideDocumentHighlights(handle, model.uri, position)); } }); - return undefined; } // --- references - $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.ReferenceProviderRegistry.register(toLanguageSelector(selector), { provideReferences: (model: IReadOnlyModel, position: EditorPosition, context: modes.ReferenceContext, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context)); + return wireCancellationToken(token, this._proxy.$provideReferences(handle, model.uri, position, context)).then(MainThreadLanguageFeatures._reviveLocationDto); } }); - return undefined; } // --- quick fix - $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.CodeActionProviderRegistry.register(toLanguageSelector(selector), { provideCodeActions: (model: IReadOnlyModel, range: EditorRange, token: CancellationToken): Thenable => { - return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range))); + return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range))).then(MainThreadLanguageFeatures._reviveCodeActionDto); } }); - return undefined; } // --- formatting - $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.DocumentFormattingEditProviderRegistry.register(toLanguageSelector(selector), { provideDocumentFormattingEdits: (model: IReadOnlyModel, options: modes.FormattingOptions, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$provideDocumentFormattingEdits(handle, model.uri, options)); } }); - return undefined; } - $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.DocumentRangeFormattingEditProviderRegistry.register(toLanguageSelector(selector), { provideDocumentRangeFormattingEdits: (model: IReadOnlyModel, range: EditorRange, options: modes.FormattingOptions, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$provideDocumentRangeFormattingEdits(handle, model.uri, range, options)); } }); - return undefined; } - $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise { + $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): void { this._registrations[handle] = modes.OnTypeFormattingEditProviderRegistry.register(toLanguageSelector(selector), { autoFormatTriggerCharacters, @@ -200,46 +239,41 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return wireCancellationToken(token, this._proxy.$provideOnTypeFormattingEdits(handle, model.uri, position, ch, options)); } }); - return undefined; } // --- navigate type - $registerNavigateTypeSupport(handle: number): TPromise { + $registerNavigateTypeSupport(handle: number): void { let lastResultId: number; this._registrations[handle] = WorkspaceSymbolProviderRegistry.register({ provideWorkspaceSymbols: (search: string): TPromise => { - return this._proxy.$provideWorkspaceSymbols(handle, search).then(result => { if (lastResultId !== undefined) { this._proxy.$releaseWorkspaceSymbols(handle, lastResultId); } lastResultId = result._id; - return result.symbols; + return MainThreadLanguageFeatures._reviveSymbolInformationDto(result.symbols); }); }, resolveWorkspaceSymbol: (item: modes.SymbolInformation): TPromise => { - return this._proxy.$resolveWorkspaceSymbol(handle, item); + return this._proxy.$resolveWorkspaceSymbol(handle, item).then(i => MainThreadLanguageFeatures._reviveSymbolInformationDto(i)); } }); - return undefined; } // --- rename - $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.RenameProviderRegistry.register(toLanguageSelector(selector), { provideRenameEdits: (model: IReadOnlyModel, position: EditorPosition, newName: string, token: CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)); + return wireCancellationToken(token, this._proxy.$provideRenameEdits(handle, model.uri, position, newName)).then(MainThreadLanguageFeatures._reviveWorkspaceEditDto); } }); - return undefined; } // --- suggest - $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): TPromise { - + $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): void { this._registrations[handle] = modes.SuggestRegistry.register(toLanguageSelector(selector), { triggerCharacters, provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, context: modes.SuggestContext, token: CancellationToken): Thenable => { @@ -258,12 +292,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha ? (model, position, suggestion, token) => wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion)) : undefined }); - return undefined; } // --- parameter hints - $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise { + $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void { this._registrations[handle] = modes.SignatureHelpProviderRegistry.register(toLanguageSelector(selector), { signatureHelpTriggerCharacters: triggerCharacter, @@ -273,12 +306,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha } }); - return undefined; } // --- links - $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void { this._registrations[handle] = modes.LinkProviderRegistry.register(toLanguageSelector(selector), { provideLinks: (model, token) => { return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideDocumentLinks(handle, model.uri))); @@ -287,12 +319,11 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha return wireCancellationToken(token, this._proxy.$resolveDocumentLink(handle, link)); } }); - return undefined; } // --- colors - $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise { + $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): void { const proxy = this._proxy; this._registrations[handle] = modes.ColorProviderRegistry.register(toLanguageSelector(selector), { provideDocumentColors: (model, token) => { @@ -322,20 +353,61 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha })); } }); - - return TPromise.as(null); } // --- configuration - $setLanguageConfiguration(handle: number, languageId: string, _configuration: vscode.LanguageConfiguration): TPromise { + private static _reviveRegExp(regExp: ISerializedRegExp): RegExp { + if (typeof regExp === 'undefined') { + return undefined; + } + if (regExp === null) { + return null; + } + return new RegExp(regExp.pattern, regExp.flags); + } + + private static _reviveIndentationRule(indentationRule: ISerializedIndentationRule): IndentationRule { + if (typeof indentationRule === 'undefined') { + return undefined; + } + if (indentationRule === null) { + return null; + } + return { + decreaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.decreaseIndentPattern), + increaseIndentPattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.increaseIndentPattern), + indentNextLinePattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.indentNextLinePattern), + unIndentedLinePattern: MainThreadLanguageFeatures._reviveRegExp(indentationRule.unIndentedLinePattern), + }; + } + + private static _reviveOnEnterRule(onEnterRule: ISerializedOnEnterRule): OnEnterRule { + return { + beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText), + afterText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText), + action: onEnterRule.action + }; + } + + private static _reviveOnEnterRules(onEnterRules: ISerializedOnEnterRule[]): OnEnterRule[] { + if (typeof onEnterRules === 'undefined') { + return undefined; + } + if (onEnterRules === null) { + return null; + } + return onEnterRules.map(MainThreadLanguageFeatures._reviveOnEnterRule); + } + + $setLanguageConfiguration(handle: number, languageId: string, _configuration: ISerializedLanguageConfiguration): void { let configuration: LanguageConfiguration = { comments: _configuration.comments, brackets: _configuration.brackets, - wordPattern: _configuration.wordPattern, - indentationRules: _configuration.indentationRules, - onEnterRules: _configuration.onEnterRules, + wordPattern: MainThreadLanguageFeatures._reviveRegExp(_configuration.wordPattern), + indentationRules: MainThreadLanguageFeatures._reviveIndentationRule(_configuration.indentationRules), + onEnterRules: MainThreadLanguageFeatures._reviveOnEnterRules(_configuration.onEnterRules), autoClosingPairs: null, surroundingPairs: null, @@ -360,8 +432,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha if (languageIdentifier) { this._registrations[handle] = LanguageConfigurationRegistry.register(languageIdentifier, configuration); } - - return undefined; } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts index 090834e9b6a..c62b072f539 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadMessageService.ts @@ -8,7 +8,6 @@ import nls = require('vs/nls'); import { IMessageService, IChoiceService } from 'vs/platform/message/common/message'; import Severity from 'vs/base/common/severity'; import { Action } from 'vs/base/common/actions'; -import { TPromise as Promise } from 'vs/base/common/winjs.base'; import { MainThreadMessageServiceShape, MainContext, IExtHostContext, MainThreadMessageOptions } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts index c85633089f0..905dc07490d 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadOutputService.ts @@ -45,7 +45,8 @@ export class MainThreadOutputService implements MainThreadOutputServiceShape { } public $reveal(channelId: string, label: string, preserveFocus: boolean): TPromise { - this._getChannel(channelId, label).show(preserveFocus); + const channel = this._getChannel(channelId, label); + this._outputService.showChannel(channel.id, preserveFocus); return undefined; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadProgress.ts b/src/vs/workbench/api/electron-browser/mainThreadProgress.ts index 5ddce6f738b..4b0f1c69aca 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadProgress.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadProgress.ts @@ -5,7 +5,6 @@ 'use strict'; import { IProgressService2, IProgress, IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress'; -import { TPromise } from 'vs/base/common/winjs.base'; import { MainThreadProgressShape, MainContext, IExtHostContext } from '../node/extHost.protocol'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; @@ -47,7 +46,7 @@ export class MainThreadProgress implements MainThreadProgressShape { private _createTask(handle: number) { return (progress: IProgress) => { - return new TPromise(resolve => { + return new Promise(resolve => { this._progress.set(handle, { resolve, progress }); }); }; diff --git a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts index bbcfba6354f..7ac56ff2c63 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadQuickOpen.ts @@ -25,7 +25,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { extHostContext: IExtHostContext, @IQuickOpenService quickOpenService: IQuickOpenService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostQuickOpen); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostQuickOpen); this._quickOpenService = quickOpenService; } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts index 88c6377b3c4..31e0348e305 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSCM.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSCM.ts @@ -241,7 +241,8 @@ class MainThreadSCMProvider implements ISCMProvider { return TPromise.as(null); } - return this.proxy.$provideOriginalResource(this.handle, uri); + return this.proxy.$provideOriginalResource(this.handle, uri.toString()) + .then(result => result && URI.parse(result)); } toJSON(): any { @@ -268,7 +269,7 @@ export class MainThreadSCM implements MainThreadSCMShape { extHostContext: IExtHostContext, @ISCMService private scmService: ISCMService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostSCM); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostSCM); } dispose(): void { @@ -391,4 +392,14 @@ export class MainThreadSCM implements MainThreadSCMShape { repository.input.placeholder = placeholder; } + + $setLineWarningLength(sourceControlHandle: number, lineWarningLength: number): void { + const repository = this._repositories[sourceControlHandle]; + + if (!repository) { + return; + } + + repository.input.lineWarningLength = lineWarningLength; + } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts index 5c6e9b6f321..970e88ad5c1 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadSaveParticipant.ts @@ -5,7 +5,6 @@ 'use strict'; -import { TPromise } from 'vs/base/common/winjs.base'; import { sequence } from 'vs/base/common/async'; import * as strings from 'vs/base/common/strings'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -16,7 +15,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; import { Position } from 'vs/editor/common/core/position'; import { trimTrailingWhitespace } from 'vs/editor/common/commands/trimTrailingWhitespaceCommand'; -import { getDocumentFormattingEdits } from 'vs/editor/contrib/format/format'; +import { getDocumentFormattingEdits, NoProviderError } from 'vs/editor/contrib/format/format'; import { EditOperationsCommand } from 'vs/editor/contrib/format/formatCommand'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; @@ -25,8 +24,14 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress'; +import { localize } from 'vs/nls'; -class TrimWhitespaceParticipant implements ISaveParticipant { +export interface ISaveParticipantParticipant extends ISaveParticipant { + // progressMessage: string; +} + +class TrimWhitespaceParticipant implements ISaveParticipantParticipant { constructor( @IConfigurationService private configurationService: IConfigurationService, @@ -82,7 +87,7 @@ function findEditor(model: IModel, codeEditorService: ICodeEditorService): ICode return candidate; } -export class FinalNewLineParticipant implements ISaveParticipant { +export class FinalNewLineParticipant implements ISaveParticipantParticipant { constructor( @IConfigurationService private configurationService: IConfigurationService, @@ -120,7 +125,7 @@ export class FinalNewLineParticipant implements ISaveParticipant { } } -export class TrimFinalNewLinesParticipant implements ISaveParticipant { +export class TrimFinalNewLinesParticipant implements ISaveParticipantParticipant { constructor( @IConfigurationService private configurationService: IConfigurationService, @@ -157,7 +162,11 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipant { currentLine = model.getLineContent(currentLineNumber); currentLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(currentLine) === -1; } - model.pushEditOperations(prevSelection, [EditOperation.delete(new Range(currentLineNumber + 1, 1, lineCount + 1, 1))], edits => prevSelection); + + const deletionRange = new Range(currentLineNumber + 1, 1, lineCount + 1, 1); + if (!deletionRange.isEmpty()) { + model.pushEditOperations(prevSelection, [EditOperation.delete(deletionRange)], edits => prevSelection); + } if (editor) { editor.setSelections(prevSelection); @@ -165,7 +174,7 @@ export class TrimFinalNewLinesParticipant implements ISaveParticipant { } } -class FormatOnSaveParticipant implements ISaveParticipant { +class FormatOnSaveParticipant implements ISaveParticipantParticipant { constructor( @ICodeEditorService private _editorService: ICodeEditorService, @@ -175,7 +184,7 @@ class FormatOnSaveParticipant implements ISaveParticipant { // Nothing } - participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): TPromise { + participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise { const model = editorModel.textEditorModel; if (env.reason === SaveReason.AUTO @@ -186,11 +195,17 @@ class FormatOnSaveParticipant implements ISaveParticipant { const versionNow = model.getVersionId(); const { tabSize, insertSpaces } = model.getOptions(); - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { setTimeout(reject, 750); getDocumentFormattingEdits(model, { tabSize, insertSpaces }) .then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits)) - .then(resolve, reject); + .then(resolve, err => { + if (!(err instanceof Error) || err.name !== NoProviderError.Name) { + reject(err); + } else { + resolve(); + } + }); }).then(edits => { if (edits && versionNow === model.getVersionId()) { @@ -233,21 +248,21 @@ class FormatOnSaveParticipant implements ISaveParticipant { } } -class ExtHostSaveParticipant implements ISaveParticipant { +class ExtHostSaveParticipant implements ISaveParticipantParticipant { private _proxy: ExtHostDocumentSaveParticipantShape; constructor(extHostContext: IExtHostContext) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostDocumentSaveParticipant); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostDocumentSaveParticipant); } - participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): TPromise { - return new TPromise((resolve, reject) => { + participate(editorModel: ITextFileEditorModel, env: { reason: SaveReason }): Promise { + return new Promise((resolve, reject) => { setTimeout(reject, 1750); this._proxy.$participateInSave(editorModel.getResource(), env.reason).then(values => { for (const success of values) { if (!success) { - return TPromise.wrapError(new Error('listener failed')); + return Promise.reject(new Error('listener failed')); } } return undefined; @@ -260,24 +275,20 @@ class ExtHostSaveParticipant implements ISaveParticipant { @extHostCustomer export class SaveParticipant implements ISaveParticipant { - private _saveParticipants: ISaveParticipant[]; + private _saveParticipants: ISaveParticipantParticipant[]; constructor( extHostContext: IExtHostContext, - @IInstantiationService instantiationService: IInstantiationService, - @IConfigurationService configurationService: IConfigurationService, - @ICodeEditorService codeEditorService: ICodeEditorService, - @IEditorWorkerService editorWorkerService: IEditorWorkerService + @IProgressService2 private _progressService: IProgressService2, + @IInstantiationService instantiationService: IInstantiationService ) { - this._saveParticipants = [ - new TrimWhitespaceParticipant(configurationService, codeEditorService), - new FormatOnSaveParticipant(codeEditorService, editorWorkerService, configurationService), - new FinalNewLineParticipant(configurationService, codeEditorService), - new TrimFinalNewLinesParticipant(configurationService, codeEditorService), - new ExtHostSaveParticipant(extHostContext) + instantiationService.createInstance(TrimWhitespaceParticipant), + instantiationService.createInstance(FormatOnSaveParticipant), + instantiationService.createInstance(FinalNewLineParticipant), + instantiationService.createInstance(TrimFinalNewLinesParticipant), + instantiationService.createInstance(ExtHostSaveParticipant, extHostContext), ]; - // Hook into model TextFileEditorModel.setSaveParticipant(this); } @@ -286,11 +297,13 @@ export class SaveParticipant implements ISaveParticipant { TextFileEditorModel.setSaveParticipant(undefined); } - participate(model: ITextFileEditorModel, env: { reason: SaveReason }): TPromise { - const promiseFactory = this._saveParticipants.map(p => () => { - return TPromise.as(p.participate(model, env)); + participate(model: ITextFileEditorModel, env: { reason: SaveReason }): Thenable { + return this._progressService.withProgress({ location: ProgressLocation.Window }, progress => { + progress.report({ message: localize('saveParticipants', "Running Save Participants...") }); + const promiseFactory = this._saveParticipants.map(p => () => { + return Promise.resolve(p.participate(model, env)); + }); + return sequence(promiseFactory).then(() => { }); }); - - return sequence(promiseFactory).then(() => { }); } } diff --git a/src/vs/workbench/api/electron-browser/mainThreadStorage.ts b/src/vs/workbench/api/electron-browser/mainThreadStorage.ts index a58f4d0f466..403ddbdde6b 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadStorage.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadStorage.ts @@ -38,7 +38,7 @@ export class MainThreadStorage implements MainThreadStorageShape { } } - $setValue(shared: boolean, key: string, value: any): TPromise { + $setValue(shared: boolean, key: string, value: any): TPromise { let jsonValue: any; try { jsonValue = JSON.stringify(value); diff --git a/src/vs/workbench/api/electron-browser/mainThreadTask.ts b/src/vs/workbench/api/electron-browser/mainThreadTask.ts index 20cccb38906..f85ee2a039a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTask.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTask.ts @@ -25,7 +25,7 @@ export class MainThreadTask implements MainThreadTaskShape { @ITaskService private _taskService: ITaskService, @IWorkspaceContextService private _workspaceContextServer: IWorkspaceContextService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostTask); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTask); this._activeHandles = Object.create(null); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts index aade4a664e3..ee934a107b5 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTerminalService.ts @@ -20,7 +20,7 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape extHostContext: IExtHostContext, @ITerminalService private terminalService: ITerminalService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostTerminalService); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTerminalService); this._toDispose = []; this._toDispose.push(terminalService.onInstanceDisposed((terminalInstance) => this._onTerminalDisposed(terminalInstance))); this._toDispose.push(terminalService.onInstanceProcessIdReady((terminalInstance) => this._onTerminalProcessIdReady(terminalInstance))); @@ -33,11 +33,12 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape // when the extension host process goes down ? } - public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], env?: { [key: string]: string }, waitOnExit?: boolean): TPromise { + public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise { const shellLaunchConfig: IShellLaunchConfig = { name, executable: shellPath, args: shellArgs, + cwd, waitOnExit, ignoreConfigurationCwd: true, env diff --git a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts index afaa490c84f..e9730fb0ed4 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadTreeViews.ts @@ -24,7 +24,7 @@ export class MainThreadTreeViews extends Disposable implements MainThreadTreeVie @IMessageService private messageService: IMessageService ) { super(); - this._proxy = extHostContext.get(ExtHostContext.ExtHostTreeViews); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTreeViews); } $registerView(treeViewId: string): void { @@ -140,4 +140,4 @@ class TreeViewDataProvider implements ITreeViewDataProvider { assign(current, treeItem); } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/electron-browser/mainThreadWindow.ts b/src/vs/workbench/api/electron-browser/mainThreadWindow.ts index 27c98a4b4a9..8c34443e0b0 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWindow.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWindow.ts @@ -20,7 +20,7 @@ export class MainThreadWindow implements MainThreadWindowShape { extHostContext: IExtHostContext, @IWindowService private windowService: IWindowService ) { - this.proxy = extHostContext.get(ExtHostContext.ExtHostWindow); + this.proxy = extHostContext.getProxy(ExtHostContext.ExtHostWindow); windowService.onDidChangeFocus(this.proxy.$onDidChangeWindowFocus, this.proxy, this.disposables); } diff --git a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts index d0820e649e0..08382f85d1a 100644 --- a/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts +++ b/src/vs/workbench/api/electron-browser/mainThreadWorkspace.ts @@ -29,7 +29,7 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { @ITextFileService private readonly _textFileService: ITextFileService, @IConfigurationService private _configurationService: IConfigurationService ) { - this._proxy = extHostContext.get(ExtHostContext.ExtHostWorkspace); + this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostWorkspace); this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose); this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose); } @@ -123,4 +123,3 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape { }); } } - diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 3af68d9fddb..5e8d988b5a9 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -46,11 +46,10 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as vscode from 'vscode'; import * as paths from 'vs/base/common/paths'; -import { MainContext, ExtHostContext, IInitData } from './extHost.protocol'; +import { MainContext, ExtHostContext, IInitData, IExtHostContext } from './extHost.protocol'; import * as languageConfiguration from 'vs/editor/common/modes/languageConfiguration'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService'; -import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; +import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { ExtHostDialogs } from 'vs/workbench/api/node/extHostDialogs'; import { ExtHostFileSystem } from 'vs/workbench/api/node/extHostFileSystem'; import { FileChangeType, FileType } from 'vs/platform/files/common/files'; @@ -64,13 +63,21 @@ export interface IExtensionApiFactory { (extension: IExtensionDescription): typeof vscode; } +export function checkProposedApiEnabled(extension: IExtensionDescription): void { + if (!extension.enableProposedApi) { + throwProposedApiError(extension); + } +} + +function throwProposedApiError(extension: IExtensionDescription): never { + throw new Error(`[${extension.id}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.id}`); +} + function proposedApiFunction(extension: IExtensionDescription, fn: T): T { if (extension.enableProposedApi) { return fn; } else { - return (() => { - throw new Error(`[${extension.id}]: Proposed API is only available when running out of dev or with the following command line switch: --enable-proposed-api ${extension.id}`); - }); + return throwProposedApiError; } } @@ -79,7 +86,7 @@ function proposedApiFunction(extension: IExtensionDescription, fn: T): T { */ export function createApiFactory( initData: IInitData, - threadService: ExtHostThreadService, + rpcProtocol: IExtHostContext, extHostWorkspace: ExtHostWorkspace, extHostConfiguration: ExtHostConfiguration, extensionService: ExtHostExtensionService, @@ -87,48 +94,46 @@ export function createApiFactory( ): IExtensionApiFactory { // Addressable instances - const extHostHeapService = threadService.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService()); - const extHostDecorations = threadService.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(threadService)); - const extHostDocumentsAndEditors = threadService.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(threadService)); - const extHostDocuments = threadService.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(threadService, extHostDocumentsAndEditors)); - const extHostDocumentContentProviders = threadService.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(threadService, extHostDocumentsAndEditors)); - const extHostDocumentSaveParticipant = threadService.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadEditors))); - const extHostEditors = threadService.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(threadService, extHostDocumentsAndEditors)); - const extHostCommands = threadService.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(threadService, extHostHeapService, logService)); - const extHostTreeViews = threadService.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(threadService.get(MainContext.MainThreadTreeViews), extHostCommands)); - threadService.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); - const extHostDebugService = threadService.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(threadService, extHostWorkspace)); - threadService.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); - const extHostDiagnostics = threadService.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(threadService)); - const languageFeatures = threadService.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); - const extHostFileSystem = threadService.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(threadService)); - const extHostFileSystemEvent = threadService.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService()); - const extHostQuickOpen = threadService.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(threadService, extHostWorkspace, extHostCommands)); - const extHostTerminalService = threadService.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(threadService)); - const extHostSCM = threadService.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(threadService, extHostCommands, logService)); - const extHostTask = threadService.set(ExtHostContext.ExtHostTask, new ExtHostTask(threadService, extHostWorkspace)); - const extHostWindow = threadService.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(threadService)); - threadService.set(ExtHostContext.ExtHostExtensionService, extensionService); + const extHostHeapService = rpcProtocol.set(ExtHostContext.ExtHostHeapService, new ExtHostHeapService()); + const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, new ExtHostDecorations(rpcProtocol)); + const extHostDocumentsAndEditors = rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new ExtHostDocumentsAndEditors(rpcProtocol)); + const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); + const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors)); + const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(logService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadEditors))); + const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); + const extHostCommands = rpcProtocol.set(ExtHostContext.ExtHostCommands, new ExtHostCommands(rpcProtocol, extHostHeapService, logService)); + const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands)); + rpcProtocol.set(ExtHostContext.ExtHostWorkspace, extHostWorkspace); + const extHostDebugService = rpcProtocol.set(ExtHostContext.ExtHostDebugService, new ExtHostDebugService(rpcProtocol, extHostWorkspace)); + rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration); + const extHostDiagnostics = rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, new ExtHostDiagnostics(rpcProtocol)); + const languageFeatures = rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, new ExtHostLanguageFeatures(rpcProtocol, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics)); + const extHostFileSystem = rpcProtocol.set(ExtHostContext.ExtHostFileSystem, new ExtHostFileSystem(rpcProtocol)); + const extHostFileSystemEvent = rpcProtocol.set(ExtHostContext.ExtHostFileSystemEventService, new ExtHostFileSystemEventService()); + const extHostQuickOpen = rpcProtocol.set(ExtHostContext.ExtHostQuickOpen, new ExtHostQuickOpen(rpcProtocol, extHostWorkspace, extHostCommands)); + const extHostTerminalService = rpcProtocol.set(ExtHostContext.ExtHostTerminalService, new ExtHostTerminalService(rpcProtocol)); + const extHostSCM = rpcProtocol.set(ExtHostContext.ExtHostSCM, new ExtHostSCM(rpcProtocol, extHostCommands, logService)); + const extHostTask = rpcProtocol.set(ExtHostContext.ExtHostTask, new ExtHostTask(rpcProtocol, extHostWorkspace)); + const extHostWindow = rpcProtocol.set(ExtHostContext.ExtHostWindow, new ExtHostWindow(rpcProtocol)); + rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService); // Check that no named customers are missing const expected: ProxyIdentifier[] = Object.keys(ExtHostContext).map((key) => ExtHostContext[key]); - threadService.assertRegistered(expected); + rpcProtocol.assertRegistered(expected); // Other instances - const extHostMessageService = new ExtHostMessageService(threadService); - const extHostDialogs = new ExtHostDialogs(threadService); - const extHostStatusBar = new ExtHostStatusBar(threadService); - const extHostProgress = new ExtHostProgress(threadService.get(MainContext.MainThreadProgress)); - const extHostOutputService = new ExtHostOutputService(threadService); - const extHostLanguages = new ExtHostLanguages(threadService); + const extHostMessageService = new ExtHostMessageService(rpcProtocol); + const extHostDialogs = new ExtHostDialogs(rpcProtocol); + const extHostStatusBar = new ExtHostStatusBar(rpcProtocol); + const extHostProgress = new ExtHostProgress(rpcProtocol.getProxy(MainContext.MainThreadProgress)); + const extHostOutputService = new ExtHostOutputService(rpcProtocol); + const extHostLanguages = new ExtHostLanguages(rpcProtocol); // Register API-ish commands ExtHostApiCommands.register(extHostCommands); return function (extension: IExtensionDescription): typeof vscode { - const EXTENSION_ID = extension.id; - if (!isFalsyOrEmpty(product.extensionAllowedProposedApi) && product.extensionAllowedProposedApi.indexOf(extension.id) >= 0 ) { @@ -395,13 +400,8 @@ export function createApiFactory( }; // namespace: workspace - let warnedRootPath = false; const workspace: typeof vscode.workspace = { get rootPath() { - if (!warnedRootPath) { - warnedRootPath = true; - extensionService.addMessage(EXTENSION_ID, Severity.Warning, 'workspace.rootPath is deprecated'); - } return extHostWorkspace.getPath(); }, set rootPath(value) { @@ -477,7 +477,7 @@ export function createApiFactory( return extHostDocuments.onDidSaveDocument(listener, thisArgs, disposables); }, onWillSaveTextDocument: (listener, thisArgs?, disposables?) => { - return extHostDocumentSaveParticipant.onWillSaveTextDocumentEvent(listener, thisArgs, disposables); + return extHostDocumentSaveParticipant.getOnWillSaveTextDocumentEvent(extension)(listener, thisArgs, disposables); }, onDidChangeConfiguration: (listener: (_: any) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) => { return extHostConfiguration.onDidChangeConfiguration(listener, thisArgs, disposables); @@ -577,6 +577,7 @@ export function createApiFactory( Hover: extHostTypes.Hover, IndentAction: languageConfiguration.IndentAction, Location: extHostTypes.Location, + LogLevel: extHostTypes.LogLevel, MarkdownString: extHostTypes.MarkdownString, OverviewRulerLane: EditorCommon.OverviewRulerLane, ParameterInformation: extHostTypes.ParameterInformation, diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index cb65d9c5c87..af46500e324 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -7,12 +7,14 @@ import { createMainContextProxyIdentifier as createMainId, createExtHostContextProxyIdentifier as createExtId, - ProxyIdentifier -} from 'vs/workbench/services/thread/common/threadService'; + ProxyIdentifier, + IRPCProtocol, + ProxyType +} from 'vs/workbench/services/extensions/node/proxyIdentifier'; import * as vscode from 'vscode'; -import URI from 'vs/base/common/uri'; +import { UriComponents } from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -29,6 +31,7 @@ import * as modes from 'vs/editor/common/modes'; import { ITextSource } from 'vs/editor/common/model/textSource'; import { IConfigurationData, ConfigurationTarget, IConfigurationModel } from 'vs/platform/configuration/common/configuration'; +import { IConfig } from 'vs/workbench/parts/debug/common/debug'; import { IPickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -46,10 +49,10 @@ import { ITreeItem } from 'vs/workbench/common/views'; import { ThemeColor } from 'vs/platform/theme/common/themeService'; import { IDisposable } from 'vs/base/common/lifecycle'; import { SerializedError } from 'vs/base/common/errors'; -import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; import { IStat, IFileChange } from 'vs/platform/files/common/files'; import { ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { CommentRule, CharacterPair, EnterAction } from 'vs/editor/common/modes/languageConfiguration'; export interface IEnvironment { isExtensionDevelopmentDebug: boolean; @@ -66,7 +69,7 @@ export interface IEnvironment { export interface IWorkspaceData { id: string; name: string; - folders: IWorkspaceFolderData[]; + folders: { uri: UriComponents, name: string, index: number }[]; } export interface IInitData { @@ -90,46 +93,33 @@ export interface IWorkspaceConfigurationChangeEventData { changedConfigurationByResource: { [folder: string]: IConfigurationModel }; } -export interface IExtHostContext { - /** - * Returns a proxy to an object addressable/named in the extension host process. - */ - get(identifier: ProxyIdentifier): T; - - /** - * Register manually created instance. - */ - set(identifier: ProxyIdentifier, instance: R): R; +export interface IExtHostContext extends IRPCProtocol { } -export interface IMainContext { - /** - * Returns a proxy to an object addressable/named in the main/renderer process. - */ - get(identifier: ProxyIdentifier): T; +export interface IMainContext extends IRPCProtocol { } // --- main thread export interface MainThreadCommandsShape extends IDisposable { - $registerCommand(id: string): TPromise; - $unregisterCommand(id: string): TPromise; + $registerCommand(id: string): void; + $unregisterCommand(id: string): void; $executeCommand(id: string, args: any[]): Thenable; $getCommands(): Thenable; } export interface MainThreadConfigurationShape extends IDisposable { - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise; - $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: URI): TPromise; + $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any, resource: UriComponents): TPromise; + $removeConfigurationOption(target: ConfigurationTarget, key: string, resource: UriComponents): TPromise; } export interface MainThreadDiagnosticsShape extends IDisposable { - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise; - $clear(owner: string): TPromise; + $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void; + $clear(owner: string): void; } export interface MainThreadDialogOpenOptions { - defaultUri?: URI; + defaultUri?: UriComponents; openLabel?: string; canSelectFiles?: boolean; canSelectFolders?: boolean; @@ -138,32 +128,32 @@ export interface MainThreadDialogOpenOptions { } export interface MainThreadDialogSaveOptions { - defaultUri?: URI; + defaultUri?: UriComponents; saveLabel?: string; filters?: { [name: string]: string[] }; } export interface MainThreadDiaglogsShape extends IDisposable { - $showOpenDialog(options: MainThreadDialogOpenOptions): TPromise; - $showSaveDialog(options: MainThreadDialogSaveOptions): TPromise; + $showOpenDialog(options: MainThreadDialogOpenOptions): Thenable; + $showSaveDialog(options: MainThreadDialogSaveOptions): Thenable; } export interface MainThreadDecorationsShape extends IDisposable { $registerDecorationProvider(handle: number, label: string): void; $unregisterDecorationProvider(handle: number): void; - $onDidChange(handle: number, resources: URI[]): void; + $onDidChange(handle: number, resources: UriComponents[]): void; } export interface MainThreadDocumentContentProvidersShape extends IDisposable { $registerTextContentProvider(handle: number, scheme: string): void; $unregisterTextContentProvider(handle: number): void; - $onVirtualDocumentChange(uri: URI, value: ITextSource): void; + $onVirtualDocumentChange(uri: UriComponents, value: ITextSource): void; } export interface MainThreadDocumentsShape extends IDisposable { - $tryCreateDocument(options?: { language?: string; content?: string; }): TPromise; - $tryOpenDocument(uri: URI): TPromise; - $trySaveDocument(uri: URI): TPromise; + $tryCreateDocument(options?: { language?: string; content?: string; }): TPromise; + $tryOpenDocument(uri: UriComponents): TPromise; + $trySaveDocument(uri: UriComponents): TPromise; } export interface ISelectionChangeEvent { @@ -211,7 +201,7 @@ export interface ITextDocumentShowOptions { } export interface IWorkspaceResourceEdit { - resource: URI; + resource: UriComponents; modelVersionId?: number; edits: { range?: IRange; @@ -221,19 +211,19 @@ export interface IWorkspaceResourceEdit { } export interface MainThreadEditorsShape extends IDisposable { - $tryShowTextDocument(resource: URI, options: ITextDocumentShowOptions): TPromise; + $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): TPromise; $registerTextEditorDecorationType(key: string, options: editorCommon.IDecorationRenderOptions): void; $removeTextEditorDecorationType(key: string): void; $tryShowEditor(id: string, position: EditorPosition): TPromise; $tryHideEditor(id: string): TPromise; - $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise; - $trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise; - $trySetDecorationsFast(id: string, key: string, ranges: string): TPromise; - $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise; - $trySetSelections(id: string, selections: ISelection[]): TPromise; + $trySetOptions(id: string, options: ITextEditorConfigurationUpdate): TPromise; + $trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise; + $trySetDecorationsFast(id: string, key: string, ranges: number[]): TPromise; + $tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise; + $trySetSelections(id: string, selections: ISelection[]): TPromise; $tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts: IApplyEditsOptions): TPromise; $tryApplyWorkspaceEdit(workspaceResourceEdits: IWorkspaceResourceEdit[]): TPromise; - $tryInsertSnippet(id: string, template: string, selections: IRange[], opts: IUndoStopOptions): TPromise; + $tryInsertSnippet(id: string, template: string, selections: IRange[], opts: IUndoStopOptions): TPromise; $getDiffInformation(id: string): TPromise; } @@ -246,28 +236,67 @@ export interface MainThreadErrorsShape extends IDisposable { $onUnexpectedError(err: any | SerializedError): void; } +export interface ISerializedRegExp { + pattern: string; + flags?: string; +} +export interface ISerializedIndentationRule { + decreaseIndentPattern: ISerializedRegExp; + increaseIndentPattern: ISerializedRegExp; + indentNextLinePattern?: ISerializedRegExp; + unIndentedLinePattern?: ISerializedRegExp; +} +export interface ISerializedOnEnterRule { + beforeText: ISerializedRegExp; + afterText?: ISerializedRegExp; + action: EnterAction; +} +export interface ISerializedLanguageConfiguration { + comments?: CommentRule; + brackets?: CharacterPair[]; + wordPattern?: ISerializedRegExp; + indentationRules?: ISerializedIndentationRule; + onEnterRules?: ISerializedOnEnterRule[]; + __electricCharacterSupport?: { + brackets?: any; + docComment?: { + scope: string; + open: string; + lineStart: string; + close?: string; + }; + }; + __characterPairSupport?: { + autoClosingPairs: { + open: string; + close: string; + notIn?: string[]; + }[]; + }; +} + export interface MainThreadLanguageFeaturesShape extends IDisposable { - $unregister(handle: number): TPromise; - $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): TPromise; - $emitCodeLensEvent(eventHandle: number, event?: any): TPromise; - $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): TPromise; - $registerNavigateTypeSupport(handle: number): TPromise; - $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): TPromise; - $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): TPromise; - $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): TPromise; - $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise; - $setLanguageConfiguration(handle: number, languageId: string, configuration: vscode.LanguageConfiguration): TPromise; + $unregister(handle: number): void; + $registerOutlineSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector, eventHandle: number): void; + $emitCodeLensEvent(eventHandle: number, event?: any): void; + $registerDeclaractionSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerImplementationSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerTypeDefinitionSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerHoverProvider(handle: number, selector: vscode.DocumentSelector): void; + $registerDocumentHighlightProvider(handle: number, selector: vscode.DocumentSelector): void; + $registerReferenceSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerDocumentFormattingSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerRangeFormattingSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerOnTypeFormattingSupport(handle: number, selector: vscode.DocumentSelector, autoFormatTriggerCharacters: string[]): void; + $registerNavigateTypeSupport(handle: number): void; + $registerRenameSupport(handle: number, selector: vscode.DocumentSelector): void; + $registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): void; + $registerSignatureHelpProvider(handle: number, selector: vscode.DocumentSelector, triggerCharacter: string[]): void; + $registerDocumentLinkProvider(handle: number, selector: vscode.DocumentSelector): void; + $registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): void; + $setLanguageConfiguration(handle: number, languageId: string, configuration: ISerializedLanguageConfiguration): void; } export interface MainThreadLanguagesShape extends IDisposable { @@ -299,7 +328,7 @@ export interface MainThreadProgressShape extends IDisposable { } export interface MainThreadTerminalServiceShape extends IDisposable { - $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], env?: { [key: string]: string }, waitOnExit?: boolean): TPromise; + $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean): TPromise; $dispose(terminalId: number): void; $hide(terminalId: number): void; $sendText(terminalId: number, text: string, addNewLine: boolean): void; @@ -323,7 +352,7 @@ export interface MainThreadStatusBarShape extends IDisposable { export interface MainThreadStorageShape extends IDisposable { $getValue(shared: boolean, key: string): TPromise; - $setValue(shared: boolean, key: string, value: any): TPromise; + $setValue(shared: boolean, key: string, value: any): TPromise; } export interface MainThreadTelemetryShape extends IDisposable { @@ -331,7 +360,7 @@ export interface MainThreadTelemetryShape extends IDisposable { } export interface MainThreadWorkspaceShape extends IDisposable { - $startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable; + $startSearch(includePattern: string, includeFolder: string, excludePattern: string, maxResults: number, requestId: number): Thenable; $cancelSearch(requestId: number): Thenable; $saveAll(includeUntitled?: boolean): Thenable; } @@ -340,11 +369,11 @@ export interface MainThreadFileSystemShape extends IDisposable { $registerFileSystemProvider(handle: number, scheme: string): void; $unregisterFileSystemProvider(handle: number): void; - $onDidAddFileSystemRoot(root: URI): void; + $onDidAddFileSystemRoot(root: UriComponents): void; $onFileSystemChange(handle: number, resource: IFileChange[]): void; - $reportFileChunk(handle: number, resource: URI, chunk: number[] | null): void; + $reportFileChunk(handle: number, resource: UriComponents, chunk: number[] | null): void; - $handleSearchProgress(handle: number, session: number, resource: URI): void; + $handleSearchProgress(handle: number, session: number, resource: UriComponents): void; } export interface MainThreadTaskShape extends IDisposable { @@ -410,6 +439,7 @@ export interface MainThreadSCMShape extends IDisposable { $setInputBoxValue(sourceControlHandle: number, value: string): void; $setInputBoxPlaceholder(sourceControlHandle: number, placeholder: string): void; + $setLineWarningLength(sourceControlHandle: number, lineWarningLength: number): void; } export type DebugSessionUUID = string; @@ -417,7 +447,7 @@ export type DebugSessionUUID = string; export interface MainThreadDebugServiceShape extends IDisposable { $registerDebugConfigurationProvider(type: string, hasProvideMethod: boolean, hasResolveMethod: boolean, handle: number): TPromise; $unregisterDebugConfigurationProvider(handle: number): TPromise; - $startDebugging(folder: URI | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise; + $startDebugging(folder: UriComponents | undefined, nameOrConfig: string | vscode.DebugConfiguration): TPromise; $customDebugAdapterRequest(id: DebugSessionUUID, command: string, args: any): TPromise; $appendDebugConsole(value: string): TPromise; $startBreakpointEvents(): TPromise; @@ -431,7 +461,7 @@ export interface MainThreadWindowShape extends IDisposable { export interface ExtHostCommandsShape { $executeContributedCommand(id: string, ...args: any[]): Thenable; - $getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }>; + $getContributedCommandHandlerDescriptions(): Thenable<{ [id: string]: string | ICommandHandlerDescription }>; } export interface ExtHostConfigurationShape { @@ -443,11 +473,11 @@ export interface ExtHostDiagnosticsShape { } export interface ExtHostDocumentContentProvidersShape { - $provideTextDocumentContent(handle: number, uri: URI): TPromise; + $provideTextDocumentContent(handle: number, uri: UriComponents): TPromise; } export interface IModelAddedData { - url: URI; + uri: UriComponents; versionId: number; lines: string[]; EOL: string; @@ -462,12 +492,12 @@ export interface ExtHostDocumentsShape { } export interface ExtHostDocumentSaveParticipantShape { - $participateInSave(resource: URI, reason: SaveReason): TPromise; + $participateInSave(resource: UriComponents, reason: SaveReason): Thenable; } export interface ITextEditorAddData { id: string; - document: URI; + documentUri: UriComponents; options: IResolvedTextEditorConfiguration; selections: ISelection[]; editorPosition: EditorPosition; @@ -503,15 +533,15 @@ export interface ExtHostWorkspaceShape { } export interface ExtHostFileSystemShape { - $utimes(handle: number, resource: URI, mtime: number, atime: number): TPromise; - $stat(handle: number, resource: URI): TPromise; - $read(handle: number, offset: number, count: number, resource: URI): TPromise; - $write(handle: number, resource: URI, content: number[]): TPromise; - $unlink(handle: number, resource: URI): TPromise; - $move(handle: number, resource: URI, target: URI): TPromise; - $mkdir(handle: number, resource: URI): TPromise; - $readdir(handle: number, resource: URI): TPromise<[URI, IStat][]>; - $rmdir(handle: number, resource: URI): TPromise; + $utimes(handle: number, resource: UriComponents, mtime: number, atime: number): TPromise; + $stat(handle: number, resource: UriComponents): TPromise; + $read(handle: number, offset: number, count: number, resource: UriComponents): TPromise; + $write(handle: number, resource: UriComponents, content: number[]): TPromise; + $unlink(handle: number, resource: UriComponents): TPromise; + $move(handle: number, resource: UriComponents, target: UriComponents): TPromise; + $mkdir(handle: number, resource: UriComponents): TPromise; + $readdir(handle: number, resource: UriComponents): TPromise<[UriComponents, IStat][]>; + $rmdir(handle: number, resource: UriComponents): TPromise; $fileFiles(handle: number, session: number, query: string): TPromise; } @@ -520,9 +550,9 @@ export interface ExtHostExtensionServiceShape { } export interface FileSystemEvents { - created: URI[]; - changed: URI[]; - deleted: URI[]; + created: UriComponents[]; + changed: UriComponents[]; + deleted: UriComponents[]; } export interface ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents): void; @@ -551,58 +581,85 @@ export interface IRawColorInfo { range: IRange; } -export interface IExtHostSuggestion extends modes.ISuggestion { - _id: number; - _parentId: number; -} - -export interface IExtHostSuggestResult { - _id: number; - suggestions: IExtHostSuggestion[]; - incomplete?: boolean; -} - -export interface IdObject { - _id: number; -} - -export namespace IdObject { - let n = 0; - export function mixin(object: T): T & IdObject { - (object)._id = n++; +export class IdObject { + _id?: number; + private static _n = 0; + static mixin(object: T): T & IdObject { + (object)._id = IdObject._n++; return object; } } -export type IWorkspaceSymbol = IdObject & modes.SymbolInformation; -export interface IWorkspaceSymbols extends IdObject { symbols: IWorkspaceSymbol[]; } +export interface SuggestionDto extends modes.ISuggestion { + _id: number; + _parentId: number; +} + +export interface SuggestResultDto extends IdObject { + suggestions: SuggestionDto[]; + incomplete?: boolean; +} + +export interface LocationDto { + uri: UriComponents; + range: IRange; +} + +export interface SymbolInformationDto extends IdObject { + name: string; + containerName?: string; + kind: modes.SymbolKind; + location: LocationDto; +} + +export interface WorkspaceSymbolsDto extends IdObject { + symbols: SymbolInformationDto[]; +} + +export interface ResourceEditDto { + resource: UriComponents; + range: IRange; + newText: string; +} + +export interface WorkspaceEditDto { + edits: ResourceEditDto[]; + rejectReason?: string; +} + +export interface CodeActionDto { + title: string; + edits?: WorkspaceEditDto; + diagnostics?: IMarkerData[]; + command?: modes.Command; +} export interface ExtHostLanguageFeaturesShape { - $provideDocumentSymbols(handle: number, resource: URI): TPromise; - $provideCodeLenses(handle: number, resource: URI): TPromise; - $resolveCodeLens(handle: number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise; - $provideDefinition(handle: number, resource: URI, position: IPosition): TPromise; - $provideImplementation(handle: number, resource: URI, position: IPosition): TPromise; - $provideTypeDefinition(handle: number, resource: URI, position: IPosition): TPromise; - $provideHover(handle: number, resource: URI, position: IPosition): TPromise; - $provideDocumentHighlights(handle: number, resource: URI, position: IPosition): TPromise; - $provideReferences(handle: number, resource: URI, position: IPosition, context: modes.ReferenceContext): TPromise; - $provideCodeActions(handle: number, resource: URI, range: IRange): TPromise; - $provideDocumentFormattingEdits(handle: number, resource: URI, options: modes.FormattingOptions): TPromise; - $provideDocumentRangeFormattingEdits(handle: number, resource: URI, range: IRange, options: modes.FormattingOptions): TPromise; - $provideOnTypeFormattingEdits(handle: number, resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise; - $provideWorkspaceSymbols(handle: number, search: string): TPromise; - $resolveWorkspaceSymbol(handle: number, symbol: modes.SymbolInformation): TPromise; + $provideDocumentSymbols(handle: number, resource: UriComponents): TPromise; + $provideCodeLenses(handle: number, resource: UriComponents): TPromise; + $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise; + $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise; + $provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise; + $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): TPromise; + $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): TPromise; + $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise; + $provideWorkspaceSymbols(handle: number, search: string): TPromise; + $resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise; $releaseWorkspaceSymbols(handle: number, id: number): void; - $provideRenameEdits(handle: number, resource: URI, position: IPosition, newName: string): TPromise; - $provideCompletionItems(handle: number, resource: URI, position: IPosition, context: modes.SuggestContext): TPromise; - $resolveCompletionItem(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise; + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise; + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.SuggestContext): TPromise; + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.ISuggestion): TPromise; $releaseCompletionItems(handle: number, id: number): void; - $provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise; - $provideDocumentLinks(handle: number, resource: URI): TPromise; + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition): TPromise; + $provideDocumentLinks(handle: number, resource: UriComponents): TPromise; $resolveDocumentLink(handle: number, link: modes.ILink): TPromise; - $provideDocumentColors(handle: number, resource: URI): TPromise; - $provideColorPresentations(handle: number, resource: URI, colorInfo: IRawColorInfo): TPromise; + $provideDocumentColors(handle: number, resource: UriComponents): TPromise; + $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo): TPromise; } export interface ExtHostQuickOpenShape { @@ -616,7 +673,7 @@ export interface ExtHostTerminalServiceShape { } export interface ExtHostSCMShape { - $provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise; + $provideOriginalResource(sourceControlHandle: number, uri: string): TPromise; $onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise; $executeResourceCommand(sourceControlHandle: number, groupHandle: number, handle: number): TPromise; } @@ -635,7 +692,7 @@ export interface IBreakpointData { export interface ISourceBreakpointData extends IBreakpointData { type: 'source'; - uri: URI; + uri: UriComponents; line: number; character: number; } @@ -652,8 +709,8 @@ export interface IBreakpointsDelta { } export interface ExtHostDebugServiceShape { - $resolveDebugConfiguration(handle: number, folder: URI | undefined, debugConfiguration: any): TPromise; - $provideDebugConfigurations(handle: number, folder: URI | undefined): TPromise; + $resolveDebugConfiguration(handle: number, folder: UriComponents | undefined, debugConfiguration: IConfig): TPromise; + $provideDebugConfigurations(handle: number, folder: UriComponents | undefined): TPromise; $acceptDebugSessionStarted(id: DebugSessionUUID, type: string, name: string): void; $acceptDebugSessionTerminated(id: DebugSessionUUID, type: string, name: string): void; $acceptDebugSessionActiveChanged(id: DebugSessionUUID | undefined, type?: string, name?: string): void; @@ -662,10 +719,17 @@ export interface ExtHostDebugServiceShape { } +export interface DecorationRequest { + readonly id: number; + readonly handle: number; + readonly uri: UriComponents; +} + export type DecorationData = [number, boolean, string, string, ThemeColor, string]; +export type DecorationReply = { [id: number]: DecorationData }; export interface ExtHostDecorationsShape { - $providerDecorations(handle: number, uri: URI): TPromise; + $provideDecorations(requests: DecorationRequest[]): TPromise; } export interface ExtHostWindowShape { @@ -675,7 +739,7 @@ export interface ExtHostWindowShape { // --- proxy identifiers export const MainContext = { - MainThreadCommands: createMainId('MainThreadCommands'), + MainThreadCommands: >createMainId('MainThreadCommands'), MainThreadConfiguration: createMainId('MainThreadConfiguration'), MainThreadDebugService: createMainId('MainThreadDebugService'), MainThreadDecorations: createMainId('MainThreadDecorations'), @@ -700,7 +764,7 @@ export const MainContext = { MainThreadFileSystem: createMainId('MainThreadFileSystem'), MainThreadExtensionService: createMainId('MainThreadExtensionService'), MainThreadSCM: createMainId('MainThreadSCM'), - MainThreadTask: createMainId('MainThreadTask'), + MainThreadTask: createMainId('MainThreadTask', ProxyType.CustomMarshaller), MainThreadWindow: createMainId('MainThreadWindow'), }; @@ -722,9 +786,10 @@ export const ExtHostContext = { ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures'), ExtHostQuickOpen: createExtId('ExtHostQuickOpen'), ExtHostExtensionService: createExtId('ExtHostExtensionService'), + // ExtHostLogService: createExtId('ExtHostLogService'), ExtHostTerminalService: createExtId('ExtHostTerminalService'), ExtHostSCM: createExtId('ExtHostSCM'), - ExtHostTask: createExtId('ExtHostTask'), + ExtHostTask: createExtId('ExtHostTask', ProxyType.CustomMarshaller), ExtHostWorkspace: createExtId('ExtHostWorkspace'), ExtHostWindow: createExtId('ExtHostWindow'), }; diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index 860a1e74ea4..66669e64fb3 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -6,7 +6,6 @@ import { validateConstraint } from 'vs/base/common/types'; import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; -import { TPromise } from 'vs/base/common/winjs.base'; import * as extHostTypes from 'vs/workbench/api/node/extHostTypes'; import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters'; import { cloneAndChange } from 'vs/base/common/objects'; @@ -16,6 +15,7 @@ import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import * as modes from 'vs/editor/common/modes'; import * as vscode from 'vscode'; import { ILogService } from 'vs/platform/log/common/log'; +import { revive } from 'vs/base/common/marshalling'; interface CommandHandler { callback: Function; @@ -29,18 +29,21 @@ export interface ArgumentProcessor { export class ExtHostCommands implements ExtHostCommandsShape { - private _commands = new Map(); - private _proxy: MainThreadCommandsShape; - private _converter: CommandsConverter; - private _argumentProcessors: ArgumentProcessor[] = []; + private readonly _commands = new Map(); + private readonly _proxy: MainThreadCommandsShape; + private readonly _converter: CommandsConverter; + private readonly _logService: ILogService; + private readonly _argumentProcessors: ArgumentProcessor[]; constructor( mainContext: IMainContext, heapService: ExtHostHeapService, - private logService: ILogService + logService: ILogService ) { - this._proxy = mainContext.get(MainContext.MainThreadCommands); + this._proxy = mainContext.getProxy(MainContext.MainThreadCommands); + this._logService = logService; this._converter = new CommandsConverter(this, heapService); + this._argumentProcessors = [{ processArgument(a) { return revive(a, 0); } }]; } get converter(): CommandsConverter { @@ -52,7 +55,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { } registerCommand(id: string, callback: (...args: any[]) => T | Thenable, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable { - this.logService.trace('ExtHostCommands#registerCommand', id); + this._logService.trace('ExtHostCommands#registerCommand', id); if (!id.trim().length) { throw new Error('invalid id'); @@ -73,12 +76,12 @@ export class ExtHostCommands implements ExtHostCommandsShape { } executeCommand(id: string, ...args: any[]): Thenable { - this.logService.trace('ExtHostCommands#executeCommand', id); + this._logService.trace('ExtHostCommands#executeCommand', id); if (this._commands.has(id)) { // we stay inside the extension host and support // to pass any kind of parameters around - return this.$executeContributedCommand(id, ...args); + return this._executeContributedCommand(id, args); } else { // automagically convert some argument types @@ -98,47 +101,42 @@ export class ExtHostCommands implements ExtHostCommandsShape { } }); - return this._proxy.$executeCommand(id, args); + return this._proxy.$executeCommand(id, args).then(result => revive(result, 0)); } - } - $executeContributedCommand(id: string, ...args: any[]): Thenable { - let command = this._commands.get(id); - if (!command) { - return TPromise.wrapError(new Error(`Contributed command '${id}' does not exist.`)); - } - - let { callback, thisArg, description } = command; - + private _executeContributedCommand(id: string, args: any[]): Thenable { + let { callback, thisArg, description } = this._commands.get(id); if (description) { for (let i = 0; i < description.args.length; i++) { try { validateConstraint(args[i], description.args[i].constraint); } catch (err) { - return TPromise.wrapError(new Error(`Running the contributed command:'${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`)); + return Promise.reject(new Error(`Running the contributed command:'${id}' failed. Illegal argument '${description.args[i].name}' - ${description.args[i].description}`)); } } } - args = args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg)); - try { let result = callback.apply(thisArg, args); - return TPromise.as(result); + return Promise.resolve(result); } catch (err) { - // console.log(err); - // try { - // console.log(toErrorMessage(err)); - // } catch (err) { - // // - // } - return TPromise.wrapError(new Error(`Running the contributed command:'${id}' failed.`)); + this._logService.error(err, id); + return Promise.reject(new Error(`Running the contributed command:'${id}' failed.`)); + } + } + + $executeContributedCommand(id: string, ...args: any[]): Thenable { + if (!this._commands.has(id)) { + return Promise.reject(new Error(`Contributed command '${id}' does not exist.`)); + } else { + args = args.map(arg => this._argumentProcessors.reduce((r, p) => p.processArgument(r), arg)); + return this._executeContributedCommand(id, args); } } getCommands(filterUnderscoreCommands: boolean = false): Thenable { - this.logService.trace('ExtHostCommands#getCommands', filterUnderscoreCommands); + this._logService.trace('ExtHostCommands#getCommands', filterUnderscoreCommands); return this._proxy.$getCommands().then(result => { if (filterUnderscoreCommands) { @@ -148,7 +146,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { }); } - $getContributedCommandHandlerDescriptions(): TPromise<{ [id: string]: string | ICommandHandlerDescription }> { + $getContributedCommandHandlerDescriptions(): Thenable<{ [id: string]: string | ICommandHandlerDescription }> { const result: { [id: string]: string | ICommandHandlerDescription } = Object.create(null); this._commands.forEach((command, id) => { let { description } = command; @@ -156,7 +154,7 @@ export class ExtHostCommands implements ExtHostCommandsShape { result[id] = description; } }); - return TPromise.as(result); + return Promise.resolve(result); } } diff --git a/src/vs/workbench/api/node/extHostDebugService.ts b/src/vs/workbench/api/node/extHostDebugService.ts index ad91917fa76..3276d4348ea 100644 --- a/src/vs/workbench/api/node/extHostDebugService.ts +++ b/src/vs/workbench/api/node/extHostDebugService.ts @@ -11,7 +11,7 @@ import { MainContext, MainThreadDebugServiceShape, ExtHostDebugServiceShape, Deb import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import * as vscode from 'vscode'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { Disposable, Position, Location, SourceBreakpoint, FunctionBreakpoint } from 'vs/workbench/api/node/extHostTypes'; @@ -61,7 +61,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._onDidChangeActiveDebugSession = new Emitter(); this._onDidReceiveDebugSessionCustomEvent = new Emitter(); - this._debugServiceProxy = mainContext.get(MainContext.MainThreadDebugService); + this._debugServiceProxy = mainContext.getProxy(MainContext.MainThreadDebugService); this._onDidChangeBreakpoints = new Emitter({ onFirstListenerAdd: () => { @@ -138,7 +138,8 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { if (bp.type === 'function') { return new FunctionBreakpoint(bp.enabled, bp.condition, bp.hitCondition, bp.functionName); } - return new SourceBreakpoint(bp.enabled, bp.condition, bp.hitCondition, new Location(bp.uri, new Position(bp.line, bp.character))); + const uri = URI.revive(bp.uri); + return new SourceBreakpoint(bp.enabled, bp.condition, bp.hitCondition, new Location(uri, new Position(bp.line, bp.character))); } public registerDebugConfigurationProvider(type: string, provider: vscode.DebugConfigurationProvider): vscode.Disposable { @@ -156,7 +157,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { }); } - public $provideDebugConfigurations(handle: number, folderUri: URI | undefined): TPromise { + public $provideDebugConfigurations(handle: number, folderUri: UriComponents | undefined): TPromise { let handler = this._handlers.get(handle); if (!handler) { return TPromise.wrapError(new Error('no handler found')); @@ -167,7 +168,7 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { return asWinJsPromise(token => handler.provideDebugConfigurations(this.getFolder(folderUri), token)); } - public $resolveDebugConfiguration(handle: number, folderUri: URI | undefined, debugConfiguration: vscode.DebugConfiguration): TPromise { + public $resolveDebugConfiguration(handle: number, folderUri: UriComponents | undefined, debugConfiguration: vscode.DebugConfiguration): TPromise { let handler = this._handlers.get(handle); if (!handler) { return TPromise.wrapError(new Error('no handler found')); @@ -232,10 +233,11 @@ export class ExtHostDebugService implements ExtHostDebugServiceShape { this._onDidReceiveDebugSessionCustomEvent.fire(ee); } - private getFolder(folderUri: URI | undefined) { - if (folderUri) { + private getFolder(_folderUri: UriComponents | undefined) { + if (_folderUri) { + const folderUriString = URI.revive(_folderUri).toString(); const folders = this._workspace.getWorkspaceFolders(); - const found = folders.filter(f => f.uri.toString() === folderUri.toString()); + const found = folders.filter(f => f.uri.toString() === folderUriString); if (found && found.length > 0) { return found[0]; } diff --git a/src/vs/workbench/api/node/extHostDecorations.ts b/src/vs/workbench/api/node/extHostDecorations.ts index b2f746e6dc4..89987b137bb 100644 --- a/src/vs/workbench/api/node/extHostDecorations.ts +++ b/src/vs/workbench/api/node/extHostDecorations.ts @@ -6,7 +6,7 @@ import * as vscode from 'vscode'; import URI from 'vs/base/common/uri'; -import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData } from 'vs/workbench/api/node/extHost.protocol'; +import { MainContext, IMainContext, ExtHostDecorationsShape, MainThreadDecorationsShape, DecorationData, DecorationRequest, DecorationReply } from 'vs/workbench/api/node/extHost.protocol'; import { TPromise } from 'vs/base/common/winjs.base'; import { Disposable } from 'vs/workbench/api/node/extHostTypes'; import { asWinJsPromise } from 'vs/base/common/async'; @@ -19,7 +19,7 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { private readonly _proxy: MainThreadDecorationsShape; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadDecorations); + this._proxy = mainContext.getProxy(MainContext.MainThreadDecorations); } registerDecorationProvider(provider: vscode.DecorationProvider, label: string): vscode.Disposable { @@ -38,10 +38,19 @@ export class ExtHostDecorations implements ExtHostDecorationsShape { }); } - $providerDecorations(handle: number, uri: URI): TPromise { - const provider = this._provider.get(handle); - return asWinJsPromise(token => provider.provideDecoration(uri, token)).then(data => { - return data && [data.priority, data.bubble, data.title, data.abbreviation, data.color, data.source]; + $provideDecorations(requests: DecorationRequest[]): TPromise { + const result: DecorationReply = Object.create(null); + return TPromise.join(requests.map(request => { + const { handle, uri, id } = request; + const provider = this._provider.get(handle); + return asWinJsPromise(token => provider.provideDecoration(URI.revive(uri), token)).then(data => { + result[id] = data && [data.priority, data.bubble, data.title, data.abbreviation, data.color, data.source]; + }, err => { + console.error(err); + }); + + })).then(() => { + return result; }); } } diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index e47a985648b..e5a2b67f695 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -224,7 +224,7 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { private _collections: DiagnosticCollection[]; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadDiagnostics); + this._proxy = mainContext.getProxy(MainContext.MainThreadDiagnostics); this._collections = []; } @@ -255,4 +255,3 @@ export class ExtHostDiagnostics implements ExtHostDiagnosticsShape { this._collections.forEach(callback); } } - diff --git a/src/vs/workbench/api/node/extHostDialogs.ts b/src/vs/workbench/api/node/extHostDialogs.ts index fc0bc2b807c..70360049526 100644 --- a/src/vs/workbench/api/node/extHostDialogs.ts +++ b/src/vs/workbench/api/node/extHostDialogs.ts @@ -13,7 +13,7 @@ export class ExtHostDialogs { private readonly _proxy: MainThreadDiaglogsShape; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadDialogs); + this._proxy = mainContext.getProxy(MainContext.MainThreadDialogs); } showOpenDialog(options: vscode.OpenDialogOptions): Thenable { diff --git a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts index dc53c36fcc8..ac478a894db 100644 --- a/src/vs/workbench/api/node/extHostDocumentContentProviders.ts +++ b/src/vs/workbench/api/node/extHostDocumentContentProviders.ts @@ -6,7 +6,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { IDisposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/workbench/api/node/extHostTypes'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -25,7 +25,7 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro private readonly _documentsAndEditors: ExtHostDocumentsAndEditors; constructor(mainContext: IMainContext, documentsAndEditors: ExtHostDocumentsAndEditors) { - this._proxy = mainContext.get(MainContext.MainThreadDocumentContentProviders); + this._proxy = mainContext.getProxy(MainContext.MainThreadDocumentContentProviders); this._documentsAndEditors = documentsAndEditors; } @@ -78,11 +78,11 @@ export class ExtHostDocumentContentProvider implements ExtHostDocumentContentPro }); } - $provideTextDocumentContent(handle: number, uri: URI): TPromise { + $provideTextDocumentContent(handle: number, uri: UriComponents): TPromise { const provider = this._documentContentProviders.get(handle); if (!provider) { return TPromise.wrapError(new Error(`unsupported uri-scheme: ${uri.scheme}`)); } - return asWinJsPromise(token => provider.provideTextDocumentContent(uri, token)); + return asWinJsPromise(token => provider.provideTextDocumentContent(URI.revive(uri), token)); } } diff --git a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts index 607c8c27b4d..3733a20adbc 100644 --- a/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts +++ b/src/vs/workbench/api/node/extHostDocumentSaveParticipant.ts @@ -5,10 +5,9 @@ 'use strict'; import Event from 'vs/base/common/event'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { sequence, always } from 'vs/base/common/async'; import { illegalState } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ExtHostDocumentSaveParticipantShape, MainThreadEditorsShape, IWorkspaceResourceEdit } from 'vs/workbench/api/node/extHost.protocol'; import { TextEdit } from 'vs/workbench/api/node/extHostTypes'; import { fromRange, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/node/extHostTypeConverters'; @@ -16,28 +15,32 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { LinkedList } from 'vs/base/common/linkedList'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ILogService } from 'vs/platform/log/common/log'; + +type Listener = [Function, any, IExtensionDescription]; export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSaveParticipantShape { - private _documents: ExtHostDocuments; - private _mainThreadEditors: MainThreadEditorsShape; - private _callbacks = new LinkedList<[Function, any]>(); - private _badListeners = new WeakMap(); - private _thresholds: { timeout: number; errors: number; }; + private readonly _callbacks = new LinkedList(); + private readonly _badListeners = new WeakMap(); - constructor(documents: ExtHostDocuments, mainThreadEditors: MainThreadEditorsShape, thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 }) { - this._documents = documents; - this._mainThreadEditors = mainThreadEditors; - this._thresholds = thresholds; + constructor( + private readonly _logService: ILogService, + private readonly _documents: ExtHostDocuments, + private readonly _mainThreadEditors: MainThreadEditorsShape, + private readonly _thresholds: { timeout: number; errors: number; } = { timeout: 1500, errors: 3 } + ) { + // } dispose(): void { this._callbacks.clear(); } - get onWillSaveTextDocumentEvent(): Event { + getOnWillSaveTextDocumentEvent(extension: IExtensionDescription): Event { return (listener, thisArg, disposables) => { - const remove = this._callbacks.push([listener, thisArg]); + const remove = this._callbacks.push([listener, thisArg, extension]); const result = { dispose: remove }; if (Array.isArray(disposables)) { disposables.push(result); @@ -46,13 +49,14 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }; } - $participateInSave(resource: URI, reason: SaveReason): TPromise { + $participateInSave(data: UriComponents, reason: SaveReason): Thenable { + const resource = URI.revive(data); const entries = this._callbacks.toArray(); let didTimeout = false; let didTimeoutHandle = setTimeout(() => didTimeout = true, this._thresholds.timeout); - const promise = sequence(entries.map(([fn, thisArg]) => { + const promise = sequence(entries.map(listener => { return () => { if (didTimeout) { @@ -61,18 +65,17 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic } const document = this._documents.getDocumentData(resource).document; - return this._deliverEventAsyncAndBlameBadListeners(fn, thisArg, { document, reason: TextDocumentSaveReason.to(reason) }); + return this._deliverEventAsyncAndBlameBadListeners(listener, { document, reason: TextDocumentSaveReason.to(reason) }); }; })); - return always(promise, () => clearTimeout(didTimeoutHandle)); } - private _deliverEventAsyncAndBlameBadListeners(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): TPromise { + private _deliverEventAsyncAndBlameBadListeners([listener, thisArg, extension]: Listener, stubEvent: vscode.TextDocumentWillSaveEvent): Promise { const errors = this._badListeners.get(listener); if (errors > this._thresholds.errors) { // bad listener - ignore - return TPromise.wrap(false); + return Promise.resolve(false); } return this._deliverEventAsync(listener, thisArg, stubEvent).then(() => { @@ -80,6 +83,10 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic return true; }, err => { + + this._logService.error('[onWillSaveTextDocument]', extension.id); + this._logService.error(err); + if (!(err instanceof Error) || (err).message !== 'concurrent_edits') { const errors = this._badListeners.get(listener); this._badListeners.set(listener, !errors ? 1 : errors + 1); @@ -93,9 +100,9 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic }); } - private _deliverEventAsync(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): TPromise { + private _deliverEventAsync(listener: Function, thisArg: any, stubEvent: vscode.TextDocumentWillSaveEvent): Promise { - const promises: TPromise[] = []; + const promises: Promise[] = []; const { document, reason } = stubEvent; const { version } = document; @@ -107,7 +114,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic if (Object.isFrozen(promises)) { throw illegalState('waitUntil can not be called async'); } - promises.push(TPromise.wrap(p)); + promises.push(Promise.resolve(p)); } }); @@ -115,16 +122,23 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic // fire event listener.apply(thisArg, [event]); } catch (err) { - return TPromise.wrapError(err); + return Promise.reject(err); } // freeze promises after event call Object.freeze(promises); - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { // join on all listener promises, reject after timeout const handle = setTimeout(() => reject(new Error('timeout')), this._thresholds.timeout); - return always(TPromise.join(promises), () => clearTimeout(handle)).then(resolve, reject); + + return Promise.all(promises).then(edits => { + clearTimeout(handle); + resolve(edits); + }).catch(err => { + clearTimeout(handle); + reject(err); + }); }).then(values => { @@ -156,7 +170,7 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic } // TODO@joh bubble this to listener? - return TPromise.wrapError(new Error('concurrent_edits')); + return Promise.reject(new Error('concurrent_edits')); }); } } diff --git a/src/vs/workbench/api/node/extHostDocuments.ts b/src/vs/workbench/api/node/extHostDocuments.ts index 131b85bd766..a8a9773b3c2 100644 --- a/src/vs/workbench/api/node/extHostDocuments.ts +++ b/src/vs/workbench/api/node/extHostDocuments.ts @@ -33,7 +33,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { private _documentLoader = new Map>(); constructor(mainContext: IMainContext, documentsAndEditors: ExtHostDocumentsAndEditors) { - this._proxy = mainContext.get(MainContext.MainThreadDocuments); + this._proxy = mainContext.getProxy(MainContext.MainThreadDocuments); this._documentsAndEditors = documentsAndEditors; this._toDispose = [ @@ -92,7 +92,7 @@ export class ExtHostDocuments implements ExtHostDocumentsShape { } public createDocumentData(options?: { language?: string; content?: string }): TPromise { - return this._proxy.$tryCreateDocument(options); + return this._proxy.$tryCreateDocument(options).then(data => URI.revive(data)); } public $acceptModelModeChanged(strURL: string, oldModeId: string, newModeId: string): void { diff --git a/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts b/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts index da9cc09702c..b3f6f63fedc 100644 --- a/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts +++ b/src/vs/workbench/api/node/extHostDocumentsAndEditors.ts @@ -11,6 +11,7 @@ import { ExtHostDocumentData } from './extHostDocumentData'; import { ExtHostTextEditor } from './extHostTextEditor'; import * as assert from 'assert'; import * as typeConverters from './extHostTypeConverters'; +import URI from 'vs/base/common/uri'; export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape { @@ -49,18 +50,19 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha if (delta.addedDocuments) { for (const data of delta.addedDocuments) { - assert.ok(!this._documents.has(data.url.toString()), `document '${data.url} already exists!'`); + const resource = URI.revive(data.uri); + assert.ok(!this._documents.has(resource.toString()), `document '${resource} already exists!'`); const documentData = new ExtHostDocumentData( - this._mainContext.get(MainContext.MainThreadDocuments), - data.url, + this._mainContext.getProxy(MainContext.MainThreadDocuments), + resource, data.lines, data.EOL, data.modeId, data.versionId, data.isDirty ); - this._documents.set(data.url.toString(), documentData); + this._documents.set(resource.toString(), documentData); addedDocuments.push(documentData); } } @@ -75,12 +77,13 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha if (delta.addedEditors) { for (const data of delta.addedEditors) { - assert.ok(this._documents.has(data.document.toString()), `document '${data.document}' does not exist`); + const resource = URI.revive(data.documentUri); + assert.ok(this._documents.has(resource.toString()), `document '${resource}' does not exist`); assert.ok(!this._editors.has(data.id), `editor '${data.id}' already exists!`); - const documentData = this._documents.get(data.document.toString()); + const documentData = this._documents.get(resource.toString()); const editor = new ExtHostTextEditor( - this._mainContext.get(MainContext.MainThreadEditors), + this._mainContext.getProxy(MainContext.MainThreadEditors), data.id, documentData, data.selections.map(typeConverters.toSelection), diff --git a/src/vs/workbench/api/node/extHostExtensionActivator.ts b/src/vs/workbench/api/node/extHostExtensionActivator.ts index 5038d467d3b..b0949051e4f 100644 --- a/src/vs/workbench/api/node/extHostExtensionActivator.ts +++ b/src/vs/workbench/api/node/extHostExtensionActivator.ts @@ -10,6 +10,7 @@ import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtHostLogger } from 'vs/workbench/api/node/extHostLogService'; const hasOwnProperty = Object.hasOwnProperty; const NO_OP_VOID_PROMISE = TPromise.wrap(void 0); @@ -26,6 +27,7 @@ export interface IExtensionContext { extensionPath: string; storagePath: string; asAbsolutePath(relativePath: string): string; + logger: ExtHostLogger; } /** diff --git a/src/vs/workbench/api/node/extHostExtensionService.ts b/src/vs/workbench/api/node/extHostExtensionService.ts index 6d15e7a8675..0767c62d320 100644 --- a/src/vs/workbench/api/node/extHostExtensionService.ts +++ b/src/vs/workbench/api/node/extHostExtensionService.ts @@ -6,30 +6,30 @@ import { dispose } from 'vs/base/common/lifecycle'; import { join } from 'path'; -import { mkdirp, dirExists } from 'vs/base/node/pfs'; +import { mkdirp, dirExists, realpath } from 'vs/base/node/pfs'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/node/extensionDescriptionRegistry'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostStorage } from 'vs/workbench/api/node/extHostStorage'; -import { createApiFactory, initializeExtensionApi } from 'vs/workbench/api/node/extHost.api.impl'; -import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape } from './extHost.protocol'; +import { createApiFactory, initializeExtensionApi, checkProposedApiEnabled } from 'vs/workbench/api/node/extHost.api.impl'; +import { MainContext, MainThreadExtensionServiceShape, IWorkspaceData, IEnvironment, IInitData, ExtHostExtensionServiceShape, MainThreadTelemetryShape, IExtHostContext } from './extHost.protocol'; import { IExtensionMemento, ExtensionsActivator, ActivatedExtension, IExtensionAPI, IExtensionContext, EmptyExtension, IExtensionModule, ExtensionActivationTimesBuilder, ExtensionActivationTimes, ExtensionActivationReason, ExtensionActivatedByEvent } from 'vs/workbench/api/node/extHostExtensionActivator'; -import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { realpath } from 'fs'; import { TernarySearchTree } from 'vs/base/common/map'; import { Barrier } from 'vs/base/common/async'; import { ILogService } from 'vs/platform/log/common/log'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; class ExtensionMemento implements IExtensionMemento { - private _id: string; - private _shared: boolean; - private _storage: ExtHostStorage; + private readonly _id: string; + private readonly _shared: boolean; + private readonly _storage: ExtHostStorage; - private _init: TPromise; + private readonly _init: TPromise; private _value: { [n: string]: any; }; constructor(id: string, global: boolean, storage: ExtHostStorage) { @@ -113,35 +113,36 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { private readonly _barrier: Barrier; private readonly _registry: ExtensionDescriptionRegistry; - private readonly _threadService: ExtHostThreadService; private readonly _mainThreadTelemetry: MainThreadTelemetryShape; private readonly _storage: ExtHostStorage; private readonly _storagePath: ExtensionStoragePath; private readonly _proxy: MainThreadExtensionServiceShape; private readonly _logService: ILogService; + private readonly _extHostLogService: ExtHostLogService; private _activator: ExtensionsActivator; private _extensionPathIndex: TPromise>; /** * This class is constructed manually because it is a service, so it doesn't use any ctor injection */ constructor(initData: IInitData, - threadService: ExtHostThreadService, + extHostContext: IExtHostContext, extHostWorkspace: ExtHostWorkspace, extHostConfiguration: ExtHostConfiguration, - logService: ILogService + logService: ILogService, + environmentService: IEnvironmentService ) { this._barrier = new Barrier(); this._registry = new ExtensionDescriptionRegistry(initData.extensions); - this._threadService = threadService; this._logService = logService; - this._mainThreadTelemetry = threadService.get(MainContext.MainThreadTelemetry); - this._storage = new ExtHostStorage(threadService); + this._mainThreadTelemetry = extHostContext.getProxy(MainContext.MainThreadTelemetry); + this._storage = new ExtHostStorage(extHostContext); this._storagePath = new ExtensionStoragePath(initData.workspace, initData.environment); - this._proxy = this._threadService.get(MainContext.MainThreadExtensionService); + this._proxy = extHostContext.getProxy(MainContext.MainThreadExtensionService); this._activator = null; + this._extHostLogService = new ExtHostLogService(environmentService); // initialize API first (i.e. do not release barrier until the API is initialized) - const apiFactory = createApiFactory(initData, threadService, extHostWorkspace, extHostConfiguration, this, logService); + const apiFactory = createApiFactory(initData, extHostContext, extHostWorkspace, extHostConfiguration, this, logService); initializeExtensionApi(this, apiFactory).then(() => { @@ -222,16 +223,8 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { if (!ext.main) { return undefined; } - return new TPromise((resolve, reject) => { - realpath(ext.extensionFolderPath, (err, path) => { - if (err) { - reject(err); - } else { - tree.set(path, ext); - resolve(void 0); - } - }); - }); + return realpath(ext.extensionFolderPath).then(value => tree.set(value, ext)); + }); this._extensionPathIndex = TPromise.join(extensions).then(() => tree); } @@ -341,13 +334,18 @@ export class ExtHostExtensionService implements ExtHostExtensionServiceShape { workspaceState.whenReady, this._storagePath.whenReady ]).then(() => { + const that = this; return Object.freeze({ globalState, workspaceState, subscriptions: [], get extensionPath() { return extensionDescription.extensionFolderPath; }, storagePath: this._storagePath.value(extensionDescription), - asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); } + asAbsolutePath: (relativePath: string) => { return join(extensionDescription.extensionFolderPath, relativePath); }, + get logger() { + checkProposedApiEnabled(extensionDescription); + return that._extHostLogService.getExtLogger(extensionDescription.id); + } }); }); } diff --git a/src/vs/workbench/api/node/extHostFileSystem.ts b/src/vs/workbench/api/node/extHostFileSystem.ts index a7b8c787310..3cdd834d7a7 100644 --- a/src/vs/workbench/api/node/extHostFileSystem.ts +++ b/src/vs/workbench/api/node/extHostFileSystem.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { MainContext, IMainContext, ExtHostFileSystemShape, MainThreadFileSystemShape } from './extHost.protocol'; import * as vscode from 'vscode'; @@ -19,7 +19,7 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { private _handlePool: number = 0; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadFileSystem); + this._proxy = mainContext.getProxy(MainContext.MainThreadFileSystem); } registerFileSystemProvider(scheme: string, provider: vscode.FileSystemProvider) { @@ -42,37 +42,37 @@ export class ExtHostFileSystem implements ExtHostFileSystemShape { }; } - $utimes(handle: number, resource: URI, mtime: number, atime: number): TPromise { - return asWinJsPromise(token => this._provider.get(handle).utimes(resource, mtime, atime)); + $utimes(handle: number, resource: UriComponents, mtime: number, atime: number): TPromise { + return asWinJsPromise(token => this._provider.get(handle).utimes(URI.revive(resource), mtime, atime)); } - $stat(handle: number, resource: URI): TPromise { - return asWinJsPromise(token => this._provider.get(handle).stat(resource)); + $stat(handle: number, resource: UriComponents): TPromise { + return asWinJsPromise(token => this._provider.get(handle).stat(URI.revive(resource))); } - $read(handle: number, offset: number, count: number, resource: URI): TPromise { + $read(handle: number, offset: number, count: number, resource: UriComponents): TPromise { const progress = { report: chunk => { this._proxy.$reportFileChunk(handle, resource, [].slice.call(chunk)); } }; - return asWinJsPromise(token => this._provider.get(handle).read(resource, offset, count, progress)); + return asWinJsPromise(token => this._provider.get(handle).read(URI.revive(resource), offset, count, progress)); } - $write(handle: number, resource: URI, content: number[]): TPromise { - return asWinJsPromise(token => this._provider.get(handle).write(resource, Buffer.from(content))); + $write(handle: number, resource: UriComponents, content: number[]): TPromise { + return asWinJsPromise(token => this._provider.get(handle).write(URI.revive(resource), Buffer.from(content))); } - $unlink(handle: number, resource: URI): TPromise { - return asWinJsPromise(token => this._provider.get(handle).unlink(resource)); + $unlink(handle: number, resource: UriComponents): TPromise { + return asWinJsPromise(token => this._provider.get(handle).unlink(URI.revive(resource))); } - $move(handle: number, resource: URI, target: URI): TPromise { - return asWinJsPromise(token => this._provider.get(handle).move(resource, target)); + $move(handle: number, resource: UriComponents, target: UriComponents): TPromise { + return asWinJsPromise(token => this._provider.get(handle).move(URI.revive(resource), URI.revive(target))); } - $mkdir(handle: number, resource: URI): TPromise { - return asWinJsPromise(token => this._provider.get(handle).mkdir(resource)); + $mkdir(handle: number, resource: UriComponents): TPromise { + return asWinJsPromise(token => this._provider.get(handle).mkdir(URI.revive(resource))); } - $readdir(handle: number, resource: URI): TPromise<[URI, IStat][], any> { - return asWinJsPromise(token => this._provider.get(handle).readdir(resource)); + $readdir(handle: number, resource: UriComponents): TPromise<[UriComponents, IStat][], any> { + return asWinJsPromise(token => this._provider.get(handle).readdir(URI.revive(resource))); } - $rmdir(handle: number, resource: URI): TPromise { - return asWinJsPromise(token => this._provider.get(handle).rmdir(resource)); + $rmdir(handle: number, resource: UriComponents): TPromise { + return asWinJsPromise(token => this._provider.get(handle).rmdir(URI.revive(resource))); } $fileFiles(handle: number, session: number, query: string): TPromise { const provider = this._provider.get(handle); diff --git a/src/vs/workbench/api/node/extHostFileSystemEventService.ts b/src/vs/workbench/api/node/extHostFileSystemEventService.ts index 1c5aa2b1e28..1ac5f892b3f 100644 --- a/src/vs/workbench/api/node/extHostFileSystemEventService.ts +++ b/src/vs/workbench/api/node/extHostFileSystemEventService.ts @@ -9,6 +9,7 @@ import { Disposable } from './extHostTypes'; import { parse, IRelativePattern } from 'vs/base/common/glob'; import { Uri, FileSystemWatcher as _FileSystemWatcher } from 'vscode'; import { FileSystemEvents, ExtHostFileSystemEventServiceShape } from './extHost.protocol'; +import URI from 'vs/base/common/uri'; class FileSystemWatcher implements _FileSystemWatcher { @@ -48,22 +49,25 @@ class FileSystemWatcher implements _FileSystemWatcher { let subscription = dispatcher(events => { if (!ignoreCreateEvents) { for (let created of events.created) { - if (parsedPattern(created.fsPath)) { - this._onDidCreate.fire(created); + let uri = URI.revive(created); + if (parsedPattern(uri.fsPath)) { + this._onDidCreate.fire(uri); } } } if (!ignoreChangeEvents) { for (let changed of events.changed) { - if (parsedPattern(changed.fsPath)) { - this._onDidChange.fire(changed); + let uri = URI.revive(changed); + if (parsedPattern(uri.fsPath)) { + this._onDidChange.fire(uri); } } } if (!ignoreDeleteEvents) { for (let deleted of events.deleted) { - if (parsedPattern(deleted.fsPath)) { - this._onDidDelete.fire(deleted); + let uri = URI.revive(deleted); + if (parsedPattern(uri.fsPath)) { + this._onDidDelete.fire(uri); } } } diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 6e5c04f836c..e2f17a69d6d 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; import { mixin } from 'vs/base/common/objects'; import * as vscode from 'vscode'; @@ -17,7 +17,7 @@ import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments'; import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands'; import { ExtHostDiagnostics, DiagnosticCollection } from 'vs/workbench/api/node/extHostDiagnostics'; import { asWinJsPromise } from 'vs/base/common/async'; -import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IExtHostSuggestResult, IExtHostSuggestion, IWorkspaceSymbols, IWorkspaceSymbol, IdObject } from './extHost.protocol'; +import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier, IRawColorInfo, IMainContext, IdObject, ISerializedRegExp, ISerializedIndentationRule, ISerializedOnEnterRule, ISerializedLanguageConfiguration, SymbolInformationDto, SuggestResultDto, WorkspaceSymbolsDto, SuggestionDto } from './extHost.protocol'; import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings'; import { IPosition } from 'vs/editor/common/core/position'; import { IRange } from 'vs/editor/common/core/range'; @@ -35,11 +35,11 @@ class OutlineAdapter { this._provider = provider; } - provideDocumentSymbols(resource: URI): TPromise { + provideDocumentSymbols(resource: URI): TPromise { let doc = this._documents.getDocumentData(resource).document; return asWinJsPromise(token => this._provider.provideDocumentSymbols(doc, token)).then(value => { if (Array.isArray(value)) { - return value.map(TypeConverters.fromSymbolInformation); + return value.map(symbol => IdObject.mixin(TypeConverters.fromSymbolInformation(symbol))); } return undefined; }); @@ -289,9 +289,8 @@ class CodeActionAdapter { } }); - return asWinJsPromise(token => this._provider.provideCodeActions2 - ? this._provider.provideCodeActions2(doc, ran, { diagnostics: allDiagnostics }, token) - : this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token) + return asWinJsPromise(token => + this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token) ).then(commandsOrActions => { if (isFalsyOrEmpty(commandsOrActions)) { return undefined; @@ -314,9 +313,7 @@ class CodeActionAdapter { title: candidate.title, command: candidate.command && this._commands.toInternal(candidate.command), diagnostics: candidate.diagnostics && candidate.diagnostics.map(DiagnosticCollection.toMarkerData), - edits: Array.isArray(candidate.edits) - ? TypeConverters.WorkspaceEdit.fromTextEdits(resource, candidate.edits) - : candidate.edits && TypeConverters.WorkspaceEdit.from(candidate.edits), + edits: candidate.edit && TypeConverters.WorkspaceEdit.from(candidate.edit), }); } } @@ -413,8 +410,8 @@ class NavigateTypeAdapter { this._provider = provider; } - provideWorkspaceSymbols(search: string): TPromise { - const result: IWorkspaceSymbols = IdObject.mixin({ symbols: [] }); + provideWorkspaceSymbols(search: string): TPromise { + const result: WorkspaceSymbolsDto = IdObject.mixin({ symbols: [] }); return asWinJsPromise(token => this._provider.provideWorkspaceSymbols(search, token)).then(value => { if (!isFalsyOrEmpty(value)) { for (const item of value) { @@ -439,7 +436,7 @@ class NavigateTypeAdapter { }); } - resolveWorkspaceSymbol(symbol: IWorkspaceSymbol): TPromise { + resolveWorkspaceSymbol(symbol: SymbolInformationDto): TPromise { if (typeof this._provider.resolveWorkspaceSymbol !== 'function') { return TPromise.as(symbol); @@ -524,7 +521,7 @@ class SuggestAdapter { this._provider = provider; } - provideCompletionItems(resource: URI, position: IPosition, context: modes.SuggestContext): TPromise { + provideCompletionItems(resource: URI, position: IPosition, context: modes.SuggestContext): TPromise { const doc = this._documents.getDocumentData(resource).document; const pos = TypeConverters.toPosition(position); @@ -535,7 +532,7 @@ class SuggestAdapter { const _id = this._idPool++; - const result: IExtHostSuggestResult = { + const result: SuggestResultDto = { _id, suggestions: [], }; @@ -577,7 +574,7 @@ class SuggestAdapter { return TPromise.as(suggestion); } - const { _parentId, _id } = (suggestion); + const { _parentId, _id } = (suggestion); const item = this._cache.has(_parentId) && this._cache.get(_parentId)[_id]; if (!item) { return TPromise.as(suggestion); @@ -605,13 +602,13 @@ class SuggestAdapter { this._cache.delete(id); } - private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, defaultRange: vscode.Range, _id: number, _parentId: number): IExtHostSuggestion { + private _convertCompletionItem(item: vscode.CompletionItem, position: vscode.Position, defaultRange: vscode.Range, _id: number, _parentId: number): SuggestionDto { if (typeof item.label !== 'string' || item.label.length === 0) { console.warn('INVALID text edit -> must have at least a label'); return undefined; } - const result: IExtHostSuggestion = { + const result: SuggestionDto = { // _id, _parentId, @@ -800,7 +797,7 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { heapMonitor: ExtHostHeapService, diagnostics: ExtHostDiagnostics ) { - this._proxy = mainContext.get(MainContext.MainThreadLanguageFeatures); + this._proxy = mainContext.getProxy(MainContext.MainThreadLanguageFeatures); this._documents = documents; this._commands = commands; this._heapService = heapMonitor; @@ -826,17 +823,22 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return callback(adapter); } + private _addNewAdapter(adapter: Adapter): number { + const handle = this._nextHandle(); + this._adapter.set(handle, adapter); + return handle; + } + // --- outline registerDocumentSymbolProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentSymbolProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new OutlineAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new OutlineAdapter(this._documents, provider)); this._proxy.$registerOutlineSupport(handle, selector); return this._createDisposable(handle); } - $provideDocumentSymbols(handle: number, resource: URI): TPromise { - return this._withAdapter(handle, OutlineAdapter, adapter => adapter.provideDocumentSymbols(resource)); + $provideDocumentSymbols(handle: number, resource: UriComponents): TPromise { + return this._withAdapter(handle, OutlineAdapter, adapter => adapter.provideDocumentSymbols(URI.revive(resource))); } // --- code lens @@ -857,150 +859,139 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { return result; } - $provideCodeLenses(handle: number, resource: URI): TPromise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(resource)); + $provideCodeLenses(handle: number, resource: UriComponents): TPromise { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.provideCodeLenses(URI.revive(resource))); } - $resolveCodeLens(handle: number, resource: URI, symbol: modes.ICodeLensSymbol): TPromise { - return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(resource, symbol)); + $resolveCodeLens(handle: number, resource: UriComponents, symbol: modes.ICodeLensSymbol): TPromise { + return this._withAdapter(handle, CodeLensAdapter, adapter => adapter.resolveCodeLens(URI.revive(resource), symbol)); } // --- declaration registerDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.DefinitionProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new DefinitionAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new DefinitionAdapter(this._documents, provider)); this._proxy.$registerDeclaractionSupport(handle, selector); return this._createDisposable(handle); } - $provideDefinition(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(resource, position)); + $provideDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, DefinitionAdapter, adapter => adapter.provideDefinition(URI.revive(resource), position)); } registerImplementationProvider(selector: vscode.DocumentSelector, provider: vscode.ImplementationProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new ImplementationAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new ImplementationAdapter(this._documents, provider)); this._proxy.$registerImplementationSupport(handle, selector); return this._createDisposable(handle); } - $provideImplementation(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(resource, position)); + $provideImplementation(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, ImplementationAdapter, adapter => adapter.provideImplementation(URI.revive(resource), position)); } registerTypeDefinitionProvider(selector: vscode.DocumentSelector, provider: vscode.TypeDefinitionProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new TypeDefinitionAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new TypeDefinitionAdapter(this._documents, provider)); this._proxy.$registerTypeDefinitionSupport(handle, selector); return this._createDisposable(handle); } - $provideTypeDefinition(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(resource, position)); + $provideTypeDefinition(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, TypeDefinitionAdapter, adapter => adapter.provideTypeDefinition(URI.revive(resource), position)); } // --- extra info registerHoverProvider(selector: vscode.DocumentSelector, provider: vscode.HoverProvider, extensionId?: string): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new HoverAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new HoverAdapter(this._documents, provider)); this._proxy.$registerHoverProvider(handle, selector); return this._createDisposable(handle); } - $provideHover(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, HoverAdapter, adpater => adpater.provideHover(resource, position)); + $provideHover(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, HoverAdapter, adpater => adpater.provideHover(URI.revive(resource), position)); } // --- occurrences registerDocumentHighlightProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentHighlightProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new DocumentHighlightAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new DocumentHighlightAdapter(this._documents, provider)); this._proxy.$registerDocumentHighlightProvider(handle, selector); return this._createDisposable(handle); } - $provideDocumentHighlights(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(resource, position)); + $provideDocumentHighlights(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, DocumentHighlightAdapter, adapter => adapter.provideDocumentHighlights(URI.revive(resource), position)); } // --- references registerReferenceProvider(selector: vscode.DocumentSelector, provider: vscode.ReferenceProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new ReferenceAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new ReferenceAdapter(this._documents, provider)); this._proxy.$registerReferenceSupport(handle, selector); return this._createDisposable(handle); } - $provideReferences(handle: number, resource: URI, position: IPosition, context: modes.ReferenceContext): TPromise { - return this._withAdapter(handle, ReferenceAdapter, adapter => adapter.provideReferences(resource, position, context)); + $provideReferences(handle: number, resource: UriComponents, position: IPosition, context: modes.ReferenceContext): TPromise { + return this._withAdapter(handle, ReferenceAdapter, adapter => adapter.provideReferences(URI.revive(resource), position, context)); } // --- quick fix registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider)); + const handle = this._addNewAdapter(new CodeActionAdapter(this._documents, this._commands.converter, this._diagnostics, provider)); this._proxy.$registerQuickFixSupport(handle, selector); return this._createDisposable(handle); } - $provideCodeActions(handle: number, resource: URI, range: IRange): TPromise { - return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(resource, range)); + $provideCodeActions(handle: number, resource: UriComponents, range: IRange): TPromise { + return this._withAdapter(handle, CodeActionAdapter, adapter => adapter.provideCodeActions(URI.revive(resource), range)); } // --- formatting registerDocumentFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentFormattingEditProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new DocumentFormattingAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new DocumentFormattingAdapter(this._documents, provider)); this._proxy.$registerDocumentFormattingSupport(handle, selector); return this._createDisposable(handle); } - $provideDocumentFormattingEdits(handle: number, resource: URI, options: modes.FormattingOptions): TPromise { - return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.provideDocumentFormattingEdits(resource, options)); + $provideDocumentFormattingEdits(handle: number, resource: UriComponents, options: modes.FormattingOptions): TPromise { + return this._withAdapter(handle, DocumentFormattingAdapter, adapter => adapter.provideDocumentFormattingEdits(URI.revive(resource), options)); } registerDocumentRangeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentRangeFormattingEditProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new RangeFormattingAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new RangeFormattingAdapter(this._documents, provider)); this._proxy.$registerRangeFormattingSupport(handle, selector); return this._createDisposable(handle); } - $provideDocumentRangeFormattingEdits(handle: number, resource: URI, range: IRange, options: modes.FormattingOptions): TPromise { - return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(resource, range, options)); + $provideDocumentRangeFormattingEdits(handle: number, resource: UriComponents, range: IRange, options: modes.FormattingOptions): TPromise { + return this._withAdapter(handle, RangeFormattingAdapter, adapter => adapter.provideDocumentRangeFormattingEdits(URI.revive(resource), range, options)); } registerOnTypeFormattingEditProvider(selector: vscode.DocumentSelector, provider: vscode.OnTypeFormattingEditProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new OnTypeFormattingAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new OnTypeFormattingAdapter(this._documents, provider)); this._proxy.$registerOnTypeFormattingSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } - $provideOnTypeFormattingEdits(handle: number, resource: URI, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise { - return this._withAdapter(handle, OnTypeFormattingAdapter, adapter => adapter.provideOnTypeFormattingEdits(resource, position, ch, options)); + $provideOnTypeFormattingEdits(handle: number, resource: UriComponents, position: IPosition, ch: string, options: modes.FormattingOptions): TPromise { + return this._withAdapter(handle, OnTypeFormattingAdapter, adapter => adapter.provideOnTypeFormattingEdits(URI.revive(resource), position, ch, options)); } // --- navigate types registerWorkspaceSymbolProvider(provider: vscode.WorkspaceSymbolProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new NavigateTypeAdapter(provider)); + const handle = this._addNewAdapter(new NavigateTypeAdapter(provider)); this._proxy.$registerNavigateTypeSupport(handle); return this._createDisposable(handle); } - $provideWorkspaceSymbols(handle: number, search: string): TPromise { + $provideWorkspaceSymbols(handle: number, search: string): TPromise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.provideWorkspaceSymbols(search)); } - $resolveWorkspaceSymbol(handle: number, symbol: IWorkspaceSymbol): TPromise { + $resolveWorkspaceSymbol(handle: number, symbol: SymbolInformationDto): TPromise { return this._withAdapter(handle, NavigateTypeAdapter, adapter => adapter.resolveWorkspaceSymbol(symbol)); } @@ -1011,31 +1002,29 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- rename registerRenameProvider(selector: vscode.DocumentSelector, provider: vscode.RenameProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new RenameAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new RenameAdapter(this._documents, provider)); this._proxy.$registerRenameSupport(handle, selector); return this._createDisposable(handle); } - $provideRenameEdits(handle: number, resource: URI, position: IPosition, newName: string): TPromise { - return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(resource, position, newName)); + $provideRenameEdits(handle: number, resource: UriComponents, position: IPosition, newName: string): TPromise { + return this._withAdapter(handle, RenameAdapter, adapter => adapter.provideRenameEdits(URI.revive(resource), position, newName)); } // --- suggestion registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new SuggestAdapter(this._documents, this._commands.converter, provider)); + const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider)); this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters, SuggestAdapter.supportsResolving(provider)); return this._createDisposable(handle); } - $provideCompletionItems(handle: number, resource: URI, position: IPosition, context: modes.SuggestContext): TPromise { - return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(resource, position, context)); + $provideCompletionItems(handle: number, resource: UriComponents, position: IPosition, context: modes.SuggestContext): TPromise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.provideCompletionItems(URI.revive(resource), position, context)); } - $resolveCompletionItem(handle: number, resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { - return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(resource, position, suggestion)); + $resolveCompletionItem(handle: number, resource: UriComponents, position: IPosition, suggestion: modes.ISuggestion): TPromise { + return this._withAdapter(handle, SuggestAdapter, adapter => adapter.resolveCompletionItem(URI.revive(resource), position, suggestion)); } $releaseCompletionItems(handle: number, id: number): void { @@ -1045,27 +1034,25 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { // --- parameter hints registerSignatureHelpProvider(selector: vscode.DocumentSelector, provider: vscode.SignatureHelpProvider, triggerCharacters: string[]): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new SignatureHelpAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new SignatureHelpAdapter(this._documents, provider)); this._proxy.$registerSignatureHelpProvider(handle, selector, triggerCharacters); return this._createDisposable(handle); } - $provideSignatureHelp(handle: number, resource: URI, position: IPosition): TPromise { - return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(resource, position)); + $provideSignatureHelp(handle: number, resource: UriComponents, position: IPosition): TPromise { + return this._withAdapter(handle, SignatureHelpAdapter, adapter => adapter.provideSignatureHelp(URI.revive(resource), position)); } // --- links registerDocumentLinkProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentLinkProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new LinkProviderAdapter(this._documents, this._heapService, provider)); + const handle = this._addNewAdapter(new LinkProviderAdapter(this._documents, this._heapService, provider)); this._proxy.$registerDocumentLinkProvider(handle, selector); return this._createDisposable(handle); } - $provideDocumentLinks(handle: number, resource: URI): TPromise { - return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(resource)); + $provideDocumentLinks(handle: number, resource: UriComponents): TPromise { + return this._withAdapter(handle, LinkProviderAdapter, adapter => adapter.provideLinks(URI.revive(resource))); } $resolveDocumentLink(handle: number, link: modes.ILink): TPromise { @@ -1073,22 +1060,67 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } registerColorProvider(selector: vscode.DocumentSelector, provider: vscode.DocumentColorProvider): vscode.Disposable { - const handle = this._nextHandle(); - this._adapter.set(handle, new ColorProviderAdapter(this._documents, provider)); + const handle = this._addNewAdapter(new ColorProviderAdapter(this._documents, provider)); this._proxy.$registerDocumentColorProvider(handle, selector); return this._createDisposable(handle); } - $provideDocumentColors(handle: number, resource: URI): TPromise { - return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(resource)); + $provideDocumentColors(handle: number, resource: UriComponents): TPromise { + return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColors(URI.revive(resource))); } - $provideColorPresentations(handle: number, resource: URI, colorInfo: IRawColorInfo): TPromise { - return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(resource, colorInfo)); + $provideColorPresentations(handle: number, resource: UriComponents, colorInfo: IRawColorInfo): TPromise { + return this._withAdapter(handle, ColorProviderAdapter, adapter => adapter.provideColorPresentations(URI.revive(resource), colorInfo)); } // --- configuration + private static _serializeRegExp(regExp: RegExp): ISerializedRegExp { + if (typeof regExp === 'undefined') { + return undefined; + } + if (regExp === null) { + return null; + } + return { + pattern: regExp.source, + flags: (regExp.global ? 'g' : '') + (regExp.ignoreCase ? 'i' : '') + (regExp.multiline ? 'm' : ''), + }; + } + + private static _serializeIndentationRule(indentationRule: vscode.IndentationRule): ISerializedIndentationRule { + if (typeof indentationRule === 'undefined') { + return undefined; + } + if (indentationRule === null) { + return null; + } + return { + decreaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.decreaseIndentPattern), + increaseIndentPattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.increaseIndentPattern), + indentNextLinePattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.indentNextLinePattern), + unIndentedLinePattern: ExtHostLanguageFeatures._serializeRegExp(indentationRule.unIndentedLinePattern), + }; + } + + private static _serializeOnEnterRule(onEnterRule: vscode.OnEnterRule): ISerializedOnEnterRule { + return { + beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText), + afterText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText), + action: onEnterRule.action + }; + } + + private static _serializeOnEnterRules(onEnterRules: vscode.OnEnterRule[]): ISerializedOnEnterRule[] { + if (typeof onEnterRules === 'undefined') { + return undefined; + } + if (onEnterRules === null) { + return null; + } + return onEnterRules.map(ExtHostLanguageFeatures._serializeOnEnterRule); + } + setLanguageConfiguration(languageId: string, configuration: vscode.LanguageConfiguration): vscode.Disposable { let { wordPattern } = configuration; @@ -1105,7 +1137,16 @@ export class ExtHostLanguageFeatures implements ExtHostLanguageFeaturesShape { } const handle = this._nextHandle(); - this._proxy.$setLanguageConfiguration(handle, languageId, configuration); + const serializedConfiguration: ISerializedLanguageConfiguration = { + comments: configuration.comments, + brackets: configuration.brackets, + wordPattern: ExtHostLanguageFeatures._serializeRegExp(configuration.wordPattern), + indentationRules: ExtHostLanguageFeatures._serializeIndentationRule(configuration.indentationRules), + onEnterRules: ExtHostLanguageFeatures._serializeOnEnterRules(configuration.onEnterRules), + __electricCharacterSupport: configuration.__electricCharacterSupport, + __characterPairSupport: configuration.__characterPairSupport, + }; + this._proxy.$setLanguageConfiguration(handle, languageId, serializedConfiguration); return this._createDisposable(handle); } } diff --git a/src/vs/workbench/api/node/extHostLanguages.ts b/src/vs/workbench/api/node/extHostLanguages.ts index e480ac1b40d..33baf762390 100644 --- a/src/vs/workbench/api/node/extHostLanguages.ts +++ b/src/vs/workbench/api/node/extHostLanguages.ts @@ -14,11 +14,10 @@ export class ExtHostLanguages { constructor( mainContext: IMainContext ) { - this._proxy = mainContext.get(MainContext.MainThreadLanguages); + this._proxy = mainContext.getProxy(MainContext.MainThreadLanguages); } getLanguages(): TPromise { return this._proxy.$getLanguages(); } } - diff --git a/src/vs/workbench/api/node/extHostLogService.ts b/src/vs/workbench/api/node/extHostLogService.ts new file mode 100644 index 00000000000..150bde4af3d --- /dev/null +++ b/src/vs/workbench/api/node/extHostLogService.ts @@ -0,0 +1,89 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as path from 'path'; +import * as vscode from 'vscode'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { mkdirp, dirExists } from 'vs/base/node/pfs'; +import Event, { Emitter } from 'vs/base/common/event'; +import { LogLevel } from 'vs/workbench/api/node/extHostTypes'; +import { ILogService } from 'vs/platform/log/common/log'; +import { createLogService } from 'vs/platform/log/node/spdlogService'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { memoize } from 'vs/base/common/decorators'; + +export class ExtHostLogService { + private _loggers: Map = new Map(); + + constructor(private _environmentService: IEnvironmentService) { + } + + getExtLogger(extensionID: string): ExtHostLogger { + if (!this._loggers.has(extensionID)) { + const logService = createLogService(extensionID, this._environmentService, extensionID); + const logsDirPath = path.join(this._environmentService.logsPath, extensionID); + this._loggers.set(extensionID, new ExtHostLogger(logService, logsDirPath)); + } + + return this._loggers.get(extensionID); + } +} + +export class ExtHostLogger implements vscode.Logger { + private _currentLevel: LogLevel; + private _onDidChangeLogLevel: Emitter; + + constructor( + private readonly _logService: ILogService, + private readonly _logDirectory: string + ) { + this._currentLevel = this._logService.getLevel(); + this._onDidChangeLogLevel = new Emitter(); + this.onDidChangeLogLevel = this._onDidChangeLogLevel.event; + } + + // TODO + readonly onDidChangeLogLevel: Event; + + get currentLevel(): LogLevel { return this._currentLevel; } + + @memoize + get logDirectory(): TPromise { + return dirExists(this._logDirectory).then(exists => { + if (exists) { + return TPromise.wrap(null); + } else { + return mkdirp(this._logDirectory); + } + }).then(() => { + return this._logDirectory; + }); + } + + trace(message: string, ...args: any[]): void { + return this._logService.trace(message, ...args); + } + + debug(message: string, ...args: any[]): void { + return this._logService.debug(message, ...args); + } + + info(message: string, ...args: any[]): void { + return this._logService.info(message, ...args); + } + + warn(message: string, ...args: any[]): void { + return this._logService.warn(message, ...args); + } + + error(message: string | Error, ...args: any[]): void { + return this._logService.error(message, ...args); + } + + critical(message: string | Error, ...args: any[]): void { + return this._logService.critical(message, ...args); + } +} diff --git a/src/vs/workbench/api/node/extHostMessageService.ts b/src/vs/workbench/api/node/extHostMessageService.ts index d0698c554bc..75f51b89b14 100644 --- a/src/vs/workbench/api/node/extHostMessageService.ts +++ b/src/vs/workbench/api/node/extHostMessageService.ts @@ -18,7 +18,7 @@ export class ExtHostMessageService { private _proxy: MainThreadMessageServiceShape; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadMessageService); + this._proxy = mainContext.getProxy(MainContext.MainThreadMessageService); } showMessage(extension: IExtensionDescription, severity: Severity, message: string, optionsOrFirstItem: vscode.MessageOptions | string, rest: string[]): Thenable; diff --git a/src/vs/workbench/api/node/extHostOutputService.ts b/src/vs/workbench/api/node/extHostOutputService.ts index ff39a9e2feb..d6901a1c2be 100644 --- a/src/vs/workbench/api/node/extHostOutputService.ts +++ b/src/vs/workbench/api/node/extHostOutputService.ts @@ -64,7 +64,7 @@ export class ExtHostOutputService { private _proxy: MainThreadOutputServiceShape; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadOutputService); + this._proxy = mainContext.getProxy(MainContext.MainThreadOutputService); } createOutputChannel(name: string): vscode.OutputChannel { diff --git a/src/vs/workbench/api/node/extHostQuickOpen.ts b/src/vs/workbench/api/node/extHostQuickOpen.ts index 03672ebf187..2d32ed719fe 100644 --- a/src/vs/workbench/api/node/extHostQuickOpen.ts +++ b/src/vs/workbench/api/node/extHostQuickOpen.ts @@ -24,7 +24,7 @@ export class ExtHostQuickOpen implements ExtHostQuickOpenShape { private _validateInput: (input: string) => string | Thenable; constructor(mainContext: IMainContext, workspace: ExtHostWorkspace, commands: ExtHostCommands) { - this._proxy = mainContext.get(MainContext.MainThreadQuickOpen); + this._proxy = mainContext.getProxy(MainContext.MainThreadQuickOpen); this._workspace = workspace; this._commands = commands; } diff --git a/src/vs/workbench/api/node/extHostSCM.ts b/src/vs/workbench/api/node/extHostSCM.ts index 9444ef00744..bcd3a92b97c 100644 --- a/src/vs/workbench/api/node/extHostSCM.ts +++ b/src/vs/workbench/api/node/extHostSCM.ts @@ -12,7 +12,7 @@ import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { asWinJsPromise } from 'vs/base/common/async'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; -import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext } from './extHost.protocol'; +import { MainContext, MainThreadSCMShape, SCMRawResource, SCMRawResourceSplice, SCMRawResourceSplices, IMainContext, ExtHostSCMShape } from './extHost.protocol'; import { sortedDiff } from 'vs/base/common/arrays'; import { comparePaths } from 'vs/base/common/comparers'; import * as vscode from 'vscode'; @@ -110,7 +110,7 @@ function compareResourceStates(a: vscode.SourceControlResourceState, b: vscode.S return result; } -export class ExtHostSCMInputBox { +export class ExtHostSCMInputBox implements vscode.SourceControlInputBox { private _value: string = ''; @@ -140,6 +140,17 @@ export class ExtHostSCMInputBox { this._placeholder = placeholder; } + private _lineWarningLength: number | undefined; + + get lineWarningLength(): number | undefined { + return this._lineWarningLength; + } + + set lineWarningLength(lineWarningLength: number) { + this._proxy.$setLineWarningLength(this._sourceControlHandle, lineWarningLength); + this._lineWarningLength = lineWarningLength; + } + constructor(private _proxy: MainThreadSCMShape, private _sourceControlHandle: number) { // noop } @@ -431,7 +442,7 @@ class ExtHostSourceControl implements vscode.SourceControl { } } -export class ExtHostSCM { +export class ExtHostSCM implements ExtHostSCMShape { private static _handlePool: number = 0; @@ -447,7 +458,7 @@ export class ExtHostSCM { private _commands: ExtHostCommands, @ILogService private logService: ILogService ) { - this._proxy = mainContext.get(MainContext.MainThreadSCM); + this._proxy = mainContext.getProxy(MainContext.MainThreadSCM); _commands.registerArgumentProcessor({ processArgument: arg => { @@ -513,8 +524,8 @@ export class ExtHostSCM { return inputBox; } - $provideOriginalResource(sourceControlHandle: number, uri: URI): TPromise { - this.logService.trace('ExtHostSCM#$provideOriginalResource', sourceControlHandle, uri); + $provideOriginalResource(sourceControlHandle: number, uriString: string): TPromise { + this.logService.trace('ExtHostSCM#$provideOriginalResource', sourceControlHandle, uriString); const sourceControl = this._sourceControls.get(sourceControlHandle); @@ -522,10 +533,8 @@ export class ExtHostSCM { return TPromise.as(null); } - return asWinJsPromise(token => { - const result = sourceControl.quickDiffProvider.provideOriginalResource(uri, token); - return result && URI.parse(result.toString()); - }); + return asWinJsPromise(token => sourceControl.quickDiffProvider.provideOriginalResource(URI.parse(uriString), token)) + .then(result => result && result.toString()); } $onInputBoxValueChange(sourceControlHandle: number, value: string): TPromise { diff --git a/src/vs/workbench/api/node/extHostStatusBar.ts b/src/vs/workbench/api/node/extHostStatusBar.ts index 2de2bec359b..348cc6cb43e 100644 --- a/src/vs/workbench/api/node/extHostStatusBar.ts +++ b/src/vs/workbench/api/node/extHostStatusBar.ts @@ -163,7 +163,7 @@ export class ExtHostStatusBar { private _statusMessage: StatusBarMessage; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadStatusBar); + this._proxy = mainContext.getProxy(MainContext.MainThreadStatusBar); this._statusMessage = new StatusBarMessage(this); } diff --git a/src/vs/workbench/api/node/extHostStorage.ts b/src/vs/workbench/api/node/extHostStorage.ts index d24797a397f..5a36bd5d550 100644 --- a/src/vs/workbench/api/node/extHostStorage.ts +++ b/src/vs/workbench/api/node/extHostStorage.ts @@ -12,7 +12,7 @@ export class ExtHostStorage { private _proxy: MainThreadStorageShape; constructor(mainContext: IMainContext) { - this._proxy = mainContext.get(MainContext.MainThreadStorage); + this._proxy = mainContext.getProxy(MainContext.MainThreadStorage); } getValue(shared: boolean, key: string, defaultValue?: T): TPromise { @@ -22,4 +22,4 @@ export class ExtHostStorage { setValue(shared: boolean, key: string, value: any): TPromise { return this._proxy.$setValue(shared, key, value); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index 09538e65693..41cb0357367 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -424,7 +424,7 @@ export class ExtHostTask implements ExtHostTaskShape { private _handlers: Map; constructor(mainContext: IMainContext, extHostWorkspace: ExtHostWorkspace) { - this._proxy = mainContext.get(MainContext.MainThreadTask); + this._proxy = mainContext.getProxy(MainContext.MainThreadTask); this._extHostWorkspace = extHostWorkspace; this._handleCounter = 0; this._handlers = new Map(); diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index dd7e1c10b60..386bb11f23e 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -5,7 +5,6 @@ 'use strict'; import vscode = require('vscode'); -import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import Event, { Emitter } from 'vs/base/common/event'; import { ExtHostTerminalServiceShape, MainContext, MainThreadTerminalServiceShape, IMainContext } from './extHost.protocol'; @@ -16,24 +15,26 @@ export class ExtHostTerminal implements vscode.Terminal { private _proxy: MainThreadTerminalServiceShape; private _disposed: boolean; private _queuedRequests: ApiRequest[]; - private _pidPromise: TPromise; - private _pidPromiseComplete: TValueCallback; + private _pidPromise: Promise; + private _pidPromiseComplete: (value: number) => any; constructor( proxy: MainThreadTerminalServiceShape, name?: string, shellPath?: string, shellArgs?: string[], + cwd?: string, env?: { [key: string]: string }, waitOnExit?: boolean ) { this._name = name; this._queuedRequests = []; this._proxy = proxy; - this._pidPromise = new TPromise(c => { + this._pidPromise = new Promise(c => { this._pidPromiseComplete = c; }); - this._proxy.$createTerminal(name, shellPath, shellArgs, env, waitOnExit).then((id) => { + + this._proxy.$createTerminal(name, shellPath, shellArgs, cwd, env, waitOnExit).then((id) => { this._id = id; this._queuedRequests.forEach((r) => { r.run(this._proxy, this._id); @@ -103,7 +104,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { constructor(mainContext: IMainContext) { this._onDidCloseTerminal = new Emitter(); - this._proxy = mainContext.get(MainContext.MainThreadTerminalService); + this._proxy = mainContext.getProxy(MainContext.MainThreadTerminalService); this._terminals = []; } @@ -114,7 +115,7 @@ export class ExtHostTerminalService implements ExtHostTerminalServiceShape { } public createTerminalFromOptions(options: vscode.TerminalOptions): vscode.Terminal { - let terminal = new ExtHostTerminal(this._proxy, options.name, options.shellPath, options.shellArgs, options.env/*, options.waitOnExit*/); + let terminal = new ExtHostTerminal(this._proxy, options.name, options.shellPath, options.shellArgs, options.cwd, options.env /*, options.waitOnExit*/); this._terminals.push(terminal); return terminal; } @@ -172,4 +173,4 @@ class ApiRequest { public run(proxy: MainThreadTerminalServiceShape, id: number) { this._callback.apply(proxy, [id].concat(this._args)); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostTextEditor.ts b/src/vs/workbench/api/node/extHostTextEditor.ts index 0f70aa99ad0..aaf41d4a9bd 100644 --- a/src/vs/workbench/api/node/extHostTextEditor.ts +++ b/src/vs/workbench/api/node/extHostTextEditor.ts @@ -435,7 +435,7 @@ export class ExtHostTextEditor implements vscode.TextEditor { return this._proxy.$trySetDecorationsFast( this._id, decorationType.key, - /*TODO: marshaller is too slow*/JSON.stringify(_ranges) + _ranges ); } } diff --git a/src/vs/workbench/api/node/extHostTextEditors.ts b/src/vs/workbench/api/node/extHostTextEditors.ts index 10232e0c87c..35ad03c3a49 100644 --- a/src/vs/workbench/api/node/extHostTextEditors.ts +++ b/src/vs/workbench/api/node/extHostTextEditors.ts @@ -37,7 +37,7 @@ export class ExtHostEditors implements ExtHostEditorsShape { mainContext: IMainContext, extHostDocumentsAndEditors: ExtHostDocumentsAndEditors, ) { - this._proxy = mainContext.get(MainContext.MainThreadEditors); + this._proxy = mainContext.getProxy(MainContext.MainThreadEditors); this._extHostDocumentsAndEditors = extHostDocumentsAndEditors; this._extHostDocumentsAndEditors.onDidChangeVisibleTextEditors(e => this._onDidChangeVisibleTextEditors.fire(e)); diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index f2e7dce967d..f6363eb2beb 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -241,18 +241,6 @@ export namespace WorkspaceEdit { return result; } - export function fromTextEdits(uri: vscode.Uri, textEdits: vscode.TextEdit[]): modes.WorkspaceEdit { - const result: modes.WorkspaceEdit = { edits: [] }; - for (let textEdit of textEdits) { - result.edits.push({ - resource: uri, - newText: textEdit.newText, - range: fromRange(textEdit.range) - }); - } - return result; - } - export function to(value: modes.WorkspaceEdit) { const result = new types.WorkspaceEdit(); for (const edit of value.edits) { @@ -626,9 +614,13 @@ function doToLanguageSelector(selector: string | vscode.DocumentFilter): string return selector; } - return { - language: selector.language, - scheme: selector.scheme, - pattern: toGlobPattern(selector.pattern) - }; + if (selector) { + return { + language: selector.language, + scheme: selector.scheme, + pattern: toGlobPattern(selector.pattern) + }; + } + + return undefined; } diff --git a/src/vs/workbench/api/node/extHostTypes.ts b/src/vs/workbench/api/node/extHostTypes.ts index b0cc9b02062..e865dee2b1c 100644 --- a/src/vs/workbench/api/node/extHostTypes.ts +++ b/src/vs/workbench/api/node/extHostTypes.ts @@ -91,10 +91,10 @@ export class Position { constructor(line: number, character: number) { if (line < 0) { - throw illegalArgument('line must be positive'); + throw illegalArgument('line must be non-negative'); } if (character < 0) { - throw illegalArgument('character must be positive'); + throw illegalArgument('character must be non-negative'); } this._line = line; this._character = character; @@ -1531,3 +1531,13 @@ export class FunctionBreakpoint extends Breakpoint { this.functionName = functionName; } } + +export enum LogLevel { + Trace = 1, + Debug = 2, + Info = 3, + Warning = 4, + Error = 5, + Critical = 6, + Off = 7 +} diff --git a/src/vs/workbench/api/node/extHostWindow.ts b/src/vs/workbench/api/node/extHostWindow.ts index cf8e0be930a..8344a08fc1c 100644 --- a/src/vs/workbench/api/node/extHostWindow.ts +++ b/src/vs/workbench/api/node/extHostWindow.ts @@ -5,8 +5,7 @@ 'use strict'; import Event, { Emitter } from 'vs/base/common/event'; -import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { ExtHostWindowShape, MainContext, MainThreadWindowShape } from './extHost.protocol'; +import { ExtHostWindowShape, MainContext, MainThreadWindowShape, IMainContext } from './extHost.protocol'; import { WindowState } from 'vscode'; export class ExtHostWindow implements ExtHostWindowShape { @@ -23,8 +22,8 @@ export class ExtHostWindow implements ExtHostWindowShape { private _state = ExtHostWindow.InitialState; get state(): WindowState { return this._state; } - constructor(threadService: IThreadService) { - this._proxy = threadService.get(MainContext.MainThreadWindow); + constructor(mainContext: IMainContext) { + this._proxy = mainContext.getProxy(MainContext.MainThreadWindow); this._proxy.$getWindowVisibility().then(isFocused => this.$onDidChangeWindowFocus(isFocused)); } @@ -36,4 +35,4 @@ export class ExtHostWindow implements ExtHostWindowShape { this._state = { ...this._state, focused }; this._onDidChangeWindowState.fire(this._state); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 73f38a7da6e..2152866058e 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -18,14 +18,23 @@ import { TernarySearchTree } from 'vs/base/common/map'; class Workspace2 extends Workspace { static fromData(data: IWorkspaceData) { - return data ? new Workspace2(data) : null; + if (!data) { + return null; + } else { + const { id, name, folders } = data; + return new Workspace2( + id, + name, + folders.map(({ uri, name, index }) => new WorkspaceFolder({ name, index, uri: URI.revive(uri) })) + ); + } } private readonly _workspaceFolders: vscode.WorkspaceFolder[] = []; private readonly _structure = TernarySearchTree.forPaths(); - private constructor(data: IWorkspaceData) { - super(data.id, data.name, data.folders.map(folder => new WorkspaceFolder(folder))); + private constructor(id: string, name: string, folders: WorkspaceFolder[]) { + super(id, name, folders); // setup the workspace folder data structure this.folders.forEach(({ name, uri, index }) => { @@ -59,7 +68,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { readonly onDidChangeWorkspace: Event = this._onDidChangeWorkspace.event; constructor(mainContext: IMainContext, data: IWorkspaceData) { - this._proxy = mainContext.get(MainContext.MainThreadWorkspace); + this._proxy = mainContext.getProxy(MainContext.MainThreadWorkspace); this._workspace = Workspace2.fromData(data); } @@ -181,7 +190,7 @@ export class ExtHostWorkspace implements ExtHostWorkspaceShape { if (token) { token.onCancellationRequested(() => this._proxy.$cancelSearch(requestId)); } - return result; + return result.then(data => data.map(URI.revive)); } saveAll(includeUntitled?: boolean): Thenable { diff --git a/src/vs/workbench/browser/actions.ts b/src/vs/workbench/browser/actions.ts index 9659c82ceb5..b6f005a6238 100644 --- a/src/vs/workbench/browser/actions.ts +++ b/src/vs/workbench/browser/actions.ts @@ -57,22 +57,6 @@ export class ActionBarContributor { * Some predefined scopes to contribute actions to */ export const Scope = { - - /** - * Actions inside viewlets. - */ - VIEWLET: 'viewlet', - - /** - * Actions inside panels. - */ - PANEL: 'panel', - - /** - * Actions inside editors. - */ - EDITOR: 'editor', - /** * Actions inside tree widgets. */ diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index fe36123d156..f41e4ae7c05 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -12,25 +12,17 @@ import { IWindowService, IWindowsService } from 'vs/platform/windows/common/wind import { ITelemetryData } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import URI from 'vs/base/common/uri'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { WORKSPACE_FILTER, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { isLinux } from 'vs/base/common/platform'; -import { dirname } from 'vs/base/common/paths'; -import * as resources from 'vs/base/common/resources'; -import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; -import { isParent, FileKind } from 'vs/platform/files/common/files'; +import { mnemonicButtonLabel } from 'vs/base/common/labels'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { IQuickOpenService, IFilePickOpenEntry, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService } from 'vs/platform/commands/common/commands'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, PICK_WORKSPACE_FOLDER_COMMAND_ID, defaultWorkspacePath, defaultFilePath, defaultFolderPath } from 'vs/workbench/browser/actions/workspaceCommands'; export class OpenFileAction extends Action { - static ID = 'workbench.action.files.openFile'; + static readonly ID = 'workbench.action.files.openFile'; static LABEL = nls.localize('openFile', "Open File..."); constructor( @@ -50,7 +42,7 @@ export class OpenFileAction extends Action { export class OpenFolderAction extends Action { - static ID = 'workbench.action.files.openFolder'; + static readonly ID = 'workbench.action.files.openFolder'; static LABEL = nls.localize('openFolder', "Open Folder..."); constructor( @@ -70,7 +62,7 @@ export class OpenFolderAction extends Action { export class OpenFileFolderAction extends Action { - static ID = 'workbench.action.files.openFileFolder'; + static readonly ID = 'workbench.action.files.openFileFolder'; static LABEL = nls.localize('openFileFolder', "Open..."); constructor( @@ -88,156 +80,37 @@ export class OpenFileFolderAction extends Action { } } -export const openFileFolderInNewWindowCommand = (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); +export class AddRootFolderAction extends Action { - windowService.pickFileFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFilePath(contextService, historyService) } }); -}; - -export const openFolderCommand = (accessor: ServicesAccessor, forceNewWindow: boolean) => { - const { windowService, historyService, contextService } = services(accessor); - - windowService.pickFolderAndOpen({ forceNewWindow, dialogOptions: { defaultPath: defaultFolderPath(contextService, historyService) } }); -}; - -export const openFolderInNewWindowCommand = (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); - - windowService.pickFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFolderPath(contextService, historyService) } }); -}; - -export const openFileInNewWindowCommand = (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService } = services(accessor); - - windowService.pickFileAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFilePath(contextService, historyService) } }); -}; - -export const openWorkspaceInNewWindowCommand = (accessor: ServicesAccessor) => { - const { windowService, historyService, contextService, environmentService } = services(accessor); - - windowService.pickWorkspaceAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultWorkspacePath(contextService, historyService, environmentService) } }); -}; - -function services(accessor: ServicesAccessor): { windowService: IWindowService, historyService: IHistoryService, contextService: IWorkspaceContextService, environmentService: IEnvironmentService } { - return { - windowService: accessor.get(IWindowService), - historyService: accessor.get(IHistoryService), - contextService: accessor.get(IWorkspaceContextService), - environmentService: accessor.get(IEnvironmentService) - }; -} - -export abstract class BaseWorkspacesAction extends Action { + static readonly ID = 'workbench.action.addRootFolder'; + static LABEL = ADD_ROOT_FOLDER_LABEL; constructor( id: string, label: string, - protected windowService: IWindowService, - protected environmentService: IEnvironmentService, - protected contextService: IWorkspaceContextService, - protected historyService: IHistoryService + @ICommandService private commandService: ICommandService ) { super(id, label); } - protected pickFolders(buttonLabel: string, title: string): string[] { - return this.windowService.showOpenDialog({ - buttonLabel, - title, - properties: ['multiSelections', 'openDirectory', 'createDirectory'], - defaultPath: defaultFolderPath(this.contextService, this.historyService) - }); - } -} - -function defaultFilePath(contextService: IWorkspaceContextService, historyService: IHistoryService): string { - let candidate: URI; - - // Check for last active file first... - candidate = historyService.getLastActiveFile(); - - // ...then for last active file root - if (!candidate) { - candidate = historyService.getLastActiveWorkspaceRoot('file'); - } - - return candidate ? dirname(candidate.fsPath) : void 0; -} - -function defaultFolderPath(contextService: IWorkspaceContextService, historyService: IHistoryService): string { - let candidate: URI; - - // Check for last active file root first... - candidate = historyService.getLastActiveWorkspaceRoot('file'); - - // ...then for last active file - if (!candidate) { - candidate = historyService.getLastActiveFile(); - } - - return candidate ? dirname(candidate.fsPath) : void 0; -} - -function defaultWorkspacePath(contextService: IWorkspaceContextService, historyService: IHistoryService, environmentService: IEnvironmentService): string { - - // Check for current workspace config file first... - if (contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !isUntitledWorkspace(contextService.getWorkspace().configuration.fsPath, environmentService)) { - return dirname(contextService.getWorkspace().configuration.fsPath); - } - - // ...then fallback to default folder path - return defaultFolderPath(contextService, historyService); -} - -function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean { - return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); -} - -export class AddRootFolderAction extends BaseWorkspacesAction { - - static ID = 'workbench.action.addRootFolder'; - static LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); - - constructor( - id: string, - label: string, - @IWindowService windowService: IWindowService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IViewletService private viewletService: IViewletService, - @IHistoryService historyService: IHistoryService - ) { - super(id, label, windowService, environmentService, contextService, historyService); - } - public run(): TPromise { - const folders = super.pickFolders(mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace")); - if (!folders || !folders.length) { - return TPromise.as(null); - } - - // Add and show Files Explorer viewlet - return this.workspaceEditingService.addFolders(folders.map(folder => ({ uri: URI.file(folder) }))).then(() => this.viewletService.openViewlet(this.viewletService.getDefaultViewletId(), true)); + return this.commandService.executeCommand(ADD_ROOT_FOLDER_COMMAND_ID); } } -export class GlobalRemoveRootFolderAction extends BaseWorkspacesAction { +export class GlobalRemoveRootFolderAction extends Action { - static ID = 'workbench.action.removeRootFolder'; + static readonly ID = 'workbench.action.removeRootFolder'; static LABEL = nls.localize('globalRemoveFolderFromWorkspace', "Remove Folder from Workspace..."); constructor( id: string, label: string, - @IWindowService windowService: IWindowService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IEnvironmentService environmentService: IEnvironmentService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @ICommandService private commandService: ICommandService, - @IHistoryService historyService: IHistoryService + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @ICommandService private commandService: ICommandService ) { - super(id, label, windowService, environmentService, contextService, historyService); + super(id, label); } public run(): TPromise { @@ -245,7 +118,7 @@ export class GlobalRemoveRootFolderAction extends BaseWorkspacesAction { // Workspace / Folder if (state === WorkbenchState.WORKSPACE || state === WorkbenchState.FOLDER) { - return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND).then(folder => { + return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID).then(folder => { if (folder) { return this.workspaceEditingService.removeFolders([folder.uri]).then(() => true); } @@ -258,82 +131,42 @@ export class GlobalRemoveRootFolderAction extends BaseWorkspacesAction { } } -export class RemoveRootFolderAction extends Action { +export class SaveWorkspaceAsAction extends Action { - static ID = 'workbench.action.removeRootFolder'; - static LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); - - constructor( - private rootUri: URI, - id: string, - label: string, - @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService - ) { - super(id, label); - } - - public run(): TPromise { - return this.workspaceEditingService.removeFolders([this.rootUri]); - } -} - -export class OpenFolderSettingsAction extends Action { - - static ID = 'workbench.action.openFolderSettings'; - static LABEL = nls.localize('openFolderSettings', "Open Folder Settings"); - - constructor( - private rootUri: URI, - id: string, - label: string, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @ICommandService private commandService: ICommandService - ) { - super(id, label); - } - - public run(): TPromise { - const workspaceFolder = this.contextService.getWorkspaceFolder(this.rootUri); - - return this.commandService.executeCommand('_workbench.action.openFolderSettings', workspaceFolder); - } -} - -export class SaveWorkspaceAsAction extends BaseWorkspacesAction { - - static ID = 'workbench.action.saveWorkspaceAs'; + static readonly ID = 'workbench.action.saveWorkspaceAs'; static LABEL = nls.localize('saveWorkspaceAsAction', "Save Workspace As..."); constructor( id: string, label: string, - @IWindowService windowService: IWindowService, - @IEnvironmentService environmentService: IEnvironmentService, - @IWorkspaceContextService contextService: IWorkspaceContextService, + @IWindowService private windowService: IWindowService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, - @IHistoryService historyService: IHistoryService + @IHistoryService private historyService: IHistoryService ) { - super(id, label, windowService, environmentService, contextService, historyService); + super(id, label); } public run(): TPromise { - const configPath = this.getNewWorkspaceConfigPath(); - if (configPath) { - switch (this.contextService.getWorkbenchState()) { - case WorkbenchState.EMPTY: - case WorkbenchState.FOLDER: - const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); - return this.workspaceEditingService.createAndEnterWorkspace(folders, configPath); + return this.getNewWorkspaceConfigPath().then(configPath => { + if (configPath) { + switch (this.contextService.getWorkbenchState()) { + case WorkbenchState.EMPTY: + case WorkbenchState.FOLDER: + const folders = this.contextService.getWorkspace().folders.map(folder => ({ uri: folder.uri })); + return this.workspaceEditingService.createAndEnterWorkspace(folders, configPath); - case WorkbenchState.WORKSPACE: - return this.workspaceEditingService.saveAndEnterWorkspace(configPath); + case WorkbenchState.WORKSPACE: + return this.workspaceEditingService.saveAndEnterWorkspace(configPath); + } } - } - return TPromise.as(null); + return null; + }); } - private getNewWorkspaceConfigPath(): string { + private getNewWorkspaceConfigPath(): TPromise { return this.windowService.showSaveDialog({ buttonLabel: mnemonicButtonLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), title: nls.localize('saveWorkspace', "Save Workspace"), @@ -345,7 +178,7 @@ export class SaveWorkspaceAsAction extends BaseWorkspacesAction { export class OpenWorkspaceAction extends Action { - static ID = 'workbench.action.openWorkspace'; + static readonly ID = 'workbench.action.openWorkspace'; static LABEL = nls.localize('openWorkspaceAction', "Open Workspace..."); constructor( @@ -411,7 +244,7 @@ export class OpenFolderAsWorkspaceInNewWindowAction extends Action { } else if (folders.length === 1) { folderPromise = TPromise.as(folders[0]); } else { - folderPromise = this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND); + folderPromise = this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); } return folderPromise.then(folder => { @@ -426,65 +259,4 @@ export class OpenFolderAsWorkspaceInNewWindowAction extends Action { }); }); } -} - -export const PICK_WORKSPACE_FOLDER_COMMAND = '_workbench.pickWorkspaceFolder'; - -CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND, function (accessor: ServicesAccessor, args?: [IPickOptions, CancellationToken]) { - const contextService = accessor.get(IWorkspaceContextService); - const quickOpenService = accessor.get(IQuickOpenService); - const environmentService = accessor.get(IEnvironmentService); - - const folders = contextService.getWorkspace().folders; - if (!folders.length) { - return void 0; - } - - const folderPicks = folders.map(folder => { - return { - label: folder.name, - description: getPathLabel(resources.dirname(folder.uri), void 0, environmentService), - folder, - resource: folder.uri, - fileKind: FileKind.ROOT_FOLDER - } as IFilePickOpenEntry; - }); - - let options: IPickOptions; - if (args) { - options = args[0]; - } - - if (!options) { - options = Object.create(null); - } - - if (!options.autoFocus) { - options.autoFocus = { autoFocusFirstEntry: true }; - } - - if (!options.placeHolder) { - options.placeHolder = nls.localize('workspaceFolderPickerPlaceholder', "Select workspace folder"); - } - - if (typeof options.matchOnDescription !== 'boolean') { - options.matchOnDescription = true; - } - - let token: CancellationToken; - if (args) { - token = args[1]; - } - - if (!token) { - token = CancellationToken.None; - } - - return quickOpenService.pick(folderPicks, options, token).then(pick => { - if (!pick) { - return void 0; - } - - return folders[folderPicks.indexOf(pick)]; - }); -}); \ No newline at end of file +} \ No newline at end of file diff --git a/src/vs/workbench/browser/actions/workspaceCommands.ts b/src/vs/workbench/browser/actions/workspaceCommands.ts new file mode 100644 index 00000000000..2ef4aa69ab4 --- /dev/null +++ b/src/vs/workbench/browser/actions/workspaceCommands.ts @@ -0,0 +1,226 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TPromise } from 'vs/base/common/winjs.base'; +import nls = require('vs/nls'); +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; +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, getPathLabel } from 'vs/base/common/labels'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { FileKind, isParent } from 'vs/platform/files/common/files'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { isLinux } from 'vs/base/common/platform'; + +export const ADD_ROOT_FOLDER_COMMAND_ID = 'workbench.command.addRootFolder'; +export const ADD_ROOT_FOLDER_LABEL = nls.localize('addFolderToWorkspace', "Add Folder to Workspace..."); + +export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'workbench.command.removeRootFolder'; +export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace"); + +export const PICK_WORKSPACE_FOLDER_COMMAND_ID = '_workbench.pickWorkspaceFolder'; + +function pickFolders(buttonLabel: string, title: string, windowService: IWindowService, contextService: IWorkspaceContextService, historyService: IHistoryService): TPromise { + return windowService.showOpenDialog({ + buttonLabel, + title, + properties: ['multiSelections', 'openDirectory', 'createDirectory'], + defaultPath: defaultFolderPath(contextService, historyService) + }); +} + +export function defaultFolderPath(contextService: IWorkspaceContextService, historyService: IHistoryService): string { + let candidate: URI; + + // Check for last active file root first... + candidate = historyService.getLastActiveWorkspaceRoot('file'); + + // ...then for last active file + if (!candidate) { + candidate = historyService.getLastActiveFile(); + } + + return candidate ? dirname(candidate.fsPath) : void 0; +} + + +function services(accessor: ServicesAccessor): { windowService: IWindowService, historyService: IHistoryService, contextService: IWorkspaceContextService, environmentService: IEnvironmentService } { + return { + windowService: accessor.get(IWindowService), + historyService: accessor.get(IHistoryService), + contextService: accessor.get(IWorkspaceContextService), + environmentService: accessor.get(IEnvironmentService) + }; +} + +export function defaultFilePath(contextService: IWorkspaceContextService, historyService: IHistoryService): string { + let candidate: URI; + + // Check for last active file first... + candidate = historyService.getLastActiveFile(); + + // ...then for last active file root + if (!candidate) { + candidate = historyService.getLastActiveWorkspaceRoot('file'); + } + + return candidate ? dirname(candidate.fsPath) : void 0; +} + +export function defaultWorkspacePath(contextService: IWorkspaceContextService, historyService: IHistoryService, environmentService: IEnvironmentService): string { + + // Check for current workspace config file first... + if (contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && !isUntitledWorkspace(contextService.getWorkspace().configuration.fsPath, environmentService)) { + return dirname(contextService.getWorkspace().configuration.fsPath); + } + + // ...then fallback to default folder path + return defaultFolderPath(contextService, historyService); +} + +function isUntitledWorkspace(path: string, environmentService: IEnvironmentService): boolean { + return isParent(path, environmentService.workspacesHome, !isLinux /* ignore case */); +} + +// Command registration + +CommandsRegistry.registerCommand({ + id: 'workbench.action.files.openFileFolderInNewWindow', + handler: (accessor: ServicesAccessor) => { + const { windowService, historyService, contextService } = services(accessor); + + windowService.pickFileFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFilePath(contextService, historyService) } }); + } +}); + +CommandsRegistry.registerCommand({ + id: '_files.pickFolderAndOpen', + handler: (accessor: ServicesAccessor, forceNewWindow: boolean) => { + const { windowService, historyService, contextService } = services(accessor); + + windowService.pickFolderAndOpen({ forceNewWindow, dialogOptions: { defaultPath: defaultFolderPath(contextService, historyService) } }); + } +}); + +CommandsRegistry.registerCommand({ + id: 'workbench.action.files.openFolderInNewWindow', + handler: (accessor: ServicesAccessor) => { + const { windowService, historyService, contextService } = services(accessor); + + windowService.pickFolderAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFolderPath(contextService, historyService) } }); + } +}); + +CommandsRegistry.registerCommand({ + id: 'workbench.action.files.openFileInNewWindow', + handler: (accessor: ServicesAccessor) => { + const { windowService, historyService, contextService } = services(accessor); + + windowService.pickFileAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultFilePath(contextService, historyService) } }); + } +}); + +CommandsRegistry.registerCommand({ + id: 'workbench.action.openWorkspaceInNewWindow', + handler: (accessor: ServicesAccessor) => { + const { windowService, historyService, contextService, environmentService } = services(accessor); + + windowService.pickWorkspaceAndOpen({ forceNewWindow: true, dialogOptions: { defaultPath: defaultWorkspacePath(contextService, historyService, environmentService) } }); + } +}); + +CommandsRegistry.registerCommand({ + id: ADD_ROOT_FOLDER_COMMAND_ID, + handler: (accessor) => { + const viewletService = accessor.get(IViewletService); + const workspaceEditingService = accessor.get(IWorkspaceEditingService); + return pickFolders(mnemonicButtonLabel(nls.localize({ key: 'add', comment: ['&& denotes a mnemonic'] }, "&&Add")), nls.localize('addFolderToWorkspaceTitle', "Add Folder to Workspace"), + accessor.get(IWindowService), accessor.get(IWorkspaceContextService), accessor.get(IHistoryService)).then(folders => { + if (!folders || !folders.length) { + return null; + } + + // Add and show Files Explorer viewlet + return workspaceEditingService.addFolders(folders.map(folder => ({ uri: URI.file(folder) }))).then(() => viewletService.openViewlet(viewletService.getDefaultViewletId(), true)); + }); + } +}); + +CommandsRegistry.registerCommand({ + id: REMOVE_ROOT_FOLDER_COMMAND_ID, + handler: (accessor, resource: URI) => { + const workspaceEditingService = accessor.get(IWorkspaceEditingService); + return workspaceEditingService.removeFolders([resource]); + } +}); + +CommandsRegistry.registerCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, function (accessor, args?: [IPickOptions, CancellationToken]) { + const contextService = accessor.get(IWorkspaceContextService); + const quickOpenService = accessor.get(IQuickOpenService); + const environmentService = accessor.get(IEnvironmentService); + + const folders = contextService.getWorkspace().folders; + if (!folders.length) { + return void 0; + } + + const folderPicks = folders.map(folder => { + return { + label: folder.name, + description: getPathLabel(resources.dirname(folder.uri), void 0, environmentService), + folder, + resource: folder.uri, + fileKind: FileKind.ROOT_FOLDER + } as IFilePickOpenEntry; + }); + + let options: IPickOptions; + if (args) { + options = args[0]; + } + + if (!options) { + options = Object.create(null); + } + + if (!options.autoFocus) { + options.autoFocus = { autoFocusFirstEntry: true }; + } + + if (!options.placeHolder) { + options.placeHolder = nls.localize('workspaceFolderPickerPlaceholder', "Select workspace folder"); + } + + if (typeof options.matchOnDescription !== 'boolean') { + options.matchOnDescription = true; + } + + let token: CancellationToken; + if (args) { + token = args[1]; + } + + if (!token) { + token = CancellationToken.None; + } + + return quickOpenService.pick(folderPicks, options, token).then(pick => { + if (!pick) { + return void 0; + } + + return folders[folderPicks.indexOf(pick)]; + }); +}); diff --git a/src/vs/workbench/browser/editor.ts b/src/vs/workbench/browser/editor.ts index 24c226052d9..16a6140a6e4 100644 --- a/src/vs/workbench/browser/editor.ts +++ b/src/vs/workbench/browser/editor.ts @@ -12,6 +12,8 @@ import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { IConstructorSignature0, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { isArray } from 'vs/base/common/types'; import URI from 'vs/base/common/uri'; +import { DataTransfers } from 'vs/base/browser/dnd'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; export interface IEditorDescriptor { instantiate(instantiationService: IInstantiationService): BaseEditor; @@ -204,20 +206,50 @@ export interface IDraggedResource { isExternal: boolean; } -export function extractResources(e: DragEvent, externalOnly?: boolean): IDraggedResource[] { - const resources: IDraggedResource[] = []; +export interface IDraggedEditor extends IDraggedResource { + backupResource?: URI; + viewState?: IEditorViewState; +} + +export interface ISerializedDraggedEditor { + resource: string; + backupResource: string; + viewState: IEditorViewState; +} + +export const CodeDataTransfers = { + EDITOR: 'CodeEditor' +}; + +export function extractResources(e: DragEvent, externalOnly?: boolean): (IDraggedResource | IDraggedEditor)[] { + const resources: (IDraggedResource | IDraggedEditor)[] = []; if (e.dataTransfer.types.length > 0) { - // Check for in-app DND + // Check for window-to-window DND if (!externalOnly) { - const rawData = e.dataTransfer.getData('URL'); - if (rawData) { + + // Data Transfer: Code Editor + const rawEditorData = e.dataTransfer.getData(CodeDataTransfers.EDITOR); + if (rawEditorData) { try { - resources.push({ resource: URI.parse(rawData), isExternal: false }); + const draggedEditor = JSON.parse(rawEditorData) as ISerializedDraggedEditor; + resources.push({ resource: URI.parse(draggedEditor.resource), backupResource: URI.parse(draggedEditor.backupResource), viewState: draggedEditor.viewState, isExternal: false }); } catch (error) { // Invalid URI } } + + // Data Transfer: URL + else { + const rawURLData = e.dataTransfer.getData(DataTransfers.URL); + if (rawURLData) { + try { + resources.push({ resource: URI.parse(rawURLData), isExternal: false }); + } catch (error) { + // Invalid URI + } + } + } } // Check for native file transfer diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index cc17759c64b..81b49fbed8d 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -682,10 +682,14 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal return this.workbenchSize.width - this.sidebarWidth - this.activitybarWidth; } - return this.workbenchSize.width - (this.partService.isVisible(Parts.PANEL_PART) ? this.panelWidth : 0) - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0); + return this.workbenchSize.width - this.panelWidth - (sidebarPosition === Position.RIGHT ? this.sidebarWidth + this.activitybarWidth : 0); } public getVerticalSashHeight(sash: Sash): number { + if (sash === this.sashXTwo && !this.partService.isVisible(Parts.PANEL_PART)) { + return 0; + } + return this.sidebarHeight; } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index d0c3072491e..9d52f097832 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -88,9 +88,9 @@ export class ActivitybarPart extends Part { this.toUnbind.push(this.compositeBar.onDidContextMenu(e => this.showContextMenu(e))); } - public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string): IDisposable { + public showActivity(viewletOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { if (this.viewletService.getViewlet(viewletOrActionId)) { - return this.compositeBar.showActivity(viewletOrActionId, badge, clazz); + return this.compositeBar.showActivity(viewletOrActionId, badge, clazz, priority); } return this.showGlobalActivity(viewletOrActionId, badge); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 7c6cdc0b7a4..2ca7e098b95 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -9,7 +9,6 @@ import 'vs/css!./media/compositepart'; import nls = require('vs/nls'); import { defaultGenerator } from 'vs/base/common/idGenerator'; import { TPromise } from 'vs/base/common/winjs.base'; -import { Registry } from 'vs/platform/registry/common/platform'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Dimension, Builder, $ } from 'vs/base/browser/builder'; import strings = require('vs/base/common/strings'); @@ -18,10 +17,10 @@ import types = require('vs/base/common/types'); import errors = require('vs/base/common/errors'); import * as DOM from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { CONTEXT as ToolBarContext, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; +import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; -import { IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions'; +import { prepareActions } from 'vs/workbench/browser/actions'; import { Action, IAction, IRunEvent } from 'vs/base/common/actions'; import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; @@ -83,7 +82,6 @@ export abstract class CompositePart extends Part { private defaultCompositeId: string, private nameForTelemetry: string, private compositeCSSClass: string, - private actionContributionScope: string, private titleForegroundColor: string, id: string, options: IPartOptions @@ -363,11 +361,6 @@ export abstract class CompositePart extends Part { primaryActions.push(...this.getActions()); secondaryActions.push(...this.getSecondaryActions()); - // From Contributions - const actionBarRegistry = Registry.as(Extensions.Actionbar); - primaryActions.push(...actionBarRegistry.getActionBarActionsForContext(this.actionContributionScope, composite)); - secondaryActions.push(...actionBarRegistry.getSecondaryActionBarActionsForContext(this.actionContributionScope, composite)); - // Return fn to set into toolbar return this.toolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions)); } @@ -484,20 +477,12 @@ export abstract class CompositePart extends Part { } private actionItemProvider(action: Action): IActionItem { - let actionItem: IActionItem; - // Check Active Composite if (this.activeComposite) { - actionItem = this.activeComposite.getActionItem(action); + return this.activeComposite.getActionItem(action); } - // Check Registry - if (!actionItem) { - const actionBarRegistry = Registry.as(Extensions.Actionbar); - actionItem = actionBarRegistry.getActionItemForContext(this.actionContributionScope, ToolBarContext, action); - } - - return actionItem; + return undefined; } public createContentArea(parent: Builder): Builder { diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts index 24b4a899842..bc8c843ee3a 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBar.ts @@ -100,14 +100,27 @@ export class CompositeBar implements ICompositeBar { } } - public showActivity(compositeId: string, badge: IBadge, clazz?: string): IDisposable { + public showActivity(compositeId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { if (!badge) { throw illegalArgument('badge'); } - const activity = { badge, clazz }; + if (typeof priority !== 'number') { + priority = 0; + } + + const activity: ICompositeActivity = { badge, clazz, priority }; const stack = this.compositeIdToActivityStack[compositeId] || (this.compositeIdToActivityStack[compositeId] = []); - stack.unshift(activity); + + for (let i = 0; i <= stack.length; i++) { + if (i === stack.length) { + stack.push(activity); + break; + } else if (stack[i].priority <= priority) { + stack.splice(i, 0, activity); + break; + } + } this.updateActivity(compositeId); @@ -197,7 +210,7 @@ export class CompositeBar implements ICompositeBar { return; // We have not been rendered yet so there is nothing to update. } - let compositesToShow = this.pinnedComposites; + let compositesToShow = this.pinnedComposites.slice(0); // never modify original array // Always show the active composite even if it is marked to be hidden if (this.activeCompositeId && !compositesToShow.some(id => id === this.activeCompositeId)) { diff --git a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts index 543b77fab64..6c4f3102f84 100644 --- a/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts +++ b/src/vs/workbench/browser/parts/compositebar/compositeBarActions.ts @@ -26,6 +26,7 @@ import Event, { Emitter } from 'vs/base/common/event'; export interface ICompositeActivity { badge: IBadge; clazz: string; + priority: number; } export interface ICompositeBar { diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 0f78423efdc..7faae973f93 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -32,7 +32,7 @@ import { NavigateBetweenGroupsAction, FocusActiveGroupAction, FocusFirstGroupAction, FocusSecondGroupAction, FocusThirdGroupAction, EvenGroupWidthsAction, MaximizeGroupAction, MinimizeOtherGroupsAction, FocusPreviousGroup, FocusNextGroup, ShowEditorsInGroupOneAction, toEditorQuickOpenEntry, CloseLeftEditorsInGroupAction, CloseRightEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction, OpenNextEditor, OpenPreviousEditor, NavigateBackwardsAction, NavigateForwardAction, NavigateLastAction, ReopenClosedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, NAVIGATE_IN_GROUP_ONE_PREFIX, OpenPreviousEditorFromHistoryAction, ShowAllEditorsAction, NAVIGATE_ALL_EDITORS_GROUP_PREFIX, ClearEditorHistoryAction, ShowEditorsInGroupTwoAction, MoveEditorRightInGroupAction, OpenNextEditorInGroup, OpenPreviousEditorInGroup, OpenNextRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorAction, - NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup + NAVIGATE_IN_GROUP_TWO_PREFIX, ShowEditorsInGroupThreeAction, NAVIGATE_IN_GROUP_THREE_PREFIX, FocusLastEditorInStackAction, OpenNextRecentlyUsedEditorInGroupAction, MoveEditorToPreviousGroupAction, MoveEditorToNextGroupAction, MoveEditorToFirstGroupAction, MoveEditorToSecondGroupAction, MoveEditorToThirdGroupAction, MoveEditorLeftInGroupAction, ClearRecentFilesAction, OpenLastEditorInGroup } from 'vs/workbench/browser/parts/editor/editorActions'; import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -342,9 +342,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(KeepEditorAction, Keep registry.registerWorkbenchAction(new SyncActionDescriptor(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_W) }), 'View: Close All Editors', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseLeftEditorsInGroupAction, CloseLeftEditorsInGroupAction.ID, CloseLeftEditorsInGroupAction.LABEL), 'View: Close Editors to the Left', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseRightEditorsInGroupAction, CloseRightEditorsInGroupAction.ID, CloseRightEditorsInGroupAction.LABEL), 'View: Close Editors to the Right', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U) }), 'View: Close Unmodified Editors in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W) }), 'View: Close All Editors in Group', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL, { primary: null, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T } }), 'View: Close Other Editors', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, CloseUnmodifiedEditorsInGroupAction.LABEL), 'View: Close Unmodified Editors in Group', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, CloseEditorsInGroupAction.LABEL), 'View: Close All Editors in Group', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, CloseOtherEditorsInGroupAction.LABEL), 'View: Close Other Editors', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorsInOtherGroupsAction, CloseEditorsInOtherGroupsAction.ID, CloseEditorsInOtherGroupsAction.LABEL), 'View: Close Editors in Other Groups', category); registry.registerWorkbenchAction(new SyncActionDescriptor(SplitEditorAction, SplitEditorAction.ID, SplitEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKSLASH }), 'View: Split Editor', category); registry.registerWorkbenchAction(new SyncActionDescriptor(JoinTwoGroupsAction, JoinTwoGroupsAction.ID, JoinTwoGroupsAction.LABEL), 'View: Join Editors of Two Groups', category); @@ -363,6 +363,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupLeftAction, M registry.registerWorkbenchAction(new SyncActionDescriptor(MoveGroupRightAction, MoveGroupRightAction.ID, MoveGroupRightAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.RightArrow) }), 'View: Move Editor Group Right', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToPreviousGroupAction, MoveEditorToPreviousGroupAction.ID, MoveEditorToPreviousGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.LeftArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.LeftArrow } }), 'View: Move Editor into Previous Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToNextGroupAction, MoveEditorToNextGroupAction.ID, MoveEditorToNextGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.RightArrow, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.RightArrow } }), 'View: Move Editor into Next Group', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToFirstGroupAction, MoveEditorToFirstGroupAction.ID, MoveEditorToFirstGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_1, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_1 } }), 'View: Move Editor into First Group', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToSecondGroupAction, MoveEditorToSecondGroupAction.ID, MoveEditorToSecondGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_2, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_2 } }), 'View: Move Editor into Second Group', category); +registry.registerWorkbenchAction(new SyncActionDescriptor(MoveEditorToThirdGroupAction, MoveEditorToThirdGroupAction.ID, MoveEditorToThirdGroupAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_3, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_3 } }), 'View: Move Editor into Third Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousGroup, FocusPreviousGroup.ID, FocusPreviousGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.LeftArrow) }), 'View: Focus Previous Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextGroup, FocusNextGroup.ID, FocusNextGroup.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.RightArrow) }), 'View: Focus Next Group', category); registry.registerWorkbenchAction(new SyncActionDescriptor(NavigateForwardAction, NavigateForwardAction.ID, NavigateForwardAction.LABEL, { primary: null, win: { primary: KeyMod.Alt | KeyCode.RightArrow }, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_MINUS }, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS } }), 'Go Forward'); @@ -413,4 +416,4 @@ if (isMacintosh) { command: { id: NavigateForwardAction.ID, title: NavigateForwardAction.LABEL, iconPath: URI.parse(require.toUrl('vs/workbench/browser/parts/editor/media/forward-tb.png')).fsPath }, group: 'navigation' }); -} \ No newline at end of file +} diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 39f0717470d..4bdfb42ef45 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -22,6 +22,7 @@ import { IEditorGroupService, GroupArrangement } from 'vs/workbench/services/gro import { ICommandService } from 'vs/platform/commands/common/commands'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { CLOSE_UNMODIFIED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; export class SplitEditorAction extends Action { @@ -531,38 +532,13 @@ export class CloseEditorAction extends Action { constructor( id: string, label: string, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @ICommandService private commandService: ICommandService ) { super(id, label, 'close-editor-action'); } public run(context?: IEditorContext): TPromise { - const position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null; - - // Close Active Editor - if (typeof position !== 'number') { - const activeEditor = this.editorService.getActiveEditor(); - if (activeEditor) { - return this.editorService.closeEditor(activeEditor.position, activeEditor.input); - } - } - - let input = context ? context.editor : null; - if (!input) { - - // Get Top Editor at Position - const visibleEditors = this.editorService.getVisibleEditors(); - if (visibleEditors[position]) { - input = visibleEditors[position].input; - } - } - - if (input) { - return this.editorService.closeEditor(position, input); - } - - return TPromise.as(false); + return this.commandService.executeCommand(CLOSE_EDITOR_COMMAND_ID, undefined, context); } } @@ -585,9 +561,14 @@ export class RevertAndCloseEditorAction extends Action { const input = activeEditor.input; const position = activeEditor.position; - return activeEditor.input.revert().then(ok => - this.editorService.closeEditor(position, input) - ); + // first try a normal revert where the contents of the editor are restored + return activeEditor.input.revert().then(() => this.editorService.closeEditor(position, input), error => { + // if that fails, since we are about to close the editor, we accept that + // the editor cannot be reverted and instead do a soft revert that just + // enables us to close the editor. With this, a user can always close a + // dirty editor even when reverting fails. + return activeEditor.input.revert({ soft: true }).then(() => this.editorService.closeEditor(position, input)); + }); } return TPromise.as(false); @@ -664,24 +645,25 @@ export class CloseAllEditorsAction extends Action { } // Otherwise ask for combined confirmation - const confirm = this.textFileService.confirmSave(); - if (confirm === ConfirmResult.CANCEL) { - return void 0; - } - - let saveOrRevertPromise: TPromise; - if (confirm === ConfirmResult.DONT_SAVE) { - saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true); - } else { - saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success)); - } - - return saveOrRevertPromise.then(success => { - if (success) { - return this.editorService.closeAllEditors(); + return this.textFileService.confirmSave().then(confirm => { + if (confirm === ConfirmResult.CANCEL) { + return void 0; } - return void 0; + let saveOrRevertPromise: TPromise; + if (confirm === ConfirmResult.DONT_SAVE) { + saveOrRevertPromise = this.textFileService.revertAll(null, { soft: true }).then(() => true); + } else { + saveOrRevertPromise = this.textFileService.saveAll(true).then(res => res.results.every(r => r.success)); + } + + return saveOrRevertPromise.then(success => { + if (success) { + return this.editorService.closeAllEditors(); + } + + return void 0; + }); }); } } @@ -694,28 +676,13 @@ export class CloseUnmodifiedEditorsInGroupAction extends Action { constructor( id: string, label: string, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @ICommandService private commandService: ICommandService ) { super(id, label); } public run(context?: IEditorContext): TPromise { - let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null; - - // If position is not passed in take the position of the active editor. - if (typeof position !== 'number') { - const active = this.editorService.getActiveEditor(); - if (active) { - position = active.position; - } - } - - if (typeof position === 'number') { - return this.editorService.closeEditors(position, { unmodifiedOnly: true }); - } - - return TPromise.as(false); + return this.commandService.executeCommand(CLOSE_UNMODIFIED_EDITORS_COMMAND_ID, undefined, context); } } @@ -758,28 +725,13 @@ export class CloseOtherEditorsInGroupAction extends Action { constructor( id: string, label: string, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @ICommandService private commandService: ICommandService, ) { super(id, label); } public run(context?: IEditorContext): TPromise { - let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null; - let input = context ? context.editor : null; - - // If position or input are not passed in take the position and input of the active editor. - const active = this.editorService.getActiveEditor(); - if (active) { - position = typeof position === 'number' ? position : active.position; - input = input ? input : active.input; - } - - if (typeof position === 'number' && input) { - return this.editorService.closeEditors(position, { except: input }); - } - - return TPromise.as(false); + return this.commandService.executeCommand(CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, undefined, context); } } @@ -791,26 +743,13 @@ export class CloseEditorsInGroupAction extends Action { constructor( id: string, label: string, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @ICommandService private commandService: ICommandService ) { super(id, label); } public run(context?: IEditorContext): TPromise { - let position = context ? this.editorGroupService.getStacksModel().positionOfGroup(context.group) : null; - if (typeof position !== 'number') { - const activeEditor = this.editorService.getActiveEditor(); - if (activeEditor) { - position = activeEditor.position; - } - } - - if (typeof position === 'number') { - return this.editorService.closeEditors(position); - } - - return TPromise.as(false); + return this.commandService.executeCommand(CLOSE_EDITORS_IN_GROUP_COMMAND_ID, undefined, context); } } @@ -1547,3 +1486,70 @@ export class MoveEditorToNextGroupAction extends Action { return TPromise.as(true); } } + +export abstract class MoveEditorToSpecificGroup extends Action { + + constructor( + id: string, + label: string, + private position: Position, + private editorGroupService: IEditorGroupService, + private editorService: IWorkbenchEditorService + ) { + super(id, label); + } + + public run(): TPromise { + const activeEditor = this.editorService.getActiveEditor(); + if (activeEditor && activeEditor.position !== this.position) { + this.editorGroupService.moveEditor(activeEditor.input, activeEditor.position, this.position); + } + + return TPromise.as(true); + } +} + +export class MoveEditorToFirstGroupAction extends MoveEditorToSpecificGroup { + + public static readonly ID = 'workbench.action.moveEditorToFirstGroup'; + public static readonly LABEL = nls.localize('moveEditorToFirstGroup', "Move Editor into First Group"); + + constructor( + id: string, + label: string, + @IEditorGroupService editorGroupService: IEditorGroupService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService + ) { + super(id, label, Position.ONE, editorGroupService, editorService); + } +} + +export class MoveEditorToSecondGroupAction extends MoveEditorToSpecificGroup { + + public static readonly ID = 'workbench.action.moveEditorToSecondGroup'; + public static readonly LABEL = nls.localize('moveEditorToSecondGroup', "Move Editor into Second Group"); + + constructor( + id: string, + label: string, + @IEditorGroupService editorGroupService: IEditorGroupService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService + ) { + super(id, label, Position.TWO, editorGroupService, editorService); + } +} + +export class MoveEditorToThirdGroupAction extends MoveEditorToSpecificGroup { + + public static readonly ID = 'workbench.action.moveEditorToThirdGroup'; + public static readonly LABEL = nls.localize('moveEditorToThirdGroup', "Move Editor into Third Group"); + + constructor( + id: string, + label: string, + @IEditorGroupService editorGroupService: IEditorGroupService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService + ) { + super(id, label, Position.THREE, editorGroupService, editorService); + } +} \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts b/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts new file mode 100644 index 00000000000..587ceeb9128 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/editorAreaDropHandler.ts @@ -0,0 +1,171 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { IDraggedResource, IDraggedEditor, extractResources } from 'vs/workbench/browser/editor'; +import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; +import { extname } from 'vs/base/common/paths'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; +import URI from 'vs/base/common/uri'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { BACKUP_FILE_RESOLVE_OPTIONS, IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { TPromise } from 'vs/base/common/winjs.base'; +import { Schemas } from 'vs/base/common/network'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { Position } from 'vs/platform/editor/common/editor'; +import { onUnexpectedError } from 'vs/base/common/errors'; + +/** + * Shared function across some editor components to handle drag & drop of external resources. E.g. of folders and workspace files + * to open them in the window instead of the editor or to handle dirty editors being dropped between instances of Code. + */ +export class EditorAreaDropHandler { + + constructor( + @IFileService private fileService: IFileService, + @IWindowsService private windowsService: IWindowsService, + @IWindowService private windowService: IWindowService, + @IWorkspacesService private workspacesService: IWorkspacesService, + @ITextFileService private textFileService: ITextFileService, + @IBackupFileService private backupFileService: IBackupFileService, + @IEditorGroupService private groupService: IEditorGroupService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + ) { + } + + public handleDrop(event: DragEvent, afterDrop: () => void, targetPosition: Position, targetIndex?: number): void { + const resources = extractResources(event).filter(r => r.resource.scheme === Schemas.file || r.resource.scheme === Schemas.untitled); + if (!resources.length) { + return; + } + + return this.doHandleDrop(resources).then(isWorkspaceOpening => { + if (isWorkspaceOpening) { + return void 0; // return early if the drop operation resulted in this window changing to a workspace + } + + // Add external ones to recently open list unless dropped resource is a workspace + const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); + if (externalResources.length) { + this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath)); + } + + // Open in Editor + return this.windowService.focusWindow() + .then(() => this.editorService.openEditors(resources.map(r => { + return { + input: { + resource: r.resource, + options: { + pinned: true, + index: targetIndex, + viewState: (r as IDraggedEditor).viewState + } + }, + position: targetPosition + }; + }))).then(() => { + + // Finish with provided function + afterDrop(); + }); + }).done(null, onUnexpectedError); + } + + private doHandleDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { + + // Check for dirty editor being dropped + if (resources.length === 1 && !resources[0].isExternal && (resources[0] as IDraggedEditor).backupResource) { + return this.handleDirtyEditorDrop(resources[0]); + } + + // Check for workspace file being dropped + if (resources.some(r => r.isExternal)) { + return this.handleWorkspaceFileDrop(resources); + } + + return TPromise.as(false); + } + + private handleDirtyEditorDrop(droppedDirtyEditor: IDraggedEditor): TPromise { + + // Untitled: always ensure that we open a new untitled for each file we drop + if (droppedDirtyEditor.resource.scheme === Schemas.untitled) { + droppedDirtyEditor.resource = this.untitledEditorService.createOrGet().getResource(); + } + + // Return early if the resource is already dirty in target or opened already + if (this.textFileService.isDirty(droppedDirtyEditor.resource) || this.groupService.getStacksModel().isOpen(droppedDirtyEditor.resource)) { + return TPromise.as(false); + } + + // Resolve the contents of the dropped dirty resource from source + return this.textFileService.resolveTextContent(droppedDirtyEditor.backupResource, BACKUP_FILE_RESOLVE_OPTIONS).then(content => { + + // Set the contents of to the resource to the target + return this.backupFileService.backupResource(droppedDirtyEditor.resource, this.backupFileService.parseBackupContent(content.value)); + }).then(() => false, () => false /* ignore any error */); + } + + private handleWorkspaceFileDrop(resources: (IDraggedResource | IDraggedEditor)[]): TPromise { + const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); + + const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = { + workspaces: [], + folders: [] + }; + + return TPromise.join(externalResources.map(resource => { + + // Check for Workspace + if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) { + externalWorkspaceResources.workspaces.push(resource); + + return void 0; + } + + // Check for Folder + return this.fileService.resolveFile(resource).then(stat => { + if (stat.isDirectory) { + externalWorkspaceResources.folders.push(stat.resource); + } + }, error => void 0); + })).then(_ => { + const { workspaces, folders } = externalWorkspaceResources; + + // Return early if no external resource is a folder or workspace + if (workspaces.length === 0 && folders.length === 0) { + return false; + } + + // Pass focus to window + this.windowService.focusWindow(); + + let workspacesToOpen: TPromise; + + // Open in separate windows if we drop workspaces or just one folder + if (workspaces.length > 0 || folders.length === 1) { + workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath)); + } + + // Multiple folders: Create new workspace with folders and open + else if (folders.length > 1) { + workspacesToOpen = this.workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]); + } + + // Open + workspacesToOpen.then(workspaces => { + this.windowsService.openWindow(workspaces, { forceReuseWindow: true }); + }); + + return true; + }); + } +} \ 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 f2dbca191ee..cee62344886 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -8,7 +8,7 @@ import * as types from 'vs/base/common/types'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible } from 'vs/workbench/common/editor'; +import { ActiveEditorMoveArguments, ActiveEditorMovePositioning, ActiveEditorMovePositioningBy, EditorCommands, TextCompareEditorVisible, IEditorContext, EditorInput } from 'vs/workbench/common/editor'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditor, Position, POSITIONS } from 'vs/platform/editor/common/editor'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; @@ -17,12 +17,20 @@ import { EditorStacksModel } from 'vs/workbench/common/editor/editorStacksModel' import { ICommandService } from 'vs/platform/commands/common/commands'; import { IMessageService, Severity, CloseAction } from 'vs/platform/message/common/message'; import { Action } from 'vs/base/common/actions'; -import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; +import { TPromise } from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; + +export const CLOSE_UNMODIFIED_EDITORS_COMMAND_ID = 'workbench.action.closeUnmodifiedEditors'; +export const CLOSE_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeEditorsInGroup'; +export const CLOSE_EDITOR_COMMAND_ID = 'workbench.action.closeActiveEditor'; +export const CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID = 'workbench.action.closeOtherEditors'; export function setup(): void { registerActiveEditorMoveCommand(); registerDiffEditorCommands(); registerOpenEditorAtIndexCommands(); + registerEditorCommands(); handleCommandDeprecations(); } @@ -268,4 +276,125 @@ function registerOpenEditorAtIndexCommands(): void { return void 0; } -} \ No newline at end of file +} + +function registerEditorCommands() { + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_UNMODIFIED_EDITORS_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_U), + handler: (accessor, resource: URI, editorContext: IEditorContext) => { + const editorGroupService = accessor.get(IEditorGroupService); + const editorService = accessor.get(IWorkbenchEditorService); + + let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null; + + // If position is not passed in take the position of the active editor. + if (typeof position !== 'number') { + const active = editorService.getActiveEditor(); + if (active) { + position = active.position; + } + } + + if (typeof position === 'number') { + return editorService.closeEditors(position, { unmodifiedOnly: true }); + } + + return TPromise.as(false); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_W), + handler: (accessor, resource: URI, editorContext: IEditorContext) => { + const editorGroupService = accessor.get(IEditorGroupService); + const editorService = accessor.get(IWorkbenchEditorService); + + let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null; + if (typeof position !== 'number') { + const activeEditor = editorService.getActiveEditor(); + if (activeEditor) { + position = activeEditor.position; + } + } + + if (typeof position === 'number') { + return editorService.closeEditors(position); + } + + return TPromise.as(false); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_EDITOR_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: KeyMod.CtrlCmd | KeyCode.KEY_W, + win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] }, + handler: (accessor, resource: URI, editorContext: IEditorContext) => { + const editorGroupService = accessor.get(IEditorGroupService); + const editorService = accessor.get(IWorkbenchEditorService); + + const position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null; + + // Close Active Editor + if (typeof position !== 'number') { + const activeEditor = editorService.getActiveEditor(); + if (activeEditor) { + return editorService.closeEditor(activeEditor.position, activeEditor.input); + } + } + + let input = editorContext ? editorContext.editor : null; + if (!input) { + + // Get Top Editor at Position + const visibleEditors = editorService.getVisibleEditors(); + if (visibleEditors[position]) { + input = visibleEditors[position].input; + } + } + + if (input) { + return editorService.closeEditor(position, input); + } + + return TPromise.as(false); + } + }); + + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: undefined, + mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_T }, + handler: (accessor, resource: URI, editorContext: IEditorContext) => { + const editorGroupService = accessor.get(IEditorGroupService); + const editorService = accessor.get(IWorkbenchEditorService); + + let position = editorContext ? editorGroupService.getStacksModel().positionOfGroup(editorContext.group) : null; + let input = editorContext ? editorContext.editor : null; + + // If position or input are not passed in take the position and input of the active editor. + const active = editorService.getActiveEditor(); + if (active) { + position = typeof position === 'number' ? position : active.position; + input = input ? input : active.input; + } + + if (typeof position === 'number' && input) { + return editorService.closeEditors(position, { except: input }); + } + + return TPromise.as(false); + } + }); +} diff --git a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts index ef5edcc636e..a6e2d0b65b7 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupsControl.ts @@ -26,20 +26,16 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { TabsTitleControl } from 'vs/workbench/browser/parts/editor/tabsTitleControl'; -import { TitleControl, ITitleAreaControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl'; +import { TitleControl, ITitleAreaControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { NoTabsTitleControl } from 'vs/workbench/browser/parts/editor/noTabsTitleControl'; import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup, EditorOptions, TextEditorOptions, IEditorIdentifier } from 'vs/workbench/common/editor'; -import { extractResources } from 'vs/workbench/browser/editor'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorBackground, contrastBorder, activeContrastBorder } from 'vs/platform/theme/common/colorRegistry'; import { Themable, EDITOR_GROUP_HEADER_TABS_BACKGROUND, EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND, EDITOR_GROUP_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, EDITOR_GROUP_BACKGROUND, EDITOR_GROUP_HEADER_TABS_BORDER } from 'vs/workbench/common/theme'; import { attachProgressBarStyler } from 'vs/platform/theme/common/styler'; -import { IMessageService } from 'vs/platform/message/common/message'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { IDisposable, combinedDisposable } from 'vs/base/common/lifecycle'; +import { EditorAreaDropHandler } from 'vs/workbench/browser/parts/editor/editorAreaDropHandler'; export enum Rochade { NONE, @@ -87,7 +83,6 @@ export interface IEditorGroupsControl { getRatio(): number[]; - dispose(): void; } @@ -149,12 +144,7 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro @IContextKeyService private contextKeyService: IContextKeyService, @IExtensionService private extensionService: IExtensionService, @IInstantiationService private instantiationService: IInstantiationService, - @IWindowService private windowService: IWindowService, - @IWindowsService private windowsService: IWindowsService, - @IThemeService themeService: IThemeService, - @IFileService private fileService: IFileService, - @IMessageService private messageService: IMessageService, - @IWorkspacesService private workspacesService: IWorkspacesService + @IThemeService themeService: IThemeService ) { super(themeService); @@ -1122,36 +1112,14 @@ export class EditorGroupsControl extends Themable implements IEditorGroupsContro // Check for URI transfer else { - const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled'); - if (droppedResources.length) { - handleWorkspaceExternalDrop(droppedResources, $this.fileService, $this.messageService, $this.windowsService, $this.windowService, $this.workspacesService).then(handled => { - if (handled) { - return; - } + const dropHandler = $this.instantiationService.createInstance(EditorAreaDropHandler); + dropHandler.handleDrop(e, () => { + if (splitEditor && splitTo !== freeGroup) { + groupService.moveGroup(freeGroup, splitTo); + } - // Add external ones to recently open list - const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource); - if (externalResources.length) { - $this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath)); - } - - // Open in Editor - $this.windowService.focusWindow() - .then(() => editorService.openEditors(droppedResources.map(d => { - return { - input: { resource: d.resource, options: { pinned: true } }, - position: splitEditor ? freeGroup : position - }; - }))).then(() => { - if (splitEditor && splitTo !== freeGroup) { - groupService.moveGroup(freeGroup, splitTo); - } - - groupService.focusGroup(splitEditor ? splitTo : position); - }) - .done(null, errors.onUnexpectedError); - }); - } + groupService.focusGroup(splitEditor ? splitTo : position); + }, splitEditor ? freeGroup : position); } } diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index d99c3e52bad..7f3b63746ec 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -41,7 +41,7 @@ import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/c import { IThemeService } from 'vs/platform/theme/common/themeService'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { EDITOR_GROUP_BACKGROUND } from 'vs/workbench/common/theme'; -import { createCSSRule, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { createCSSRule } from 'vs/base/browser/dom'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/paths'; import { IEditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; @@ -822,14 +822,20 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Switch to editor that we want to handle return this.openEditor(identifier.editor, null, this.stacks.positionOfGroup(identifier.group)).then(() => { - return this.ensureEditorOpenedBeforePrompt().then(() => { - const res = editor.confirmSave(); + return editor.confirmSave().then(res => { switch (res) { case ConfirmResult.SAVE: return editor.save().then(ok => !ok); case ConfirmResult.DONT_SAVE: - return editor.revert().then(ok => !ok); + // first try a normal revert where the contents of the editor are restored + return editor.revert().then(ok => !ok, error => { + // if that fails, since we are about to close the editor, we accept that + // the editor cannot be reverted and instead do a soft revert that just + // enables us to close the editor. With this, a user can always close a + // dirty editor even when reverting fails. + return editor.revert({ soft: true }).then(ok => !ok); + }); case ConfirmResult.CANCEL: return true; // veto @@ -838,27 +844,6 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService }); } - private ensureEditorOpenedBeforePrompt(): TPromise { - - // Force title area update - this.editorGroupsControl.updateTitleAreas(true /* refresh active group */); - - // TODO@Ben our dialogs currently use the sync API, which means they block the JS - // thread when showing. As such, any UI update will not happen unless we wait a little - // bit. We wait for 2 request animation frames before showing the confirm. The first - // frame is where the UI is updating and the second is good enough to bring up the dialog. - // See also https://github.com/Microsoft/vscode/issues/39536 - return new TPromise(c => { - scheduleAtNextAnimationFrame(() => { - // Here the UI is updating - scheduleAtNextAnimationFrame(() => { - // Here we can show a blocking dialog - c(void 0); - }); - }); - }); - } - private countEditors(editor: EditorInput): number { const editors = [editor]; if (editor instanceof SideBySideEditorInput) { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index 77917b92d2c..1d1d304fae8 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -765,7 +765,7 @@ function isWritableBaseEditor(e: IBaseEditor): boolean { export class ShowLanguageExtensionsAction extends Action { - static ID = 'workbench.action.showLanguageExtensions'; + static readonly ID = 'workbench.action.showLanguageExtensions'; constructor( private fileExtension: string, @@ -885,7 +885,7 @@ export class ChangeModeAction extends Action { picks.unshift(autoDetectMode); } - return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode") }).then(pick => { + return this.quickOpenService.pick(picks, { placeHolder: nls.localize('pickLanguage', "Select Language Mode"), matchOnDescription: true }).then(pick => { if (!pick) { return; } diff --git a/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x.svg b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x.svg new file mode 100644 index 00000000000..12a8e9f981a --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x.svg @@ -0,0 +1 @@ + diff --git a/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x_inverse.svg b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x_inverse.svg new file mode 100644 index 00000000000..d44a01472d4 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpaceChecked_16x_inverse.svg @@ -0,0 +1 @@ + diff --git a/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x.svg b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x.svg new file mode 100644 index 00000000000..05b193a5d02 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x_inverse.svg b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x_inverse.svg new file mode 100644 index 00000000000..3b904eafa79 --- /dev/null +++ b/src/vs/workbench/browser/parts/editor/media/IgnoreTrimWhiteSpace_16x_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/media/textdiffeditor.css b/src/vs/workbench/browser/parts/editor/media/textdiffeditor.css index 58ac50f9b94..86c6b735e47 100644 --- a/src/vs/workbench/browser/parts/editor/media/textdiffeditor.css +++ b/src/vs/workbench/browser/parts/editor/media/textdiffeditor.css @@ -19,4 +19,20 @@ .vs-dark .monaco-workbench .textdiff-editor-action.previous, .hc-black .monaco-workbench .textdiff-editor-action.previous { background: url('previous-diff-inverse.svg') center center no-repeat; -} \ No newline at end of file +} + +.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace { + background: url('IgnoreTrimWhiteSpace_16x.svg') center center no-repeat; +} +.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace, +.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace { + background: url('IgnoreTrimWhiteSpace_16x_inverse.svg ') center center no-repeat; +} + +.vs .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked { + background: url('IgnoreTrimWhiteSpaceChecked_16x.svg') center center no-repeat; +} +.vs-dark .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked, +.hc-black .monaco-workbench .textdiff-editor-action.toggleIgnoreTrimWhitespace.is-checked { + background: url('IgnoreTrimWhiteSpaceChecked_16x_inverse.svg ') center center no-repeat; +} diff --git a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts index e1b0e1ddd58..4d277676c43 100644 --- a/src/vs/workbench/browser/parts/editor/rangeDecorations.ts +++ b/src/vs/workbench/browser/parts/editor/rangeDecorations.ts @@ -10,7 +10,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IRange } from 'vs/editor/common/core/range'; import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; export interface IRangeHighlightDecoration { diff --git a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts index 157d3cdb771..a80ab21c3f0 100644 --- a/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts +++ b/src/vs/workbench/browser/parts/editor/tabsTitleControl.ts @@ -30,22 +30,24 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IMenuService } from 'vs/platform/actions/common/actions'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; -import { TitleControl, handleWorkspaceExternalDrop } from 'vs/workbench/browser/parts/editor/titleControl'; +import { TitleControl } from 'vs/workbench/browser/parts/editor/titleControl'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle'; import { ScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; import { ScrollbarVisibility } from 'vs/base/common/scrollable'; -import { extractResources } from 'vs/workbench/browser/editor'; +import { CodeDataTransfers, ISerializedDraggedEditor } from 'vs/workbench/browser/editor'; import { getOrSet } from 'vs/base/common/map'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; -import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER } from 'vs/workbench/common/theme'; +import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder } from 'vs/platform/theme/common/colorRegistry'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { Dimension } from 'vs/base/browser/builder'; import { scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { DataTransfers } from 'vs/base/browser/dnd'; +import { EditorAreaDropHandler } from 'vs/workbench/browser/parts/editor/editorAreaDropHandler'; +import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; interface IEditorInputLabel { name: string; @@ -78,11 +80,9 @@ export class TabsTitleControl extends TitleControl { @IMessageService messageService: IMessageService, @IMenuService menuService: IMenuService, @IQuickOpenService quickOpenService: IQuickOpenService, - @IWindowService private windowService: IWindowService, - @IWindowsService private windowsService: IWindowsService, @IThemeService themeService: IThemeService, - @IFileService private fileService: IFileService, - @IWorkspacesService private workspacesService: IWorkspacesService + @ITextFileService private textFileService: ITextFileService, + @IBackupFileService private backupFileService: IBackupFileService ) { super(contextMenuService, instantiationService, editorService, editorGroupService, contextKeyService, keybindingService, telemetryService, messageService, menuService, quickOpenService, themeService); @@ -281,7 +281,9 @@ export class TabsTitleControl extends TitleControl { const isGroupActive = this.stacks.isActive(group); if (isGroupActive) { DOM.addClass(this.titleContainer, 'active'); + DOM.removeClass(this.titleContainer, 'inactive'); } else { + DOM.addClass(this.titleContainer, 'inactive'); DOM.removeClass(this.titleContainer, 'active'); } @@ -761,12 +763,22 @@ export class TabsTitleControl extends TitleControl { const resource = toResource(editor, { supportSideBySide: true }); if (resource) { const resourceStr = resource.toString(); - e.dataTransfer.setData('URL', resourceStr); // enables cross window DND of tabs - e.dataTransfer.setData('text/plain', getPathLabel(resource)); // enables dropping tab resource path into text controls + + e.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(resource)); // enables dropping tab resource path into text controls if (resource.scheme === 'file') { - e.dataTransfer.setData('DownloadURL', [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop + e.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, editor.getName(), resourceStr].join(':')); // enables support to drag a tab as file to desktop } + + // Prepare IDraggedEditor transfer + const activeEditor = this.editorService.getActiveEditor(); + const draggedEditor: ISerializedDraggedEditor = { + resource: resourceStr, + backupResource: this.textFileService.isDirty(resource) ? this.backupFileService.toBackupResource(resource).toString() : void 0, + viewState: activeEditor instanceof BaseTextEditor ? activeEditor.getControl().saveViewState() : void 0 + }; + + e.dataTransfer.setData(CodeDataTransfers.EDITOR, JSON.stringify(draggedEditor)); // enables cross window DND of tabs into the editor area } // Fixes https://github.com/Microsoft/vscode/issues/18733 @@ -847,13 +859,14 @@ export class TabsTitleControl extends TitleControl { } private onDrop(e: DragEvent, group: IEditorGroup, targetPosition: Position, targetIndex: number): void { + DOM.EventHelper.stop(e, true); + this.updateDropFeedback(this.tabsContainer, false); DOM.removeClass(this.tabsContainer, 'scroll'); // Local DND const draggedEditor = TabsTitleControl.getDraggedEditor(); if (draggedEditor) { - DOM.EventHelper.stop(e, true); // Move editor to target position and index if (this.isMoveOperation(e, draggedEditor.group, group)) { @@ -870,37 +883,8 @@ export class TabsTitleControl extends TitleControl { // External DND else { - this.handleExternalDrop(e, targetPosition, targetIndex); - } - } - - private handleExternalDrop(e: DragEvent, targetPosition: Position, targetIndex: number): void { - const droppedResources = extractResources(e).filter(r => r.resource.scheme === 'file' || r.resource.scheme === 'untitled'); - if (droppedResources.length) { - DOM.EventHelper.stop(e, true); - - handleWorkspaceExternalDrop(droppedResources, this.fileService, this.messageService, this.windowsService, this.windowService, this.workspacesService).then(handled => { - if (handled) { - return; - } - - // Add external ones to recently open list - const externalResources = droppedResources.filter(d => d.isExternal).map(d => d.resource); - if (externalResources.length) { - this.windowsService.addRecentlyOpened(externalResources.map(resource => resource.fsPath)); - } - - // Open in Editor - this.windowService.focusWindow() - .then(() => this.editorService.openEditors(droppedResources.map(d => { - return { - input: { resource: d.resource, options: { pinned: true, index: targetIndex } }, - position: targetPosition - }; - }))).then(() => { - this.editorGroupService.focusGroup(targetPosition); - }).done(null, errors.onUnexpectedError); - }); + const dropHandler = this.instantiationService.createInstance(EditorAreaDropHandler); + dropHandler.handleDrop(e, () => this.editorGroupService.focusGroup(targetPosition), targetPosition, targetIndex); } } @@ -958,4 +942,42 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } -}); \ No newline at end of file + + // Hover Background + const tabHoverBackground = theme.getColor(TAB_HOVER_BACKGROUND); + if (tabHoverBackground) { + collector.addRule(` + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover { + background: ${tabHoverBackground} !important; + } + `); + } + + const tabUnfocusedHoverBackground = theme.getColor(TAB_UNFOCUSED_HOVER_BACKGROUND); + if (tabUnfocusedHoverBackground) { + collector.addRule(` + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover { + background: ${tabUnfocusedHoverBackground} !important; + } + `); + } + + // Hover Border + const tabHoverBorder = theme.getColor(TAB_HOVER_BORDER); + if (tabHoverBorder) { + collector.addRule(` + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.active .tabs-container > .tab:hover { + box-shadow: ${tabHoverBorder} 0 -1px inset !important; + } + `); + } + + const tabUnfocusedHoverBorder = theme.getColor(TAB_UNFOCUSED_HOVER_BORDER); + if (tabUnfocusedHoverBorder) { + collector.addRule(` + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.inactive .tabs-container > .tab:hover { + box-shadow: ${tabUnfocusedHoverBorder} 0 -1px inset !important; + } + `); + } +}); diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index d940c64917c..026b8f1b8fe 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -34,6 +34,8 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IEditorInput } from 'vs/platform/editor/common/editor'; import { ScrollType } from 'vs/editor/common/editorCommon'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IDisposable } from 'vs/base/common/lifecycle'; /** * The text editor that leverages the diff text editor for the editing experience. @@ -45,18 +47,27 @@ export class TextDiffEditor extends BaseTextEditor { private diffNavigator: DiffNavigator; private nextDiffAction: NavigateAction; private previousDiffAction: NavigateAction; + private toggleIgnoreTrimWhitespaceAction: ToggleIgnoreTrimWhitespaceAction; + private _configurationListener: IDisposable; constructor( @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IConfigurationService private readonly _actualConfigurationService: IConfigurationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, @IEditorGroupService editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { super(TextDiffEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService); + + this._configurationListener = this._actualConfigurationService.onDidChangeConfiguration((e) => { + if (e.affectsConfiguration('diffEditor.ignoreTrimWhitespace')) { + this.updateIgnoreTrimWhitespaceAction(); + } + }); } public getTitle(): string { @@ -72,6 +83,8 @@ export class TextDiffEditor extends BaseTextEditor { // Actions this.nextDiffAction = new NavigateAction(this, true); this.previousDiffAction = new NavigateAction(this, false); + this.toggleIgnoreTrimWhitespaceAction = new ToggleIgnoreTrimWhitespaceAction(this._actualConfigurationService); + this.updateIgnoreTrimWhitespaceAction(); // Support navigation within the diff editor by overriding the editor service within const delegatingEditorService = this.instantiationService.createInstance(DelegatingWorkbenchEditorService); @@ -163,6 +176,7 @@ export class TextDiffEditor extends BaseTextEditor { this.nextDiffAction.updateEnablement(); this.previousDiffAction.updateEnablement(); }); + this.updateIgnoreTrimWhitespaceAction(); }, error => { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -176,6 +190,13 @@ export class TextDiffEditor extends BaseTextEditor { }); } + private updateIgnoreTrimWhitespaceAction(): void { + const ignoreTrimWhitespace = this.configurationService.getValue(this.getResource(), 'diffEditor.ignoreTrimWhitespace'); + if (this.toggleIgnoreTrimWhitespaceAction) { + this.toggleIgnoreTrimWhitespaceAction.updateClassName(ignoreTrimWhitespace); + } + } + private openAsBinary(input: EditorInput, options: EditorOptions): boolean { if (input instanceof DiffEditorInput) { const originalInput = input.originalInput; @@ -273,6 +294,7 @@ export class TextDiffEditor extends BaseTextEditor { public getActions(): IAction[] { return [ + this.toggleIgnoreTrimWhitespaceAction, this.previousDiffAction, this.nextDiffAction ]; @@ -303,6 +325,8 @@ export class TextDiffEditor extends BaseTextEditor { this.diffNavigator.dispose(); } + this._configurationListener.dispose(); + super.dispose(); } } @@ -340,6 +364,29 @@ class NavigateAction extends Action { } } +class ToggleIgnoreTrimWhitespaceAction extends Action { + static ID = 'workbench.action.compareEditor.toggleIgnoreTrimWhitespace'; + + private _isChecked: boolean; + + constructor( + @IConfigurationService private readonly _configurationService: IConfigurationService + ) { + super(ToggleIgnoreTrimWhitespaceAction.ID); + this.label = nls.localize('toggleIgnoreTrimWhitespace.label', "Ignore Trim Whitespace"); + } + + public updateClassName(ignoreTrimWhitespace: boolean): void { + this._isChecked = ignoreTrimWhitespace; + this.class = `textdiff-editor-action toggleIgnoreTrimWhitespace${this._isChecked ? ' is-checked' : ''}`; + } + + public run(): TPromise { + this._configurationService.updateValue(`diffEditor.ignoreTrimWhitespace`, !this._isChecked); + return null; + } +} + class ToggleEditorModeAction extends Action { private static readonly ID = 'toggle.diff.editorMode'; private static readonly INLINE_LABEL = nls.localize('inlineDiffLabel', "Switch to Inline View"); diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 01f01f5761b..ac8a9d85d3d 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -28,11 +28,10 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; * An editor implementation that is capable of showing the contents of resource inputs. Uses * the TextEditor widget to show the contents. */ -export class TextResourceEditor extends BaseTextEditor { - - public static readonly ID = 'workbench.editors.textResourceEditor'; +export class AbstractTextResourceEditor extends BaseTextEditor { constructor( + id: string, @ITelemetryService telemetryService: ITelemetryService, @IInstantiationService instantiationService: IInstantiationService, @IStorageService storageService: IStorageService, @@ -41,7 +40,7 @@ export class TextResourceEditor extends BaseTextEditor { @IEditorGroupService editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService); + super(id, telemetryService, instantiationService, storageService, configurationService, themeService, textFileService, editorGroupService); } public getTitle(): string { @@ -195,4 +194,21 @@ export class TextResourceEditor extends BaseTextEditor { }); } } +} + +export class TextResourceEditor extends AbstractTextResourceEditor { + + public static readonly ID = 'workbench.editors.textResourceEditor'; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @ITextResourceConfigurationService configurationService: ITextResourceConfigurationService, + @IThemeService themeService: IThemeService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @ITextFileService textFileService: ITextFileService + ) { + super(TextResourceEditor.ID, telemetryService, instantiationService, storageService, configurationService, themeService, editorGroupService, textFileService); + } } \ No newline at end of file diff --git a/src/vs/workbench/browser/parts/editor/titleControl.ts b/src/vs/workbench/browser/parts/editor/titleControl.ts index 83aed1cf0b3..34db83138c4 100644 --- a/src/vs/workbench/browser/parts/editor/titleControl.ts +++ b/src/vs/workbench/browser/parts/editor/titleControl.ts @@ -7,8 +7,7 @@ import 'vs/css!./media/titlecontrol'; import nls = require('vs/nls'); -import { Registry } from 'vs/platform/registry/common/platform'; -import { Scope, IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actions'; +import { prepareActions } from 'vs/workbench/browser/actions'; import { IAction, Action, IRunEvent } from 'vs/base/common/actions'; import errors = require('vs/base/common/errors'); import DOM = require('vs/base/browser/dom'); @@ -37,12 +36,6 @@ import { IMenuService, MenuId, IMenu, ExecuteCommandAction } from 'vs/platform/a import { ResourceContextKey } from 'vs/workbench/common/resources'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { Themable } from 'vs/workbench/common/theme'; -import { IDraggedResource } from 'vs/workbench/browser/editor'; -import { WORKSPACE_EXTENSION, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { extname } from 'vs/base/common/paths'; -import { IFileService } from 'vs/platform/files/common/files'; -import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; -import URI from 'vs/base/common/uri'; import { isDiffEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser'; import { Dimension } from 'vs/base/browser/builder'; @@ -287,12 +280,6 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl actionItem = editor.getActionItem(action); } - // Check Registry - if (!actionItem) { - const actionBarRegistry = Registry.as(Extensions.Actionbar); - actionItem = actionBarRegistry.getActionItemForContext(Scope.EDITOR, { input: editor && editor.input, editor, position }, action); - } - // Check extensions if (!actionItem) { actionItem = createActionItem(action, this.keybindingService, this.messageService); @@ -447,7 +434,7 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl return keybinding ? keybinding.getLabel() : void 0; } - protected getContextMenuActions(identifier: IEditorIdentifier): IAction[] { + private getContextMenuActions(identifier: IEditorIdentifier): IAction[] { const { editor, group } = identifier; // Enablement @@ -460,8 +447,8 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl this.closeEditorAction, this.closeOtherEditorsAction ]; - const tabOptions = this.editorGroupService.getTabOptions(); + const tabOptions = this.editorGroupService.getTabOptions(); if (tabOptions.showTabs) { actions.push(this.closeRightEditorsAction); } @@ -500,74 +487,3 @@ export abstract class TitleControl extends Themable implements ITitleAreaControl this.editorActionsToolbar.dispose(); } } - -/** - * Shared function across some editor components to handle drag & drop of folders and workspace files - * to open them in the window instead of the editor. - */ -export function handleWorkspaceExternalDrop( - resources: IDraggedResource[], - fileService: IFileService, - messageService: IMessageService, - windowsService: IWindowsService, - windowService: IWindowService, - workspacesService: IWorkspacesService -): TPromise { - - // Return early if there are no external resources - const externalResources = resources.filter(d => d.isExternal).map(d => d.resource); - if (!externalResources.length) { - return TPromise.as(false); - } - - const externalWorkspaceResources: { workspaces: URI[], folders: URI[] } = { - workspaces: [], - folders: [] - }; - - return TPromise.join(externalResources.map(resource => { - - // Check for Workspace - if (extname(resource.fsPath) === `.${WORKSPACE_EXTENSION}`) { - externalWorkspaceResources.workspaces.push(resource); - - return void 0; - } - - // Check for Folder - return fileService.resolveFile(resource).then(stat => { - if (stat.isDirectory) { - externalWorkspaceResources.folders.push(stat.resource); - } - }, error => void 0); - })).then(_ => { - const { workspaces, folders } = externalWorkspaceResources; - - // Return early if no external resource is a folder or workspace - if (workspaces.length === 0 && folders.length === 0) { - return false; - } - - // Pass focus to window - windowService.focusWindow(); - - let workspacesToOpen: TPromise; - - // Open in separate windows if we drop workspaces or just one folder - if (workspaces.length > 0 || folders.length === 1) { - workspacesToOpen = TPromise.as([...workspaces, ...folders].map(resources => resources.fsPath)); - } - - // Multiple folders: Create new workspace with folders and open - else if (folders.length > 1) { - workspacesToOpen = workspacesService.createWorkspace(folders.map(folder => ({ uri: folder }))).then(workspace => [workspace.configPath]); - } - - // Open - workspacesToOpen.then(workspaces => { - windowsService.openWindow(workspaces, { forceReuseWindow: true }); - }); - - return true; - }); -} diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index a2c6d72ff21..a72286fe791 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -50,7 +50,8 @@ } .monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar { - line-height: 32px; + line-height: 30px; + height: 35px; } .monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:first-child { @@ -62,7 +63,8 @@ padding-left: 16px; padding-right: 16px; font-size: 11px; - padding-bottom: 4px; /* puts the bottom border down */ + padding-bottom: 3px; /* puts the bottom border down */ + padding-top: 2px; display: flex; } diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index e97e283ff42..d572d7e30b8 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -18,7 +18,7 @@ import { ActivityAction } from 'vs/workbench/browser/parts/compositebar/composit import { IActivity } from 'vs/workbench/common/activity'; export class ClosePanelAction extends Action { - static ID = 'workbench.action.closePanel'; + static readonly ID = 'workbench.action.closePanel'; static LABEL = nls.localize('closePanel', "Close Panel"); constructor( @@ -35,7 +35,7 @@ export class ClosePanelAction extends Action { } export class TogglePanelAction extends Action { - static ID = 'workbench.action.togglePanel'; + static readonly ID = 'workbench.action.togglePanel'; static LABEL = nls.localize('togglePanel', "Toggle Panel"); constructor( diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 38da57ef74a..17228613cdd 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -10,7 +10,6 @@ import Event from 'vs/base/common/event'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { Registry } from 'vs/platform/registry/common/platform'; import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Scope } from 'vs/workbench/browser/actions'; import { IPanel } from 'vs/workbench/common/panel'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { Panel, PanelRegistry, Extensions as PanelExtensions } from 'vs/workbench/browser/panel'; @@ -70,7 +69,6 @@ export class PanelPart extends CompositePart implements IPanelService { Registry.as(PanelExtensions.Panels).getDefaultPanelId(), 'panel', 'panel', - Scope.PANEL, null, id, { hasTitle: true } @@ -331,7 +329,7 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { outline-style: solid; border-bottom: none; padding-bottom: 0; - outline-offset: 3px; + outline-offset: 1px; } .monaco-workbench > .part.panel > .title > .panel-switcher-container > .monaco-action-bar .action-item:not(.checked) .action-label:hover { @@ -339,4 +337,4 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index b594ff1ea96..c5e59196388 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -857,7 +857,7 @@ export class QuickOpenController extends Component implements IQuickOpenService const result = handlerResults[i]; const resource = result.getResource(); - if (!result.isFile() || !resource || !mapEntryToResource[resource.toString()]) { + if (!result.mergeWithEditorHistory() || !resource || !mapEntryToResource[resource.toString()]) { additionalHandlerResults.push(result); } } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index bdb3dae5d54..35f15ee7ae2 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -16,7 +16,6 @@ import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IPartService, Parts, Position as SideBarPosition } from 'vs/workbench/services/part/common/partService'; import { IViewlet } from 'vs/workbench/common/viewlet'; -import { Scope } from 'vs/workbench/browser/actions'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMessageService } from 'vs/platform/message/common/message'; @@ -64,7 +63,6 @@ export class SidebarPart extends CompositePart { Registry.as(ViewletExtensions.Viewlets).getDefaultViewletId(), 'sideBar', 'viewlet', - Scope.VIEWLET, SIDE_BAR_TITLE_FOREGROUND, id, { hasTitle: true, borderWidth: () => (this.getColor(SIDE_BAR_BORDER) || this.getColor(contrastBorder)) ? 1 : 0 } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index bd5633c031b..0b7eceecfcd 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -11,7 +11,7 @@ import { Builder, $, Dimension } from 'vs/base/browser/builder'; import * as DOM from 'vs/base/browser/dom'; import * as paths from 'vs/base/common/paths'; import { Part } from 'vs/workbench/browser/part'; -import { ITitleService } from 'vs/workbench/services/title/common/titleService'; +import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; import * as errors from 'vs/base/common/errors'; @@ -19,7 +19,6 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IAction, Action } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; -import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; @@ -31,7 +30,7 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/ import { Verbosity } from 'vs/platform/editor/common/editor'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER } from 'vs/workbench/common/theme'; -import { isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh, isWindows } from 'vs/base/common/platform'; import URI from 'vs/base/common/uri'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -40,6 +39,7 @@ export class TitlebarPart extends Part implements ITitleService { public _serviceBrand: any; private static readonly NLS_UNSUPPORTED = nls.localize('patchedWindowTitle', "[Unsupported]"); + private static readonly NLS_USER_IS_ADMIN = isWindows ? nls.localize('userIsAdmin', "[Administrator]") : nls.localize('userIsSudo', "[Superuser]"); private static readonly NLS_EXTENSION_HOST = nls.localize('devExtensionWindowTitlePrefix', "[Extension Development Host]"); private static readonly TITLE_DIRTY = '\u25cf '; private static readonly TITLE_SEPARATOR = isMacintosh ? ' — ' : ' - '; // macOS uses special - separator @@ -52,7 +52,7 @@ export class TitlebarPart extends Part implements ITitleService { private isInactive: boolean; - private isPure: boolean; + private properties: ITitleProperties; private activeEditorListeners: IDisposable[]; constructor( @@ -63,7 +63,6 @@ export class TitlebarPart extends Part implements ITitleService { @IWindowsService private windowsService: IWindowsService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, - @IIntegrityService private integrityService: IIntegrityService, @IEnvironmentService private environmentService: IEnvironmentService, @IWorkspaceContextService private contextService: IWorkspaceContextService, @IThemeService themeService: IThemeService, @@ -71,7 +70,7 @@ export class TitlebarPart extends Part implements ITitleService { ) { super(id, { hasTitle: false }, themeService); - this.isPure = true; + this.properties = { isPure: true, isAdmin: false }; this.activeEditorListeners = []; this.init(); @@ -83,14 +82,6 @@ export class TitlebarPart extends Part implements ITitleService { // Initial window title when loading is done this.lifecycleService.when(LifecyclePhase.Running).then(() => this.setTitle(this.getWindowTitle())); - - // Integrity for window title - this.integrityService.isPure().then(r => { - if (!r.isPure) { - this.isPure = false; - this.setTitle(this.getWindowTitle()); - } - }); } private registerListeners(): void { @@ -149,7 +140,11 @@ export class TitlebarPart extends Part implements ITitleService { title = this.environmentService.appNameLong; } - if (!this.isPure) { + if (this.properties.isAdmin) { + title = `${title} ${TitlebarPart.NLS_USER_IS_ADMIN}`; + } + + if (!this.properties.isPure) { title = `${title} ${TitlebarPart.NLS_UNSUPPORTED}`; } @@ -161,6 +156,18 @@ export class TitlebarPart extends Part implements ITitleService { return title; } + public updateProperties(properties: ITitleProperties): void { + const isAdmin = typeof properties.isAdmin === 'boolean' ? properties.isAdmin : this.properties.isAdmin; + const isPure = typeof properties.isPure === 'boolean' ? properties.isPure : this.properties.isPure; + + if (isAdmin !== this.properties.isAdmin || isPure !== this.properties.isPure) { + this.properties.isAdmin = isAdmin; + this.properties.isPure = isPure; + + this.setTitle(this.getWindowTitle()); + } + } + /** * Possible template values: * diff --git a/src/vs/workbench/browser/parts/views/viewsRegistry.ts b/src/vs/workbench/browser/parts/views/viewsRegistry.ts index be3861f6162..aa8ccb4bdca 100644 --- a/src/vs/workbench/browser/parts/views/viewsRegistry.ts +++ b/src/vs/workbench/browser/parts/views/viewsRegistry.ts @@ -45,7 +45,7 @@ export interface IViewDescriptor { readonly order?: number; - readonly size?: number; + readonly weight?: number; readonly collapsed?: boolean; diff --git a/src/vs/workbench/browser/parts/views/viewsViewlet.ts b/src/vs/workbench/browser/parts/views/viewsViewlet.ts index cd2d2213b2d..7bc93ba609f 100644 --- a/src/vs/workbench/browser/parts/views/viewsViewlet.ts +++ b/src/vs/workbench/browser/parts/views/viewsViewlet.ts @@ -266,6 +266,7 @@ export class ViewsViewlet extends PanelViewlet { private readonly viewsContextKeys: Set = new Set(); private viewsViewletPanels: ViewsViewletPanel[] = []; private didLayout = false; + private dimension: Dimension; protected viewsStates: Map = new Map(); private areExtensionsReady: boolean = false; @@ -289,7 +290,7 @@ export class ViewsViewlet extends PanelViewlet { async create(parent: Builder): TPromise { await super.create(parent); - this._register(this.onDidSashChange(() => this.updateAllViewsSizes())); + this._register(this.onDidSashChange(() => this.snapshotViewsStates())); this._register(ViewsRegistry.onViewsRegistered(this.onViewsRegistered, this)); this._register(ViewsRegistry.onViewsDeregistered(this.onViewsDeregistered, this)); this._register(this.contextKeyService.onDidChangeContext(this.onContextChanged, this)); @@ -325,13 +326,14 @@ export class ViewsViewlet extends PanelViewlet { layout(dimension: Dimension): void { super.layout(dimension); - - if (!this.didLayout) { + this.dimension = dimension; + if (this.didLayout) { + this.snapshotViewsStates(); + } else { this.didLayout = true; - this._resizePanels(); + this.resizePanels(); } - this.updateAllViewsSizes(); } getOptimalWidth(): number { @@ -349,22 +351,15 @@ export class ViewsViewlet extends PanelViewlet { const view = this.getView(id); let viewState = this.viewsStates.get(id); - if ((visible === true && view) || (visible === false && !view)) { + if (!viewState || (visible === true && view) || (visible === false && !view)) { return; } - if (view) { - viewState = viewState || this.createViewState(view); - viewState.isHidden = true; - } else { - viewState = viewState || { collapsed: true, size: void 0, isHidden: false, order: void 0 }; - viewState.isHidden = false; - } - this.viewsStates.set(id, viewState); + viewState.isHidden = !!view; this.updateViews(); } - private onViewsRegistered(views: IViewDescriptor[]): TPromise { + private onViewsRegistered(views: IViewDescriptor[]): void { this.viewsContextKeys.clear(); for (const viewDescriptor of this.getViewDescriptorsFromRegistry()) { if (viewDescriptor.when) { @@ -374,11 +369,11 @@ export class ViewsViewlet extends PanelViewlet { } } - return this.updateViews(); + this.updateViews(); } - private onViewsDeregistered(views: IViewDescriptor[]): TPromise { - return this.updateViews(views); + private onViewsDeregistered(views: IViewDescriptor[]): void { + this.updateViews(views); } private onContextChanged(event: IContextKeyChangeEvent): void { @@ -414,19 +409,12 @@ export class ViewsViewlet extends PanelViewlet { const toCreate: ViewsViewletPanel[] = []; if (toAdd.length || toRemove.length) { - const panels = [...this.viewsViewletPanels]; - for (const view of panels) { - let viewState = this.viewsStates.get(view.id); - if (!viewState || typeof viewState.size === 'undefined' || !view.isExpanded() !== viewState.collapsed) { - this.updateViewStateSize(view); - } - } + this.snapshotViewsStates(); if (toRemove.length) { for (const viewDescriptor of toRemove) { let view = this.getView(viewDescriptor.id); - this.updateViewStateSize(view); this.removePanel(view); this.viewsViewletPanels.splice(this.viewsViewletPanels.indexOf(view), 1); } @@ -445,38 +433,58 @@ export class ViewsViewlet extends PanelViewlet { }); toCreate.push(view); - const size = (viewState && viewState.size) || viewDescriptor.size || 200; + const size = (viewState && viewState.size) || 200; this.addPanel(view, size, index); this.viewsViewletPanels.splice(index, 0, view); - - this.updateViewStateSize(view); } return TPromise.join(toCreate.map(view => view.create())) .then(() => this.onViewsUpdated()) - .then(() => this._resizePanels()) - .then(() => toCreate); + .then(() => { + this.resizePanels(toCreate); + return toCreate; + }); } return TPromise.as([]); } - private updateAllViewsSizes(): void { - for (const view of this.viewsViewletPanels) { - this.updateViewStateSize(view); - } - } - - private _resizePanels(): void { + private resizePanels(panels: ViewsViewletPanel[] = this.viewsViewletPanels): void { if (!this.didLayout) { + // Do not do anything if layout has not happened yet return; } - for (const panel of this.viewsViewletPanels) { + let initialSizes; + for (const panel of panels) { const viewState = this.viewsStates.get(panel.id); - const size = (viewState && viewState.size) || 200; - this.resizePanel(panel, size); + if (viewState && viewState.size) { + this.resizePanel(panel, viewState.size); + } else { + initialSizes = initialSizes ? initialSizes : this.computeInitialSizes(); + this.resizePanel(panel, initialSizes[panel.id] || 200); + } } + + this.snapshotViewsStates(); + } + + private computeInitialSizes(): { [id: string]: number } { + let sizes = {}; + if (this.dimension) { + let totalWeight = 0; + const allViewDescriptors = this.getViewDescriptorsFromRegistry(); + const viewDescriptors: IViewDescriptor[] = []; + for (const panel of this.viewsViewletPanels) { + const viewDescriptor = allViewDescriptors.filter(viewDescriptor => viewDescriptor.id === panel.id)[0]; + totalWeight = totalWeight + (viewDescriptor.weight || 20); + viewDescriptors.push(viewDescriptor); + } + for (const viewDescriptor of viewDescriptors) { + sizes[viewDescriptor.id] = this.dimension.height * (viewDescriptor.weight || 20) / totalWeight; + } + } + return sizes; } movePanel(from: ViewletPanel, to: ViewletPanel): void { @@ -572,16 +580,22 @@ export class ViewsViewlet extends PanelViewlet { if (this.length > 1) { return false; } - // Check in cache so that view do not jump. See #29609 - if (ViewLocation.getContributedViewLocation(this.location.id) && !this.areExtensionsReady) { + + if (ViewLocation.getContributedViewLocation(this.location.id)) { let visibleViewsCount = 0; - this.viewsStates.forEach((viewState, id) => { - if (!viewState.isHidden) { - visibleViewsCount++; - } - }); + if (this.areExtensionsReady) { + visibleViewsCount = this.getViewDescriptorsFromRegistry().reduce((visibleViewsCount, v) => visibleViewsCount + (this.canBeVisible(v) ? 1 : 0), 0); + } else { + // Check in cache so that view do not jump. See #29609 + this.viewsStates.forEach((viewState, id) => { + if (!viewState.isHidden) { + visibleViewsCount++; + } + }); + } return visibleViewsCount === 1; } + return super.isSingleView(); } @@ -616,25 +630,30 @@ export class ViewsViewlet extends PanelViewlet { return this.viewsViewletPanels.filter(view => view.id === id)[0]; } - private updateViewStateSize(view: ViewsViewletPanel): void { - const currentState = this.viewsStates.get(view.id); - if (currentState && !this.didLayout) { - // Do not update to new state if the layout has not happened yet - return; + private snapshotViewsStates(): void { + for (const view of this.viewsViewletPanels) { + const currentState = this.viewsStates.get(view.id); + if (currentState && !this.didLayout) { + // Do not update to new state if the layout has not happened yet + return; + } + + const collapsed = !view.isExpanded(); + const order = this.viewsViewletPanels.indexOf(view); + const panelSize = this.getPanelSize(view); + if (currentState) { + currentState.collapsed = collapsed; + currentState.size = collapsed ? currentState.size : panelSize; + currentState.order = order; + } else { + this.viewsStates.set(view.id, { + collapsed, + size: this.didLayout ? panelSize : void 0, + isHidden: false, + order, + }); + } } - - const newViewState = this.createViewState(view); - const stateToUpdate = currentState ? { ...currentState, collapsed: newViewState.collapsed, size: newViewState.size } : newViewState; - this.viewsStates.set(view.id, stateToUpdate); - } - - protected createViewState(view: ViewsViewletPanel): IViewState { - return { - collapsed: !view.isExpanded(), - size: this.getPanelSize(view), - isHidden: false, - order: this.viewsViewletPanels.indexOf(view) - }; } } @@ -674,7 +693,12 @@ export class PersistentViewsViewlet extends ViewsViewlet { const view = this.getView(id); if (view) { - viewsStates[id] = this.createViewState(view); + viewsStates[id] = { + collapsed: !view.isExpanded(), + size: this.getPanelSize(view), + isHidden: false, + order: viewState.order + }; } else { const viewDescriptor = registeredViewDescriptors.filter(v => v.id === id)[0]; if (viewDescriptor) { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 2a1bbf27e88..f3748bf3300 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -11,7 +11,7 @@ import types = require('vs/base/common/types'); import URI from 'vs/base/common/uri'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle'; import { IEditor, IEditorViewState, IModel, ScrollType } from 'vs/editor/common/editorCommon'; -import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity, IEditor as IBaseEditor } from 'vs/platform/editor/common/editor'; +import { IEditorInput, IEditorModel, IEditorOptions, ITextEditorOptions, IBaseResourceInput, Position, Verbosity, IEditor as IBaseEditor, IRevertOptions } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature0 } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -200,8 +200,8 @@ export abstract class EditorInput implements IEditorInput { /** * Subclasses should bring up a proper dialog for the user if the editor is dirty and return the result. */ - public confirmSave(): ConfirmResult { - return ConfirmResult.DONT_SAVE; + public confirmSave(): TPromise { + return TPromise.wrap(ConfirmResult.DONT_SAVE); } /** @@ -214,7 +214,7 @@ export abstract class EditorInput implements IEditorInput { /** * Reverts the editor if it is dirty. Subclasses return a promise with a boolean indicating the success of the operation. */ - public revert(): TPromise { + public revert(options?: IRevertOptions): TPromise { return TPromise.as(true); } @@ -372,7 +372,7 @@ export class SideBySideEditorInput extends EditorInput { return this.master.isDirty(); } - public confirmSave(): ConfirmResult { + public confirmSave(): TPromise { return this.master.confirmSave(); } @@ -945,4 +945,4 @@ export const Extensions = { EditorInputFactories: 'workbench.contributions.editor.inputFactories' }; -Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); \ No newline at end of file +Registry.add(Extensions.EditorInputFactories, new EditorInputFactoryRegistry()); diff --git a/src/vs/workbench/common/editor/resourceEditorInput.ts b/src/vs/workbench/common/editor/resourceEditorInput.ts index 4f9a57314ec..850154f02bc 100644 --- a/src/vs/workbench/common/editor/resourceEditorInput.ts +++ b/src/vs/workbench/common/editor/resourceEditorInput.ts @@ -93,7 +93,8 @@ export class ResourceEditorInput extends EditorInput { if (!(model instanceof ResourceEditorModel)) { ref.dispose(); this.modelReference = null; - return TPromise.wrapError(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); // TODO@Ben eventually also files should be supported, but we guard due to the dangerous dispose of the model in dispose() + + return TPromise.wrapError(new Error(`Unexpected model for ResourceInput: ${this.resource}`)); } return model; diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index 45092f33e98..78c322eb9fe 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -180,7 +180,7 @@ export class UntitledEditorInput extends EditorInput implements IEncodingSupport return this.hasAssociatedFilePath; } - public confirmSave(): ConfirmResult { + public confirmSave(): TPromise { return this.textFileService.confirmSave([this.resource]); } diff --git a/src/vs/workbench/common/resources.ts b/src/vs/workbench/common/resources.ts index 2446c202c76..b4f626742fe 100644 --- a/src/vs/workbench/common/resources.ts +++ b/src/vs/workbench/common/resources.ts @@ -10,6 +10,7 @@ import paths = require('vs/base/common/paths'); import { basename } 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 { @@ -18,22 +19,29 @@ export class ResourceContextKey implements IContextKey { static LangId = new RawContextKey('resourceLangId', undefined); 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; private _filenameKey: IContextKey; private _langIdKey: IContextKey; private _extensionKey: IContextKey; + private _hasResource: IContextKey; + private _isFile: IContextKey; constructor( @IContextKeyService contextKeyService: IContextKeyService, - @IModeService private _modeService: IModeService + @IModeService private _modeService: IModeService, + @IFileService private _fileService: IFileService ) { this._schemeKey = ResourceContextKey.Scheme.bindTo(contextKeyService); this._filenameKey = ResourceContextKey.Filename.bindTo(contextKeyService); this._langIdKey = ResourceContextKey.LangId.bindTo(contextKeyService); 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) { @@ -42,6 +50,8 @@ export class ResourceContextKey implements IContextKey { this._filenameKey.set(value && basename(value.fsPath)); 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 { @@ -50,6 +60,8 @@ export class ResourceContextKey implements IContextKey { this._resourceKey.reset(); this._langIdKey.reset(); this._extensionKey.reset(); + this._hasResource.reset(); + this._isFile.reset(); } public get(): URI { diff --git a/src/vs/workbench/common/theme.ts b/src/vs/workbench/common/theme.ts index 77887fd950d..480ecc85f0b 100644 --- a/src/vs/workbench/common/theme.ts +++ b/src/vs/workbench/common/theme.ts @@ -23,6 +23,18 @@ export const TAB_INACTIVE_BACKGROUND = registerColor('tab.inactiveBackground', { hc: null }, nls.localize('tabInactiveBackground', "Inactive tab background color. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_HOVER_BACKGROUND = registerColor('tab.hoverBackground', { + dark: null, + light: null, + hc: null +}, nls.localize('tabHoverBackground', "Tab background color when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_HOVER_BACKGROUND = registerColor('tab.unfocusedHoverBackground', { + dark: transparent(TAB_HOVER_BACKGROUND, 0.5), + light: transparent(TAB_HOVER_BACKGROUND, 0.7), + hc: null +}, nls.localize('tabUnfocusedHoverBackground', "Tab background color in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_BORDER = registerColor('tab.border', { dark: '#252526', light: '#F3F3F3', @@ -41,6 +53,18 @@ export const TAB_UNFOCUSED_ACTIVE_BORDER = registerColor('tab.unfocusedActiveBor hc: null }, nls.localize('tabActiveUnfocusedBorder', "Border to highlight active tabs in an unfocused group. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); +export const TAB_HOVER_BORDER = registerColor('tab.hoverBorder', { + dark: null, + light: null, + hc: null +}, nls.localize('tabHoverBorder', "Border to highlight tabs when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + +export const TAB_UNFOCUSED_HOVER_BORDER = registerColor('tab.unfocusedHoverBorder', { + dark: transparent(TAB_HOVER_BORDER, 0.5), + light: transparent(TAB_HOVER_BORDER, 0.7), + hc: null +}, nls.localize('tabUnfocusedHoverBorder', "Border to highlight tabs in an unfocused group when hovering. Tabs are the containers for editors in the editor area. Multiple tabs can be opened in one editor group. There can be multiple editor groups.")); + export const TAB_ACTIVE_FOREGROUND = registerColor('tab.activeForeground', { dark: Color.white, light: '#333333', @@ -90,7 +114,7 @@ export const EDITOR_GROUP_HEADER_NO_TABS_BACKGROUND = registerColor('editorGroup dark: editorBackground, light: editorBackground, hc: editorBackground -}, nls.localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled. Editor groups are the containers of editors.")); +}, nls.localize('editorGroupHeaderBackground', "Background color of the editor group title header when tabs are disabled (`\"workbench.editor.showTabs\": false`). Editor groups are the containers of editors.")); export const EDITOR_GROUP_BORDER = registerColor('editorGroup.border', { dark: '#444444', @@ -199,13 +223,13 @@ export const STATUS_BAR_PROMINENT_ITEM_BACKGROUND = registerColor('statusBarItem dark: '#388A34', light: '#388A34', hc: '#3883A4' -}, nls.localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); +}, nls.localize('statusBarProminentItemBackground', "Status bar prominent items background color. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); export const STATUS_BAR_PROMINENT_ITEM_HOVER_BACKGROUND = registerColor('statusBarItem.prominentHoverBackground', { dark: '#369432', light: '#369432', hc: '#369432' -}, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. The status bar is shown in the bottom of the window.")); +}, nls.localize('statusBarProminentItemHoverBackground', "Status bar prominent items background color when hovering. Prominent items stand out from other status bar entries to indicate importance. Change mode `Toggle Tab Key Moves Focus` from command palette to see an example. The status bar is shown in the bottom of the window.")); diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index 3b49a85201f..43d12cb7058 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -25,7 +25,7 @@ import { IExtensionManagementService, LocalExtensionType, ILocalExtension, IExte import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import paths = require('vs/base/common/paths'); import { isMacintosh, isLinux, language } from 'vs/base/common/platform'; -import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickOpenService, IFilePickOpenEntry, ISeparator, IPickOpenAction, IPickOpenItem } from 'vs/platform/quickOpen/common/quickOpen'; import * as browser from 'vs/base/browser/browser'; import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; import { IEntryRunContext } from 'vs/base/parts/quickopen/common/quickOpen'; @@ -46,33 +46,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IExtensionService, ActivationTimes } from 'vs/platform/extensions/common/extensions'; import { getEntries } from 'vs/base/common/performance'; import { IEditor } from 'vs/platform/editor/common/editor'; -import { ILogService, LogLevel } from 'vs/platform/log/common/log'; // --- actions -export class CloseEditorAction extends Action { - - public static readonly ID = 'workbench.action.closeActiveEditor'; - public static readonly LABEL = nls.localize('closeActiveEditor', "Close Editor"); - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(id, label); - } - - public run(): TPromise { - const activeEditor = this.editorService.getActiveEditor(); - if (activeEditor) { - return this.editorService.closeEditor(activeEditor.position, activeEditor.input); - } - - return TPromise.as(null); - } -} - export class CloseCurrentWindowAction extends Action { public static readonly ID = 'workbench.action.closeWindow'; @@ -91,7 +67,7 @@ export class CloseCurrentWindowAction extends Action { export class CloseWorkspaceAction extends Action { - static ID = 'workbench.action.closeFolder'; + static readonly ID = 'workbench.action.closeFolder'; static LABEL = nls.localize('closeWorkspace', "Close Workspace"); constructor( @@ -117,7 +93,7 @@ export class CloseWorkspaceAction extends Action { export class NewWindowAction extends Action { - static ID = 'workbench.action.newWindow'; + static readonly ID = 'workbench.action.newWindow'; static LABEL = nls.localize('newWindow', "New Window"); constructor( @@ -135,7 +111,7 @@ export class NewWindowAction extends Action { export class ToggleFullScreenAction extends Action { - static ID = 'workbench.action.toggleFullScreen'; + static readonly ID = 'workbench.action.toggleFullScreen'; static LABEL = nls.localize('toggleFullScreen', "Toggle Full Screen"); constructor(id: string, label: string, @IWindowService private windowService: IWindowService) { @@ -149,7 +125,7 @@ export class ToggleFullScreenAction extends Action { export class ToggleMenuBarAction extends Action { - static ID = 'workbench.action.toggleMenuBar'; + static readonly ID = 'workbench.action.toggleMenuBar'; static LABEL = nls.localize('toggleMenuBar', "Toggle Menu Bar"); private static readonly menuBarVisibilityKey = 'window.menuBarVisibility'; @@ -183,7 +159,7 @@ export class ToggleMenuBarAction extends Action { export class ToggleDevToolsAction extends Action { - static ID = 'workbench.action.toggleDevTools'; + static readonly ID = 'workbench.action.toggleDevTools'; static LABEL = nls.localize('toggleDevTools', "Toggle Developer Tools"); constructor(id: string, label: string, @IWindowService private windowsService: IWindowService) { @@ -558,7 +534,7 @@ export class ShowStartupPerformance extends Action { export class ReloadWindowAction extends Action { - static ID = 'workbench.action.reloadWindow'; + static readonly ID = 'workbench.action.reloadWindow'; static LABEL = nls.localize('reloadWindow', "Reload Window"); constructor( @@ -654,7 +630,7 @@ class CloseWindowAction extends Action implements IPickOpenAction { export class SwitchWindow extends BaseSwitchWindow { - static ID = 'workbench.action.switchWindow'; + static readonly ID = 'workbench.action.switchWindow'; static LABEL = nls.localize('switchWindow', "Switch Window..."); constructor( @@ -676,7 +652,7 @@ export class SwitchWindow extends BaseSwitchWindow { export class QuickSwitchWindow extends BaseSwitchWindow { - static ID = 'workbench.action.quickSwitchWindow'; + static readonly ID = 'workbench.action.quickSwitchWindow'; static LABEL = nls.localize('quickSwitchWindow', "Quick Switch Window..."); constructor( @@ -999,13 +975,13 @@ export class ReportPerformanceIssueAction extends Action { } public run(appendix?: string): TPromise { - return this.integrityService.isPure().then(res => { + this.integrityService.isPure().then(res => { const issueUrl = this.generatePerformanceIssueUrl(product.reportIssueUrl, pkg.name, pkg.version, product.commit, product.date, res.isPure, appendix); window.open(issueUrl); - - return TPromise.as(true); }); + + return TPromise.wrap(true); } private generatePerformanceIssueUrl(baseUrl: string, name: string, version: string, commit: string, date: string, isPure: boolean, appendix?: string): string { @@ -1189,7 +1165,7 @@ export class OpenTipsAndTricksUrlAction extends Action { export class ToggleSharedProcessAction extends Action { - static ID = 'workbench.action.toggleSharedProcess'; + static readonly ID = 'workbench.action.toggleSharedProcess'; static LABEL = nls.localize('toggleSharedProcess', "Toggle Shared Process"); constructor(id: string, label: string, @IWindowsService private windowsService: IWindowsService) { @@ -1684,81 +1660,3 @@ export class ConfigureLocaleAction extends Action { }); } } - -export class OpenLogsFolderAction extends Action { - - static ID = 'workbench.action.openLogsFolder'; - static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); - - constructor(id: string, label: string, - @IEnvironmentService private environmentService: IEnvironmentService, - @IWindowsService private windowsService: IWindowsService, - ) { - super(id, label); - } - - run(): TPromise { - return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log')); - } -} - -export class ShowLogsAction extends Action { - - static ID = 'workbench.action.showLogs'; - static LABEL = nls.localize('showLogs', "Show Logs..."); - - constructor(id: string, label: string, - @IEnvironmentService private environmentService: IEnvironmentService, - @IWindowService private windowService: IWindowService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IQuickOpenService private quickOpenService: IQuickOpenService - ) { - super(id, label); - } - - run(): TPromise { - const entries: IPickOpenEntry[] = [ - { id: 'main', label: nls.localize('mainProcess', "Main"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, 'main.log')) }) }, - { id: 'shared', label: nls.localize('sharedProcess', "Shared"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, 'sharedprocess.log')) }) }, - { id: 'renderer', label: nls.localize('rendererProcess', "Renderer"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)) }) }, - { id: 'extenshionHost', label: nls.localize('extensionHost', "Extension Host"), run: () => this.editorService.openEditor({ resource: URI.file(paths.join(this.environmentService.logsPath, `exthost${this.windowService.getCurrentWindowId()}.log`)) }) } - ]; - - return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select process") }).then(entry => { - if (entry) { - entry.run(null); - } - }); - } -} - -export class SetLogLevelAction extends Action { - - static ID = 'workbench.action.setLogLevel'; - static LABEL = nls.localize('setLogLevel', "Set Log Level"); - - constructor(id: string, label: string, - @IQuickOpenService private quickOpenService: IQuickOpenService, - @ILogService private logService: ILogService - ) { - super(id, label); - } - - run(): TPromise { - const entries = [ - { label: nls.localize('trace', "Trace"), level: LogLevel.Trace }, - { label: nls.localize('debug', "Debug"), level: LogLevel.Debug }, - { label: nls.localize('info', "Info"), level: LogLevel.Info }, - { label: nls.localize('warn', "Warning"), level: LogLevel.Warning }, - { label: nls.localize('err', "Error"), level: LogLevel.Error }, - { label: nls.localize('critical', "Critical"), level: LogLevel.Critical }, - { label: nls.localize('off', "Off"), level: LogLevel.Off } - ]; - - return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => { - if (entry) { - this.logService.setLevel(entry.level); - } - }); - } -} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/bootstrap/index.html b/src/vs/workbench/electron-browser/bootstrap/index.html index c14ebbe8653..b0d29bb22fa 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.html +++ b/src/vs/workbench/electron-browser/bootstrap/index.html @@ -3,7 +3,7 @@ - + diff --git a/src/vs/workbench/electron-browser/bootstrap/index.js b/src/vs/workbench/electron-browser/bootstrap/index.js index 4fcb345a224..867ed81842e 100644 --- a/src/vs/workbench/electron-browser/bootstrap/index.js +++ b/src/vs/workbench/electron-browser/bootstrap/index.js @@ -171,23 +171,20 @@ function main() { } // Perf Counters - const timers = window.MonacoEnvironment.timers = { + window.MonacoEnvironment.timers = { isInitialStartup: !!configuration.isInitialStartup, hasAccessibilitySupport: !!configuration.accessibilitySupport, start: configuration.perfStartTime, - appReady: configuration.perfAppReady, - windowLoad: configuration.perfWindowLoadTime, - beforeLoadWorkbenchMain: Date.now() + windowLoad: configuration.perfWindowLoadTime }; - const workbenchMainClock = perf.time('loadWorkbenchMain'); + perf.mark('willLoadWorkbenchMain'); require([ 'vs/workbench/workbench.main', 'vs/nls!vs/workbench/workbench.main', 'vs/css!vs/workbench/workbench.main' ], function () { - workbenchMainClock.stop(); - timers.afterLoadWorkbenchMain = Date.now(); + perf.mark('didLoadWorkbenchMain'); process.lazyEnv.then(function () { perf.mark('main/startup'); diff --git a/src/vs/workbench/electron-browser/commands.ts b/src/vs/workbench/electron-browser/commands.ts index 3b8202afec7..ca2c37e005e 100644 --- a/src/vs/workbench/electron-browser/commands.ts +++ b/src/vs/workbench/electron-browser/commands.ts @@ -18,9 +18,10 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import URI from 'vs/base/common/uri'; import { IEditorOptions, Position as EditorPosition } from 'vs/platform/editor/common/editor'; -import { openFolderCommand, openFileInNewWindowCommand, openFileFolderInNewWindowCommand, openFolderInNewWindowCommand, openWorkspaceInNewWindowCommand } from 'vs/workbench/browser/actions/workspaceActions'; -import { WorkbenchListFocusContextKey, IListService } from 'vs/platform/list/browser/listService'; +import { WorkbenchListFocusContextKey, IListService, WorkbenchListSupportsMultiSelectContextKey } from 'vs/platform/list/browser/listService'; import { PagedList } from 'vs/base/browser/ui/list/listPaging'; +import { range } from 'vs/base/common/arrays'; +import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; // --- List Commands @@ -300,6 +301,22 @@ export function registerCommands(): void { } }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: 'list.selectAll', + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ContextKeyExpr.and(WorkbenchListFocusContextKey, WorkbenchListSupportsMultiSelectContextKey), + primary: KeyMod.CtrlCmd | KeyCode.KEY_A, + handler: (accessor) => { + const focused = accessor.get(IListService).lastFocusedList; + + // List + if (focused instanceof List || focused instanceof PagedList) { + const list = focused; + list.setSelection(range(list.length)); + } + } + }); + KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'list.toggleExpand', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), @@ -410,11 +427,4 @@ export function registerCommands(): void { return void 0; }); }); - - CommandsRegistry.registerCommand('_files.pickFolderAndOpen', openFolderCommand); - - CommandsRegistry.registerCommand('workbench.action.files.openFileInNewWindow', openFileInNewWindowCommand); - CommandsRegistry.registerCommand('workbench.action.files.openFolderInNewWindow', openFolderInNewWindowCommand); - CommandsRegistry.registerCommand('workbench.action.files.openFileFolderInNewWindow', openFileFolderInNewWindowCommand); - CommandsRegistry.registerCommand('workbench.action.openWorkspaceInNewWindow', openWorkspaceInNewWindowCommand); } diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 24565c1ea2d..d55efeac8fc 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 } from 'v 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 { CloseEditorAction, KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, ReportIssueAction, ReportPerformanceIssueAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ConfigureLocaleAction, ShowLogsAction, OpenLogsFolderAction, SetLogLevelAction } from 'vs/workbench/electron-browser/actions'; +import { KeybindingsReferenceAction, OpenDocumentationUrlAction, OpenIntroductoryVideosUrlAction, OpenTipsAndTricksUrlAction, ReportIssueAction, ReportPerformanceIssueAction, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleFullScreenAction, ToggleMenuBarAction, CloseWorkspaceAction, CloseCurrentWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction, NavigateUpAction, NavigateDownAction, NavigateLeftAction, NavigateRightAction, IncreaseViewSizeAction, DecreaseViewSizeAction, ShowStartupPerformance, ToggleSharedProcessAction, QuickSwitchWindow, QuickOpenRecentAction, inRecentFilesPickerContextKey, ConfigureLocaleAction } from 'vs/workbench/electron-browser/actions'; import { MessagesVisibleContext } from 'vs/workbench/electron-browser/workbench'; import { IJSONSchema } from 'vs/base/common/jsonSchema'; import { registerCommands } from 'vs/workbench/electron-browser/commands'; @@ -31,15 +31,12 @@ registerCommands(); const viewCategory = nls.localize('view', "View"); const helpCategory = nls.localize('help', "Help"); const fileCategory = nls.localize('file', "File"); -const devCategory = nls.localize('developer', "Developer"); const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseCurrentWindowAction, CloseCurrentWindowAction.ID, CloseCurrentWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL, { primary: null, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_W } }), 'Switch Window...'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickSwitchWindow, QuickSwitchWindow.ID, QuickSwitchWindow.LABEL), 'Quick Switch Window...'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsAction, ShowLogsAction.ID, ShowLogsAction.LABEL), 'Developer: Show Logs...', devCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Log Folder', devCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level', devCategory); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenRecentAction, QuickOpenRecentAction.ID, QuickOpenRecentAction.LABEL), 'File: Quick Open Recent...', fileCategory); @@ -85,7 +82,6 @@ workbenchActionsRegistry.registerWorkbenchAction( }), 'View: Reset Zoom', viewCategory ); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseMessagesAction, CloseMessagesAction.ID, CloseMessagesAction.LABEL, { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape] }, MessagesVisibleContext), 'Close Notification Messages'); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseEditorAction, CloseEditorAction.ID, CloseEditorAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_W, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] } }), 'View: Close Editor', viewCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleFullScreenAction, ToggleFullScreenAction.ID, ToggleFullScreenAction.LABEL, { primary: KeyCode.F11, mac: { primary: KeyMod.CtrlCmd | KeyMod.WinCtrl | KeyCode.KEY_F } }), 'View: Toggle Full Screen', viewCategory); if (isWindows || isLinux) { workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleMenuBarAction, ToggleMenuBarAction.ID, ToggleMenuBarAction.LABEL), 'View: Toggle Menu Bar', viewCategory); @@ -136,142 +132,141 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // Configuration: Workbench const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); -let workbenchProperties: { [path: string]: IJSONSchema; } = { - 'workbench.editor.showTabs': { - 'type': 'boolean', - 'description': nls.localize('showEditorTabs', "Controls if opened editors should show in tabs or not."), - 'default': true - }, - 'workbench.editor.labelFormat': { - 'type': 'string', - 'enum': ['default', 'short', 'medium', 'long'], - 'enumDescriptions': [ - nls.localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguinshing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), - nls.localize('workbench.editor.labelFormat.short', "Show the name of the file followed by it's directory name."), - nls.localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by it's path relative to the workspace folder."), - 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"), - }, - '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'.") - }, - '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.") - }, - '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."), - '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."), - '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)."), - 'default': true - }, - '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.") - }, - '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."), - 'default': false - }, - 'workbench.commandPalette.history': { - 'type': 'number', - 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), - 'default': 50 - }, - '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."), - 'default': false - }, - 'workbench.quickOpen.closeOnFocusLost': { - 'type': 'boolean', - 'description': nls.localize('closeOnFocusLost', "Controls if 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."), - 'default': true - }, - 'workbench.sideBar.location': { - 'type': 'string', - 'enum': ['left', 'right'], - 'default': 'left', - 'description': nls.localize('sideBarLocation', "Controls the location of the sidebar. It can either show on the left or right of the workbench.") - }, - 'workbench.statusBar.visible': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") - }, - 'workbench.activityBar.visible': { - 'type': 'boolean', - 'default': true, - 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") - }, - 'workbench.editor.closeOnFileDelete': { - 'type': 'boolean', - 'description': nls.localize('closeOnFileDelete', "Controls if 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 - } -}; - -if (product.quality !== 'stable') { - workbenchProperties['workbench.settings.enableNaturalLanguageSearch'] = { - 'type': 'boolean', - 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings."), - 'default': true - }; -} - -if (isMacintosh) { - workbenchProperties['workbench.fontAliasing'] = { - 'type': 'string', - 'enum': ['default', 'antialiased', 'none'], - '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"), - '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."), - nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges.") - ], - }; - - workbenchProperties['workbench.editor.swipeToNavigate'] = { - 'type': 'boolean', - 'description': nls.localize('swipeToNavigate', "Navigate between open files using three-finger swipe horizontally."), - 'default': false - }; -} - - configurationRegistry.registerConfiguration({ 'id': 'workbench', 'order': 7, 'title': nls.localize('workbenchConfigurationTitle', "Workbench"), 'type': 'object', - 'properties': workbenchProperties + 'properties': { + 'workbench.editor.showTabs': { + 'type': 'boolean', + 'description': nls.localize('showEditorTabs', "Controls if opened editors should show in tabs or not."), + 'default': true + }, + 'workbench.editor.labelFormat': { + 'type': 'string', + 'enum': ['default', 'short', 'medium', 'long'], + 'enumDescriptions': [ + nls.localize('workbench.editor.labelFormat.default', "Show the name of the file. When tabs are enabled and two files have the same name in one group the distinguinshing sections of each file's path are added. When tabs are disabled, the path relative to the workspace folder is shown if the editor is active."), + nls.localize('workbench.editor.labelFormat.short', "Show the name of the file followed by it's directory name."), + nls.localize('workbench.editor.labelFormat.medium', "Show the name of the file followed by it's path relative to the workspace folder."), + 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"), + }, + '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'.") + }, + '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.") + }, + '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."), + '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."), + '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)."), + 'default': true + }, + 'workbench.editor.closeOnFileDelete': { + 'type': 'boolean', + 'description': nls.localize('closeOnFileDelete', "Controls if 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 + }, + '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.") + }, + '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."), + 'default': false + }, + 'workbench.editor.swipeToNavigate': { + 'type': 'boolean', + 'description': nls.localize('swipeToNavigate', "Navigate between open files using three-finger swipe horizontally."), + 'default': false, + 'included': isMacintosh + }, + 'workbench.commandPalette.history': { + 'type': 'number', + 'description': nls.localize('commandHistory', "Controls the number of recently used commands to keep in history for the command palette. Set to 0 to disable command history."), + 'default': 50 + }, + '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."), + 'default': false + }, + 'workbench.quickOpen.closeOnFocusLost': { + 'type': 'boolean', + 'description': nls.localize('closeOnFocusLost', "Controls if 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."), + 'default': true + }, + 'workbench.sideBar.location': { + 'type': 'string', + 'enum': ['left', 'right'], + 'default': 'left', + 'description': nls.localize('sideBarLocation', "Controls the location of the sidebar. It can either show on the left or right of the workbench.") + }, + 'workbench.panel.defaultLocation': { + 'type': 'string', + 'enum': ['bottom', 'right'], + 'default': 'bottom', + 'description': nls.localize('panelDefaultLocation', "Controls the default location of the panel. It can either show at the bottom or on the right of the workbench.") + }, + 'workbench.statusBar.visible': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('statusBarVisibility', "Controls the visibility of the status bar at the bottom of the workbench.") + }, + 'workbench.activityBar.visible': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('activityBarVisibility', "Controls the visibility of the activity bar in the workbench.") + }, + 'workbench.fontAliasing': { + 'type': 'string', + 'enum': ['default', 'antialiased', 'none'], + '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"), + '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."), + nls.localize('workbench.fontAliasing.none', "Disables font smoothing. Text will show with jagged sharp edges.") + ], + 'included': isMacintosh + }, + 'workbench.settings.enableNaturalLanguageSearch': { + 'type': 'boolean', + 'description': nls.localize('enableNaturalLanguageSettingsSearch', "Controls whether to enable the natural language search mode for settings."), + 'default': true, + 'included': product.quality !== 'stable' + } + } }); @@ -304,8 +299,7 @@ configurationRegistry.registerConfiguration({ 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', - '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)." - ) + '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).") }, 'window.restoreWindows': { 'type': 'string', @@ -460,4 +454,4 @@ const schema: IJSONSchema = }; const jsonRegistry = Registry.as(JSONExtensions.JSONContribution); -jsonRegistry.registerSchema(schemaId, schema); \ No newline at end of file +jsonRegistry.registerSchema(schemaId, schema); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 4549d6a1c33..719c6d914d6 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -6,6 +6,7 @@ 'use strict'; import nls = require('vs/nls'); +import * as perf from 'vs/base/common/performance'; import { TPromise } from 'vs/base/common/winjs.base'; import { WorkbenchShell } from 'vs/workbench/electron-browser/shell'; import * as browser from 'vs/base/browser/browser'; @@ -32,7 +33,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { StorageService, inMemoryLocalStorageInstance } from 'vs/platform/storage/common/storageService'; import { Client as ElectronIPCClient } from 'vs/base/parts/ipc/electron-browser/ipc.electron-browser'; -import { webFrame, remote } from 'electron'; +import { webFrame } from 'electron'; import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc'; import { IUpdateService } from 'vs/platform/update/common/update'; import { URLChannelClient } from 'vs/platform/url/common/urlIpc'; @@ -45,8 +46,6 @@ import fs = require('fs'); import { ConsoleLogService, MultiplexLogService } from 'vs/platform/log/common/log'; gracefulFs.gracefulify(fs); // enable gracefulFs -const currentWindowId = remote.getCurrentWindow().id; - export function startup(configuration: IWindowConfiguration): TPromise { // Ensure others can listen to zoom level changes @@ -70,11 +69,11 @@ export function startup(configuration: IWindowConfiguration): TPromise { } function openWorkbench(configuration: IWindowConfiguration): TPromise { - const mainProcessClient = new ElectronIPCClient(String(`window${currentWindowId}`)); - const mainServices = createMainProcessServices(mainProcessClient); + const mainProcessClient = new ElectronIPCClient(String(`window${configuration.windowId}`)); + const mainServices = createMainProcessServices(mainProcessClient, configuration); const environmentService = new EnvironmentService(configuration, configuration.execPath); - const spdlogService = createLogService(`renderer${currentWindowId}`, environmentService); + const spdlogService = createLogService(`renderer${configuration.windowId}`, environmentService); const consoleLogService = new ConsoleLogService(environmentService); const logService = new MultiplexLogService([consoleLogService, spdlogService]); @@ -87,13 +86,10 @@ function openWorkbench(configuration: IWindowConfiguration): TPromise { const timerService = new TimerService((window).MonacoEnvironment.timers as IInitData, workspaceService.getWorkbenchState() === WorkbenchState.EMPTY); const storageService = createStorageService(workspaceService, environmentService); - timerService.beforeDOMContentLoaded = Date.now(); - return domContentLoaded().then(() => { - timerService.afterDOMContentLoaded = Date.now(); // Open Shell - timerService.beforeWorkbenchOpen = Date.now(); + perf.mark('willStartWorkbench'); const shell = new WorkbenchShell(document.body, { contextService: workspaceService, configurationService: workspaceService, @@ -194,7 +190,7 @@ function createStorageService(workspaceService: IWorkspaceContextService, enviro return new StorageService(storage, storage, workspaceId, secondaryWorkspaceId); } -function createMainProcessServices(mainProcessClient: ElectronIPCClient): ServiceCollection { +function createMainProcessServices(mainProcessClient: ElectronIPCClient, configuration: IWindowConfiguration): ServiceCollection { const serviceCollection = new ServiceCollection(); const windowsChannel = mainProcessClient.getChannel('windows'); @@ -204,7 +200,7 @@ function createMainProcessServices(mainProcessClient: ElectronIPCClient): Servic serviceCollection.set(IUpdateService, new SyncDescriptor(UpdateChannelClient, updateChannel)); const urlChannel = mainProcessClient.getChannel('url'); - serviceCollection.set(IURLService, new SyncDescriptor(URLChannelClient, urlChannel, currentWindowId)); + serviceCollection.set(IURLService, new SyncDescriptor(URLChannelClient, urlChannel, configuration.windowId)); const workspacesChannel = mainProcessClient.getChannel('workspaces'); serviceCollection.set(IWorkspacesService, new WorkspacesChannelClient(workspacesChannel)); diff --git a/src/vs/workbench/electron-browser/media/shell.css b/src/vs/workbench/electron-browser/media/shell.css index 166dc1f5b07..4da763a8f93 100644 --- a/src/vs/workbench/electron-browser/media/shell.css +++ b/src/vs/workbench/electron-browser/media/shell.css @@ -124,7 +124,8 @@ outline-style: solid; } -.monaco-shell .monaco-tree.focused.no-focused-item:focus:before { +.monaco-shell .monaco-tree.focused.no-focused-item:focus:before, +.monaco-shell .monaco-list:not(.element-focused):focus:before { position: absolute; top: 0; left: 0; @@ -157,7 +158,8 @@ outline: 0 !important; /* outline is not going well with decoration */ } -.monaco-shell .monaco-tree.focused:focus { +.monaco-shell .monaco-tree.focused:focus, +.monaco-shell .monaco-list:focus { outline: 0 !important; /* tree indicates focus not via outline but through the focused item */ } diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 74046987d31..ace1369afb0 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -7,8 +7,8 @@ import 'vs/css!./media/shell'; -import * as nls from 'vs/nls'; import * as platform from 'vs/base/common/platform'; +import * as perf from 'vs/base/common/performance'; import { Dimension, Builder, $ } from 'vs/base/browser/builder'; import dom = require('vs/base/browser/dom'); import aria = require('vs/base/browser/ui/aria/aria'); @@ -51,7 +51,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; -import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { ILifecycleService, LifecyclePhase, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IMessageService, IChoiceService, Severity } from 'vs/platform/message/common/message'; @@ -71,7 +71,6 @@ import { IExtensionManagementChannel, ExtensionManagementChannelClient } from 'v import { IExtensionManagementService, IExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { ITimerService } from 'vs/workbench/services/timer/common/timerService'; -import { remote } from 'electron'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { restoreFontInfo, readFontInfo, saveFontInfo } from 'vs/editor/browser/config/configuration'; import * as browser from 'vs/base/browser/browser'; @@ -101,8 +100,6 @@ export interface ICoreServices { storageService: IStorageService; } -const currentWindow = remote.getCurrentWindow(); - /** * The workbench shell contains the workbench with a rich header containing navigation and the activity bar. * With the Shell being the top level element in the page, it is also responsible for driving the layouting. @@ -196,11 +193,6 @@ export class WorkbenchShell { // Startup Telemetry this.logStartupTelemetry(info); - // Root Warning - if ((platform.isLinux || platform.isMacintosh) && process.getuid() === 0) { - this.messageService.show(Severity.Warning, nls.localize('runningAsRoot', "It is recommended not to run Code as 'root'.")); - } - // Set lifecycle phase to `Runnning` so that other contributions can now do something this.lifecycleService.phase = LifecyclePhase.Running; @@ -260,9 +252,7 @@ export class WorkbenchShell { }); // Telemetry: startup metrics - this.timerService.workbenchStarted = Date.now(); - this.timerService.restoreEditorsDuration = info.restoreEditorsDuration; - this.timerService.restoreViewletDuration = info.restoreViewletDuration; + perf.mark('didStartWorkbench'); this.extensionService.whenInstalledExtensionsRegistered().done(() => { /* __GDPR__ "startupTime" : { @@ -276,14 +266,12 @@ export class WorkbenchShell { } private initServiceCollection(container: HTMLElement): [IInstantiationService, ServiceCollection] { - const disposables: IDisposable[] = []; - const serviceCollection = new ServiceCollection(); serviceCollection.set(IWorkspaceContextService, this.contextService); serviceCollection.set(IConfigurationService, this.configurationService); serviceCollection.set(IEnvironmentService, this.environmentService); serviceCollection.set(ILogService, this.logService); - disposables.push(this.logService); + this.toUnbind.push(this.logService); serviceCollection.set(ITimerService, this.timerService); serviceCollection.set(IStorageService, this.storageService); @@ -293,13 +281,13 @@ export class WorkbenchShell { const instantiationService: IInstantiationService = new InstantiationService(serviceCollection, true); - this.broadcastService = new BroadcastService(currentWindow.id); + this.broadcastService = new BroadcastService(this.configuration.windowId); serviceCollection.set(IBroadcastService, this.broadcastService); - serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, currentWindow.id, this.configuration)); + serviceCollection.set(IWindowService, new SyncDescriptor(WindowService, this.configuration.windowId, this.configuration)); const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() - .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${currentWindow.id}`)); + .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${this.configuration.windowId}`)); sharedProcess .done(client => client.registerChannel('choice', instantiationService.createInstance(ChoiceChannel))); @@ -332,13 +320,13 @@ export class WorkbenchShell { const errorTelemetry = new ErrorTelemetry(telemetryService); - disposables.push(telemetryService, errorTelemetry); + this.toUnbind.push(telemetryService, errorTelemetry); } else { this.telemetryService = NullTelemetryService; } serviceCollection.set(ITelemetryService, this.telemetryService); - disposables.push(configurationTelemetry(this.telemetryService, this.configurationService)); + this.toUnbind.push(configurationTelemetry(this.telemetryService, this.configurationService)); let crashReporterService = NullCrashReporterService; if (!this.environmentService.disableCrashReporter && product.crashReporter && product.hockeyApp) { @@ -351,8 +339,7 @@ export class WorkbenchShell { serviceCollection.set(IChoiceService, this.messageService); const lifecycleService = instantiationService.createInstance(LifecycleService); - this.toUnbind.push(lifecycleService.onShutdown(reason => dispose(disposables))); - this.toUnbind.push(lifecycleService.onShutdown(reason => saveFontInfo(this.storageService))); + this.toUnbind.push(lifecycleService.onShutdown(reason => this.dispose(reason))); serviceCollection.set(ILifecycleService, lifecycleService); this.lifecycleService = lifecycleService; @@ -361,14 +348,14 @@ export class WorkbenchShell { const extensionEnablementService = instantiationService.createInstance(ExtensionEnablementService); serviceCollection.set(IExtensionEnablementService, extensionEnablementService); - disposables.push(extensionEnablementService); + this.toUnbind.push(extensionEnablementService); this.extensionService = instantiationService.createInstance(ExtensionService); serviceCollection.set(IExtensionService, this.extensionService); - this.timerService.beforeExtensionLoad = Date.now(); + perf.mark('willLoadExtensions'); this.extensionService.whenInstalledExtensionsRegistered().done(() => { - this.timerService.afterExtensionLoad = Date.now(); + perf.mark('didLoadExtensions'); }); this.themeService = instantiationService.createInstance(WorkbenchThemeService, document.body); @@ -466,20 +453,18 @@ export class WorkbenchShell { this.workbench.layout(); } - public dispose(): void { + public dispose(reason = ShutdownReason.QUIT): void { - // Workbench - if (this.workbench) { - this.workbench.dispose(); - } - - this.contextViewService.dispose(); - - // Listeners + // Dispose bindings this.toUnbind = dispose(this.toUnbind); - // Container - $(this.container).empty(); + // Keep font info for next startup around + saveFontInfo(this.storageService); + + // Dispose Workbench + if (this.workbench) { + this.workbench.dispose(reason); + } } } @@ -578,18 +563,19 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const focusOutline = theme.getColor(focusBorder); if (focusOutline) { collector.addRule(` - .monaco-shell [tabindex="0"]:focus, - .monaco-shell .synthetic-focus, - .monaco-shell select:focus, - .monaco-shell .monaco-tree.focused.no-focused-item:focus:before, - .monaco-shell input[type="button"]:focus, - .monaco-shell input[type="text"]:focus, - .monaco-shell button:focus, - .monaco-shell textarea:focus, - .monaco-shell input[type="search"]:focus, - .monaco-shell input[type="checkbox"]:focus { - outline-color: ${focusOutline}; - } + .monaco-shell [tabindex="0"]:focus, + .monaco-shell .synthetic-focus, + .monaco-shell select:focus, + .monaco-shell .monaco-tree.focused.no-focused-item:focus:before, + .monaco-shell .monaco-list:not(.element-focused):focus:before, + .monaco-shell input[type="button"]:focus, + .monaco-shell input[type="text"]:focus, + .monaco-shell button:focus, + .monaco-shell textarea:focus, + .monaco-shell input[type="search"]:focus, + .monaco-shell input[type="checkbox"]:focus { + outline-color: ${focusOutline}; + } `); } }); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 77b52b1db13..e4d861d3d9e 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -6,7 +6,6 @@ 'use strict'; import nls = require('vs/nls'); -import platform = require('vs/base/common/platform'); import URI from 'vs/base/common/uri'; import errors = require('vs/base/common/errors'); import types = require('vs/base/common/types'); @@ -46,6 +45,9 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { LifecyclePhase, ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceFolderCreationData } from 'vs/platform/workspaces/common/workspaces'; +import { IIntegrityService } from 'vs/platform/integrity/common/integrity'; +import { AccessibilitySupport, isRootUser, isWindows, isMacintosh } from 'vs/base/common/platform'; +import product from 'vs/platform/node/product'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), null, true, () => document.execCommand('undo') && TPromise.as(true)), @@ -91,7 +93,8 @@ export class ElectronWindow extends Themable { @IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService, @IFileService private fileService: IFileService, @IMenuService private menuService: IMenuService, - @ILifecycleService private lifecycleService: ILifecycleService + @ILifecycleService private lifecycleService: ILifecycleService, + @IIntegrityService private integrityService: IIntegrityService ) { super(themeService); @@ -239,7 +242,7 @@ export class ElectronWindow extends Themable { // keyboard layout changed event ipc.on('vscode:accessibilitySupportChanged', (_event: any, accessibilitySupportEnabled: boolean) => { - browser.setAccessibilitySupport(accessibilitySupportEnabled ? platform.AccessibilitySupport.Enabled : platform.AccessibilitySupport.Disabled); + browser.setAccessibilitySupport(accessibilitySupportEnabled ? AccessibilitySupport.Enabled : AccessibilitySupport.Disabled); }); // Zoom level changes @@ -317,13 +320,42 @@ export class ElectronWindow extends Themable { // Touchbar Support this.updateTouchbarMenu(); + + // Integrity warning + this.integrityService.isPure().then(res => this.titleService.updateProperties({ isPure: res.isPure })); + + // Root warning + this.lifecycleService.when(LifecyclePhase.Running).then(() => { + let isAdminPromise: Promise; + if (isWindows) { + isAdminPromise = import('native-is-elevated').then(isElevated => isElevated()); + } else { + isAdminPromise = Promise.resolve(isRootUser); + } + + return isAdminPromise.then(isAdmin => { + + // Update title + this.titleService.updateProperties({ isAdmin }); + + // Show warning message (unix only) + if (isAdmin && !isWindows) { + this.messageService.show(Severity.Warning, nls.localize('runningAsRoot', "It is not recommended to run {0} as root user.", product.nameShort)); + } + }); + }); } private updateTouchbarMenu(): void { - if (!platform.isMacintosh) { + if (!isMacintosh) { return; // macOS only } + const touchbarEnabled = this.configurationService.getValue('keyboard.touchbar.enabled'); + if (!touchbarEnabled) { + return; // disabled via setting + } + // Dispose old this.touchBarDisposables = dispose(this.touchBarDisposables); diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index f2fc7efb4be..2301bbcf844 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -15,8 +15,7 @@ import DOM = require('vs/base/browser/dom'); import { Builder, $ } from 'vs/base/browser/builder'; import { Delayer, RunOnceScheduler } from 'vs/base/common/async'; import * as browser from 'vs/base/browser/browser'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { time } from 'vs/base/common/performance'; +import * as perf from 'vs/base/common/performance'; import errors = require('vs/base/common/errors'); import { BackupFileService } from 'vs/workbench/services/backup/node/backupFileService'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; @@ -120,8 +119,6 @@ interface IZenModeSettings { export interface IWorkbenchStartedInfo { customKeybindingsCount: number; - restoreViewletDuration: number; - restoreEditorsDuration: number; pinnedViewlets: string[]; restoredViewlet: string; restoredEditors: string[]; @@ -155,6 +152,7 @@ export class Workbench implements IPartService { private static readonly panelHiddenStorageKey = 'workbench.panel.hidden'; private static readonly zenModeActiveStorageKey = 'workbench.zenmode.active'; private static readonly panelPositionStorageKey = 'workbench.panel.location'; + private static readonly defaultPanelPositionStorageKey = 'workbench.panel.defaultLocation'; private static readonly sidebarPositionConfigurationKey = 'workbench.sideBar.location'; private static readonly statusbarVisibleConfigurationKey = 'workbench.statusBar.visible'; @@ -190,8 +188,7 @@ export class Workbench implements IPartService { private statusbarPart: StatusbarPart; private quickOpen: QuickOpenController; private workbenchLayout: WorkbenchLayout; - private toDispose: IDisposable[]; - private toShutdown: { shutdown: () => void; }[]; + private toUnbind: IDisposable[]; private sideBarHidden: boolean; private statusBarHidden: boolean; private activityBarHidden: boolean; @@ -240,8 +237,7 @@ export class Workbench implements IPartService { (configuration.filesToOpen && configuration.filesToOpen.length > 0) || (configuration.filesToDiff && configuration.filesToDiff.length > 0); - this.toDispose = []; - this.toShutdown = []; + this.toUnbind = []; this.editorBackgroundDelayer = new Delayer(50); this.closeEmptyWindowScheduler = new RunOnceScheduler(() => this.onAllEditorsClosed(), 50); @@ -281,7 +277,7 @@ export class Workbench implements IPartService { const inputFocused = InputFocusedContext.bindTo(this.contextKeyService); const onWindowsFocusIn = domEvent(window, 'focusin', true); - onWindowsFocusIn(() => inputFocused.set(document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')), null, this.toDispose); + onWindowsFocusIn(() => inputFocused.set(document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.tagName === 'TEXTAREA')), null, this.toUnbind); // Set workbench state context const WorkbenchStateContext = new RawContextKey('workbenchState', getWorkbenchStateString(this.configurationService.getWorkbenchState())); @@ -290,8 +286,8 @@ export class Workbench implements IPartService { const WorkspaceFolderCountContext = new RawContextKey('workspaceFolderCount', this.configurationService.getWorkspace().folders.length); const workspaceFolderCountContext = WorkspaceFolderCountContext.bindTo(this.contextKeyService); - this.toDispose.push(this.configurationService.onDidChangeWorkbenchState(() => workbenchStateContext.set(getWorkbenchStateString(this.configurationService.getWorkbenchState())))); - this.toDispose.push(this.configurationService.onDidChangeWorkspaceFolders(() => workspaceFolderCountContext.set(this.configurationService.getWorkspace().folders.length))); + this.toUnbind.push(this.configurationService.onDidChangeWorkbenchState(() => workbenchStateContext.set(getWorkbenchStateString(this.configurationService.getWorkbenchState())))); + this.toUnbind.push(this.configurationService.onDidChangeWorkspaceFolders(() => workspaceFolderCountContext.set(this.configurationService.getWorkspace().folders.length))); // Register Listeners this.registerListeners(); @@ -321,8 +317,7 @@ export class Workbench implements IPartService { const restorePromises: TPromise[] = []; // Restore Editors - const editorRestoreStopWatch = StopWatch.create(); - const editorRestoreClock = time('restore:editors'); + perf.mark('willRestoreEditors'); const restoredEditors: string[] = []; restorePromises.push(this.resolveEditorsToOpen().then(inputs => { @@ -339,8 +334,7 @@ export class Workbench implements IPartService { return editorOpenPromise.then(editors => { this.handleEditorBackground(); // make sure we show the proper background in the editor area - editorRestoreClock.stop(); - editorRestoreStopWatch.stop(); + perf.mark('didRestoreEditors'); for (const editor of editors) { if (editor) { @@ -355,7 +349,6 @@ export class Workbench implements IPartService { })); // Restore Sidebar - let viewletRestoreStopWatch: StopWatch; let viewletIdToRestore: string; if (!this.sideBarHidden) { this.sideBarVisibleContext.set(true); @@ -368,11 +361,9 @@ export class Workbench implements IPartService { viewletIdToRestore = this.viewletService.getDefaultViewletId(); } - viewletRestoreStopWatch = StopWatch.create(); - const viewletRestoreClock = time('restore:viewlet'); + perf.mark('willRestoreViewlet'); restorePromises.push(this.viewletService.openViewlet(viewletIdToRestore).then(() => { - viewletRestoreStopWatch.stop(); - viewletRestoreClock.stop(); + perf.mark('didRestoreViewlet'); })); } @@ -397,8 +388,6 @@ export class Workbench implements IPartService { return { customKeybindingsCount: this.keybindingService.customKeybindingsCount(), - restoreViewletDuration: viewletRestoreStopWatch ? Math.round(viewletRestoreStopWatch.elapsed()) : 0, - restoreEditorsDuration: Math.round(editorRestoreStopWatch.elapsed()), pinnedViewlets: this.activitybarPart.getPinned(), restoredViewlet: viewletIdToRestore, restoredEditors @@ -511,8 +500,6 @@ export class Workbench implements IPartService { private initServices(): void { const { serviceCollection } = this.workbenchParams; - this.toDispose.push(this.lifecycleService.onShutdown(this.shutdownComponents, this)); - // Services we contribute serviceCollection.set(IPartService, this); @@ -521,8 +508,7 @@ export class Workbench implements IPartService { // Status bar this.statusbarPart = this.instantiationService.createInstance(StatusbarPart, Identifiers.STATUSBAR_PART); - this.toDispose.push(this.statusbarPart); - this.toShutdown.push(this.statusbarPart); + this.toUnbind.push({ dispose: () => this.statusbarPart.shutdown() }); serviceCollection.set(IStatusbarService, this.statusbarPart); // Progress 2 @@ -546,8 +532,7 @@ export class Workbench implements IPartService { // Sidebar part this.sidebarPart = this.instantiationService.createInstance(SidebarPart, Identifiers.SIDEBAR_PART); - this.toDispose.push(this.sidebarPart); - this.toShutdown.push(this.sidebarPart); + this.toUnbind.push({ dispose: () => this.sidebarPart.shutdown() }); // Viewlet service this.viewletService = this.instantiationService.createInstance(ViewletService, this.sidebarPart); @@ -555,34 +540,30 @@ export class Workbench implements IPartService { // Panel service (panel part) this.panelPart = this.instantiationService.createInstance(PanelPart, Identifiers.PANEL_PART); - this.toDispose.push(this.panelPart); - this.toShutdown.push(this.panelPart); + this.toUnbind.push({ dispose: () => this.panelPart.shutdown() }); serviceCollection.set(IPanelService, this.panelPart); // Activity service (activitybar part) this.activitybarPart = this.instantiationService.createInstance(ActivitybarPart, Identifiers.ACTIVITYBAR_PART); - this.toDispose.push(this.activitybarPart); - this.toShutdown.push(this.activitybarPart); + this.toUnbind.push({ dispose: () => this.activitybarPart.shutdown() }); const activityService = this.instantiationService.createInstance(ActivityService, this.activitybarPart, this.panelPart); serviceCollection.set(IActivityService, activityService); // File Service this.fileService = this.instantiationService.createInstance(RemoteFileService); serviceCollection.set(IFileService, this.fileService); - this.toDispose.push(this.fileService.onFileChanges(e => this.configurationService.handleWorkspaceFileEvents(e))); + this.toUnbind.push(this.fileService.onFileChanges(e => this.configurationService.handleWorkspaceFileEvents(e))); // Editor service (editor part) this.editorPart = this.instantiationService.createInstance(EditorPart, Identifiers.EDITOR_PART, !this.hasFilesToCreateOpenOrDiff); - this.toDispose.push(this.editorPart); - this.toShutdown.push(this.editorPart); + this.toUnbind.push({ dispose: () => this.editorPart.shutdown() }); this.editorService = this.instantiationService.createInstance(WorkbenchEditorService, this.editorPart); serviceCollection.set(IWorkbenchEditorService, this.editorService); serviceCollection.set(IEditorGroupService, this.editorPart); // Title bar this.titlebarPart = this.instantiationService.createInstance(TitlebarPart, Identifiers.TITLEBAR_PART); - this.toDispose.push(this.titlebarPart); - this.toShutdown.push(this.titlebarPart); + this.toUnbind.push({ dispose: () => this.titlebarPart.shutdown() }); serviceCollection.set(ITitleService, this.titlebarPart); // History @@ -619,8 +600,7 @@ export class Workbench implements IPartService { // Quick open service (quick open controller) this.quickOpen = this.instantiationService.createInstance(QuickOpenController); - this.toDispose.push(this.quickOpen); - this.toShutdown.push(this.quickOpen); + this.toUnbind.push({ dispose: () => this.quickOpen.shutdown() }); serviceCollection.set(IQuickOpenService, this.quickOpen); // Contributed services @@ -656,8 +636,7 @@ export class Workbench implements IPartService { this.sideBarPosition = (sideBarPosition === 'right') ? Position.RIGHT : Position.LEFT; // Panel position - const panelPosition = this.storageService.get(Workbench.panelPositionStorageKey, StorageScope.WORKSPACE, 'bottom'); - this.panelPosition = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; + this.setPanelPositionFromStorageOrConfig(); // Statusbar visibility const statusBarVisible = this.configurationService.getValue(Workbench.statusbarVisibleConfigurationKey); @@ -679,6 +658,12 @@ export class Workbench implements IPartService { }; } + private setPanelPositionFromStorageOrConfig() { + const defaultPanelPosition = this.configurationService.getValue(Workbench.defaultPanelPositionStorageKey); + const panelPosition = this.storageService.get(Workbench.panelPositionStorageKey, StorageScope.WORKSPACE, defaultPanelPosition); + this.panelPosition = (panelPosition === 'right') ? Position.RIGHT : Position.BOTTOM; + } + /** * Returns whether the workbench has been started. */ @@ -962,26 +947,7 @@ export class Workbench implements IPartService { document.body.style['-webkit-font-smoothing'] = (aliasing === 'default' ? '' : aliasing); } - public dispose(): void { - if (this.isStarted()) { - this.shutdownComponents(); - this.workbenchShutdown = true; - } - - this.toDispose = dispose(this.toDispose); - } - - /** - * Asks the workbench and all its UI components inside to lay out according to - * the containers dimension the workbench is living in. - */ - public layout(options?: ILayoutOptions): void { - if (this.isStarted()) { - this.workbenchLayout.layout(options); - } - } - - private shutdownComponents(reason = ShutdownReason.QUIT): void { + public dispose(reason = ShutdownReason.QUIT): void { // Restore sidebar if we are being shutdown as a matter of a reload if (reason === ShutdownReason.RELOAD) { @@ -997,14 +963,27 @@ export class Workbench implements IPartService { this.storageService.remove(Workbench.zenModeActiveStorageKey, StorageScope.WORKSPACE); } + // Dispose bindings + this.toUnbind = dispose(this.toUnbind); + // Pass shutdown on to each participant - this.toShutdown.forEach(s => s.shutdown()); + this.workbenchShutdown = true; + } + + /** + * Asks the workbench and all its UI components inside to lay out according to + * the containers dimension the workbench is living in. + */ + public layout(options?: ILayoutOptions): void { + if (this.isStarted()) { + this.workbenchLayout.layout(options); + } } private registerListeners(): void { // Listen to editor changes - this.toDispose.push(this.editorPart.onEditorsChanged(() => this.onEditorsChanged())); + this.toUnbind.push(this.editorPart.onEditorsChanged(() => this.onEditorsChanged())); // Listen to editor closing (if we run with --wait) const filesToWait = this.workbenchParams.configuration.filesToWait; @@ -1013,21 +992,21 @@ export class Workbench implements IPartService { const waitMarkerFile = URI.file(filesToWait.waitMarkerFilePath); const listenerDispose = this.editorPart.getStacksModel().onEditorClosed(() => this.onEditorClosed(listenerDispose, resourcesToWaitFor, waitMarkerFile)); - this.toDispose.push(listenerDispose); + this.toUnbind.push(listenerDispose); } // Handle message service and quick open events - this.toDispose.push((this.messageService).onMessagesShowing(() => this.messagesVisibleContext.set(true))); - this.toDispose.push((this.messageService).onMessagesCleared(() => this.messagesVisibleContext.reset())); + this.toUnbind.push((this.messageService).onMessagesShowing(() => this.messagesVisibleContext.set(true))); + this.toUnbind.push((this.messageService).onMessagesCleared(() => this.messagesVisibleContext.reset())); - this.toDispose.push(this.quickOpen.onShow(() => (this.messageService).suspend())); // when quick open is open, don't show messages behind - this.toDispose.push(this.quickOpen.onHide(() => (this.messageService).resume())); // resume messages once quick open is closed again + this.toUnbind.push(this.quickOpen.onShow(() => (this.messageService).suspend())); // when quick open is open, don't show messages behind + this.toUnbind.push(this.quickOpen.onHide(() => (this.messageService).resume())); // resume messages once quick open is closed again // Configuration changes - this.toDispose.push(this.configurationService.onDidChangeConfiguration(() => this.onDidUpdateConfiguration())); + this.toUnbind.push(this.configurationService.onDidChangeConfiguration(() => this.onDidUpdateConfiguration())); // Fullscreen changes - this.toDispose.push(browser.onDidChangeFullscreen(() => this.onFullscreenChanged())); + this.toUnbind.push(browser.onDidChangeFullscreen(() => this.onFullscreenChanged())); } private onFullscreenChanged(): void { @@ -1111,6 +1090,8 @@ export class Workbench implements IPartService { this.setSideBarPosition(newSidebarPosition); } + this.setPanelPositionFromStorageOrConfig(); + const fontAliasing = this.configurationService.getValue(Workbench.fontAliasingConfigurationKey); if (fontAliasing !== this.fontAliasing) { this.setFontAliasing(fontAliasing); @@ -1143,8 +1124,6 @@ export class Workbench implements IPartService { }, this.quickOpen // Quickopen ); - - this.toDispose.push(this.workbenchLayout); } private renderWorkbench(): void { diff --git a/src/vs/workbench/node/extensionHostMain.ts b/src/vs/workbench/node/extensionHostMain.ts index 952ef7e4f96..76041a685a1 100644 --- a/src/vs/workbench/node/extensionHostMain.ts +++ b/src/vs/workbench/node/extensionHostMain.ts @@ -9,9 +9,7 @@ import nls = require('vs/nls'); import pfs = require('vs/base/node/pfs'); import { TPromise } from 'vs/base/common/winjs.base'; import { join } from 'path'; -import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; import { ExtHostExtensionService } from 'vs/workbench/api/node/extHostExtensionService'; -import { ExtHostThreadService } from 'vs/workbench/services/thread/node/extHostThreadService'; import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; @@ -26,6 +24,9 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { createLogService } from 'vs/platform/log/node/spdlogService'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; +import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; +import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; +import URI from 'vs/base/common/uri'; // const nativeExit = process.exit.bind(process); function patchProcess(allowExit: boolean) { @@ -80,7 +81,7 @@ export class ExtensionHostMain { private _logService: ILogService; private disposables: IDisposable[] = []; - constructor(rpcProtocol: RPCProtocol, initData: IInitData) { + constructor(protocol: IMessagePassingProtocol, initData: IInitData) { this._environment = initData.environment; this._workspace = initData.workspace; @@ -88,8 +89,8 @@ export class ExtensionHostMain { patchProcess(allowExit); // services - const threadService = new ExtHostThreadService(rpcProtocol); - const extHostWorkspace = new ExtHostWorkspace(threadService, initData.workspace); + const rpcProtocol = new RPCProtocol(protocol); + const extHostWorkspace = new ExtHostWorkspace(rpcProtocol, initData.workspace); const environmentService = new EnvironmentService(initData.args, initData.execPath); this._logService = createLogService(`exthost${initData.windowId}`, environmentService); this.disposables.push(this._logService); @@ -97,8 +98,8 @@ export class ExtensionHostMain { this._logService.info('extension host started'); this._logService.trace('initData', initData); - this._extHostConfiguration = new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration); - this._extensionService = new ExtHostExtensionService(initData, threadService, extHostWorkspace, this._extHostConfiguration, this._logService); + this._extHostConfiguration = new ExtHostConfiguration(rpcProtocol.getProxy(MainContext.MainThreadConfiguration), extHostWorkspace, initData.configuration); + this._extensionService = new ExtHostExtensionService(initData, rpcProtocol, extHostWorkspace, this._extHostConfiguration, this._logService, environmentService); // error forwarding and stack trace scanning const extensionErrors = new WeakMap(); @@ -119,8 +120,8 @@ export class ExtensionHostMain { return `${error.name || 'Error'}: ${error.message || ''}${stackTraceMessage}`; }; }); - const mainThreadExtensions = threadService.get(MainContext.MainThreadExtensionService); - const mainThreadErrors = threadService.get(MainContext.MainThreadErrors); + const mainThreadExtensions = rpcProtocol.getProxy(MainContext.MainThreadExtensionService); + const mainThreadErrors = rpcProtocol.getProxy(MainContext.MainThreadErrors); errors.setUnexpectedErrorHandler(err => { const data = errors.transformErrorForSerialization(err); const extension = extensionErrors.get(err); @@ -234,7 +235,7 @@ export class ExtensionHostMain { // find exact path for (const { uri } of this._workspace.folders) { - if (await pfs.exists(join(uri.fsPath, fileName))) { + if (await pfs.exists(join(URI.revive(uri).fsPath, fileName))) { // the file was found return ( this._extensionService.activateById(extensionId, new ExtensionActivatedByEvent(true, `workspaceContains:${fileName}`)) @@ -261,7 +262,7 @@ export class ExtensionHostMain { includes[globPattern] = true; }); - const folderQueries = this._workspace.folders.map(folder => ({ folder: folder.uri })); + const folderQueries = this._workspace.folders.map(folder => ({ folder: URI.revive(folder.uri) })); const config = this._extHostConfiguration.getConfiguration('search'); const useRipgrep = config.get('useRipgrep', true); const followSymlinks = config.get('followSymlinks', true); diff --git a/src/vs/workbench/node/extensionHostProcess.ts b/src/vs/workbench/node/extensionHostProcess.ts index d44ebcc379c..0f118c8cddb 100644 --- a/src/vs/workbench/node/extensionHostProcess.ts +++ b/src/vs/workbench/node/extensionHostProcess.ts @@ -6,10 +6,7 @@ 'use strict'; import { onUnexpectedError } from 'vs/base/common/errors'; -import { TPromise } from 'vs/base/common/winjs.base'; import { ExtensionHostMain, exit } from 'vs/workbench/node/extensionHostMain'; -import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; -import { parse } from 'vs/base/common/marshalling'; import { IInitData } from 'vs/workbench/api/node/extHost.protocol'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { Protocol } from 'vs/base/parts/ipc/node/ipc.net'; @@ -17,7 +14,7 @@ import { createConnection } from 'net'; import Event, { filterEvent } from 'vs/base/common/event'; interface IRendererConnection { - rpcProtocol: RPCProtocol; + protocol: IMessagePassingProtocol; initData: IInitData; } @@ -27,11 +24,11 @@ let onTerminate = function () { exit(); }; -function createExtHostProtocol(): TPromise { +function createExtHostProtocol(): Promise { const pipeName = process.env.VSCODE_IPC_HOOK_EXTHOST; - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { const socket = createConnection(pipeName, () => { socket.removeListener('error', reject); @@ -63,21 +60,20 @@ function createExtHostProtocol(): TPromise { }); } -function connectToRenderer(protocol: IMessagePassingProtocol): TPromise { - return new TPromise((c, e) => { +function connectToRenderer(protocol: IMessagePassingProtocol): Promise { + return new Promise((c, e) => { // Listen init data message const first = protocol.onMessage(raw => { first.dispose(); - const initData = parse(raw); - const rpcProtocol = new RPCProtocol(protocol); + const initData = JSON.parse(raw); // Print a console message when rejection isn't handled within N seconds. For details: // see https://nodejs.org/api/process.html#process_event_unhandledrejection // and https://nodejs.org/api/process.html#process_event_rejectionhandled - const unhandledPromises: TPromise[] = []; - process.on('unhandledRejection', (reason: any, promise: TPromise) => { + const unhandledPromises: Promise[] = []; + process.on('unhandledRejection', (reason: any, promise: Promise) => { unhandledPromises.push(promise); setTimeout(() => { const idx = unhandledPromises.indexOf(promise); @@ -88,7 +84,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): TPromise) => { + process.on('rejectionHandled', (promise: Promise) => { const idx = unhandledPromises.indexOf(promise); if (idx >= 0) { unhandledPromises.splice(idx, 1); @@ -112,7 +108,7 @@ function connectToRenderer(protocol: IMessagePassingProtocol): TPromise { return connectToRenderer(protocol); }).then(renderer => { // setup things - const extensionHostMain = new ExtensionHostMain(renderer.rpcProtocol, renderer.initData); + const extensionHostMain = new ExtensionHostMain(renderer.protocol, renderer.initData); onTerminate = () => extensionHostMain.terminate(); return extensionHostMain.start(); -}).done(null, err => console.error(err)); +}).catch(err => console.error(err)); diff --git a/src/vs/workbench/parts/backup/common/backupRestorer.ts b/src/vs/workbench/parts/backup/common/backupRestorer.ts index d1b4010f586..6e1d90e3e7e 100644 --- a/src/vs/workbench/parts/backup/common/backupRestorer.ts +++ b/src/vs/workbench/parts/backup/common/backupRestorer.ts @@ -93,8 +93,6 @@ export class BackupRestorer implements IWorkbenchContribution { const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors }; if (resource.scheme === UNTITLED_SCHEMA && !BackupRestorer.UNTITLED_REGEX.test(resource.fsPath)) { - // TODO@Ben debt: instead of guessing if an untitled file has an associated file path or not - // this information should be provided by the backup service and stored as meta data within return { filePath: resource.fsPath, options }; } 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 32a8c8227fc..af31a3e601a 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -30,7 +30,7 @@ function isAvailable(): TPromise { class InstallAction extends Action { - static ID = 'workbench.action.installCommandLine'; + static readonly ID = 'workbench.action.installCommandLine'; static LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName); constructor( @@ -111,7 +111,7 @@ class InstallAction extends Action { class UninstallAction extends Action { - static ID = 'workbench.action.uninstallCommandLine'; + static readonly ID = 'workbench.action.uninstallCommandLine'; static LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName); constructor( diff --git a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts index 9f1df5fea58..a9133e661c8 100644 --- a/src/vs/workbench/parts/debug/browser/breakpointWidget.ts +++ b/src/vs/workbench/parts/debug/browser/breakpointWidget.ts @@ -71,7 +71,7 @@ export class BreakpointWidget extends ZoneWidget { this.hitCountContext = breakpoint && breakpoint.hitCondition && !breakpoint.condition; const selected = this.hitCountContext ? 1 : 0; - const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count")], selected); + const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count")], selected, this.contextViewService); this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService)); selectBox.render(dom.append(container, $('.breakpoint-select-container'))); selectBox.onDidSelect(e => { diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index 914a20cfadd..12887c33f44 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -19,6 +19,7 @@ import { IThemeService } from 'vs/platform/theme/common/themeService'; import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { selectBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; const $ = dom.$; @@ -40,10 +41,11 @@ export class StartDebugActionItem implements IActionItem { @IDebugService private debugService: IDebugService, @IThemeService private themeService: IThemeService, @IConfigurationService private configurationService: IConfigurationService, - @ICommandService private commandService: ICommandService + @ICommandService private commandService: ICommandService, + @IContextViewService contextViewService: IContextViewService, ) { this.toDispose = []; - this.selectBox = new SelectBox([], -1); + this.selectBox = new SelectBox([], -1, contextViewService); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService, { selectBackground: SIDE_BAR_BACKGROUND })); @@ -184,9 +186,10 @@ export class FocusProcessActionItem extends SelectActionItem { constructor( action: IAction, @IDebugService private debugService: IDebugService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService ) { - super(null, action, [], -1); + super(null, action, [], -1, contextViewService); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 0e71397c23c..e98c1e52ab3 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -71,7 +71,7 @@ export abstract class AbstractDebugAction extends Action { } export class ConfigureAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.configure'; + static readonly ID = 'workbench.action.debug.configure'; static LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json'); constructor(id: string, label: string, @@ -160,7 +160,7 @@ export class StartAction extends AbstractDebugAction { } export class RunAction extends StartAction { - static ID = 'workbench.action.debug.run'; + static readonly ID = 'workbench.action.debug.run'; static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging"); protected isNoDebug(): boolean { @@ -169,7 +169,7 @@ export class RunAction extends StartAction { } export class SelectAndStartAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.selectandstart'; + static readonly ID = 'workbench.action.debug.selectandstart'; static LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging"); constructor(id: string, label: string, @@ -190,7 +190,7 @@ export class SelectAndStartAction extends AbstractDebugAction { } export class RestartAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.restart'; + static readonly ID = 'workbench.action.debug.restart'; static LABEL = nls.localize('restartDebug', "Restart"); static RECONNECT_LABEL = nls.localize('reconnectDebug', "Reconnect"); @@ -224,7 +224,7 @@ export class RestartAction extends AbstractDebugAction { } export class StepOverAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.stepOver'; + static readonly ID = 'workbench.action.debug.stepOver'; static LABEL = nls.localize('stepOverDebug', "Step Over"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -245,7 +245,7 @@ export class StepOverAction extends AbstractDebugAction { } export class StepIntoAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.stepInto'; + static readonly ID = 'workbench.action.debug.stepInto'; static LABEL = nls.localize('stepIntoDebug', "Step Into"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -266,7 +266,7 @@ export class StepIntoAction extends AbstractDebugAction { } export class StepOutAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.stepOut'; + static readonly ID = 'workbench.action.debug.stepOut'; static LABEL = nls.localize('stepOutDebug', "Step Out"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -287,7 +287,7 @@ export class StepOutAction extends AbstractDebugAction { } export class StopAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.stop'; + static readonly ID = 'workbench.action.debug.stop'; static LABEL = nls.localize('stopDebug', "Stop"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -308,7 +308,7 @@ export class StopAction extends AbstractDebugAction { } export class DisconnectAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.disconnect'; + static readonly ID = 'workbench.action.debug.disconnect'; static LABEL = nls.localize('disconnectDebug', "Disconnect"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -326,7 +326,7 @@ export class DisconnectAction extends AbstractDebugAction { } export class ContinueAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.continue'; + static readonly ID = 'workbench.action.debug.continue'; static LABEL = nls.localize('continueDebug', "Continue"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -347,7 +347,7 @@ export class ContinueAction extends AbstractDebugAction { } export class PauseAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.pause'; + static readonly ID = 'workbench.action.debug.pause'; static LABEL = nls.localize('pauseDebug', "Pause"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -368,7 +368,7 @@ export class PauseAction extends AbstractDebugAction { } export class RestartFrameAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.restartFrame'; + static readonly ID = 'workbench.action.debug.restartFrame'; static LABEL = nls.localize('restartFrame', "Restart Frame"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -385,7 +385,7 @@ export class RestartFrameAction extends AbstractDebugAction { } export class RemoveBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.removeBreakpoint'; + static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint'; static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -399,7 +399,7 @@ export class RemoveBreakpointAction extends AbstractDebugAction { } export class RemoveAllBreakpointsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.removeAllBreakpoints'; + static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints'; static LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -418,7 +418,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction { } export class EnableBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.enableBreakpoint'; + static readonly ID = 'workbench.debug.viewlet.action.enableBreakpoint'; static LABEL = nls.localize('enableBreakpoint', "Enable Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -431,7 +431,7 @@ export class EnableBreakpointAction extends AbstractDebugAction { } export class DisableBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.disableBreakpoint'; + static readonly ID = 'workbench.debug.viewlet.action.disableBreakpoint'; static LABEL = nls.localize('disableBreakpoint', "Disable Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -444,7 +444,7 @@ export class DisableBreakpointAction extends AbstractDebugAction { } export class EnableAllBreakpointsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.enableAllBreakpoints'; + static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints'; static LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -463,7 +463,7 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction { } export class DisableAllBreakpointsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.disableAllBreakpoints'; + static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints'; static LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -482,7 +482,7 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction { } export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction'; + static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction'; static ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints"); static DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints"); @@ -506,7 +506,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction { } export class ReapplyBreakpointsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction'; + static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction'; static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -526,7 +526,7 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction { } export class AddFunctionBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction'; + static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction'; static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -546,7 +546,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction { } export class AddConditionalBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.addConditionalBreakpointAction'; + static readonly ID = 'workbench.debug.viewlet.action.addConditionalBreakpointAction'; static LABEL = nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."); constructor(id: string, label: string, @@ -565,7 +565,7 @@ export class AddConditionalBreakpointAction extends AbstractDebugAction { } export class EditConditionalBreakpointAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.editConditionalBreakpointAction'; + static readonly ID = 'workbench.debug.viewlet.action.editConditionalBreakpointAction'; static LABEL = nls.localize('editConditionalBreakpoint', "Edit Breakpoint..."); constructor(id: string, label: string, @@ -584,7 +584,7 @@ export class EditConditionalBreakpointAction extends AbstractDebugAction { export class SetValueAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.setValue'; + static readonly ID = 'workbench.debug.viewlet.action.setValue'; static LABEL = nls.localize('setValue', "Set Value"); constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -607,7 +607,7 @@ export class SetValueAction extends AbstractDebugAction { export class AddWatchExpressionAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.addWatchExpression'; + static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression'; static LABEL = nls.localize('addWatchExpression', "Add Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -616,7 +616,8 @@ export class AddWatchExpressionAction extends AbstractDebugAction { } public run(): TPromise { - return this.debugService.addWatchExpression(); + this.debugService.addWatchExpression(); + return TPromise.as(undefined); } protected isEnabled(state: State): boolean { @@ -625,7 +626,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction { } export class EditWatchExpressionAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.editWatchExpression'; + static readonly ID = 'workbench.debug.viewlet.action.editWatchExpression'; static LABEL = nls.localize('editWatchExpression', "Edit Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -639,7 +640,7 @@ export class EditWatchExpressionAction extends AbstractDebugAction { } export class AddToWatchExpressionsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.addToWatchExpressions'; + static readonly ID = 'workbench.debug.viewlet.action.addToWatchExpressions'; static LABEL = nls.localize('addToWatchExpressions', "Add to Watch"); constructor(id: string, label: string, private expression: IExpression, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -648,12 +649,14 @@ export class AddToWatchExpressionsAction extends AbstractDebugAction { public run(): TPromise { const name = this.expression instanceof Variable ? this.expression.evaluateName : this.expression.name; - return this.debugService.addWatchExpression(name); + this.debugService.addWatchExpression(name); + return TPromise.as(undefined); + } } export class RemoveWatchExpressionAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.removeWatchExpression'; + static readonly ID = 'workbench.debug.viewlet.action.removeWatchExpression'; static LABEL = nls.localize('removeWatchExpression', "Remove Expression"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -667,7 +670,7 @@ export class RemoveWatchExpressionAction extends AbstractDebugAction { } export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { - static ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; + static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions'; static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -686,7 +689,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction { } export class ClearReplAction extends AbstractDebugAction { - static ID = 'workbench.debug.panel.action.clearReplAction'; + static readonly ID = 'workbench.debug.panel.action.clearReplAction'; static LABEL = nls.localize('clearRepl', "Clear Console"); constructor(id: string, label: string, @@ -706,7 +709,7 @@ export class ClearReplAction extends AbstractDebugAction { } export class ToggleReplAction extends TogglePanelAction { - static ID = 'workbench.debug.action.toggleRepl'; + static readonly ID = 'workbench.debug.action.toggleRepl'; static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console'); private toDispose: lifecycle.IDisposable[]; @@ -748,7 +751,7 @@ export class ToggleReplAction extends TogglePanelAction { export class FocusReplAction extends Action { - static ID = 'workbench.debug.action.focusRepl'; + static readonly ID = 'workbench.debug.action.focusRepl'; static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus Debug Console'); @@ -764,7 +767,7 @@ export class FocusReplAction extends Action { } export class FocusProcessAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.focusProcess'; + static readonly ID = 'workbench.action.debug.focusProcess'; static LABEL = nls.localize('focusProcess', "Focus Process"); constructor(id: string, label: string, @@ -778,19 +781,19 @@ export class FocusProcessAction extends AbstractDebugAction { public run(processName: string): TPromise { const isMultiRoot = this.debugService.getConfigurationManager().getLaunches().length > 1; const process = this.debugService.getModel().getProcesses().filter(p => p.getName(isMultiRoot) === processName).pop(); - return this.debugService.focusStackFrameAndEvaluate(null, process, true).then(() => { - const stackFrame = this.debugService.getViewModel().focusedStackFrame; - if (stackFrame) { - return stackFrame.openInEditor(this.editorService, true); - } - return undefined; - }); + this.debugService.focusStackFrame(undefined, undefined, process, true); + const stackFrame = this.debugService.getViewModel().focusedStackFrame; + if (stackFrame) { + return stackFrame.openInEditor(this.editorService, true); + } + + return TPromise.as(undefined); } } // Actions used by the chakra debugger export class StepBackAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.stepBack'; + static readonly ID = 'workbench.action.debug.stepBack'; static LABEL = nls.localize('stepBackDebug', "Step Back"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { @@ -813,7 +816,7 @@ export class StepBackAction extends AbstractDebugAction { } export class ReverseContinueAction extends AbstractDebugAction { - static ID = 'workbench.action.debug.reverseContinue'; + static readonly ID = 'workbench.action.debug.reverseContinue'; static LABEL = nls.localize('reverseContinue', "Reverse"); constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index 10db5a16c85..313156f9059 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -29,6 +29,7 @@ import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/c import { localize } from 'vs/nls'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; const $ = builder.$; const DEBUG_ACTIONS_WIDGET_POSITION_KEY = 'debug.actionswidgetposition'; @@ -38,6 +39,11 @@ export const debugToolBarBackground = registerColor('debugToolBar.background', { light: '#F3F3F3', hc: '#000000' }, localize('debugToolBarBackground', "Debug toolbar background color.")); +export const debugToolBarBorder = registerColor('debugToolBar.border', { + dark: null, + light: null, + hc: null +}, localize('debugToolBarBorder', "Debug toolbar border color.")); export class DebugActionsWidget extends Themable implements IWorkbenchContribution { @@ -59,7 +65,8 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi @IConfigurationService private configurationService: IConfigurationService, @IThemeService themeService: IThemeService, @IKeybindingService private keybindingService: IKeybindingService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IContextViewService contextViewService: IContextViewService ) { super(themeService); @@ -75,7 +82,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi orientation: ActionsOrientation.HORIZONTAL, actionItemProvider: (action: IAction) => { if (action.id === FocusProcessAction.ID) { - return new FocusProcessActionItem(action, this.debugService, this.themeService); + return new FocusProcessActionItem(action, this.debugService, this.themeService, contextViewService); } return null; @@ -159,9 +166,16 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi this.$el.style('box-shadow', widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null); const contrastBorderColor = this.getColor(contrastBorder); - this.$el.style('border-style', contrastBorderColor ? 'solid' : null); - this.$el.style('border-width', contrastBorderColor ? '1px' : null); - this.$el.style('border-color', contrastBorderColor); + const borderColor = this.getColor(debugToolBarBorder); + + if (contrastBorderColor) { + this.$el.style('border', `1px solid ${contrastBorderColor}`); + } else { + this.$el.style({ + 'border': borderColor ? `solid ${borderColor}` : 'none', + 'border-width': '1px 0' + }); + } } } @@ -282,4 +296,4 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi delete this.$el; } } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index 010a06285ce..6f1d8608797 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -32,7 +32,7 @@ interface IDebugEditorModelData { const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges; export class DebugEditorModelManager implements IWorkbenchContribution { - static ID = 'breakpointManager'; + static readonly ID = 'breakpointManager'; private modelDataMap: Map; private toDispose: lifecycle.IDisposable[]; diff --git a/src/vs/workbench/parts/debug/browser/debugStatus.ts b/src/vs/workbench/parts/debug/browser/debugStatus.ts index 6e2ef96a9d9..da4007a0a80 100644 --- a/src/vs/workbench/parts/debug/browser/debugStatus.ts +++ b/src/vs/workbench/parts/debug/browser/debugStatus.ts @@ -92,8 +92,13 @@ export class DebugStatus extends Themable implements IStatusbarItem { private setLabel(): void { if (this.label && this.statusBarItem) { const manager = this.debugService.getConfigurationManager(); - const name = manager.selectedName || ''; - this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedLaunch.workspace.name})` : name; + if (manager.selectedName) { + const name = manager.selectedName; + this.statusBarItem.style.display = 'block'; + this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedLaunch.workspace.name})` : name; + } else { + this.statusBarItem.style.display = 'none'; + } } } diff --git a/src/vs/workbench/parts/debug/browser/debugViewlet.ts b/src/vs/workbench/parts/debug/browser/debugViewlet.ts index cdd88383ed6..803cb32cb64 100644 --- a/src/vs/workbench/parts/debug/browser/debugViewlet.ts +++ b/src/vs/workbench/parts/debug/browser/debugViewlet.ts @@ -59,7 +59,6 @@ export class DebugViewlet extends PersistentViewsViewlet { const el = parent.getHTMLElement(); DOM.addClass(el, 'debug-viewlet'); - this.updateBreakpointsMaxSize(); } public focus(): void { @@ -116,6 +115,7 @@ export class DebugViewlet extends PersistentViewsViewlet { // attach event listener to if (panel.id === BREAKPOINTS_VIEW_ID) { this.breakpointView = panel; + this.updateBreakpointsMaxSize(); } else { this.panelListeners.set(panel.id, panel.onDidChange(() => this.updateBreakpointsMaxSize())); } @@ -128,15 +128,17 @@ export class DebugViewlet extends PersistentViewsViewlet { } private updateBreakpointsMaxSize(): void { - // We need to update the breakpoints view since all other views are collapsed #25384 - const allOtherCollapsed = this.views.every(view => !view.isExpanded() || view === this.breakpointView); - this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize; + if (this.breakpointView) { + // We need to update the breakpoints view since all other views are collapsed #25384 + const allOtherCollapsed = this.views.every(view => !view.isExpanded() || view === this.breakpointView); + this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize; + } } } export class FocusVariablesViewAction extends Action { - static ID = 'workbench.debug.action.focusVariablesView'; + static readonly ID = 'workbench.debug.action.focusVariablesView'; static LABEL = nls.localize('debugFocusVariablesView', 'Focus Variables'); constructor(id: string, label: string, @@ -154,7 +156,7 @@ export class FocusVariablesViewAction extends Action { export class FocusWatchViewAction extends Action { - static ID = 'workbench.debug.action.focusWatchView'; + static readonly ID = 'workbench.debug.action.focusWatchView'; static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusWatchView' }, 'Focus Watch'); constructor(id: string, label: string, @@ -172,7 +174,7 @@ export class FocusWatchViewAction extends Action { export class FocusCallStackViewAction extends Action { - static ID = 'workbench.debug.action.focusCallStackView'; + static readonly ID = 'workbench.debug.action.focusCallStackView'; static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusCallStackView' }, 'Focus CallStack'); constructor(id: string, label: string, @@ -190,7 +192,7 @@ export class FocusCallStackViewAction extends Action { export class FocusBreakpointsViewAction extends Action { - static ID = 'workbench.debug.action.focusBreakpointsView'; + static readonly ID = 'workbench.debug.action.focusBreakpointsView'; static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusBreakpointsView' }, 'Focus Breakpoints'); constructor(id: string, label: string, diff --git a/src/vs/workbench/parts/debug/browser/linkDetector.ts b/src/vs/workbench/parts/debug/browser/linkDetector.ts index 650b4fc5319..2f09a546c81 100644 --- a/src/vs/workbench/parts/debug/browser/linkDetector.ts +++ b/src/vs/workbench/parts/debug/browser/linkDetector.ts @@ -11,6 +11,7 @@ import * as nls from 'vs/nls'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; export class LinkDetector { + private static readonly MAX_LENGTH = 500; private static FILE_LOCATION_PATTERNS: RegExp[] = [ // group 0: full path with line and column // group 1: full path without line and column, matched by `*.*` in the end to work only on paths with extensions in the end (s.t. node:10352 would not match) @@ -34,8 +35,11 @@ export class LinkDetector { * If no links were detected, returns the original string. */ public handleLinks(text: string): HTMLElement | string { - let linkContainer: HTMLElement; + if (text.length > LinkDetector.MAX_LENGTH) { + return text; + } + let linkContainer: HTMLElement; for (let pattern of LinkDetector.FILE_LOCATION_PATTERNS) { pattern.lastIndex = 0; // the holy grail of software development let lastMatchIndex = 0; diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 912b7131c86..8be52a3d1f9 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -465,7 +465,7 @@ export interface ILaunch { /** * Opens the launch.json file. Creates if it does not exist. */ - openConfigFile(sideBySide: boolean, type?: string): TPromise; + openConfigFile(sideBySide: boolean, type?: string): TPromise<{ editor: IEditor; configFileCreated: boolean; }>; } // Debug service interfaces @@ -512,7 +512,7 @@ export interface IDebugService { /** * Sets the focused stack frame and evaluates all expressions against the newly focused stack frame, */ - focusStackFrameAndEvaluate(focusedStackFrame: IStackFrame, process?: IProcess, explicit?: boolean): TPromise; + focusStackFrame(focusedStackFrame: IStackFrame, thread?: IThread, process?: IProcess, explicit?: boolean): void; /** * Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes. @@ -520,9 +520,9 @@ export interface IDebugService { addBreakpoints(uri: uri, rawBreakpoints: IRawBreakpoint[]): TPromise; /** - * Updates the breakpoints and notifies the debug adapter of breakpoint changes. + * Updates the breakpoints. */ - updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise; + updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): void; /** * Enables or disables all breakpoints. If breakpoint is passed only enables or disables the passed breakpoint. @@ -577,12 +577,12 @@ export interface IDebugService { /** * Adds a new watch expression and evaluates it against the debug adapter. */ - addWatchExpression(name?: string): TPromise; + addWatchExpression(name?: string): void; /** * Renames a watch expression and evaluates it against the debug adapter. */ - renameWatchExpression(id: string, newName: string): TPromise; + renameWatchExpression(id: string, newName: string): void; /** * Moves a watch expression to a new possition. Used for reordering watch expressions. @@ -594,11 +594,6 @@ export interface IDebugService { */ removeWatchExpressions(id?: string): void; - /** - * Evaluates all watch expression. - */ - evaluateWatchExpressions(): TPromise; - /** * Starts debugging. If the configOrName is not passed uses the selected configuration in the debug dropdown. * Also saves all files, manages if compounds are present in the configuration diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index c1ecd12ab90..2923456a42c 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -852,6 +852,7 @@ export class Model implements IModel { const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop(); return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : d.default); }); + this._onDidChangeBreakpoints.fire(); } } @@ -1046,45 +1047,18 @@ export class Model implements IModel { return this.watchExpressions; } - public addWatchExpression(process: IProcess, stackFrame: IStackFrame, name: string): TPromise { + public addWatchExpression(process: IProcess, stackFrame: IStackFrame, name: string): void { const we = new Expression(name); this.watchExpressions.push(we); - if (!name) { - this._onDidChangeWatchExpressions.fire(we); - return TPromise.as(null); - } - - return this.evaluateWatchExpressions(process, stackFrame, we.getId()); + this._onDidChangeWatchExpressions.fire(we); } - public renameWatchExpression(process: IProcess, stackFrame: IStackFrame, id: string, newName: string): TPromise { + public renameWatchExpression(process: IProcess, stackFrame: IStackFrame, id: string, newName: string): void { const filtered = this.watchExpressions.filter(we => we.getId() === id); if (filtered.length === 1) { filtered[0].name = newName; - // Evaluate all watch expressions again since the new watch expression might have changed some. - return this.evaluateWatchExpressions(process, stackFrame).then(() => { - this._onDidChangeWatchExpressions.fire(filtered[0]); - }); + this._onDidChangeWatchExpressions.fire(filtered[0]); } - - return TPromise.as(null); - } - - public evaluateWatchExpressions(process: IProcess, stackFrame: IStackFrame, id: string = null): TPromise { - if (id) { - const filtered = this.watchExpressions.filter(we => we.getId() === id); - if (filtered.length !== 1) { - return TPromise.as(null); - } - - return filtered[0].evaluate(process, stackFrame, 'watch').then(() => { - this._onDidChangeWatchExpressions.fire(filtered[0]); - }); - } - - return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => { - this._onDidChangeWatchExpressions.fire(); - }); } public removeWatchExpressions(id: string = null): void { diff --git a/src/vs/workbench/parts/debug/common/debugViewModel.ts b/src/vs/workbench/parts/debug/common/debugViewModel.ts index 8cf6ba7bc33..7e877a1ebb1 100644 --- a/src/vs/workbench/parts/debug/common/debugViewModel.ts +++ b/src/vs/workbench/parts/debug/common/debugViewModel.ts @@ -10,6 +10,7 @@ export class ViewModel implements debug.IViewModel { private _focusedStackFrame: debug.IStackFrame; private _focusedProcess: debug.IProcess; + private _focusedThread: debug.IThread; private selectedExpression: debug.IExpression; private selectedFunctionBreakpoint: debug.IFunctionBreakpoint; private _onDidFocusProcess: Emitter; @@ -40,13 +41,19 @@ export class ViewModel implements debug.IViewModel { return this._focusedStackFrame; } - public setFocusedStackFrame(stackFrame: debug.IStackFrame, process: debug.IProcess, explicit: boolean): void { - this._focusedStackFrame = stackFrame; - if (process !== this._focusedProcess) { + public setFocus(stackFrame: debug.IStackFrame, thread: debug.IThread, process: debug.IProcess, explicit: boolean): void { + let shouldEmit = this._focusedProcess !== process || this._focusedThread !== thread || this._focusedStackFrame !== stackFrame; + + if (this._focusedProcess !== process) { this._focusedProcess = process; this._onDidFocusProcess.fire(process); } - this._onDidFocusStackFrame.fire({ stackFrame, explicit }); + this._focusedThread = thread; + this._focusedStackFrame = stackFrame; + + if (shouldEmit) { + this._onDidFocusStackFrame.fire({ stackFrame, explicit }); + } } public get onDidFocusProcess(): Event { diff --git a/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts b/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts index 1edf13d6ec5..60a3941b1cb 100644 --- a/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/baseDebugView.ts @@ -147,7 +147,7 @@ export function renderRenameBox(debugService: IDebugService, contextViewService: if (!disposed) { disposed = true; if (element instanceof Expression && renamed && inputBox.value) { - debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, onUnexpectedError); + debugService.renameWatchExpression(element.getId(), inputBox.value); } else if (element instanceof Expression && !element.name) { debugService.removeWatchExpressions(element.getId()); } else if (element instanceof FunctionBreakpoint && inputBox.value) { @@ -161,7 +161,8 @@ export function renderRenameBox(debugService: IDebugService, contextViewService: // if everything went fine we need to refresh ui elements since the variable update can change watch and variables view .done(() => { tree.refresh(element, false); - debugService.evaluateWatchExpressions(); + // Need to force watch expressions to update since a variable change can have an effect on watches + debugService.focusStackFrame(debugService.getViewModel().focusedStackFrame); }, onUnexpectedError); } } diff --git a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts index 04d49976b8c..de6a551619b 100644 --- a/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/breakpointsView.ts @@ -267,7 +267,7 @@ class BreakpointsRenderer implements IRenderer { if (event && event.payload && event.payload.origin === 'keyboard') { const element = this.tree.getFocus(); - if (element instanceof ThreadAndProcessIds) { - controller.showMoreStackFrames(this.tree, element); - } else if (element instanceof StackFrame) { - controller.focusStackFrame(element, event, false); - } + controller.handleSelectionChange(this.tree, element, false, event); } })); @@ -184,16 +180,10 @@ export class CallStackView extends TreeViewsViewletPanel { class CallStackController extends BaseDebugController { protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean { - if (element instanceof ThreadAndProcessIds) { - return this.showMoreStackFrames(tree, element); - } - if (element instanceof StackFrame) { - super.onLeftClick(tree, element, event); - this.focusStackFrame(element, event, event.detail !== 2); - return true; - } + super.onLeftClick(tree, element, event); + this.handleSelectionChange(tree, element, event.detail !== 2, event); - return super.onLeftClick(tree, element, event); + return true; } protected getContext(element: any): any { @@ -221,11 +211,21 @@ class CallStackController extends BaseDebugController { return true; } - public focusStackFrame(stackFrame: IStackFrame, event: any, preserveFocus: boolean): void { - this.debugService.focusStackFrameAndEvaluate(stackFrame, undefined, true).then(() => { + public handleSelectionChange(tree: ITree, element: any, preserveFocus: boolean, event: any): void { + if (element instanceof StackFrame) { + this.debugService.focusStackFrame(element, element.thread, element.thread.process, true); const sideBySide = (event && (event.ctrlKey || event.metaKey)); - return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide); - }, errors.onUnexpectedError); + element.openInEditor(this.editorService, preserveFocus, sideBySide).done(undefined, errors.onUnexpectedError); + } + if (element instanceof Thread) { + this.debugService.focusStackFrame(undefined, element, element.process, true); + } + if (element instanceof Process) { + this.debugService.focusStackFrame(undefined, undefined, element, true); + } + if (element instanceof ThreadAndProcessIds) { + this.showMoreStackFrames(tree, element); + } } } 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 b393541ca3f..043476e62ce 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -107,10 +107,10 @@ Registry.as(PanelExtensions.Panels).registerPanel(new PanelDescri Registry.as(PanelExtensions.Panels).setDefaultPanelId(REPL_ID); // Register default debug views -ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, size: 40, location: ViewLocation.Debug, canToggleVisibility: true }]); -ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, size: 10, location: ViewLocation.Debug, canToggleVisibility: true }]); -ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, size: 30, location: ViewLocation.Debug, canToggleVisibility: true }]); -ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, size: 20, location: ViewLocation.Debug, canToggleVisibility: true }]); +ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, weight: 40, location: ViewLocation.Debug, canToggleVisibility: true }]); +ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, weight: 10, location: ViewLocation.Debug, canToggleVisibility: true }]); +ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, weight: 30, location: ViewLocation.Debug, canToggleVisibility: true }]); +ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, weight: 20, location: ViewLocation.Debug, canToggleVisibility: true }]); // register action to open viewlet const registry = Registry.as(WorkbenchActionRegistryExtensions.WorkbenchActions); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts index 689ba933e35..37e840a1666 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugCommands.ts @@ -170,9 +170,9 @@ export function registerCommands(): void { } const launch = manager.getLaunches().filter(l => l.workspace.uri.toString() === workspaceUri).pop() || manager.selectedLaunch; - return launch.openConfigFile(false).done(editor => { - if (editor) { - const codeEditor = editor.getControl(); + return launch.openConfigFile(false).done(result => { + if (result.editor && !result.configFileCreated) { + const codeEditor = result.editor.getControl(); if (codeEditor) { return codeEditor.getContribution(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration(); } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index 452bd2314cb..b78b1a5b0f0 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -380,8 +380,8 @@ export class ConfigurationManager implements IConfigurationManager { public canSetBreakpointsIn(model: IModel): boolean { const modeId = model ? model.getLanguageIdentifier().language : null; - if (!modeId || modeId === 'jsonc') { - // do not allow breakpoints in our settings files + if (!modeId || modeId === 'jsonc' || modeId === 'log') { + // do not allow breakpoints in our settings files and output return false; } if (this.configurationService.getValue('debug').allowBreakpointsEverywhere) { @@ -515,7 +515,7 @@ class Launch implements ILaunch { return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') }); } - public openConfigFile(sideBySide: boolean, type?: string): TPromise { + public openConfigFile(sideBySide: boolean, type?: string): TPromise<{ editor: IEditor; configFileCreated: boolean; }> { const resource = this.uri; let configFileCreated = false; @@ -545,7 +545,7 @@ class Launch implements ILaunch { }); }).then(content => { if (!content) { - return undefined; + return { editor: undefined, configFileCreated }; } const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`); let startLineNumber = 1; @@ -564,7 +564,7 @@ class Launch implements ILaunch { pinned: configFileCreated, // pin only if config file is created #8727 revealIfVisible: true }, - }, sideBySide); + }, sideBySide).then(editor => ({ editor, configFileCreated })); }, (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/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index f12f16ae613..cc1d68b69f7 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -592,11 +592,14 @@ export class DebugEditorContribution implements IDebugEditorContribution { model.forceTokenization(lineNumber); const lineTokens = model.getLineTokens(lineNumber); - for (let token = lineTokens.firstToken(); !!token; token = token.next()) { - const tokenStr = lineContent.substring(token.startOffset, token.endOffset); + for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) { + const tokenStartOffset = lineTokens.getStartOffset(tokenIndex); + const tokenEndOffset = lineTokens.getEndOffset(tokenIndex); + const tokenType = lineTokens.getStandardTokenType(tokenIndex); + const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset); // Token is a word and not a comment - if (token.tokenType === StandardTokenType.Other) { + if (tokenType === StandardTokenType.Other) { DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr); @@ -606,7 +609,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { this.wordToLineNumbersMap.set(word, []); } - this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, token.startOffset)); + this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, tokenStartOffset)); } } } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 59f9d612c4d..26eadcb5141 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -252,7 +252,7 @@ export class DebugService implements debug.IDebugService { return TPromise.as(null); } - this.focusStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError); + this.focusStackFrame(stackFrameToFocus); if (thread.stoppedDetails) { this.windowService.focusWindow(); aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", thread.stoppedDetails.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.range.startLineNumber)); @@ -320,7 +320,7 @@ export class DebugService implements debug.IDebugService { const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId; this.model.clearThreads(session.getId(), false, threadId); if (this.viewModel.focusedProcess.getId() === session.getId()) { - this.focusStackFrameAndEvaluate(null, this.viewModel.focusedProcess).done(null, errors.onUnexpectedError); + this.focusStackFrame(undefined, this.viewModel.focusedThread, this.viewModel.focusedProcess); } this.updateStateAndEmit(session.getId(), debug.State.Running); })); @@ -534,21 +534,34 @@ export class DebugService implements debug.IDebugService { } } - public focusStackFrameAndEvaluate(stackFrame: debug.IStackFrame, process?: debug.IProcess, explicit?: boolean): TPromise { + public focusStackFrame(stackFrame: debug.IStackFrame, thread?: debug.IThread, process?: debug.IProcess, explicit?: boolean): void { if (!process) { - const processes = this.model.getProcesses(); - process = stackFrame ? stackFrame.thread.process : processes.length ? processes[0] : null; + if (stackFrame || thread) { + process = stackFrame ? stackFrame.thread.process : thread.process; + } else { + const processes = this.model.getProcesses(); + process = processes.length ? processes[0] : undefined; + } } + + if (!thread) { + if (stackFrame) { + thread = stackFrame.thread; + } else { + const threads = process ? process.getAllThreads() : undefined; + thread = threads && threads.length ? threads[0] : undefined; + } + } + if (!stackFrame) { - const threads = process ? process.getAllThreads() : null; - const callStack = threads && threads.length === 1 ? threads[0].getCallStack() : null; - stackFrame = callStack && callStack.length ? callStack[0] : null; + if (thread) { + const callStack = thread.getCallStack(); + stackFrame = callStack && callStack.length ? callStack[0] : null; + } } - this.viewModel.setFocusedStackFrame(stackFrame, process, explicit); + this.viewModel.setFocus(stackFrame, thread, process, explicit); this.updateStateAndEmit(); - - return this.model.evaluateWatchExpressions(process, stackFrame); } public enableOrDisableBreakpoints(enable: boolean, breakpoint?: debug.IEnablement): TPromise { @@ -574,9 +587,9 @@ export class DebugService implements debug.IDebugService { return this.sendBreakpoints(uri); } - public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise { + public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): void { this.model.updateBreakpoints(data); - return this.sendBreakpoints(uri); + this.breakpointsToSendOnResourceSaved.add(uri.toString()); } public removeBreakpoints(id?: string): TPromise { @@ -612,7 +625,7 @@ export class DebugService implements debug.IDebugService { public addReplExpression(name: string): TPromise { return this.model.addReplExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name) // Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some. - .then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame, this.viewModel.focusedProcess)); + .then(() => this.focusStackFrame(this.viewModel.focusedStackFrame, this.viewModel.focusedThread, this.viewModel.focusedProcess)); } public removeReplExpressions(): void { @@ -628,11 +641,11 @@ export class DebugService implements debug.IDebugService { } } - public addWatchExpression(name: string): TPromise { + public addWatchExpression(name: string): void { return this.model.addWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name); } - public renameWatchExpression(id: string, newName: string): TPromise { + public renameWatchExpression(id: string, newName: string): void { return this.model.renameWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, id, newName); } @@ -644,10 +657,6 @@ export class DebugService implements debug.IDebugService { this.model.removeWatchExpressions(id); } - public evaluateWatchExpressions(): TPromise { - return this.model.evaluateWatchExpressions(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame); - } - public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false, topCompoundName?: string): TPromise { // make sure to save all files and that the configuration is up to date @@ -720,7 +729,7 @@ export class DebugService implements debug.IDebugService { return this.createProcess(root, config, sessionId); } if (launch) { - return launch.openConfigFile(false, type).then(editor => undefined); + return launch.openConfigFile(false, type).then(() => undefined); } return undefined; @@ -797,8 +806,8 @@ export class DebugService implements debug.IDebugService { return undefined; } - return this.configurationManager.selectedLaunch.openConfigFile(false).then(openend => { - if (openend) { + return this.configurationManager.selectedLaunch.openConfigFile(false).then(result => { + if (result.configFileCreated) { this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application. {0}", err.message)); } return undefined; @@ -870,8 +879,8 @@ export class DebugService implements debug.IDebugService { if (session.disconnected) { return TPromise.as(null); } + this.focusStackFrame(undefined, undefined, process); this._onDidNewProcess.fire(process); - this.focusStackFrameAndEvaluate(null, process); const internalConsoleOptions = configuration.internalConsoleOptions || this.configurationService.getValue('debug').internalConsoleOptions; if (internalConsoleOptions === 'openOnSessionStart' || (this.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) { @@ -1012,45 +1021,43 @@ export class DebugService implements debug.IDebugService { } public restartProcess(process: debug.IProcess, restartData?: any): TPromise { - return this.textFileService.saveAll().then(() => { - if (process.session.capabilities.supportsRestartRequest) { - return process.session.custom('restart', null); - } - const focusedProcess = this.viewModel.focusedProcess; - const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId(); + if (process.session.capabilities.supportsRestartRequest) { + return process.session.custom('restart', null); + } + const focusedProcess = this.viewModel.focusedProcess; + const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId(); - return process.session.disconnect(true).then(() => { - if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) { - return this.broadcastService.broadcast({ - channel: EXTENSION_RELOAD_BROADCAST_CHANNEL, - payload: [process.session.root.uri.fsPath] - }); - } - - return new TPromise((c, e) => { - setTimeout(() => { - // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration - let config = process.configuration; - if (this.launchJsonChanged && this.configurationManager.selectedLaunch) { - this.launchJsonChanged = false; - config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config; - // Take the type from the process since the debug extension might overwrite it #21316 - config.type = process.configuration.type; - config.noDebug = process.configuration.noDebug; - } - config.__restart = restartData; - this.createProcess(process.session.root, config, process.getId()).then(() => c(null), err => e(err)); - }, 300); + return process.session.disconnect(true).then(() => { + if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) { + return this.broadcastService.broadcast({ + channel: EXTENSION_RELOAD_BROADCAST_CHANNEL, + payload: [process.session.root.uri.fsPath] }); - }).then(() => { - if (preserveFocus) { - // Restart should preserve the focused process - const restartedProcess = this.model.getProcesses().filter(p => p.configuration.name === process.configuration.name).pop(); - if (restartedProcess && restartedProcess !== this.viewModel.focusedProcess) { - this.focusStackFrameAndEvaluate(null, restartedProcess); + } + + return new TPromise((c, e) => { + setTimeout(() => { + // Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration + let config = process.configuration; + if (this.launchJsonChanged && this.configurationManager.selectedLaunch) { + this.launchJsonChanged = false; + config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config; + // Take the type from the process since the debug extension might overwrite it #21316 + config.type = process.configuration.type; + config.noDebug = process.configuration.noDebug; } - } + config.__restart = restartData; + this.startDebugging(process.session.root, config).then(() => c(null), err => e(err)); + }, 300); }); + }).then(() => { + if (preserveFocus) { + // Restart should preserve the focused process + const restartedProcess = this.model.getProcesses().filter(p => p.configuration.name === process.configuration.name).pop(); + if (restartedProcess && restartedProcess !== this.viewModel.focusedProcess) { + this.focusStackFrame(undefined, undefined, restartedProcess); + } + } }); } @@ -1098,7 +1105,7 @@ export class DebugService implements debug.IDebugService { this.toDisposeOnSessionEnd.set(session.getId(), lifecycle.dispose(this.toDisposeOnSessionEnd.get(session.getId()))); const focusedProcess = this.viewModel.focusedProcess; if (focusedProcess && focusedProcess.getId() === session.getId()) { - this.focusStackFrameAndEvaluate(null).done(null, errors.onUnexpectedError); + this.focusStackFrame(null); } this.updateStateAndEmit(session.getId(), debug.State.Inactive); @@ -1146,11 +1153,6 @@ export class DebugService implements debug.IDebugService { if (!session.readyForBreakpoints) { return TPromise.as(null); } - if (this.textFileService.isDirty(modelUri)) { - // Only send breakpoints for a file once it is not dirty #8077 - this.breakpointsToSendOnResourceSaved.add(modelUri.toString()); - return TPromise.as(null); - } const breakpointsToSend = this.model.getBreakpoints().filter(bp => this.model.areBreakpointsActivated() && bp.enabled && bp.uri.toString() === modelUri.toString()); diff --git a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts b/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts index eae64f5c6c4..96e069a9a59 100644 --- a/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts +++ b/src/vs/workbench/parts/debug/electron-browser/electronDebugActions.ts @@ -13,7 +13,7 @@ import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debu import { clipboard } from 'electron'; export class CopyValueAction extends Action { - static ID = 'workbench.debug.viewlet.action.copyValue'; + static readonly ID = 'workbench.debug.viewlet.action.copyValue'; static LABEL = nls.localize('copyValue', "Copy Value"); constructor(id: string, label: string, private value: any, @IDebugService private debugService: IDebugService) { @@ -35,7 +35,7 @@ export class CopyValueAction extends Action { } export class CopyAction extends Action { - static ID = 'workbench.debug.action.copy'; + static readonly ID = 'workbench.debug.action.copy'; static LABEL = nls.localize('copy', "Copy"); public run(): TPromise { @@ -45,7 +45,7 @@ export class CopyAction extends Action { } export class CopyAllAction extends Action { - static ID = 'workbench.debug.action.copyAll'; + static readonly ID = 'workbench.debug.action.copyAll'; static LABEL = nls.localize('copyAll', "Copy All"); constructor(id: string, label: string, private tree: ITree) { @@ -69,7 +69,7 @@ export class CopyAllAction extends Action { } export class CopyStackTraceAction extends Action { - static ID = 'workbench.action.debug.copyStackTrace'; + static readonly ID = 'workbench.action.debug.copyStackTrace'; static LABEL = nls.localize('copyStackTrace', "Copy Call Stack"); public run(frame: IStackFrame): TPromise { diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 81264733319..86bd470365c 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -118,7 +118,7 @@ export class Repl extends Panel implements IPrivateReplService { this.refreshTimeoutHandle = null; const previousScrollPosition = this.tree.getScrollPosition(); this.tree.refresh().then(() => { - if (previousScrollPosition === 1 || previousScrollPosition === 0) { + if (previousScrollPosition === 1) { // Only scroll if we were scrolled all the way down before tree refreshed #10486 this.tree.setScrollPosition(1); } diff --git a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts index aba0c419a6d..10832f32bcc 100644 --- a/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts +++ b/src/vs/workbench/parts/debug/electron-browser/watchExpressionsView.ts @@ -39,7 +39,6 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { private static readonly MEMENTO = 'watchexpressionsview.memento'; private onWatchExpressionsUpdatedScheduler: RunOnceScheduler; - private toReveal: IExpression; private settings: any; private needsRefresh: boolean; @@ -56,18 +55,9 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService); this.settings = options.viewletSettings; - this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => { - // only expand when a new watch expression is added. - if (we instanceof Expression) { - this.setExpanded(true); - } - })); - this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => { this.needsRefresh = false; - this.tree.refresh().done(() => { - return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true); - }, errors.onUnexpectedError); + this.tree.refresh().done(undefined, errors.onUnexpectedError); }, 50); } @@ -77,7 +67,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { const actionProvider = new WatchExpressionsActionProvider(this.debugService, this.keybindingService); this.tree = new WorkbenchTree(this.treeContainer, { - dataSource: new WatchExpressionsDataSource(), + dataSource: new WatchExpressionsDataSource(this.debugService), renderer: this.instantiationService.createInstance(WatchExpressionsRenderer), accessibilityProvider: new WatchExpressionsAccessibilityProvider(), controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext), @@ -103,10 +93,19 @@ export class WatchExpressionsView extends TreeViewsViewletPanel { return; } + this.tree.refresh().done(() => { + return we instanceof Expression ? this.tree.reveal(we) : TPromise.as(true); + }, errors.onUnexpectedError); + })); + this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => { + if (!this.isExpanded() || !this.isVisible()) { + this.needsRefresh = true; + return; + } + if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) { this.onWatchExpressionsUpdatedScheduler.schedule(); } - this.toReveal = we; })); this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => { @@ -200,6 +199,10 @@ class WatchExpressionsActionProvider implements IActionProvider { class WatchExpressionsDataSource implements IDataSource { + constructor(private debugService: IDebugService) { + // noop + } + public getId(tree: ITree, element: any): string { return element.getId(); } @@ -215,7 +218,8 @@ class WatchExpressionsDataSource implements IDataSource { public getChildren(tree: ITree, element: any): TPromise { if (element instanceof Model) { - return TPromise.as((element).getWatchExpressions()); + const viewModel = this.debugService.getViewModel(); + return TPromise.join(element.getWatchExpressions().map(we => we.evaluate(viewModel.focusedProcess, viewModel.focusedStackFrame, 'watch').then(() => we))); } let expression = element; @@ -344,6 +348,10 @@ class WatchExpressionsController extends BaseDebugController { const expression = element; this.debugService.getViewModel().setSelectedExpression(expression); return true; + } else if (element instanceof Model && event.detail === 2) { + // Double click in watch panel triggers to add a new watch expression + this.debugService.addWatchExpression(); + return true; } return super.onLeftClick(tree, element, event); diff --git a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts index cd85afc69dd..ff921163c20 100644 --- a/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts +++ b/src/vs/workbench/parts/debug/test/common/debugViewModel.test.ts @@ -26,7 +26,7 @@ suite('Debug - View Model', () => { const process = new Process({ name: 'mockProcess', type: 'node', request: 'launch' }, mockSession); const thread = new Thread(process, 'myThread', 1); const frame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startColumn: 1, startLineNumber: 1, endColumn: undefined, endLineNumber: undefined }, 0); - model.setFocusedStackFrame(frame, process, false); + model.setFocus(frame, thread, process, false); assert.equal(model.focusedStackFrame.getId(), frame.getId()); assert.equal(model.focusedThread.threadId, 1); diff --git a/src/vs/workbench/parts/debug/test/common/mockDebug.ts b/src/vs/workbench/parts/debug/test/common/mockDebug.ts index 1f2688168f5..7d722ef668d 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebug.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebug.ts @@ -36,17 +36,14 @@ export class MockDebugService implements debug.IDebugService { return null; } - public focusStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame): TPromise { - return TPromise.as(null); + public focusStackFrame(focusedStackFrame: debug.IStackFrame): void { } public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise { return TPromise.as(null); } - public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise { - return TPromise.as(null); - } + public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): void { } public enableOrDisableBreakpoints(enabled: boolean): TPromise { return TPromise.as(null); @@ -88,10 +85,6 @@ export class MockDebugService implements debug.IDebugService { public removeWatchExpressions(id?: string): void { } - public evaluateWatchExpressions(): TPromise { - return TPromise.as(null); - } - public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug?: boolean): TPromise { return TPromise.as(null); } diff --git a/src/vs/workbench/parts/debug/test/node/debugModel.test.ts b/src/vs/workbench/parts/debug/test/node/debugModel.test.ts index 99ed1b8a150..b4fc6e4080f 100644 --- a/src/vs/workbench/parts/debug/test/node/debugModel.test.ts +++ b/src/vs/workbench/parts/debug/test/node/debugModel.test.ts @@ -305,16 +305,15 @@ suite('Debug - Model', () => { const process = new Process({ name: 'mockProcess', type: 'node', request: 'launch' }, rawSession); const thread = new Thread(process, 'mockthread', 1); const stackFrame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined, endColumn: undefined }, 0); - model.addWatchExpression(process, stackFrame, 'console').done(); - model.addWatchExpression(process, stackFrame, 'console').done(); + model.addWatchExpression(process, stackFrame, 'console'); + model.addWatchExpression(process, stackFrame, 'console'); let watchExpressions = model.getWatchExpressions(); assertWatchExpressions(watchExpressions, 'console'); - model.renameWatchExpression(process, stackFrame, watchExpressions[0].getId(), 'new_name').done(); - model.renameWatchExpression(process, stackFrame, watchExpressions[1].getId(), 'new_name').done(); + model.renameWatchExpression(process, stackFrame, watchExpressions[0].getId(), 'new_name'); + model.renameWatchExpression(process, stackFrame, watchExpressions[1].getId(), 'new_name'); assertWatchExpressions(model.getWatchExpressions(), 'new_name'); - model.evaluateWatchExpressions(process, null); assertWatchExpressions(model.getWatchExpressions(), 'new_name'); model.addWatchExpression(process, stackFrame, 'mockExpression'); diff --git a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts index 6b63890e976..22f7f41cfba 100644 --- a/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts +++ b/src/vs/workbench/parts/execution/electron-browser/execution.contribution.ts @@ -8,19 +8,12 @@ import * as nls from 'vs/nls'; import * as env from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; import { Registry } from 'vs/platform/registry/common/platform'; -import { IAction, Action } from 'vs/base/common/actions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import paths = require('vs/base/common/paths'); -import resources = require('vs/base/common/resources'); -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions'; import uri from 'vs/base/common/uri'; -import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ITerminalService } from 'vs/workbench/parts/execution/common/execution'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { toResource } from 'vs/workbench/common/editor'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; @@ -29,6 +22,9 @@ import { ITerminalService as IIntegratedTerminalService, KEYBINDING_CONTEXT_TERM import { DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX, ITerminalConfiguration } from 'vs/workbench/parts/execution/electron-browser/terminal'; import { WinTerminalService, MacTerminalService, LinuxTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { IFileService } from 'vs/platform/files/common/files'; if (env.isWindows) { registerSingleton(ITerminalService, WinTerminalService); @@ -78,152 +74,68 @@ DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => { }); }); +const OPEN_CONSOLE_COMMAND_ID = 'workbench.command.terminal.openNativeConsole'; -export abstract class AbstractOpenInTerminalAction extends Action { - private resource: uri; - - constructor( - id: string, - label: string, - @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IHistoryService protected historyService: IHistoryService - ) { - super(id, label); - - this.order = 49; // Allow other actions to position before or after - } - - public setResource(resource: uri): void { - this.resource = resource; - this.enabled = !paths.isUNC(this.resource.fsPath); - } - - public getPathToOpen(): string { - let pathToOpen: string; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: OPEN_CONSOLE_COMMAND_ID, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C, + when: KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + handler: (accessor, resource: uri) => { + const configurationService = accessor.get(IConfigurationService); + const historyService = accessor.get(IHistoryService); + const editorService = accessor.get(IWorkbenchEditorService); + const fileService = accessor.get(IFileService); + const integratedTerminalService = accessor.get(IIntegratedTerminalService); + const terminalService = accessor.get(ITerminalService); // Try workspace path first - const root = this.historyService.getLastActiveWorkspaceRoot('file'); - pathToOpen = this.resource ? this.resource.fsPath : (root && root.fsPath); + const root = historyService.getLastActiveWorkspaceRoot('file'); + return !resource ? TPromise.as(root && root.fsPath) : fileService.resolveFile(resource).then(stat => { + return stat.isDirectory ? stat.resource.fsPath : paths.dirname(stat.resource.fsPath); + }).then(directoryToOpen => { - // Otherwise check if we have an active file open - if (!pathToOpen) { - const file = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); - if (file) { - pathToOpen = paths.dirname(file.fsPath); // take parent folder of file + // Otherwise check if we have an active file open + if (!directoryToOpen) { + const file = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); + if (file) { + directoryToOpen = paths.dirname(file.fsPath); // take parent folder of file + } } - } - return pathToOpen; + if (configurationService.getValue().terminal.explorerKind === 'integrated') { + const instance = integratedTerminalService.createInstance({ cwd: directoryToOpen }, true); + if (instance) { + integratedTerminalService.setActiveInstance(instance); + integratedTerminalService.showPanel(true); + } + } else { + terminalService.openTerminal(directoryToOpen); + } + }); } -} +}); -export class OpenConsoleAction extends AbstractOpenInTerminalAction { +const openConsoleCommand = { + id: OPEN_CONSOLE_COMMAND_ID, + title: env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") : + nls.localize('scopedConsoleActionMacLinux', "Open in Terminal") +}; - public static readonly ID = 'workbench.action.terminal.openNativeConsole'; - public static readonly Label = env.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") : - nls.localize('globalConsoleActionMacLinux', "Open New Terminal"); - public static readonly ScopedLabel = env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") : - nls.localize('scopedConsoleActionMacLinux', "Open in Terminal"); +MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: openConsoleCommand +}); - constructor( - id: string, - label: string, - @ITerminalService private terminalService: ITerminalService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IHistoryService historyService: IHistoryService - ) { - super(id, label, editorService, contextService, historyService); - } +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 30, + command: openConsoleCommand, + when: ResourceContextKey.Scheme.isEqualTo('file') +}); - public run(event?: any): TPromise { - let pathToOpen = this.getPathToOpen(); - this.terminalService.openTerminal(pathToOpen); - - return TPromise.as(null); - } -} - -export class OpenIntegratedTerminalAction extends AbstractOpenInTerminalAction { - - public static readonly ID = 'workbench.action.terminal.openFolderInIntegratedTerminal'; - public static readonly Label = nls.localize('openFolderInIntegratedTerminal', "Open in Terminal"); - - constructor( - id: string, - label: string, - @IIntegratedTerminalService private integratedTerminalService: IIntegratedTerminalService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IHistoryService historyService: IHistoryService - ) { - super(id, label, editorService, contextService, historyService); - } - - public run(event?: any): TPromise { - let pathToOpen = this.getPathToOpen(); - - const instance = this.integratedTerminalService.createInstance({ cwd: pathToOpen }, true); - if (instance) { - this.integratedTerminalService.setActiveInstance(instance); - this.integratedTerminalService.showPanel(true); - } - return TPromise.as(null); - } -} - -export class ExplorerViewerActionContributor extends ActionBarContributor { - - constructor( - @IInstantiationService private instantiationService: IInstantiationService, - @IConfigurationService private configurationService: IConfigurationService - ) { - super(); - } - - public hasSecondaryActions(context: any): boolean { - const fileResource = explorerItemToFileResource(context.element); - return fileResource && fileResource.resource.scheme === 'file'; - } - - public getSecondaryActions(context: any): IAction[] { - let fileResource = explorerItemToFileResource(context.element); - let resource = fileResource.resource; - - // We want the parent unless this resource is a directory - if (!fileResource.isDirectory) { - resource = resources.dirname(resource); - } - - const configuration = this.configurationService.getValue(); - const explorerKind = configuration.terminal.explorerKind; - - if (explorerKind === 'integrated') { - let action = this.instantiationService.createInstance(OpenIntegratedTerminalAction, OpenIntegratedTerminalAction.ID, OpenIntegratedTerminalAction.Label); - action.setResource(resource); - - return [action]; - } else { - let action = this.instantiationService.createInstance(OpenConsoleAction, OpenConsoleAction.ID, OpenConsoleAction.ScopedLabel); - action.setResource(resource); - - return [action]; - } - } -} - -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActionContributor); - -// Register Global Action to Open Console -Registry.as(ActionExtensions.WorkbenchActions).registerWorkbenchAction( - new SyncActionDescriptor( - OpenConsoleAction, - OpenConsoleAction.ID, - OpenConsoleAction.Label, - { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C }, - KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED - ), - env.isWindows ? 'Open New Command Prompt' : 'Open New Terminal' -); +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 30, + command: openConsoleCommand, + when: ResourceContextKey.Scheme.isEqualTo('file') +}); diff --git a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts index af0184e53df..95a51e0fadc 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionEditor.ts @@ -67,10 +67,13 @@ function renderBody(body: string): string { - + - ${body} + + + ${body} + `; } diff --git a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts index 677b4981ba6..fa54bc803d7 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsActions.ts @@ -38,12 +38,12 @@ import { Color } from 'vs/base/common/color'; import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing'; import { ITextEditorSelection } from 'vs/platform/editor/common/editor'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions'; import Severity from 'vs/base/common/severity'; import { PagedModel } from 'vs/base/common/paging'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; export class InstallAction extends Action { @@ -350,7 +350,7 @@ export class DropDownMenuActionItem extends ActionItem { export class ManageExtensionAction extends Action { - static ID = 'extensions.manage'; + static readonly ID = 'extensions.manage'; private static readonly Class = 'extension-action manage'; private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`; @@ -413,7 +413,7 @@ export class ManageExtensionAction extends Action { export class EnableForWorkspaceAction extends Action implements IExtensionAction { - static ID = 'extensions.enableForWorkspace'; + static readonly ID = 'extensions.enableForWorkspace'; static LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)"); private disposables: IDisposable[] = []; @@ -453,7 +453,7 @@ export class EnableForWorkspaceAction extends Action implements IExtensionAction export class EnableGloballyAction extends Action implements IExtensionAction { - static ID = 'extensions.enableGlobally'; + static readonly ID = 'extensions.enableGlobally'; static LABEL = localize('enableGloballyAction', "Enable"); private disposables: IDisposable[] = []; @@ -491,7 +491,7 @@ export class EnableGloballyAction extends Action implements IExtensionAction { export class EnableAction extends Action { - static ID = 'extensions.enable'; + static readonly ID = 'extensions.enable'; private static readonly EnabledClass = 'extension-action prominent enable'; private static readonly DisabledClass = `${EnableAction.EnabledClass} disabled`; @@ -549,7 +549,7 @@ export class EnableAction extends Action { export class DisableForWorkspaceAction extends Action implements IExtensionAction { - static ID = 'extensions.disableForWorkspace'; + static readonly ID = 'extensions.disableForWorkspace'; static LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)"); private disposables: IDisposable[] = []; @@ -588,7 +588,7 @@ export class DisableForWorkspaceAction extends Action implements IExtensionActio export class DisableGloballyAction extends Action implements IExtensionAction { - static ID = 'extensions.disableGlobally'; + static readonly ID = 'extensions.disableGlobally'; static LABEL = localize('disableGloballyAction', "Disable"); private disposables: IDisposable[] = []; @@ -625,7 +625,7 @@ export class DisableGloballyAction extends Action implements IExtensionAction { export class DisableAction extends Action { - static ID = 'extensions.disable'; + static readonly ID = 'extensions.disable'; private static readonly EnabledClass = 'extension-action disable'; private static readonly DisabledClass = `${DisableAction.EnabledClass} disabled`; @@ -680,7 +680,7 @@ export class DisableAction extends Action { export class CheckForUpdatesAction extends Action { - static ID = 'workbench.extensions.action.checkForUpdates'; + static readonly ID = 'workbench.extensions.action.checkForUpdates'; static LABEL = localize('checkForUpdates', "Check for Updates"); constructor( @@ -720,7 +720,7 @@ export class ToggleAutoUpdateAction extends Action { export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { - static ID = 'workbench.extensions.action.enableAutoUpdate'; + static readonly ID = 'workbench.extensions.action.enableAutoUpdate'; static LABEL = localize('enableAutoUpdate', "Enable Auto Updating Extensions"); constructor( @@ -734,7 +734,7 @@ export class EnableAutoUpdateAction extends ToggleAutoUpdateAction { export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { - static ID = 'workbench.extensions.action.disableAutoUpdate'; + static readonly ID = 'workbench.extensions.action.disableAutoUpdate'; static LABEL = localize('disableAutoUpdate', "Disable Auto Updating Extensions"); constructor( @@ -748,7 +748,7 @@ export class DisableAutoUpdateAction extends ToggleAutoUpdateAction { export class UpdateAllAction extends Action { - static ID = 'workbench.extensions.action.updateAllExtensions'; + static readonly ID = 'workbench.extensions.action.updateAllExtensions'; static LABEL = localize('updateAll', "Update All Extensions"); private disposables: IDisposable[] = []; @@ -899,7 +899,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction { export class ShowEnabledExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showEnabledExtensions'; + static readonly ID = 'workbench.extensions.action.showEnabledExtensions'; static LABEL = localize('showEnabledExtensions', 'Show Enabled Extensions'); constructor( @@ -922,7 +922,7 @@ export class ShowEnabledExtensionsAction extends Action { export class ShowInstalledExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showInstalledExtensions'; + static readonly ID = 'workbench.extensions.action.showInstalledExtensions'; static LABEL = localize('showInstalledExtensions', "Show Installed Extensions"); constructor( @@ -945,7 +945,7 @@ export class ShowInstalledExtensionsAction extends Action { export class ShowDisabledExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showDisabledExtensions'; + static readonly ID = 'workbench.extensions.action.showDisabledExtensions'; static LABEL = localize('showDisabledExtensions', "Show Disabled Extensions"); constructor( @@ -968,7 +968,7 @@ export class ShowDisabledExtensionsAction extends Action { export class ClearExtensionsInputAction extends Action { - static ID = 'workbench.extensions.action.clearExtensionsInput'; + static readonly ID = 'workbench.extensions.action.clearExtensionsInput'; static LABEL = localize('clearExtensionsInput', "Clear Extensions Input"); private disposables: IDisposable[] = []; @@ -1004,7 +1004,7 @@ export class ClearExtensionsInputAction extends Action { export class ShowOutdatedExtensionsAction extends Action { - static ID = 'workbench.extensions.action.listOutdatedExtensions'; + static readonly ID = 'workbench.extensions.action.listOutdatedExtensions'; static LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions"); constructor( @@ -1027,7 +1027,7 @@ export class ShowOutdatedExtensionsAction extends Action { export class ShowPopularExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showPopularExtensions'; + static readonly ID = 'workbench.extensions.action.showPopularExtensions'; static LABEL = localize('showPopularExtensions', "Show Popular Extensions"); constructor( @@ -1050,7 +1050,7 @@ export class ShowPopularExtensionsAction extends Action { export class ShowRecommendedExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showRecommendedExtensions'; + static readonly ID = 'workbench.extensions.action.showRecommendedExtensions'; static LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions"); constructor( @@ -1073,7 +1073,7 @@ export class ShowRecommendedExtensionsAction extends Action { export class InstallWorkspaceRecommendedExtensionsAction extends Action { - static ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; + static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions'; static LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions"); private disposables: IDisposable[] = []; @@ -1150,7 +1150,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action { export class InstallRecommendedExtensionAction extends Action { - static ID = 'workbench.extensions.action.installRecommendedExtension'; + static readonly ID = 'workbench.extensions.action.installRecommendedExtension'; static LABEL = localize('installRecommendedExtension', "Install Recommended Extension"); private extensionId: string; @@ -1200,7 +1200,7 @@ export class InstallRecommendedExtensionAction extends Action { export class ShowRecommendedKeymapExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; + static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions'; static SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps"); constructor( @@ -1223,7 +1223,7 @@ export class ShowRecommendedKeymapExtensionsAction extends Action { export class ShowLanguageExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showLanguageExtensions'; + static readonly ID = 'workbench.extensions.action.showLanguageExtensions'; static SHORT_LABEL = localize('showLanguageExtensionsShort', "Language Extensions"); constructor( @@ -1246,7 +1246,7 @@ export class ShowLanguageExtensionsAction extends Action { export class ShowAzureExtensionsAction extends Action { - static ID = 'workbench.extensions.action.showAzureExtensions'; + static readonly ID = 'workbench.extensions.action.showAzureExtensions'; static SHORT_LABEL = localize('showAzureExtensionsShort', "Azure Extensions"); constructor( @@ -1445,7 +1445,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { - static ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; + static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions'; static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)"); private disposables: IDisposable[] = []; @@ -1486,7 +1486,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction { - static ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; + static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions'; static LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)"); private disposables: IDisposable[] = []; @@ -1512,7 +1512,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac public run(): TPromise { const folderCount = this.contextService.getWorkspace().folders.length; - const pickFolderPromise = folderCount === 1 ? TPromise.as(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND); + const pickFolderPromise = folderCount === 1 ? TPromise.as(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID); return pickFolderPromise .then(workspaceFolder => { if (workspaceFolder) { @@ -1555,7 +1555,7 @@ export class BuiltinStatusLabelAction extends Action { export class DisableAllAction extends Action { - static ID = 'workbench.extensions.action.disableAll'; + static readonly ID = 'workbench.extensions.action.disableAll'; static LABEL = localize('disableAll', "Disable All Installed Extensions"); private disposables: IDisposable[] = []; @@ -1585,7 +1585,7 @@ export class DisableAllAction extends Action { export class DisableAllWorkpsaceAction extends Action { - static ID = 'workbench.extensions.action.disableAllWorkspace'; + static readonly ID = 'workbench.extensions.action.disableAllWorkspace'; static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace"); private disposables: IDisposable[] = []; @@ -1617,7 +1617,7 @@ export class DisableAllWorkpsaceAction extends Action { export class EnableAllAction extends Action { - static ID = 'workbench.extensions.action.enableAll'; + static readonly ID = 'workbench.extensions.action.enableAll'; static LABEL = localize('enableAll', "Enable All Installed Extensions"); private disposables: IDisposable[] = []; @@ -1648,7 +1648,7 @@ export class EnableAllAction extends Action { export class EnableAllWorkpsaceAction extends Action { - static ID = 'workbench.extensions.action.enableAllWorkspace'; + static readonly ID = 'workbench.extensions.action.enableAllWorkspace'; static LABEL = localize('enableAllWorkspace', "Enable All Installed Extensions for this Workspace"); private disposables: IDisposable[] = []; diff --git a/src/vs/workbench/parts/extensions/browser/extensionsList.ts b/src/vs/workbench/parts/extensions/browser/extensionsList.ts index c5c4b2fb467..2179edff031 100644 --- a/src/vs/workbench/parts/extensions/browser/extensionsList.ts +++ b/src/vs/workbench/parts/extensions/browser/extensionsList.ts @@ -16,7 +16,7 @@ import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging'; import { once } from 'vs/base/common/event'; import { domEvent } from 'vs/base/browser/event'; import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions'; -import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground } from 'vs/workbench/parts/extensions/browser/extensionsActions'; +import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground } from 'vs/workbench/parts/extensions/browser/extensionsActions'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { Label, RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; @@ -61,8 +61,10 @@ export class Renderer implements IPagedRenderer { const bookmark = append(root, $('span.bookmark')); append(bookmark, $('span.octicon.octicon-star')); const applyBookmarkStyle = (theme) => { - const borderColor = theme.getColor(extensionButtonProminentBackground); - bookmark.style.borderTopColor = borderColor ? borderColor.toString() : 'transparent'; + const bgColor = theme.getColor(extensionButtonProminentBackground); + const fgColor = theme.getColor(extensionButtonProminentForeground); + bookmark.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent'; + bookmark.style.color = fgColor ? fgColor.toString() : 'white'; }; applyBookmarkStyle(this.themeService.getTheme()); const bookmarkStyler = this.themeService.onThemeChange(applyBookmarkStyle.bind(this)); diff --git a/src/vs/workbench/parts/extensions/browser/media/markdown.css b/src/vs/workbench/parts/extensions/browser/media/markdown.css index 7c7d27cf047..41d5c29e6b9 100644 --- a/src/vs/workbench/parts/extensions/browser/media/markdown.css +++ b/src/vs/workbench/parts/extensions/browser/media/markdown.css @@ -91,6 +91,54 @@ code > div { overflow: auto; } +#scroll-to-top { + position: fixed; + width: 40px; + height: 40px; + right: 25px; + bottom: 25px; + background-color:#444444; + border-radius: 50%; + cursor: pointer; + box-shadow: 1px 1px 1px rgba(0,0,0,.25); + outline: none; + display: flex; + justify-content: center; + align-items: center; +} + +#scroll-to-top:hover { + background-color:#007acc; + box-shadow: 2px 2px 2px rgba(0,0,0,.25); +} + +body.vscode-light #scroll-to-top { + background-color: #949494; +} + +body.vscode-high-contrast #scroll-to-top:hover { + background-color: #007acc; +} + +body.vscode-high-contrast #scroll-to-top { + background-color: black; + border: 2px solid #6fc3df; + box-shadow: none; +} +body.vscode-high-contrast #scroll-to-top:hover { + background-color: #007acc; +} + + +#scroll-to-top span.icon::before { + content: ""; + display: block; + /* Chevron up icon */ + background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo='); + width: 16px; + height: 16px; +} + /** Theming */ .vscode-light { diff --git a/src/vs/workbench/parts/extensions/common/extensionsInput.ts b/src/vs/workbench/parts/extensions/common/extensionsInput.ts index 79893822bf5..02f28094501 100644 --- a/src/vs/workbench/parts/extensions/common/extensionsInput.ts +++ b/src/vs/workbench/parts/extensions/common/extensionsInput.ts @@ -13,7 +13,7 @@ import URI from 'vs/base/common/uri'; export class ExtensionsInput extends EditorInput { - static get ID() { return 'workbench.extensions.input2'; } + static readonly ID = 'workbench.extensions.input2'; get extension(): IExtension { return this._extension; } constructor(private _extension: IExtension) { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index ec1a3d56ead..ea422baaf7b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -28,6 +28,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import * as pfs from 'vs/base/node/pfs'; import * as os from 'os'; import { flatten, distinct } from 'vs/base/common/arrays'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; interface IExtensionsContent { recommendations: string[]; @@ -43,12 +44,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe private _fileBasedRecommendations: { [id: string]: number; } = Object.create(null); private _exeBasedRecommendations: { [id: string]: string; } = Object.create(null); private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null); - private importantRecommendations: { [id: string]: { name: string; pattern: string; } } = Object.create(null); - private importantRecommendationsIgnoreList: string[]; - private _allRecommendations: string[] = []; private _disposables: IDisposable[] = []; private _allWorkspaceRecommendedExtensions: string[] = []; + public promptWorkspaceRecommendationsPromise: TPromise; constructor( @IExtensionGalleryService private _galleryService: IExtensionGalleryService, @@ -61,17 +60,17 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe @IWorkspaceContextService private contextService: IWorkspaceContextService, @IConfigurationService private configurationService: IConfigurationService, @IMessageService private messageService: IMessageService, - @ITelemetryService private telemetryService: ITelemetryService + @ITelemetryService private telemetryService: ITelemetryService, + @IEnvironmentService private environmentService: IEnvironmentService ) { super(); - if (!this._galleryService.isEnabled()) { + if (!this._galleryService.isEnabled() || this.environmentService.extensionDevelopmentPath) { return; } - - this._suggestTips(); - this._suggestWorkspaceRecommendations(); + this._suggestFileBasedRecommendations(); + this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations(); // Executable based recommendations carry out a lot of file stats, so run them after 10 secs // So that the startup is not affected @@ -109,14 +108,54 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe .then(content => this.processWorkspaceRecommendations(json.parse(content.value, [])), err => []); } - private processWorkspaceRecommendations(extensionsContent: IExtensionsContent): string[] { - if (extensionsContent && extensionsContent.recommendations) { - const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); - return extensionsContent.recommendations.filter((element, position) => { - return extensionsContent.recommendations.indexOf(element) === position && regEx.test(element); + private processWorkspaceRecommendations(extensionsContent: IExtensionsContent): TPromise { + const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN); + + if (extensionsContent && extensionsContent.recommendations && extensionsContent.recommendations.length) { + let countBadRecommendations = 0; + let badRecommendationsString = ''; + let filteredRecommendations = extensionsContent.recommendations.filter((element, position) => { + if (extensionsContent.recommendations.indexOf(element) !== position) { + // This is a duplicate entry, it doesn't hurt anybody + // but it shouldn't be sent in the gallery query + return false; + } else if (!regEx.test(element)) { + countBadRecommendations++; + badRecommendationsString += `${element} (bad format) Expected: .\n`; + return false; + } + + return true; + }); + + return this._galleryService.query({ names: filteredRecommendations }).then(pager => { + let page = pager.firstPage; + let validRecommendations = page.map(extension => { + return extension.identifier.id.toLowerCase(); + }); + + if (validRecommendations.length !== filteredRecommendations.length) { + filteredRecommendations.forEach(element => { + if (validRecommendations.indexOf(element.toLowerCase()) === -1) { + countBadRecommendations++; + badRecommendationsString += `${element} (not found in marketplace)\n`; + } + }); + } + + if (countBadRecommendations > 0) { + console.log('The below ' + + countBadRecommendations + + ' extension(s) in workspace recommendations have issues:\n' + + badRecommendationsString); + } + + return validRecommendations; }); } - return []; + + return TPromise.as([]); + } private onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): void { @@ -136,7 +175,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe const fileBased = Object.keys(this._fileBasedRecommendations) .sort((a, b) => { if (this._fileBasedRecommendations[a] === this._fileBasedRecommendations[b]) { - if (product.extensionImportantTips[a]) { + if (!product.extensionImportantTips || product.extensionImportantTips[a]) { return -1; } if (product.extensionImportantTips[b]) { @@ -152,19 +191,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe return Object.keys(this._exeBasedRecommendations); } - - getKeymapRecommendations(): string[] { return product.keymapExtensionTips || []; } - private _suggestTips() { + private _suggestFileBasedRecommendations() { const extensionTips = product.extensionTips; if (!extensionTips) { return; } - this.importantRecommendations = product.extensionImportantTips || Object.create(null); - this.importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); // group ids by pattern, like {**/*.md} -> [ext.foo1, ext.bar2] this._availableRecommendations = Object.create(null); @@ -189,8 +224,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe } }); + const allRecommendations = []; forEach(this._availableRecommendations, ({ value: ids }) => { - this._allRecommendations.push(...ids); + allRecommendations.push(...ids); }); // retrieve ids of previous recommendations @@ -198,7 +234,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe if (Array.isArray(storedRecommendationsJson)) { for (let id of storedRecommendationsJson) { - if (this._allRecommendations.indexOf(id) > -1) { + if (allRecommendations.indexOf(id) > -1) { this._fileBasedRecommendations[id] = Date.now(); } } @@ -207,7 +243,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe forEach(storedRecommendationsJson, entry => { if (typeof entry.value === 'number') { const diff = (now - entry.value) / milliSecondsInADay; - if (diff <= 7 && this._allRecommendations.indexOf(entry.key) > -1) { + if (diff <= 7 && allRecommendations.indexOf(entry.key) > -1) { this._fileBasedRecommendations[entry.key] = entry.value; } } @@ -246,17 +282,18 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe ); const config = this.configurationService.getValue(ConfigurationKey); + const importantRecommendationsIgnoreList = JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]')); - if (config.ignoreRecommendations) { + if (config.ignoreRecommendations || !product.extensionImportantTips) { return; } this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { - Object.keys(this.importantRecommendations) - .filter(id => this.importantRecommendationsIgnoreList.indexOf(id) === -1) + Object.keys(product.extensionImportantTips) + .filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1) .filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id)) .forEach(id => { - const { pattern, name } = this.importantRecommendations[id]; + const { pattern, name } = product.extensionImportantTips[id]; if (!match(pattern, uri.fsPath)) { return; @@ -297,10 +334,10 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe */ this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name }); return recommendationsAction.run(); - case 2: this.importantRecommendationsIgnoreList.push(id); + case 2: importantRecommendationsIgnoreList.push(id); this.storageService.store( 'extensionsAssistant/importantRecommendationsIgnore', - JSON.stringify(this.importantRecommendationsIgnoreList), + JSON.stringify(importantRecommendationsIgnoreList), StorageScope.GLOBAL ); /* __GDPR__ @@ -334,24 +371,16 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe }); } - private _suggestWorkspaceRecommendations() { + private _suggestWorkspaceRecommendations(): TPromise { const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore'; - - if (this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { - return; - } - const config = this.configurationService.getValue(ConfigurationKey); - if (config.ignoreRecommendations) { - return; - } - this.getWorkspaceRecommendations().done(allRecommendations => { - if (!allRecommendations.length) { + return this.getWorkspaceRecommendations().then(allRecommendations => { + if (!allRecommendations.length || config.ignoreRecommendations || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) { return; } - this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { + return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => { const recommendations = allRecommendations .filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id)); @@ -370,7 +399,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe localize('close', "Close") ]; - this.choiceService.choose(Severity.Info, message, options, 3).done(choice => { + return this.choiceService.choose(Severity.Info, message, options, 3).done(choice => { switch (choice) { case 0: /* __GDPR__ diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index e128026b9e0..f4c1b8f3578 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -20,7 +20,7 @@ import { mnemonicButtonLabel } from 'vs/base/common/labels'; export class OpenExtensionsFolderAction extends Action { - static ID = 'workbench.extensions.action.openExtensionsFolder'; + static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); constructor( @@ -51,7 +51,7 @@ export class OpenExtensionsFolderAction extends Action { export class InstallVSIXAction extends Action { - static ID = 'workbench.extensions.action.installVSIX'; + static readonly ID = 'workbench.extensions.action.installVSIX'; static LABEL = localize('installVSIX', "Install from VSIX..."); constructor( @@ -66,25 +66,25 @@ export class InstallVSIXAction extends Action { } run(): TPromise { - const result = this.windowsService.showOpenDialog({ + return this.windowsService.showOpenDialog({ title: localize('installFromVSIX', "Install from VSIX"), filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }], properties: ['openFile'], buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install")) - }); + }).then(result => { + if (!result) { + return TPromise.as(null); + } - if (!result) { - return TPromise.as(null); - } - - return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { - this.messageService.show( - severity.Info, - { - message: localize('InstallVSIXAction.success', "Successfully installed the extension. Restart to enable it."), - actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('InstallVSIXAction.reloadNow', "Reload Now"))] - } - ); + return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => { + this.messageService.show( + severity.Info, + { + message: localize('InstallVSIXAction.success', "Successfully installed the extension. Restart to enable it."), + actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('InstallVSIXAction.reloadNow', "Reload Now"))] + } + ); + }); }); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index d3981741f79..5adfee8f751 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -133,7 +133,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: ExtensionsListView, when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('recommendedExtensions')), - size: 100 + weight: 100 }; } @@ -144,7 +144,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: InstalledExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')), - size: 200 + weight: 30 }; } @@ -155,7 +155,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: InstalledExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')), - size: 50 + weight: 100 }; } @@ -166,7 +166,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: RecommendedExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')), - size: 600, + weight: 70, canToggleVisibility: true }; } @@ -178,7 +178,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: RecommendedExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions')), - size: 600, + weight: 50, canToggleVisibility: true, order: 2 }; @@ -191,7 +191,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens location: ViewLocation.Extensions, ctor: WorkspaceRecommendedExtensionsView, when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')), - size: 200, + weight: 50, canToggleVisibility: true, order: 1 }; diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css index ac002d15828..7927dffe0ab 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionsViewlet.css @@ -77,7 +77,6 @@ position: absolute; top: 1px; left: 1px; - color: white; font-size: 90%; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts index ed0034b4bb3..fe50f9bbe7b 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor.ts @@ -26,7 +26,7 @@ import { IExtensionService, IExtensionDescription, IExtensionsStatus, IExtension import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list'; import { WorkbenchList, IListService } from 'vs/platform/list/browser/listService'; import { append, $, addClass, toggleClass } from 'vs/base/browser/dom'; -import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -39,6 +39,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment' import { memoize } from 'vs/base/common/decorators'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import Event from 'vs/base/common/event'; +import { DisableForWorkspaceAction, DisableGloballyAction } from 'vs/workbench/parts/extensions/browser/extensionsActions'; export const IExtensionHostProfileService = createDecorator('extensionHostProfileService'); @@ -373,7 +374,13 @@ export class RuntimeExtensionsEditor extends BaseEditor { this._list.onContextMenu((e) => { const actions: IAction[] = []; - actions.push(this.saveExtensionHostProfileAction, this.extensionHostProfileAction); + if (e.element.marketplaceInfo.type === LocalExtensionType.User) { + actions.push(this._instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL)); + actions.push(this._instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL)); + actions.forEach((a: DisableForWorkspaceAction | DisableGloballyAction) => a.extension = e.element.marketplaceInfo); + actions.push(new Separator()); + } + actions.push(this.extensionHostProfileAction, this.saveExtensionHostProfileAction); this._contextMenuService.showContextMenu({ getAnchor: () => e.anchor, @@ -406,7 +413,7 @@ export class RuntimeExtensionsEditor extends BaseEditor { export class RuntimeExtensionsInput extends EditorInput { - static ID = 'workbench.runtimeExtensions.input'; + static readonly ID = 'workbench.runtimeExtensions.input'; constructor() { super(); @@ -444,7 +451,7 @@ export class RuntimeExtensionsInput extends EditorInput { } export class ShowRuntimeExtensionsAction extends Action { - static ID = 'workbench.action.showRuntimeExtensions'; + static readonly ID = 'workbench.action.showRuntimeExtensions'; static LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions"); constructor( @@ -461,7 +468,7 @@ export class ShowRuntimeExtensionsAction extends Action { } class ReportExtensionIssueAction extends Action { - static ID = 'workbench.extensions.action.reportExtensionIssue'; + static readonly ID = 'workbench.extensions.action.reportExtensionIssue'; static LABEL = nls.localize('reportExtensionIssue', "Report Issue"); constructor( @@ -499,7 +506,7 @@ class ReportExtensionIssueAction extends Action { } class ExtensionHostProfileAction extends Action { - static ID = 'workbench.extensions.action.extensionHostProfile'; + static readonly ID = 'workbench.extensions.action.extensionHostProfile'; static LABEL_START = nls.localize('extensionHostProfileStart', "Start Extension Host Profile"); static LABEL_STOP = nls.localize('extensionHostProfileStop', "Stop Extension Host Profile"); static STOP_CSS_CLASS = 'extension-host-profile-stop'; @@ -542,7 +549,7 @@ class ExtensionHostProfileAction extends Action { class SaveExtensionHostProfileAction extends Action { static LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile"); - static ID = 'workbench.extensions.action.saveExtensionHostProfile'; + static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile'; constructor( id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL, @@ -558,7 +565,7 @@ class SaveExtensionHostProfileAction extends Action { } async run(): TPromise { - let picked = this._windowService.showSaveDialog({ + let picked = await this._windowService.showSaveDialog({ title: 'Save Extension Host Profile', buttonLabel: 'Save', defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`, diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 9208d1a21ed..10a5d3b69ed 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -34,6 +34,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { IURLService } from 'vs/platform/url/common/url'; import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput'; import product from 'vs/platform/node/product'; +import { ILogService } from 'vs/platform/log/common/log'; interface IExtensionStateProvider { (extension: Extension): ExtensionState; @@ -343,7 +344,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { @IChoiceService private choiceService: IChoiceService, @IURLService urlService: IURLService, @IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService, - @IWindowService private windowService: IWindowService + @IWindowService private windowService: IWindowService, + @ILogService private logService: ILogService ) { this.stateProvider = ext => this.getExtensionState(ext); @@ -588,6 +590,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { return TPromise.wrapError(new Error('Missing local')); } + this.logService.info(`Requested uninstalling the extension ${extension.id} from window ${this.windowService.getCurrentWindowId()}`); return this.extensionService.uninstall(local); } @@ -774,6 +777,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } private onUninstallExtension({ id }: IExtensionIdentifier): void { + this.logService.info(`Uninstalling the extension ${id} from window ${this.windowService.getCurrentWindowId()}`); const extension = this.installed.filter(e => e.local.identifier.id === id)[0]; const newLength = this.installed.filter(e => e.local.identifier.id !== id).length; // TODO: Ask @Joao why is this? diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts index f6f9b37077e..0e697f0d9ef 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsActions.test.ts @@ -29,8 +29,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IExtensionService } from 'vs/platform/extensions/common/extensions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { IWindowService } from 'vs/platform/windows/common/windows'; suite('ExtensionsActions Test', () => { @@ -51,6 +53,8 @@ suite('ExtensionsActions Test', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(ILogService, NullLogService); + instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IConfigurationService, { onDidUpdateConfiguration: () => { }, onDidChangeConfiguration: () => { }, getConfiguration: () => ({}) }); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts new file mode 100644 index 00000000000..4050a47d62e --- /dev/null +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsTipsService.test.ts @@ -0,0 +1,359 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as path from 'path'; +import * as fs from 'fs'; +import * as os from 'os'; +import { TPromise } from 'vs/base/common/winjs.base'; +import uuid = require('vs/base/common/uuid'); +import { mkdirp } from 'vs/base/node/pfs'; +import { + IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, LocalExtensionType, + IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier +} from 'vs/platform/extensionManagement/common/extensionManagement'; +import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService'; +import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService'; +import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; +import { Emitter } from 'vs/base/common/event'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService } from 'vs/workbench/test/workbenchTestServices'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import URI from 'vs/base/common/uri'; +import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; +import { IFileService } from 'vs/platform/files/common/files'; +import { FileService } from 'vs/workbench/services/files/node/fileService'; +import extfs = require('vs/base/node/extfs'); +import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; +import { IPager } from 'vs/base/common/paging'; +import { assign } from 'vs/base/common/objects'; +import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; +import { generateUuid } from 'vs/base/common/uuid'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { IExtensionsWorkbenchService, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions'; +import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService'; +import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService'; +import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test'; +import { IURLService } from 'vs/platform/url/common/url'; +import { IChoiceService } from 'vs/platform/message/common/message'; +import product from 'vs/platform/node/product'; +import { IModel } from 'vs/editor/common/editorCommon'; +import { IModelService } from 'vs/editor/common/services/modelService'; + +const mockExtensionGallery: IGalleryExtension[] = [ + aGalleryExtension('MockExtension1', { + displayName: 'Mock Extension 1', + version: '1.5', + publisherId: 'mockPublisher1Id', + publisher: 'mockPublisher1', + publisherDisplayName: 'Mock Publisher 1', + description: 'Mock Description', + installCount: 1000, + rating: 4, + ratingCount: 100 + }, { + dependencies: ['pub.1'], + }, { + manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' }, + readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' }, + changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' }, + download: { uri: 'uri:download', fallbackUri: 'fallback:download' }, + icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, + license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, + repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + }), + aGalleryExtension('MockExtension2', { + displayName: 'Mock Extension 2', + version: '1.5', + publisherId: 'mockPublisher2Id', + publisher: 'mockPublisher2', + publisherDisplayName: 'Mock Publisher 2', + description: 'Mock Description', + installCount: 1000, + rating: 4, + ratingCount: 100 + }, { + dependencies: ['pub.1', 'pub.2'], + }, { + manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' }, + readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' }, + changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' }, + download: { uri: 'uri:download', fallbackUri: 'fallback:download' }, + icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' }, + license: { uri: 'uri:license', fallbackUri: 'fallback:license' }, + repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' }, + }) +]; + +const mockExtensionLocal = [ + { + type: LocalExtensionType.User, + identifier: mockExtensionGallery[0].identifier, + manifest: { + name: mockExtensionGallery[0].name, + publisher: mockExtensionGallery[0].publisher, + version: mockExtensionGallery[0].version + }, + metadata: null, + path: 'somepath', + readmeUrl: 'some readmeUrl', + changelogUrl: 'some changelogUrl' + }, + { + type: LocalExtensionType.User, + identifier: mockExtensionGallery[1].identifier, + manifest: { + name: mockExtensionGallery[1].name, + publisher: mockExtensionGallery[1].publisher, + version: mockExtensionGallery[1].version + }, + metadata: null, + path: 'somepath', + readmeUrl: 'some readmeUrl', + changelogUrl: 'some changelogUrl' + } +]; + +const mockTestData = { + recommendedExtensions: [ + 'mockPublisher1.mockExtension1', + 'MOCKPUBLISHER2.mockextension2', + 'badlyformattedextension', + 'MOCKPUBLISHER2.mockextension2', + 'unknown.extension' + ], + validRecommendedExtensions: [ + 'mockPublisher1.mockExtension1', + 'MOCKPUBLISHER2.mockextension2' + ] +}; + +function aPage(...objects: T[]): IPager { + return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null }; +} + +const noAssets: IGalleryExtensionAssets = { + changelog: null, + download: null, + icon: null, + license: null, + manifest: null, + readme: null, + repository: null +}; + +function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension { + const galleryExtension = Object.create({}); + assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties); + assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties); + assign(galleryExtension.assets, assets); + galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() }; + return galleryExtension; +} + +suite('ExtensionsTipsService Test', () => { + let workspaceService: IWorkspaceContextService; + let instantiationService: TestInstantiationService; + let extensionsWorkbenchService: IExtensionsWorkbenchService; + let testConfigurationService: TestConfigurationService; + let testObject: ExtensionTipsService; + let parentResource: string; + let installEvent: Emitter, + didInstallEvent: Emitter, + uninstallEvent: Emitter, + didUninstallEvent: Emitter; + let prompted: boolean; + let onModelAddedEvent: Emitter; + + suiteSetup(() => { + instantiationService = new TestInstantiationService(); + installEvent = new Emitter(); + didInstallEvent = new Emitter(); + uninstallEvent = new Emitter(); + didUninstallEvent = new Emitter(); + instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); + + testConfigurationService = new TestConfigurationService(); + instantiationService.stub(IConfigurationService, testConfigurationService); + instantiationService.stub(IExtensionManagementService, ExtensionManagementService); + instantiationService.stub(IExtensionManagementService, 'onInstallExtension', installEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidInstallExtension', didInstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onUninstallExtension', uninstallEvent.event); + instantiationService.stub(IExtensionManagementService, 'onDidUninstallExtension', didUninstallEvent.event); + instantiationService.stub(IExtensionEnablementService, new TestExtensionEnablementService(instantiationService)); + instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); + instantiationService.stub(ITelemetryService, NullTelemetryService); + + onModelAddedEvent = new Emitter(); + + product.extensionTips = { + 'ms-vscode.csharp': '{**/*.cs,**/project.json,**/global.json,**/*.csproj,**/*.sln,**/appsettings.json}', + 'msjsdiag.debugger-for-chrome': '{**/*.ts,**/*.tsx**/*.js,**/*.jsx,**/*.es6,**/.babelrc}', + 'lukehoban.Go': '**/*.go' + }; + product.extensionImportantTips = { + 'ms-python.python': { + 'name': 'Python', + 'pattern': '{**/*.py}' + }, + 'ms-vscode.PowerShell': { + 'name': 'PowerShell', + 'pattern': '{**/*.ps,**/*.ps1}' + } + }; + }); + + setup(() => { + instantiationService.stub(IEnvironmentService, { extensionDevelopmentPath: false }); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); + instantiationService.stub(IExtensionGalleryService, 'isEnabled', true); + instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(...mockExtensionGallery)); + extensionsWorkbenchService = instantiationService.createInstance(ExtensionsWorkbenchService); + instantiationService.stub(IExtensionsWorkbenchService, extensionsWorkbenchService); + + prompted = false; + instantiationService.stub(IChoiceService, { + choose: () => { + prompted = true; + return TPromise.as(3); + } + }); + + testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: false }); + instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => c, store: () => { } }); + instantiationService.stub(IModelService, { + getModels(): any { return []; }, + onModelAdded: onModelAddedEvent.event + }); + }); + + teardown((done) => { + (testObject).dispose(); + (extensionsWorkbenchService).dispose(); + if (parentResource) { + extfs.del(parentResource, os.tmpdir(), () => { }, done); + } + }); + + function setUpFolderWorkspace(folderName: string, recommendedExtensions: string[]): TPromise { + const id = uuid.generateUuid(); + parentResource = path.join(os.tmpdir(), 'vsctests', id); + return setUpFolder(folderName, parentResource, recommendedExtensions); + } + + function setUpFolder(folderName: string, parentDir: string, recommendedExtensions: string[]): TPromise { + const folderDir = path.join(parentDir, folderName); + const workspaceSettingsDir = path.join(folderDir, '.vscode'); + return mkdirp(workspaceSettingsDir, 493).then(() => { + const configPath = path.join(workspaceSettingsDir, 'extensions.json'); + fs.writeFileSync(configPath, JSON.stringify({ + 'recommendations': recommendedExtensions + }, null, '\t')); + + const myWorkspace = testWorkspace(URI.from({ scheme: 'file', path: folderDir })); + workspaceService = new TestContextService(myWorkspace); + instantiationService.stub(IWorkspaceContextService, workspaceService); + instantiationService.stub(IFileService, new FileService(workspaceService, new TestTextResourceConfigurationService(), new TestConfigurationService(), new TestLifecycleService(), { disableWatcher: true })); + }); + } + + function testNoPromptForValidRecommendations(recommendations: string[]) { + return setUpFolderWorkspace('myFolder', recommendations).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + const promise = testObject.promptWorkspaceRecommendationsPromise || testObject.getWorkspaceRecommendations(); + return promise.then(() => { + assert.equal(Object.keys(testObject.getAllRecommendationsWithReason()).length, recommendations.length); + assert.ok(!prompted); + }); + }); + } + + test('ExtensionTipsService: No Prompt for valid workspace recommendations when galleryService is absent', () => { + instantiationService.stub(IExtensionGalleryService, 'isEnabled', false); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); + }); + + test('ExtensionTipsService: No Prompt for valid workspace recommendations during extension development', () => { + instantiationService.stub(IEnvironmentService, { extensionDevelopmentPath: true }); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); + }); + + test('ExtensionTipsService: No workspace recommendations or prompts when extensions.json has empty array', () => { + return testNoPromptForValidRecommendations([]); + }); + + test('ExtensionTipsService: Prompt for valid workspace recommendations', () => { + return setUpFolderWorkspace('myFolder', mockTestData.recommendedExtensions).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + return testObject.promptWorkspaceRecommendationsPromise.then(() => { + const recommendations = Object.keys(testObject.getAllRecommendationsWithReason()); + + assert.equal(recommendations.length, mockTestData.validRecommendedExtensions.length); + mockTestData.validRecommendedExtensions.forEach(x => { + assert.equal(recommendations.indexOf(x.toLowerCase()) > -1, true); + }); + + assert.ok(prompted); + }); + }); + }); + + test('ExtensionTipsService: No Prompt for valid workspace recommendations if they are already installed', () => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); + }); + + test('ExtensionTipsService: No Prompt for valid workspace recommendations with casing mismatch if they are already installed', () => { + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', mockExtensionLocal); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions.map(x => x.toUpperCase())); + }); + + test('ExtensionTipsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set', () => { + testConfigurationService.setUserConfiguration(ConfigurationKey, { ignoreRecommendations: true }); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); + }); + + test('ExtensionTipsService: No Prompt for valid workspace recommendations if ignoreRecommendations is set for current workspace', () => { + instantiationService.stub(IStorageService, { get: (a, b, c) => c, getBoolean: (a, b, c) => a === 'extensionsAssistant/workspaceRecommendationsIgnore' || c }); + return testNoPromptForValidRecommendations(mockTestData.validRecommendedExtensions); + }); + + test('ExtensionTipsService: Get file based recommendations from storage (old format)', () => { + const storedRecommendations = '["ms-vscode.csharp", "ms-python.python", "eg2.tslint"]'; + instantiationService.stub(IStorageService, { get: (a, b, c) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + + return setUpFolderWorkspace('myFolder', []).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + const recommendations = testObject.getFileBasedRecommendations(); + assert.equal(recommendations.length, 2); + assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + }); + }); + + test('ExtensionTipsService: Get file based recommendations from storage (new format)', () => { + const milliSecondsInADay = 1000 * 60 * 60 * 24; + const now = Date.now(); + const tenDaysOld = 10 * milliSecondsInADay; + const storedRecommendations = `{"ms-vscode.csharp": ${now}, "ms-python.python": ${now}, "eg2.tslint": ${now}, "lukehoban.Go": ${tenDaysOld}}`; + instantiationService.stub(IStorageService, { get: (a, b, c) => a === 'extensionsAssistant/recommendations' ? storedRecommendations : c }); + + return setUpFolderWorkspace('myFolder', []).then(() => { + testObject = instantiationService.createInstance(ExtensionTipsService); + const recommendations = testObject.getFileBasedRecommendations(); + assert.equal(recommendations.length, 2); + assert.ok(recommendations.indexOf('ms-vscode.csharp') > -1); // stored recommendation that exists in product.extensionTips + assert.ok(recommendations.indexOf('ms-python.python') > -1); // stored recommendation that exists in product.extensionImportantTips + assert.ok(recommendations.indexOf('eg2.tslint') === -1); // stored recommendation that is no longer in neither product.extensionTips nor product.extensionImportantTips + assert.ok(recommendations.indexOf('lukehoban.Go') === -1); //stored recommendation that is older than a week + }); + }); +}); \ 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 38d0d179cfe..0e32124f83d 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 @@ -29,9 +29,11 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices'; import { IChoiceService } from 'vs/platform/message/common/message'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ILogService, NullLogService } from 'vs/platform/log/common/log'; +import { IWindowService } from 'vs/platform/windows/common/windows'; suite('ExtensionsWorkbenchService Test', () => { @@ -52,6 +54,8 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService = new TestInstantiationService(); instantiationService.stub(IURLService, { onOpenURL: new Emitter().event }); instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(ILogService, NullLogService); + instantiationService.stub(IWindowService, TestWindowService); instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService); diff --git a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts index 1baf91a02d9..a4b89ff6e86 100644 --- a/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts +++ b/src/vs/workbench/parts/files/browser/editors/fileEditorTracker.ts @@ -142,8 +142,7 @@ export class FileEditorTracker implements IWorkbenchContribution { // We have received reports of users seeing delete events even though the file still // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). // Since we do not want to close an editor without reason, we have to check if the - // file is really gone and not just a faulty file event (TODO@Ben revisit when we - // have a more stable file watcher in place for this scenario). + // file is really gone and not just a faulty file event. // This only applies to external file events, so we need to check for the isExternal // flag. let checkExists: TPromise; diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index 0b7c07c9f74..f6a8ad3ec7d 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle'; import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils'; -import { Verbosity } from 'vs/platform/editor/common/editor'; +import { Verbosity, IRevertOptions } from 'vs/platform/editor/common/editor'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IHashService } from 'vs/workbench/services/hash/common/hashService'; @@ -214,7 +214,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return model.isDirty(); } - public confirmSave(): ConfirmResult { + public confirmSave(): TPromise { return this.textFileService.confirmSave([this.resource]); } @@ -222,8 +222,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { return this.textFileService.save(this.resource); } - public revert(): TPromise { - return this.textFileService.revert(this.resource); + public revert(options?: IRevertOptions): TPromise { + return this.textFileService.revert(this.resource, options); } public getPreferredEditorId(candidates: string[]): string { @@ -240,7 +240,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput { // Resolve as text return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, reload: refresh }).then(model => { - // TODO@Ben this is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary + // This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary // or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into // loadOrCreate ensures we are not creating model references for these kind of resources. // In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet. diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index ba9c28aa478..55dd42ff22e 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -20,6 +20,7 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IViewlet } from 'vs/workbench/common/viewlet'; +import { InputFocusedContextKey } from 'vs/platform/workbench/common/contextkeys'; /** * Explorer viewlet id. @@ -43,17 +44,19 @@ const openEditorsVisibleId = 'openEditorsVisible'; const openEditorsFocusId = 'openEditorsFocus'; const explorerViewletFocusId = 'explorerViewletFocus'; const explorerResourceIsFolderId = 'explorerResourceIsFolder'; +const explorerResourceIsRootId = 'explorerResourceIsRoot'; export const ExplorerViewletVisibleContext = new RawContextKey(explorerViewletVisibleId, true); export const ExplorerFolderContext = new RawContextKey(explorerResourceIsFolderId, false); +export const ExplorerRootContext = new RawContextKey(explorerResourceIsRootId, false); export const FilesExplorerFocusedContext = new RawContextKey(filesExplorerFocusId, true); export const OpenEditorsVisibleContext = new RawContextKey(openEditorsVisibleId, false); export const OpenEditorsFocusedContext = new RawContextKey(openEditorsFocusId, true); export const ExplorerFocusedContext = new RawContextKey(explorerViewletFocusId, true); export const OpenEditorsVisibleCondition = ContextKeyExpr.has(openEditorsVisibleId); -export const FilesExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(filesExplorerFocusId)); -export const ExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(explorerViewletFocusId)); +export const FilesExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(filesExplorerFocusId), ContextKeyExpr.not(InputFocusedContextKey)); +export const ExplorerFocusCondition = ContextKeyExpr.and(ContextKeyExpr.has(explorerViewletVisibleId), ContextKeyExpr.has(explorerViewletFocusId), ContextKeyExpr.not(InputFocusedContextKey)); /** * File editor input id. 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 5621838a480..af659fddb2c 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.contribution.ts @@ -6,193 +6,29 @@ import nls = require('vs/nls'); import { Registry } from 'vs/platform/registry/common/platform'; -import { Action, IAction } from 'vs/base/common/actions'; -import { ActionItem, BaseActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions'; -import { GlobalNewUntitledFileAction, SaveFileAsAction, ShowOpenedFileInNewWindow, CopyPathAction, GlobalCopyPathAction, RevealInOSAction, GlobalRevealInOSAction, pasteIntoFocusedFilesExplorerViewItem, FocusOpenEditorsView, FocusFilesExplorer, GlobalCompareResourcesAction, GlobalNewFileAction, GlobalNewFolderAction, RevertFileAction, SaveFilesAction, SaveAllAction, SaveFileAction, MoveFileToTrashAction, TriggerRenameFileAction, PasteFileAction, CopyFileAction, SelectResourceForCompareAction, CompareResourcesAction, NewFolderAction, NewFileAction, OpenToSideAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithSavedAction, CompareWithClipboardAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { GlobalNewUntitledFileAction, ShowOpenedFileInNewWindow, CopyPathAction, GlobalRevealInOSAction, FocusOpenEditorsView, FocusFilesExplorer, GlobalCompareResourcesAction, GlobalNewFileAction, GlobalNewFolderAction, SaveAllAction, ShowActiveFileInExplorer, CollapseExplorerView, RefreshExplorerView, CompareWithClipboardAction, NEW_FILE_COMMAND_ID, NEW_FILE_LABEL, NEW_FOLDER_COMMAND_ID, NEW_FOLDER_LABEL, TRIGGER_RENAME_LABEL, MOVE_FILE_TO_TRASH_LABEL, COPY_FILE_LABEL, PASTE_FILE_LABEL, FileCopiedContext, renameHandler, moveFileToTrashHandler, copyFileHandler, pasteFileHandler, deleteFileHandler } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTION_CONTEXT } from 'vs/workbench/parts/files/electron-browser/saveErrorHandler'; import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; -import { FileStat, Model } from 'vs/workbench/parts/files/common/explorerModel'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { AddRootFolderAction, RemoveRootFolderAction, OpenFolderSettingsAction } from 'vs/workbench/browser/actions/workspaceActions'; -import { copyFocusedFilesExplorerViewItem, revealInOSFocusedFilesExplorerItem, openFocusedExplorerItemSideBySideCommand, copyPathOfFocusedExplorerItem, copyPathCommand, revealInExplorerCommand, revealInOSCommand, openWindowCommand, deleteFocusedFilesExplorerViewItemCommand, moveFocusedFilesExplorerViewItemToTrashCommand, renameFocusedFilesExplorerViewItemCommand } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL } from 'vs/workbench/parts/files/electron-browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { explorerItemToFileResource, ExplorerFocusCondition, FilesExplorerFocusCondition } from 'vs/workbench/parts/files/common/files'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext } from 'vs/workbench/parts/files/common/files'; +import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; +import { CLOSE_UNMODIFIED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { OPEN_FOLDER_SETTINGS_COMMAND, OPEN_FOLDER_SETTINGS_LABEL } from 'vs/workbench/parts/preferences/browser/preferencesActions'; +import { AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; -class FilesViewerActionContributor extends ActionBarContributor { - - constructor( - @IInstantiationService private instantiationService: IInstantiationService, - @IKeybindingService private keybindingService: IKeybindingService - ) { - super(); - } - - public hasSecondaryActions(context: any): boolean { - const element = context.element; - - // Contribute only on Stat Objects (File Explorer) - return element instanceof FileStat || element instanceof Model; - } - - public getSecondaryActions(context: any): IAction[] { - const stat = (context.element); - const tree = context.viewer; - const actions: IAction[] = []; - let separateOpen = false; - if (stat instanceof Model) { - return [this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL)]; - } - - // Open side by side - if (!stat.isDirectory) { - actions.push(this.instantiationService.createInstance(OpenToSideAction, tree, stat.resource, false)); - separateOpen = true; - } - - if (separateOpen) { - actions.push(new Separator(null, 50)); - } - - // Directory Actions - if (stat.isDirectory && !stat.nonexistentRoot) { - - // New File - actions.push(this.instantiationService.createInstance(NewFileAction, tree, stat)); - - // New Folder - actions.push(this.instantiationService.createInstance(NewFolderAction, tree, stat)); - - actions.push(new Separator(null, 50)); - } - - // Compare Files (of same extension) - else if (!stat.isDirectory) { - - // Run Compare - const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, stat.resource, tree); - if (runCompareAction._isEnabled()) { - actions.push(runCompareAction); - } - - // Select for Compare - actions.push(this.instantiationService.createInstance(SelectResourceForCompareAction, stat.resource, tree)); - - actions.push(new Separator(null, 100)); - } - - // Workspace Root Folder Actions - if (stat.isRoot) { - const addRootFolderAction: Action = this.instantiationService.createInstance(AddRootFolderAction, AddRootFolderAction.ID, AddRootFolderAction.LABEL); - addRootFolderAction.order = 52; - actions.push(addRootFolderAction); - - const openFolderSettingsActions = this.instantiationService.createInstance(OpenFolderSettingsAction, stat.resource, OpenFolderSettingsAction.ID, OpenFolderSettingsAction.LABEL); - openFolderSettingsActions.order = 53; - actions.push(openFolderSettingsActions); - const removeRootFolderAction = this.instantiationService.createInstance(RemoveRootFolderAction, stat.resource, RemoveRootFolderAction.ID, RemoveRootFolderAction.LABEL); - removeRootFolderAction.order = 54; - actions.push(removeRootFolderAction); - - actions.push(new Separator(null, 55)); - } - - // Copy File/Folder - if (!stat.isRoot) { - actions.push(this.instantiationService.createInstance(CopyFileAction, tree, stat)); - } - - // Paste File/Folder - if (stat.isDirectory) { - actions.push(this.instantiationService.createInstance(PasteFileAction, tree, stat)); - } - - // Rename File/Folder - if (!stat.isRoot) { - actions.push(new Separator(null, 150)); - actions.push(this.instantiationService.createInstance(TriggerRenameFileAction, tree, stat)); - // Delete File/Folder - actions.push(this.instantiationService.createInstance(MoveFileToTrashAction, tree, stat)); - } - - // Set Order - let curOrder = 10; - for (let i = 0; i < actions.length; i++) { - const action = actions[i]; - if (!action.order) { - curOrder += 10; - action.order = curOrder; - } else { - curOrder = action.order; - } - } - - return actions; - } - - public getActionItem(context: any, action: Action): BaseActionItem { - if (context && context.element instanceof FileStat) { - - // Any other item with keybinding - const keybinding = this.keybindingService.lookupKeybinding(action.id); - if (keybinding) { - return new ActionItem(context, action, { label: true, keybinding: keybinding.getLabel() }); - } - } - - return null; - } -} - -class ExplorerViewersActionContributor extends ActionBarContributor { - - constructor( @IInstantiationService private instantiationService: IInstantiationService) { - super(); - } - - public hasSecondaryActions(context: any): boolean { - const element = context.element; - - // Contribute only on Files (File Explorer and Open Files Viewer) - return !!explorerItemToFileResource(element); - } - - public getSecondaryActions(context: any): IAction[] { - const actions: IAction[] = []; - const fileResource = explorerItemToFileResource(context.element); - const resource = fileResource.resource; - - // Reveal file in OS native explorer - if (resource.scheme === 'file') { - actions.push(this.instantiationService.createInstance(RevealInOSAction, resource)); - } - - // Copy Path - actions.push(this.instantiationService.createInstance(CopyPathAction, resource)); - - return actions; - } -} - -// Contribute to Viewers that show Files -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, FilesViewerActionContributor); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewersActionContributor); // Contribute Global Actions const category = nls.localize('filesCategory', "File"); const registry = Registry.as(ActionExtensions.WorkbenchActions); -registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCopyPathAction, GlobalCopyPathAction.ID, GlobalCopyPathAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_P) }), 'File: Copy Path of Active File', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_S }), 'File: Save', category); registry.registerWorkbenchAction(new SyncActionDescriptor(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL, { primary: void 0, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_S }, win: { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_S) } }), 'File: Save All', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFilesAction, SaveFilesAction.ID, SaveFilesAction.LABEL), 'File: Save All Files', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL), 'File: Revert File', category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFileAction, GlobalNewFileAction.ID, GlobalNewFileAction.LABEL), 'File: New File', category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewFolderAction, GlobalNewFolderAction.ID, GlobalNewFolderAction.LABEL), 'File: New Folder', category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalCompareResourcesAction, GlobalCompareResourcesAction.ID, GlobalCompareResourcesAction.LABEL), 'File: Compare Active File With...', category); @@ -201,11 +37,9 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(FocusFilesExplorer, Fo registry.registerWorkbenchAction(new SyncActionDescriptor(ShowActiveFileInExplorer, ShowActiveFileInExplorer.ID, ShowActiveFileInExplorer.LABEL), 'File: Reveal Active File in Side Bar', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CollapseExplorerView, CollapseExplorerView.ID, CollapseExplorerView.LABEL), 'File: Collapse Folders in Explorer', category); registry.registerWorkbenchAction(new SyncActionDescriptor(RefreshExplorerView, RefreshExplorerView.ID, RefreshExplorerView.LABEL), 'File: Refresh Explorer', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }), 'File: Save As...', category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'File: New Untitled File', category); registry.registerWorkbenchAction(new SyncActionDescriptor(GlobalRevealInOSAction, GlobalRevealInOSAction.ID, GlobalRevealInOSAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R) }), 'File: Reveal Active File', category); registry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'File: Open Active File in New Window', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithSavedAction, CompareWithSavedAction.ID, CompareWithSavedAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D) }), 'File: Compare Active File with Saved', category); registry.registerWorkbenchAction(new SyncActionDescriptor(CompareWithClipboardAction, CompareWithClipboardAction.ID, CompareWithClipboardAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_C) }), 'File: Compare Active File with Clipboard', category); // Commands @@ -213,37 +47,28 @@ CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); const explorerCommandsWeightBonus = 10; // give our commands a little bit more weight over other default list/tree commands +const RENAME_ID = 'renameFile'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'explorer.openToSide', - weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), - when: ExplorerFocusCondition, - primary: KeyMod.CtrlCmd | KeyCode.Enter, - mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter - }, - handler: openFocusedExplorerItemSideBySideCommand -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'renameFile', + id: RENAME_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), when: FilesExplorerFocusCondition, primary: KeyCode.F2, mac: { primary: KeyCode.Enter }, - handler: renameFocusedFilesExplorerViewItemCommand + handler: renameHandler }); +const MOVE_FILE_TO_TRASH_ID = 'moveFileToTrash'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'moveFileToTrash', + id: MOVE_FILE_TO_TRASH_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), when: FilesExplorerFocusCondition, primary: KeyCode.Delete, mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace }, - handler: moveFocusedFilesExplorerViewItemToTrashCommand + handler: moveFileToTrashHandler }); KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -254,56 +79,34 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Backspace }, - handler: deleteFocusedFilesExplorerViewItemCommand + handler: deleteFileHandler }); +const COPY_FILE_ID = 'filesExplorer.copy'; KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'filesExplorer.copy', + id: COPY_FILE_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), when: FilesExplorerFocusCondition, primary: KeyMod.CtrlCmd | KeyCode.KEY_C, - handler: copyFocusedFilesExplorerViewItem + handler: copyFileHandler, }); +const PASTE_FILE_ID = 'filesExplorer.paste'; + KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'filesExplorer.paste', + id: PASTE_FILE_ID, weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), when: FilesExplorerFocusCondition, primary: KeyMod.CtrlCmd | KeyCode.KEY_V, - handler: pasteIntoFocusedFilesExplorerViewItem -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'copyFilePath', - weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), - when: ExplorerFocusCondition, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C - }, - handler: copyPathOfFocusedExplorerItem -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: 'revealFileInOS', - weight: KeybindingsRegistry.WEIGHT.workbenchContrib(explorerCommandsWeightBonus), - when: ExplorerFocusCondition, - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R - }, - handler: revealInOSFocusedFilesExplorerItem + handler: pasteFileHandler }); // Editor Title Context Menu -appendEditorTitleContextMenuItem('_workbench.action.files.revealInOS', RevealInOSAction.LABEL, revealInOSCommand); -appendEditorTitleContextMenuItem('_workbench.action.files.copyPath', CopyPathAction.LABEL, copyPathCommand); -appendEditorTitleContextMenuItem('_workbench.action.files.revealInExplorer', nls.localize('revealInSideBar', "Reveal in Side Bar"), revealInExplorerCommand); +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL); +appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, CopyPathAction.LABEL); +appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar")); -function appendEditorTitleContextMenuItem(id: string, title: string, command: ICommandHandler): void { - - // Command - CommandsRegistry.registerCommand(id, command); +function appendEditorTitleContextMenuItem(id: string, title: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -329,4 +132,295 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, iconClas group: 'navigation', order }); -} \ No newline at end of file +} + +// Menu registration - command palette + +function appendToCommandPalette(id: string, title: string, category: string): void { + MenuRegistry.appendMenuItem(MenuId.CommandPalette, { + command: { + id, + title, + category + } + }); +} +appendToCommandPalette(COPY_PATH_COMMAND_ID, nls.localize('copyPathOfActive', "Copy Path of Active File"), category); +appendToCommandPalette(SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, category); +appendToCommandPalette(SAVE_ALL_IN_GROUP_COMMAND_ID, nls.localize('saveAllInGroup', "Save All in Group"), category); +appendToCommandPalette(REVERT_FILE_COMMAND_ID, nls.localize('revert', "Revert File"), category); +appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), category); +appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, category); +appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, category); +appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, nls.localize('closeEditor', "Close Editor"), nls.localize('view', "View")); + + +// Menu registration - open editors + +const openToSideCommand = { + id: OPEN_TO_SIDE_COMMAND_ID, + title: nls.localize('openToSide', "Open to the Side") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 10, + command: openToSideCommand, + when: ResourceContextKey.HasResource +}); + +const revealInOsCommand = { + id: REVEAL_IN_OS_COMMAND_ID, + title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.Scheme.isEqualTo('file') +}); + +const copyPathCommand = { + id: COPY_PATH_COMMAND_ID, + title: nls.localize('copyPath', "Copy Path") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 40, + command: copyPathCommand, + when: ResourceContextKey.HasResource +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '2_save', + order: 10, + command: { + id: SAVE_FILE_COMMAND_ID, + title: SAVE_FILE_LABEL + }, + when: ContextKeyExpr.and(ResourceContextKey.IsFile, AutoSaveContext.notEqualsTo('afterDelay')) +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '2_save', + order: 20, + command: { + id: REVERT_FILE_COMMAND_ID, + title: nls.localize('revert', "Revert File") + }, + when: ContextKeyExpr.and(ResourceContextKey.IsFile, AutoSaveContext.notEqualsTo('afterDelay')) +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '2_save', + command: { + id: SAVE_FILE_AS_COMMAND_ID, + title: SAVE_FILE_AS_LABEL + }, + when: ResourceContextKey.Scheme.isEqualTo('untitled') +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '2_save', + command: { + id: SAVE_ALL_IN_GROUP_COMMAND_ID, + title: nls.localize('saveAll', "Save All") + }, + when: ContextKeyExpr.and(OpenEditorsGroupContext, AutoSaveContext.notEqualsTo('afterDelay')) +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '3_compare', + order: 10, + command: { + id: COMPARE_WITH_SAVED_COMMAND_ID, + title: nls.localize('compareWithSaved', "Compare with Saved") + }, + when: ResourceContextKey.IsFile +}); + +const compareResourceCommand = { + id: COMPARE_RESOURCE_COMMAND_ID, + title: nls.localize('compareWithSelected', "Compare with Selected") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '3_compare', + order: 20, + command: compareResourceCommand, + when: ContextKeyExpr.and(ResourceContextKey.HasResource, ResourceSelectedForCompareContext) +}); + +const selectForCompareCommand = { + id: SELECT_FOR_COMPARE_COMMAND_ID, + title: nls.localize('compareSource', "Select for Compare") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '3_compare', + order: 30, + command: selectForCompareCommand, + when: ResourceContextKey.HasResource +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '4_close', + order: 10, + command: { + id: CLOSE_EDITOR_COMMAND_ID, + title: nls.localize('close', "Close") + }, + when: OpenEditorsGroupContext.toNegated() +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '4_close', + order: 20, + command: { + id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, + title: nls.localize('closeOthers', "Close Others") + }, + when: OpenEditorsGroupContext.toNegated() +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '4_close', + order: 30, + command: { + id: CLOSE_UNMODIFIED_EDITORS_COMMAND_ID, + title: nls.localize('closeUnmodified', "Close Unmodified") + } +}); + +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: '4_close', + order: 40, + command: { + id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, + title: nls.localize('closeAll', "Close All") + } +}); + +// Menu registration - explorer + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 4, + command: { + id: NEW_FILE_COMMAND_ID, + title: NEW_FILE_LABEL + }, + when: ExplorerFolderContext +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 6, + command: { + id: NEW_FOLDER_COMMAND_ID, + title: NEW_FOLDER_LABEL + }, + when: ExplorerFolderContext +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 10, + command: openToSideCommand, + when: ContextKeyExpr.and(ResourceContextKey.Scheme.isEqualTo('file'), ExplorerFolderContext.toNegated()) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.HasResource +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '3_compare', + order: 20, + command: compareResourceCommand, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.IsFile, ResourceSelectedForCompareContext) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '3_compare', + order: 30, + command: selectForCompareCommand, + when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.IsFile) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '5_cutcopypaste', + order: 10, + command: { + id: COPY_FILE_ID, + title: COPY_FILE_LABEL + }, + when: ExplorerRootContext.toNegated() +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '5_cutcopypaste', + order: 20, + command: { + id: PASTE_FILE_ID, + title: PASTE_FILE_LABEL + }, + when: ContextKeyExpr.and(ExplorerFolderContext, FileCopiedContext) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '5_cutcopypaste', + order: 30, + command: copyPathCommand, + when: ResourceContextKey.Scheme.isEqualTo('file') +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '2_workspace', + order: 10, + command: { + id: ADD_ROOT_FOLDER_COMMAND_ID, + title: ADD_ROOT_FOLDER_LABEL + }, + when: ExplorerRootContext +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '2_workspace', + order: 20, + command: { + id: OPEN_FOLDER_SETTINGS_COMMAND, + title: OPEN_FOLDER_SETTINGS_LABEL + }, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '2_workspace', + order: 30, + command: { + id: REMOVE_ROOT_FOLDER_COMMAND_ID, + title: REMOVE_ROOT_FOLDER_LABEL + }, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '7_modification', + order: 10, + command: { + id: RENAME_ID, + title: TRIGGER_RENAME_LABEL + }, + when: ExplorerRootContext.toNegated() +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: '7_modification', + order: 20, + command: { + id: MOVE_FILE_TO_TRASH_ID, + title: MOVE_FILE_TO_TRASH_LABEL + }, + when: ExplorerRootContext.toNegated() +}); diff --git a/src/vs/workbench/parts/files/electron-browser/fileActions.ts b/src/vs/workbench/parts/files/electron-browser/fileActions.ts index 97dac5b3dad..e63b9945caf 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileActions.ts @@ -20,13 +20,12 @@ import severity from 'vs/base/common/severity'; import diagnostics = require('vs/base/common/diagnostics'); import { Action, IAction } from 'vs/base/common/actions'; import { MessageType, IInputValidator } from 'vs/base/browser/ui/inputbox/inputBox'; -import { ITree, IHighlightEvent, IActionProvider } from 'vs/base/parts/tree/browser/tree'; +import { ITree, IHighlightEvent } from 'vs/base/parts/tree/browser/tree'; import { dispose, IDisposable } from 'vs/base/common/lifecycle'; -import { VIEWLET_ID, FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files'; -import labels = require('vs/base/common/labels'); +import { VIEWLET_ID } from 'vs/workbench/parts/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IFileService, IFileStat } from 'vs/platform/files/common/files'; -import { toResource, IEditorIdentifier } from 'vs/workbench/common/editor'; +import { toResource } from 'vs/workbench/common/editor'; import { FileStat, Model, NewStatPlaceholder } from 'vs/workbench/parts/files/common/explorerModel'; import { ExplorerView } from 'vs/workbench/parts/files/electron-browser/views/explorerView'; import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; @@ -36,35 +35,57 @@ import { CollapseAction } from 'vs/workbench/browser/viewlet'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { Position, IResourceInput, IUntitledResourceInput } from 'vs/platform/editor/common/editor'; +import { IUntitledResourceInput } from 'vs/platform/editor/common/editor'; import { IInstantiationService, IConstructorSignature2, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, IMessageWithAction, IConfirmation, Severity, CancelAction, IConfirmationResult } from 'vs/platform/message/common/message'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService'; -import { IEditorViewState, IModel } from 'vs/editor/common/editorCommon'; +import { IModel } from 'vs/editor/common/editorCommon'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { withFocusedFilesExplorer, revealInOSCommand, revealInExplorerCommand, copyPathCommand } from 'vs/workbench/parts/files/electron-browser/fileCommands'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, SAVE_ALL_COMMAND_ID, SAVE_ALL_LABEL, SAVE_FILES_COMMAND_ID, SAVE_FILES_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID } from 'vs/workbench/parts/files/electron-browser/fileCommands'; import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { once } from 'vs/base/common/event'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { IListService, ListWidget } from 'vs/platform/list/browser/listService'; +import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IEvent } from 'vs/platform/contextview/browser/contextView'; export interface IEditableData { action: IAction; validator: IInputValidator; } +export interface IExplorerContext { + viewletState: IFileViewletState; + event?: IEvent; + stat: FileStat; +} + export interface IFileViewletState { - actionProvider: IActionProvider; getEditableData(stat: IFileStat): IEditableData; setEditable(stat: IFileStat, editableData: IEditableData): void; clearEditable(stat: IFileStat): void; } +export const NEW_FILE_COMMAND_ID = 'workbench.command.files.newFile'; +export const NEW_FILE_LABEL = nls.localize('newFile', "New File"); + +export const NEW_FOLDER_COMMAND_ID = 'workbench.command.files.newFolder'; +export const NEW_FOLDER_LABEL = nls.localize('newFolder', "New Folder"); + +export const TRIGGER_RENAME_LABEL = nls.localize('rename', "Rename"); + +export const MOVE_FILE_TO_TRASH_LABEL = nls.localize('delete', "Delete"); + +export const COPY_FILE_LABEL = nls.localize('copyFile', "Copy"); + +export const PASTE_FILE_LABEL = nls.localize('pasteFile', "Paste"); + +export const FileCopiedContext = new RawContextKey('fileCopied', false); + export class BaseErrorReportingAction extends Action { constructor( @@ -107,46 +128,30 @@ export class BaseErrorReportingAction extends Action { } export class BaseFileAction extends BaseErrorReportingAction { - private _element: FileStat; + public element: FileStat; constructor( id: string, label: string, - @IFileService private _fileService: IFileService, + @IFileService protected fileService: IFileService, @IMessageService _messageService: IMessageService, - @ITextFileService private _textFileService: ITextFileService + @ITextFileService protected textFileService: ITextFileService ) { super(id, label, _messageService); this.enabled = false; } - public get fileService() { - return this._fileService; - } - - public get textFileService() { - return this._textFileService; - } - - public get element() { - return this._element; - } - - public set element(element: FileStat) { - this._element = element; - } - _isEnabled(): boolean { return true; } _updateEnablement(): void { - this.enabled = !!(this._fileService && this._isEnabled()); + this.enabled = !!(this.fileService && this._isEnabled()); } } -export class TriggerRenameFileAction extends BaseFileAction { +class TriggerRenameFileAction extends BaseFileAction { public static readonly ID = 'renameFile'; @@ -161,7 +166,7 @@ export class TriggerRenameFileAction extends BaseFileAction { @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super(TriggerRenameFileAction.ID, nls.localize('rename', "Rename"), fileService, messageService, textFileService); + super(TriggerRenameFileAction.ID, TRIGGER_RENAME_LABEL, fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -447,7 +452,7 @@ export class NewFileAction extends BaseNewAction { @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super('explorer.newFile', nls.localize('newFile', "New File"), tree, true, instantiationService.createInstance(CreateFileAction, element), null, fileService, messageService, textFileService); + super('explorer.newFile', NEW_FILE_LABEL, tree, true, instantiationService.createInstance(CreateFileAction, element), null, fileService, messageService, textFileService); this.class = 'explorer-action new-file'; this._updateEnablement(); @@ -465,7 +470,7 @@ export class NewFolderAction extends BaseNewAction { @ITextFileService textFileService: ITextFileService, @IInstantiationService instantiationService: IInstantiationService ) { - super('explorer.newFolder', nls.localize('newFolder', "New Folder"), tree, false, instantiationService.createInstance(CreateFolderAction, element), null, fileService, messageService, textFileService); + super('explorer.newFolder', NEW_FOLDER_LABEL, tree, false, instantiationService.createInstance(CreateFolderAction, element), null, fileService, messageService, textFileService); this.class = 'explorer-action new-folder'; this._updateEnablement(); @@ -572,7 +577,7 @@ export abstract class BaseCreateAction extends BaseRenameAction { } /* Create New File (only used internally by explorerViewer) */ -export class CreateFileAction extends BaseCreateAction { +class CreateFileAction extends BaseCreateAction { public static readonly ID = 'workbench.files.action.createFileFromExplorer'; public static readonly LABEL = nls.localize('createNewFile', "New File"); @@ -600,7 +605,7 @@ export class CreateFileAction extends BaseCreateAction { } /* Create New Folder (only used internally by explorerViewer) */ -export class CreateFolderAction extends BaseCreateAction { +class CreateFolderAction extends BaseCreateAction { public static readonly ID = 'workbench.files.action.createFolderFromExplorer'; public static readonly LABEL = nls.localize('createNewFolder', "New Folder"); @@ -624,7 +629,7 @@ export class CreateFolderAction extends BaseCreateAction { } } -export class BaseDeleteFileAction extends BaseFileAction { +class BaseDeleteFileAction extends BaseFileAction { private static readonly CONFIRM_DELETE_SETTING_KEY = 'explorer.confirmDelete'; @@ -633,8 +638,6 @@ export class BaseDeleteFileAction extends BaseFileAction { private skipConfirm: boolean; constructor( - id: string, - label: string, tree: ITree, element: FileStat, useTrash: boolean, @@ -643,7 +646,7 @@ export class BaseDeleteFileAction extends BaseFileAction { @ITextFileService textFileService: ITextFileService, @IConfigurationService private configurationService: IConfigurationService ) { - super(id, label, fileService, messageService, textFileService); + super('moveFileToTrash', MOVE_FILE_TO_TRASH_LABEL, fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -679,7 +682,7 @@ export class BaseDeleteFileAction extends BaseFileAction { } // Handle dirty - let revertPromise: TPromise = TPromise.as(null); + let confirmDirtyPromise: TPromise = TPromise.as(true); const dirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, this.element.resource, !isLinux /* ignorecase */)); if (dirty.length) { let message: string; @@ -693,33 +696,37 @@ export class BaseDeleteFileAction extends BaseFileAction { message = nls.localize('dirtyMessageFileDelete', "You are deleting a file with unsaved changes. Do you want to continue?"); } - const res = this.messageService.confirm({ + confirmDirtyPromise = this.messageService.confirm({ message, type: 'warning', detail: nls.localize('dirtyWarning', "Your changes will be lost if you don't save them."), primaryButton + }).then(confirmed => { + if (!confirmed) { + return false; + } + + this.skipConfirm = true; // since we already asked for confirmation + return this.textFileService.revertAll(dirty).then(() => true); }); - - if (!res) { - return TPromise.as(null); - } - - this.skipConfirm = true; // since we already asked for confirmation - revertPromise = this.textFileService.revertAll(dirty); } // Check if file is dirty in editor and save it to avoid data loss - return revertPromise.then(() => { - let confirmPromise: TPromise; + return confirmDirtyPromise.then(confirmed => { + if (!confirmed) { + return null; + } + + let confirmDeletePromise: TPromise; // Check if we need to ask for confirmation at all if (this.skipConfirm || (this.useTrash && this.configurationService.getValue(BaseDeleteFileAction.CONFIRM_DELETE_SETTING_KEY) === false)) { - confirmPromise = TPromise.as({ confirmed: true } as IConfirmationResult); + confirmDeletePromise = TPromise.as({ confirmed: true } as IConfirmationResult); } // Confirm for moving to trash else if (this.useTrash) { - confirmPromise = this.messageService.confirmWithCheckbox({ + confirmDeletePromise = this.messageService.confirmWithCheckbox({ message: this.element.isDirectory ? nls.localize('confirmMoveTrashMessageFolder', "Are you sure you want to delete '{0}' and its contents?", this.element.name) : nls.localize('confirmMoveTrashMessageFile', "Are you sure you want to delete '{0}'?", this.element.name), detail: isWindows ? nls.localize('undoBin', "You can restore from the recycle bin.") : nls.localize('undoTrash', "You can restore from the trash."), primaryButton, @@ -732,7 +739,7 @@ export class BaseDeleteFileAction extends BaseFileAction { // Confirm for deleting permanently else { - confirmPromise = this.messageService.confirmWithCheckbox({ + confirmDeletePromise = this.messageService.confirmWithCheckbox({ message: this.element.isDirectory ? nls.localize('confirmDeleteMessageFolder', "Are you sure you want to permanently delete '{0}' and its contents?", this.element.name) : nls.localize('confirmDeleteMessageFile', "Are you sure you want to permanently delete '{0}'?", this.element.name), detail: nls.localize('irreversible', "This action is irreversible!"), primaryButton, @@ -740,7 +747,7 @@ export class BaseDeleteFileAction extends BaseFileAction { }); } - return confirmPromise.then(confirmation => { + return confirmDeletePromise.then(confirmation => { // Check for confirmation checkbox let updateConfirmSettingsPromise: TPromise = TPromise.as(void 0); @@ -781,26 +788,9 @@ export class BaseDeleteFileAction extends BaseFileAction { } } -/* Move File/Folder to trash */ -export class MoveFileToTrashAction extends BaseDeleteFileAction { - public static readonly ID = 'moveFileToTrash'; - - constructor( - tree: ITree, - element: FileStat, - @IFileService fileService: IFileService, - @IMessageService messageService: IMessageService, - @ITextFileService textFileService: ITextFileService, - @IConfigurationService configurationService: IConfigurationService - ) { - super(MoveFileToTrashAction.ID, nls.localize('delete', "Delete"), tree, element, true, fileService, messageService, textFileService, configurationService); - } -} - /* Import File */ export class ImportFileAction extends BaseFileAction { - public static readonly ID = 'workbench.files.action.importFile'; private tree: ITree; constructor( @@ -812,7 +802,7 @@ export class ImportFileAction extends BaseFileAction { @IMessageService messageService: IMessageService, @ITextFileService textFileService: ITextFileService ) { - super(ImportFileAction.ID, nls.localize('importFiles', "Import Files"), fileService, messageService, textFileService); + super('workbench.files.action.importFile', nls.localize('importFiles', "Import Files"), fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -850,7 +840,7 @@ export class ImportFileAction extends BaseFileAction { targetNames[isLinux ? child.name : child.name.toLowerCase()] = child; }); - let overwrite = true; + let overwritePromise = TPromise.as(true); if (resources.some(resource => { return !!targetNames[isLinux ? paths.basename(resource.fsPath) : paths.basename(resource.fsPath).toLowerCase()]; })) { @@ -861,41 +851,43 @@ export class ImportFileAction extends BaseFileAction { type: 'warning' }; - overwrite = this.messageService.confirm(confirm); + overwritePromise = this.messageService.confirm(confirm); } - if (!overwrite) { - return void 0; - } + return overwritePromise.then(overwrite => { + if (!overwrite) { + return void 0; + } - // Run import in sequence - const importPromisesFactory: ITask>[] = []; - resources.forEach(resource => { - importPromisesFactory.push(() => { - const sourceFile = resource; - const targetFile = targetElement.resource.with({ path: paths.join(targetElement.resource.path, paths.basename(sourceFile.path)) }); + // Run import in sequence + const importPromisesFactory: ITask>[] = []; + resources.forEach(resource => { + importPromisesFactory.push(() => { + const sourceFile = resource; + const targetFile = targetElement.resource.with({ path: paths.join(targetElement.resource.path, paths.basename(sourceFile.path)) }); - // 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 imported file. since we already - // confirmed the overwrite before, this is OK. - let revertPromise = TPromise.wrap(null); - if (this.textFileService.isDirty(targetFile)) { - revertPromise = this.textFileService.revertAll([targetFile], { soft: true }); - } + // 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 imported file. since we already + // confirmed the overwrite before, this is OK. + let revertPromise = TPromise.wrap(null); + if (this.textFileService.isDirty(targetFile)) { + revertPromise = this.textFileService.revertAll([targetFile], { soft: true }); + } - return revertPromise.then(() => { - return this.fileService.importFile(sourceFile, targetElement.resource).then(res => { + return revertPromise.then(() => { + return this.fileService.importFile(sourceFile, targetElement.resource).then(res => { - // if we only import one file, just open it directly - if (resources.length === 1) { - this.editorService.openEditor({ resource: res.stat.resource, options: { pinned: true } }).done(null, errors.onUnexpectedError); - } - }, error => this.onError(error)); + // if we only import one file, just open it directly + if (resources.length === 1) { + this.editorService.openEditor({ resource: res.stat.resource, options: { pinned: true } }).done(null, errors.onUnexpectedError); + } + }, error => this.onError(error)); + }); }); }); - }); - return sequence(importPromisesFactory); + return sequence(importPromisesFactory); + }); }); } @@ -913,9 +905,9 @@ export class ImportFileAction extends BaseFileAction { // Copy File/Folder let fileToCopy: FileStat; -export class CopyFileAction extends BaseFileAction { +let fileCopiedContextKey: IContextKey; - public static readonly ID = 'filesExplorer.copy'; +class CopyFileAction extends BaseFileAction { private tree: ITree; constructor( @@ -923,12 +915,16 @@ export class CopyFileAction extends BaseFileAction { element: FileStat, @IFileService fileService: IFileService, @IMessageService messageService: IMessageService, - @ITextFileService textFileService: ITextFileService + @ITextFileService textFileService: ITextFileService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(CopyFileAction.ID, nls.localize('copyFile', "Copy"), fileService, messageService, textFileService); + super('filesExplorer.copy', COPY_FILE_LABEL, fileService, messageService, textFileService); this.tree = tree; this.element = element; + if (!fileCopiedContextKey) { + fileCopiedContextKey = FileCopiedContext.bindTo(contextKeyService); + } this._updateEnablement(); } @@ -936,6 +932,7 @@ export class CopyFileAction extends BaseFileAction { // Remember as file/folder to copy fileToCopy = this.element; + fileCopiedContextKey.set(!!this.element); // Remove highlight if (this.tree) { @@ -949,7 +946,7 @@ export class CopyFileAction extends BaseFileAction { } // Paste File/Folder -export class PasteFileAction extends BaseFileAction { +class PasteFileAction extends BaseFileAction { public static readonly ID = 'filesExplorer.paste'; @@ -963,7 +960,7 @@ export class PasteFileAction extends BaseFileAction { @ITextFileService textFileService: ITextFileService, @IInstantiationService private instantiationService: IInstantiationService ) { - super(PasteFileAction.ID, nls.localize('pasteFile', "Paste"), fileService, messageService, textFileService); + super(PasteFileAction.ID, PASTE_FILE_LABEL, fileService, messageService, textFileService); this.tree = tree; this.element = element; @@ -974,30 +971,20 @@ export class PasteFileAction extends BaseFileAction { this._updateEnablement(); } - _isEnabled(): boolean { + public run(): TPromise { - // Need at least a file to copy - if (!fileToCopy) { - return false; - } - - // Check if file was deleted or moved meanwhile const exists = fileToCopy.root.find(fileToCopy.resource); if (!exists) { fileToCopy = null; - return false; + fileCopiedContextKey.set(false); + throw new Error(nls.localize('fileDeleted', "File was deleted or moved meanwhile")); } // Check if target is ancestor of pasted folder if (this.element.resource.toString() !== fileToCopy.resource.toString() && resources.isEqualOrParent(this.element.resource, fileToCopy.resource, !isLinux /* ignorecase */)) { - return false; + throw new Error(nls.localize('fileIsAncestor', "File to copy is an ancestor of the desitnation folder")); } - return true; - } - - public run(): TPromise { - // Find target let target: FileStat; if (this.element.resource.toString() === fileToCopy.resource.toString()) { @@ -1015,19 +1002,6 @@ export class PasteFileAction extends BaseFileAction { } } -export const pasteIntoFocusedFilesExplorerViewItem = (accessor: ServicesAccessor) => { - const instantiationService = accessor.get(IInstantiationService); - - withFocusedFilesExplorer(accessor).then(res => { - if (res) { - const pasteAction = instantiationService.createInstance(PasteFileAction, res.tree, res.tree.getFocus()); - if (pasteAction._isEnabled()) { - pasteAction.run().done(null, errors.onUnexpectedError); - } - } - }); -}; - // Duplicate File/Folder export class DuplicateFileAction extends BaseFileAction { private tree: ITree; @@ -1108,81 +1082,6 @@ export class DuplicateFileAction extends BaseFileAction { } } -// Open to the side -export class OpenToSideAction extends Action { - - public static readonly ID = 'explorer.openToSide'; - public static readonly LABEL = nls.localize('openToSide', "Open to the Side"); - - private tree: ITree; - private resource: URI; - private preserveFocus: boolean; - - constructor( - tree: ITree, - resource: URI, - preserveFocus: boolean, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService - ) { - super(OpenToSideAction.ID, OpenToSideAction.LABEL); - - this.tree = tree; - this.preserveFocus = preserveFocus; - this.resource = resource; - - this.updateEnablement(); - } - - private updateEnablement(): void { - const activeEditor = this.editorService.getActiveEditor(); - this.enabled = (!activeEditor || activeEditor.position !== Position.THREE); - } - - public run(): TPromise { - - // Remove highlight - if (this.tree) { - this.tree.clearHighlight(); - } - - // Set side input - return this.editorService.openEditor({ - resource: this.resource, - options: { - preserveFocus: this.preserveFocus - } - }, true); - } -} - -let globalResourceToCompare: URI; -export class SelectResourceForCompareAction extends Action { - private resource: URI; - private tree: ITree; - - constructor(resource: URI, tree: ITree) { - super('workbench.files.action.selectForCompare', nls.localize('compareSource', "Select for Compare")); - - this.tree = tree; - this.resource = resource; - this.enabled = true; - } - - public run(): TPromise { - - // Remember as source file to compare - globalResourceToCompare = this.resource; - - // Remove highlight - if (this.tree) { - this.tree.clearHighlight(); - this.tree.DOMFocus(); - } - - return TPromise.as(null); - } -} - // Global Compare with export class GlobalCompareResourcesAction extends Action { @@ -1230,88 +1129,6 @@ export class GlobalCompareResourcesAction extends Action { } } -// Compare with Resource -export class CompareResourcesAction extends Action { - private tree: ITree; - private resource: URI; - - constructor( - resource: URI, - tree: ITree, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IEnvironmentService environmentService: IEnvironmentService - ) { - super('workbench.files.action.compareFiles', CompareResourcesAction.computeLabel(resource, contextService, environmentService)); - - this.tree = tree; - this.resource = resource; - } - - private static computeLabel(resource: URI, contextService: IWorkspaceContextService, environmentService: IEnvironmentService): string { - if (globalResourceToCompare) { - let leftResourceName = paths.basename(globalResourceToCompare.fsPath); - let rightResourceName = paths.basename(resource.fsPath); - - // If the file names are identical, add more context by looking at the parent folder - if (leftResourceName === rightResourceName) { - const folderPaths = labels.shorten([ - labels.getPathLabel(resources.dirname(globalResourceToCompare), contextService, environmentService), - labels.getPathLabel(resources.dirname(resource), contextService, environmentService) - ]); - - leftResourceName = paths.join(folderPaths[0], leftResourceName); - rightResourceName = paths.join(folderPaths[1], rightResourceName); - } - - return nls.localize('compareWith', "Compare '{0}' with '{1}'", leftResourceName, rightResourceName); - } - - return nls.localize('compareFiles', "Compare Files"); - } - - public _isEnabled(): boolean { - - // Need at least a resource to compare - if (!globalResourceToCompare) { - return false; - } - - // Check if file was deleted or moved meanwhile (explorer only) - if (this.tree) { - const input = this.tree.getInput(); - if (input instanceof FileStat || input instanceof Model) { - const exists = input instanceof Model ? input.findClosest(globalResourceToCompare) : input.find(globalResourceToCompare); - if (!exists) { - globalResourceToCompare = null; - - return false; - } - } - } - - // Check if target is identical to source - if (this.resource.toString() === globalResourceToCompare.toString()) { - return false; - } - - return true; - } - - public run(): TPromise { - - // Remove highlight - if (this.tree) { - this.tree.clearHighlight(); - } - - return this.editorService.openEditor({ - leftResource: globalResourceToCompare, - rightResource: this.resource - }); - } -} - // Refresh Explorer Viewer export class RefreshViewExplorerAction extends Action { @@ -1320,166 +1137,17 @@ export class RefreshViewExplorerAction extends Action { } } -export abstract class BaseSaveFileAction extends BaseErrorReportingAction { - constructor( - id: string, - label: string, - messageService: IMessageService - ) { - super(id, label, messageService); - } - - public run(context?: any): TPromise { - return this.doRun(context).then(() => true, error => { - this.onError(error); - return null; - }); - } - - protected abstract doRun(context?: any): TPromise; -} - -export abstract class BaseSaveOneFileAction extends BaseSaveFileAction { - private resource: URI; - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @ITextFileService private textFileService: ITextFileService, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @IMessageService messageService: IMessageService, - @IFileService private fileService: IFileService - ) { - super(id, label, messageService); - - this.enabled = true; - } - - public abstract isSaveAs(): boolean; - - public setResource(resource: URI): void { - this.resource = resource; - } - - protected doRun(context: any): TPromise { - let source: URI; - if (this.resource) { - source = this.resource; - } else { - source = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true }); - } - - if (source && (this.fileService.canHandleResource(source) || source.scheme === 'untitled')) { - - // Save As (or Save untitled with associated path) - if (this.isSaveAs() || source.scheme === 'untitled') { - let encodingOfSource: string; - if (source.scheme === 'untitled') { - encodingOfSource = this.untitledEditorService.getEncoding(source); - } else if (source.scheme === 'file') { - const textModel = this.textFileService.models.get(source); - encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! - } - - let viewStateOfSource: IEditorViewState; - const activeEditor = this.editorService.getActiveEditor(); - const editor = getCodeEditor(activeEditor); - if (editor) { - const activeResource = toResource(activeEditor.input, { supportSideBySide: true }); - if (activeResource && (this.fileService.canHandleResource(activeResource) || source.scheme === 'untitled') && activeResource.toString() === source.toString()) { - viewStateOfSource = editor.saveViewState(); - } - } - - // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true - let savePromise: TPromise; - if (!this.isSaveAs() && source.scheme === 'untitled' && this.untitledEditorService.hasAssociatedFilePath(source)) { - savePromise = this.textFileService.save(source).then((result) => { - if (result) { - return URI.file(source.fsPath); - } - - return null; - }); - } - - // Otherwise, really "Save As..." - else { - savePromise = this.textFileService.saveAs(source); - } - - return savePromise.then((target) => { - if (!target || target.toString() === source.toString()) { - return void 0; // save canceled or same resource used - } - - const replaceWith: IResourceInput = { - resource: target, - encoding: encodingOfSource, - options: { - pinned: true, - viewState: viewStateOfSource - } - }; - - return this.editorService.replaceEditors([{ - toReplace: { resource: source }, - replaceWith - }]).then(() => true); - }); - } - - // Pin the active editor if we are saving it - if (!this.resource) { - const editor = this.editorService.getActiveEditor(); - if (editor) { - this.editorGroupService.pinEditor(editor.position, editor.input); - } - } - - // Just save - return this.textFileService.save(source, { force: true /* force a change to the file to trigger external watchers if any */ }); - } - - return TPromise.as(false); - } -} - -export class SaveFileAction extends BaseSaveOneFileAction { - - public static readonly ID = 'workbench.action.files.save'; - public static readonly LABEL = nls.localize('save', "Save"); - - public isSaveAs(): boolean { - return false; - } -} - -export class SaveFileAsAction extends BaseSaveOneFileAction { - - public static readonly ID = 'workbench.action.files.saveAs'; - public static readonly LABEL = nls.localize('saveAs', "Save As..."); - - public isSaveAs(): boolean { - return true; - } -} - -export abstract class BaseSaveAllAction extends BaseSaveFileAction { +export abstract class BaseSaveAllAction extends BaseErrorReportingAction { private toDispose: IDisposable[]; private lastIsDirty: boolean; constructor( id: string, label: string, - @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, - @IEditorGroupService private editorGroupService: IEditorGroupService, @ITextFileService private textFileService: ITextFileService, @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @ICommandService protected commandService: ICommandService, @IMessageService messageService: IMessageService, - @IFileService protected fileService: IFileService ) { super(id, label, messageService); @@ -1490,8 +1158,8 @@ export abstract class BaseSaveAllAction extends BaseSaveFileAction { this.registerListeners(); } - protected abstract getSaveAllArguments(context?: any): any; protected abstract includeUntitled(): boolean; + protected abstract doRun(context: any): TPromise; private registerListeners(): void { @@ -1513,79 +1181,10 @@ export abstract class BaseSaveAllAction extends BaseSaveFileAction { } } - protected doRun(context: any): TPromise { - const stacks = this.editorGroupService.getStacksModel(); - - // Store some properties per untitled file to restore later after save is completed - const mapUntitledToProperties: { [resource: string]: { encoding: string; indexInGroups: number[]; activeInGroups: boolean[] } } = Object.create(null); - this.untitledEditorService.getDirty().forEach(resource => { - const activeInGroups: boolean[] = []; - const indexInGroups: number[] = []; - const encoding = this.untitledEditorService.getEncoding(resource); - - // For each group - stacks.groups.forEach((group, groupIndex) => { - - // Find out if editor is active in group - const activeEditor = group.activeEditor; - const activeResource = toResource(activeEditor, { supportSideBySide: true }); - activeInGroups[groupIndex] = (activeResource && activeResource.toString() === resource.toString()); - - // Find index of editor in group - indexInGroups[groupIndex] = -1; - group.getEditors().forEach((editor, editorIndex) => { - const editorResource = toResource(editor, { supportSideBySide: true }); - if (editorResource && editorResource.toString() === resource.toString()) { - indexInGroups[groupIndex] = editorIndex; - return; - } - }); - }); - - mapUntitledToProperties[resource.toString()] = { encoding, indexInGroups, activeInGroups }; - }); - - // Save all - return this.textFileService.saveAll(this.getSaveAllArguments(context)).then(results => { - - // Reopen saved untitled editors - const untitledToReopen: { input: IResourceInput, position: Position }[] = []; - - results.results.forEach(result => { - if (!result.success || result.source.scheme !== 'untitled') { - return; - } - - const untitledProps = mapUntitledToProperties[result.source.toString()]; - if (!untitledProps) { - return; - } - - // For each position where the untitled file was opened - untitledProps.indexInGroups.forEach((indexInGroup, index) => { - if (indexInGroup >= 0) { - untitledToReopen.push({ - input: { - resource: result.target, - encoding: untitledProps.encoding, - options: { - pinned: true, - index: indexInGroup, - preserveFocus: true, - inactive: !untitledProps.activeInGroups[index] - } - }, - position: index - }); - } - }); - }); - - if (untitledToReopen.length) { - return this.editorService.openEditors(untitledToReopen).then(() => true); - } - - return void 0; + public run(context?: any): TPromise { + return this.doRun(context).then(() => true, error => { + this.onError(error); + return null; }); } @@ -1599,14 +1198,14 @@ export abstract class BaseSaveAllAction extends BaseSaveFileAction { export class SaveAllAction extends BaseSaveAllAction { public static readonly ID = 'workbench.action.files.saveAll'; - public static readonly LABEL = nls.localize('saveAll', "Save All"); + public static readonly LABEL = SAVE_ALL_LABEL; public get class(): string { return 'explorer-action save-all'; } - protected getSaveAllArguments(): boolean { - return this.includeUntitled(); + protected doRun(context: any): TPromise { + return this.commandService.executeCommand(SAVE_ALL_COMMAND_ID); } protected includeUntitled(): boolean { @@ -1623,21 +1222,8 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { return 'explorer-action save-all'; } - protected getSaveAllArguments(editorIdentifier: IEditorIdentifier): any { - if (!editorIdentifier) { - return this.includeUntitled(); - } - - const editorGroup = editorIdentifier.group; - const resourcesToSave: URI[] = []; - editorGroup.getEditors().forEach(editor => { - const resource = toResource(editor, { supportSideBySide: true }); - if (resource && (resource.scheme === 'untitled' || this.fileService.canHandleResource(resource))) { - resourcesToSave.push(resource); - } - }); - - return resourcesToSave; + protected doRun(context: any): TPromise { + return this.commandService.executeCommand(SAVE_ALL_IN_GROUP_COMMAND_ID); } protected includeUntitled(): boolean { @@ -1648,10 +1234,10 @@ export class SaveAllInGroupAction extends BaseSaveAllAction { export class SaveFilesAction extends BaseSaveAllAction { public static readonly ID = 'workbench.action.files.saveFiles'; - public static readonly LABEL = nls.localize('saveFiles', "Save All Files"); + public static readonly LABEL = SAVE_FILES_LABEL; - protected getSaveAllArguments(): boolean { - return this.includeUntitled(); + protected doRun(context: any): TPromise { + return this.commandService.executeCommand(SAVE_FILES_COMMAND_ID, false); } protected includeUntitled(): boolean { @@ -1659,44 +1245,6 @@ export class SaveFilesAction extends BaseSaveAllAction { } } -export class RevertFileAction extends Action { - - public static readonly ID = 'workbench.action.files.revert'; - public static readonly LABEL = nls.localize('revert', "Revert File"); - - private resource: URI; - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @ITextFileService private textFileService: ITextFileService - ) { - super(id, label); - - this.enabled = true; - } - - public setResource(resource: URI): void { - this.resource = resource; - } - - public run(): TPromise { - let resource: URI; - if (this.resource) { - resource = this.resource; - } else { - resource = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); - } - - if (resource && resource.scheme !== 'untitled') { - return this.textFileService.revert(resource, { force: true }); - } - - return TPromise.as(true); - } -} - export class FocusOpenEditorsView extends Action { public static readonly ID = 'workbench.files.action.focusOpenEditorsView'; @@ -1754,8 +1302,8 @@ export class ShowActiveFileInExplorer extends Action { id: string, label: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IInstantiationService private instantiationService: IInstantiationService, - @IMessageService private messageService: IMessageService + @IMessageService private messageService: IMessageService, + @ICommandService private commandService: ICommandService ) { super(id, label); } @@ -1763,7 +1311,7 @@ export class ShowActiveFileInExplorer extends Action { public run(): TPromise { const resource = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true }); if (resource) { - this.instantiationService.invokeFunction.apply(this.instantiationService, [revealInExplorerCommand, resource]); + this.commandService.executeCommand(REVEAL_IN_EXPLORER_COMMAND_ID, { resource }); } else { this.messageService.show(severity.Info, nls.localize('openFileToShow', "Open a file first to show it in the explorer")); } @@ -1850,26 +1398,6 @@ export class ShowOpenedFileInNewWindow extends Action { } } -export class RevealInOSAction extends Action { - - public static readonly LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); - - constructor( - private resource: URI, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super('revealFileInOS', RevealInOSAction.LABEL); - - this.order = 45; - } - - public run(): TPromise { - this.instantiationService.invokeFunction.apply(this.instantiationService, [revealInOSCommand, this.resource]); - - return TPromise.as(true); - } -} - export class GlobalRevealInOSAction extends Action { public static readonly ID = 'workbench.action.files.revealActiveFileInWindows'; @@ -1878,15 +1406,13 @@ export class GlobalRevealInOSAction extends Action { constructor( id: string, label: string, - @IInstantiationService private instantiationService: IInstantiationService + @ICommandService private commandService: ICommandService ) { super(id, label); } public run(): TPromise { - this.instantiationService.invokeFunction.apply(this.instantiationService, [revealInOSCommand]); - - return TPromise.as(true); + return this.commandService.executeCommand(REVEAL_IN_OS_COMMAND_ID); } } @@ -1896,7 +1422,7 @@ export class CopyPathAction extends Action { constructor( private resource: URI, - @IInstantiationService private instantiationService: IInstantiationService + @ICommandService private commandService: ICommandService ) { super('copyFilePath', CopyPathAction.LABEL); @@ -1904,29 +1430,7 @@ export class CopyPathAction extends Action { } public run(): TPromise { - this.instantiationService.invokeFunction.apply(this.instantiationService, [copyPathCommand, this.resource]); - - return TPromise.as(true); - } -} - -export class GlobalCopyPathAction extends Action { - - public static readonly ID = 'workbench.action.files.copyPathOfActiveFile'; - public static readonly LABEL = nls.localize('copyPathOfActive', "Copy Path of Active File"); - - constructor( - id: string, - label: string, - @IInstantiationService private instantiationService: IInstantiationService - ) { - super(id, label); - } - - public run(): TPromise { - this.instantiationService.invokeFunction.apply(this.instantiationService, [copyPathCommand]); - - return TPromise.as(true); + return this.commandService.executeCommand(COPY_PATH_COMMAND_ID, this.resource); } } @@ -1991,64 +1495,6 @@ export function getWellFormedFileName(filename: string): string { return filename; } -export class CompareWithSavedAction extends Action { - - public static readonly ID = 'workbench.files.action.compareWithSaved'; - public static readonly LABEL = nls.localize('compareWithSaved', "Compare Active File with Saved"); - - private static readonly SCHEME = 'showModifications'; - - private resource: URI; - private toDispose: IDisposable[]; - - constructor( - id: string, - label: string, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IInstantiationService instantiationService: IInstantiationService, - @ITextModelService textModelService: ITextModelService - ) { - super(id, label); - - this.enabled = true; - this.toDispose = []; - - const provider = instantiationService.createInstance(FileOnDiskContentProvider); - this.toDispose.push(provider); - - const registrationDisposal = textModelService.registerTextModelContentProvider(CompareWithSavedAction.SCHEME, provider); - this.toDispose.push(registrationDisposal); - } - - public setResource(resource: URI) { - this.resource = resource; - } - - public run(): TPromise { - let resource: URI; - if (this.resource) { - resource = this.resource; - } else { - resource = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); - } - - if (resource && resource.scheme === 'file') { - const name = paths.basename(resource.fsPath); - const editorLabel = nls.localize('modifiedLabel', "{0} (on disk) ↔ {1}", name, name); - - return this.editorService.openEditor({ leftResource: URI.from({ scheme: CompareWithSavedAction.SCHEME, path: resource.fsPath }), rightResource: resource, label: editorLabel }); - } - - return TPromise.as(true); - } - - public dispose(): void { - super.dispose(); - - this.toDispose = dispose(this.toDispose); - } -} - export class CompareWithClipboardAction extends Action { public static readonly ID = 'workbench.files.action.compareWithClipboard'; @@ -2120,3 +1566,86 @@ if (!diag) { console.log(args[1] + ' - ' + args[0] + ' (time: ' + args[2].getTime() + ' [' + args[2].toUTCString() + '])'); }); } + +// TODO@isidor these commands are calling into actions due to the complex inheritance action structure. +// It should be the other way around, that actions call into commands. +CommandsRegistry.registerCommand({ + id: NEW_FILE_COMMAND_ID, + handler: (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + const newFileAction = instantationService.createInstance(NewFileAction, listService.lastFocusedList, explorerContext.stat); + + return newFileAction.run(explorerContext); + } +}); + +CommandsRegistry.registerCommand({ + id: NEW_FOLDER_COMMAND_ID, + handler: (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + const newFolderAction = instantationService.createInstance(NewFolderAction, listService.lastFocusedList, explorerContext.stat); + + return newFolderAction.run(explorerContext); + } +}); + +function getContext(tree: ListWidget, viewletService: IViewletService): IExplorerContext { + return { stat: tree.getFocus(), viewletState: (viewletService.getActiveViewlet()).getViewletState() }; +} + +export const renameHandler = (accessor: ServicesAccessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + if (!explorerContext) { + explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService)); + } + + const renameAction = instantationService.createInstance(TriggerRenameFileAction, listService.lastFocusedList, explorerContext.stat); + return renameAction.run(explorerContext); +}; + +export const moveFileToTrashHandler = (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + if (!explorerContext) { + explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService)); + } + + const moveFileToTrashAction = instantationService.createInstance(BaseDeleteFileAction, listService.lastFocusedList, explorerContext.stat, true); + return moveFileToTrashAction.run(explorerContext); +}; + +export const deleteFileHandler = (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + if (!explorerContext) { + explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService)); + } + + const deleteFileAction = instantationService.createInstance(BaseDeleteFileAction, listService.lastFocusedList, explorerContext.stat, false); + return deleteFileAction.run(explorerContext); +}; + +export const copyFileHandler = (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + if (!explorerContext) { + explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService)); + } + + const copyFileAction = instantationService.createInstance(CopyFileAction, listService.lastFocusedList, explorerContext.stat); + return copyFileAction.run(); +}; + +export const pasteFileHandler = (accessor, resource: URI, explorerContext: IExplorerContext) => { + const instantationService = accessor.get(IInstantiationService); + const listService = accessor.get(IListService); + if (!explorerContext) { + explorerContext = getContext(listService.lastFocusedList, accessor.get(IViewletService)); + } + + const pasteFileAction = instantationService.createInstance(PasteFileAction, listService.lastFocusedList, explorerContext.stat); + return pasteFileAction.run(); +}; diff --git a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts index 40455bd3cb2..6238bc45d8f 100644 --- a/src/vs/workbench/parts/files/electron-browser/fileCommands.ts +++ b/src/vs/workbench/parts/files/electron-browser/fileCommands.ts @@ -9,246 +9,483 @@ import nls = require('vs/nls'); import paths = require('vs/base/common/paths'); import severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; +import * as labels from 'vs/base/common/labels'; import URI from 'vs/base/common/uri'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { toResource } from 'vs/workbench/common/editor'; +import { toResource, IEditorContext } from 'vs/workbench/common/editor'; import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { ExplorerViewlet } from 'vs/workbench/parts/files/electron-browser/explorerViewlet'; -import { VIEWLET_ID, explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; -import { FileStat, OpenEditor } from 'vs/workbench/parts/files/common/explorerModel'; -import errors = require('vs/base/common/errors'); -import { ITree } from 'vs/base/parts/tree/browser/tree'; +import { VIEWLET_ID, ExplorerFocusCondition, FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; -import { IMessageService } from 'vs/platform/message/common/message'; -import { getPathLabel } from 'vs/base/common/labels'; +import { IMessageService, Severity } from 'vs/platform/message/common/message'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { basename } from 'vs/base/common/paths'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; +import { CommandsRegistry } from 'vs/platform/commands/common/commands'; +import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IResourceInput, Position } from 'vs/platform/editor/common/editor'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { IEditorViewState } from 'vs/editor/common/editorCommon'; +import { getCodeEditor } from 'vs/editor/browser/services/codeEditorService'; +import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; // Commands -export const copyPathCommand = (accessor: ServicesAccessor, resource?: URI) => { +export const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; +export const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); +export const REVEAL_IN_EXPLORER_COMMAND_ID = 'workbench.command.files.revealInExplorer'; +export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; +export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; +export const SELECT_FOR_COMPARE_COMMAND_ID = 'workbench.files.command.selectForCompare'; +export const COMPARE_RESOURCE_COMMAND_ID = 'workbench.files.command.compareFiles'; +export const COMPARE_WITH_SAVED_COMMAND_ID = 'workbench.files.action.compareWithSaved'; +export const COPY_PATH_COMMAND_ID = 'copyFilePath'; - // Without resource, try to look at the active editor - if (!resource) { - const editorGroupService = accessor.get(IEditorGroupService); - const editorService = accessor.get(IWorkbenchEditorService); - const activeEditor = editorService.getActiveEditor(); +export const SAVE_FILE_AS_COMMAND_ID = 'workbench.action.files.saveAs'; +export const SAVE_FILE_AS_LABEL = nls.localize('saveAs', "Save As..."); +export const SAVE_FILE_COMMAND_ID = 'workbench.action.files.save'; +export const SAVE_FILE_LABEL = nls.localize('save', "Save"); - resource = activeEditor ? toResource(activeEditor.input, { supportSideBySide: true }) : void 0; - if (activeEditor) { - editorGroupService.focusGroup(activeEditor.position); // focus back to active editor group - } - } +export const SAVE_ALL_COMMAND_ID = 'workbench.command.files.saveAll'; +export const SAVE_ALL_LABEL = nls.localize('saveAll', "Save All"); - if (resource) { - const clipboardService = accessor.get(IClipboardService); - clipboardService.writeText(resource.scheme === 'file' ? getPathLabel(resource) : resource.toString()); - } else { - const messageService = accessor.get(IMessageService); - messageService.show(severity.Info, nls.localize('openFileToCopy', "Open a file first to copy its path")); - } -}; +export const SAVE_ALL_IN_GROUP_COMMAND_ID = 'workbench.files.action.saveAllInGroup'; + +export const SAVE_FILES_COMMAND_ID = 'workbench.command.files.saveFiles'; +export const SAVE_FILES_LABEL = nls.localize('saveFiles', "Save All Files"); + +export const OpenEditorsGroupContext = new RawContextKey('groupFocusedInOpenEditors', false); +export const ResourceSelectedForCompareContext = new RawContextKey('resourceSelectedForCompare', false); export const openWindowCommand = (accessor: ServicesAccessor, paths: string[], forceNewWindow: boolean) => { const windowsService = accessor.get(IWindowsService); windowsService.openWindow(paths, { forceNewWindow }); }; -export const revealInOSCommand = (accessor: ServicesAccessor, resource?: URI) => { +function save(resource: URI, isSaveAs: boolean, editorService: IWorkbenchEditorService, fileService: IFileService, untitledEditorService: IUntitledEditorService, + textFileService: ITextFileService, editorGroupService: IEditorGroupService): TPromise { - // Without resource, try to look at the active editor - if (!resource) { - const editorService = accessor.get(IWorkbenchEditorService); - - resource = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); - } - - if (resource) { - const windowsService = accessor.get(IWindowsService); - windowsService.showItemInFolder(paths.normalize(resource.fsPath, true)); + let source: URI; + if (resource instanceof URI) { + source = resource; } else { - const messageService = accessor.get(IMessageService); - messageService.show(severity.Info, nls.localize('openFileToReveal', "Open a file first to reveal")); + source = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true }); } -}; -export const revealInExplorerCommand = (accessor: ServicesAccessor, resource: URI) => { - const viewletService = accessor.get(IViewletService); - const contextService = accessor.get(IWorkspaceContextService); + if (source && (fileService.canHandleResource(source) || source.scheme === 'untitled')) { - viewletService.openViewlet(VIEWLET_ID, false).then((viewlet: ExplorerViewlet) => { - const isInsideWorkspace = contextService.isInsideWorkspace(resource); - if (isInsideWorkspace) { - const explorerView = viewlet.getExplorerView(); - if (explorerView) { - explorerView.setExpanded(true); - explorerView.select(resource, true); - } - } else { - const openEditorsView = viewlet.getOpenEditorsView(); - if (openEditorsView) { - openEditorsView.setExpanded(true); - } - } - }); -}; - -function openFocusedFilesExplorerViewItem(accessor: ServicesAccessor, sideBySide: boolean): void { - withFocusedFilesExplorerViewItem(accessor).then(res => { - if (res) { - - // Directory: Toggle expansion - if (res.item.isDirectory) { - res.tree.toggleExpansion(res.item); + // Save As (or Save untitled with associated path) + if (isSaveAs || source.scheme === 'untitled') { + let encodingOfSource: string; + if (source.scheme === 'untitled') { + encodingOfSource = untitledEditorService.getEncoding(source); + } else if (source.scheme === 'file') { + const textModel = textFileService.models.get(source); + encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! } - // File: Open + let viewStateOfSource: IEditorViewState; + const activeEditor = editorService.getActiveEditor(); + const editor = getCodeEditor(activeEditor); + if (editor) { + const activeResource = toResource(activeEditor.input, { supportSideBySide: true }); + if (activeResource && (fileService.canHandleResource(activeResource) || source.scheme === 'untitled') && activeResource.toString() === source.toString()) { + viewStateOfSource = editor.saveViewState(); + } + } + + // Special case: an untitled file with associated path gets saved directly unless "saveAs" is true + let savePromise: TPromise; + if (!isSaveAs && source.scheme === 'untitled' && untitledEditorService.hasAssociatedFilePath(source)) { + savePromise = textFileService.save(source).then((result) => { + if (result) { + return URI.file(source.fsPath); + } + + return null; + }); + } + + // Otherwise, really "Save As..." else { - const editorService = accessor.get(IWorkbenchEditorService); - editorService.openEditor({ resource: res.item.resource }, sideBySide).done(null, errors.onUnexpectedError); + savePromise = textFileService.saveAs(source); + } + + return savePromise.then((target) => { + if (!target || target.toString() === source.toString()) { + return void 0; // save canceled or same resource used + } + + const replaceWith: IResourceInput = { + resource: target, + encoding: encodingOfSource, + options: { + pinned: true, + viewState: viewStateOfSource + } + }; + + return editorService.replaceEditors([{ + toReplace: { resource: source }, + replaceWith + }]).then(() => true); + }); + } + + // Pin the active editor if we are saving it + if (!resource) { + const editor = editorService.getActiveEditor(); + if (editor) { + editorGroupService.pinEditor(editor.position, editor.input); } } - }); -} -function openFocusedOpenedEditorsViewItem(accessor: ServicesAccessor, sideBySide: boolean): void { - withFocusedOpenEditorsViewItem(accessor).then(res => { - if (res) { - const editorService = accessor.get(IWorkbenchEditorService); - - editorService.openEditor(res.item.editorInput, null, sideBySide); - } - }); -} - -function runActionOnFocusedFilesExplorerViewItem(accessor: ServicesAccessor, id: string, context?: any): void { - withFocusedFilesExplorerViewItem(accessor).then(res => { - if (res) { - res.explorer.getViewletState().actionProvider.runAction(res.tree, res.item, id, context).done(null, errors.onUnexpectedError); - } - }); -} - -function withVisibleExplorer(accessor: ServicesAccessor): TPromise { - const viewletService = accessor.get(IViewletService); - - const activeViewlet = viewletService.getActiveViewlet(); - if (!activeViewlet || activeViewlet.getId() !== VIEWLET_ID) { - return TPromise.as(void 0); // Return early if the active viewlet is not the explorer + // Just save + return textFileService.save(source, { force: true /* force a change to the file to trigger external watchers if any */ }); } - return viewletService.openViewlet(VIEWLET_ID, false) as TPromise; + return TPromise.as(false); } -export function withFocusedFilesExplorerViewItem(accessor: ServicesAccessor): TPromise<{ explorer: ExplorerViewlet, tree: ITree, item: FileStat }> { - return withFocusedFilesExplorer(accessor).then(res => { - if (!res) { - return void 0; - } +function saveAll(saveAllArguments: any, editorService: IWorkbenchEditorService, untitledEditorService: IUntitledEditorService, + textFileService: ITextFileService, editorGroupService: IEditorGroupService): TPromise { - const { tree, explorer } = res; - if (!tree || !tree.getFocus()) { - return void 0; - } + const stacks = editorGroupService.getStacksModel(); - return { explorer, tree, item: tree.getFocus() }; + // Store some properties per untitled file to restore later after save is completed + const mapUntitledToProperties: { [resource: string]: { encoding: string; indexInGroups: number[]; activeInGroups: boolean[] } } = Object.create(null); + untitledEditorService.getDirty().forEach(resource => { + const activeInGroups: boolean[] = []; + const indexInGroups: number[] = []; + const encoding = untitledEditorService.getEncoding(resource); + + // For each group + stacks.groups.forEach((group, groupIndex) => { + + // Find out if editor is active in group + const activeEditor = group.activeEditor; + const activeResource = toResource(activeEditor, { supportSideBySide: true }); + activeInGroups[groupIndex] = (activeResource && activeResource.toString() === resource.toString()); + + // Find index of editor in group + indexInGroups[groupIndex] = -1; + group.getEditors().forEach((editor, editorIndex) => { + const editorResource = toResource(editor, { supportSideBySide: true }); + if (editorResource && editorResource.toString() === resource.toString()) { + indexInGroups[groupIndex] = editorIndex; + return; + } + }); + }); + + mapUntitledToProperties[resource.toString()] = { encoding, indexInGroups, activeInGroups }; }); -} -export function withFocusedFilesExplorer(accessor: ServicesAccessor): TPromise<{ explorer: ExplorerViewlet, tree: ITree }> { - return withVisibleExplorer(accessor).then(explorer => { - if (!explorer || !explorer.getExplorerView()) { - return void 0; // empty folder or hidden explorer - } + // Save all + return textFileService.saveAll(saveAllArguments).then(results => { - const tree = explorer.getExplorerView().getViewer(); + // Reopen saved untitled editors + const untitledToReopen: { input: IResourceInput, position: Position }[] = []; - // Ignore if in highlight mode or not focused - if (tree.getHighlight() || !tree.isDOMFocused()) { - return void 0; - } - - return { explorer, tree }; - }); -} - -function withFocusedOpenEditorsViewItem(accessor: ServicesAccessor): TPromise<{ explorer: ExplorerViewlet, item: OpenEditor }> { - return withVisibleExplorer(accessor).then(explorer => { - if (!explorer || !explorer.getOpenEditorsView() || !explorer.getOpenEditorsView().getList()) { - return void 0; // empty folder or hidden explorer - } - - const list = explorer.getOpenEditorsView().getList(); - - // Ignore if in highlight mode or not focused - const focused = list.getFocusedElements(); - const focus = focused.length ? focused[0] : undefined; - if (!list.isDOMFocused() || !(focus instanceof OpenEditor)) { - return void 0; - } - - return { explorer, item: focus }; - }); -} - -function withFocusedExplorerItem(accessor: ServicesAccessor): TPromise { - return withFocusedFilesExplorerViewItem(accessor).then(res => { - if (res) { - return res.item; - } - - return withFocusedOpenEditorsViewItem(accessor).then(res => { - if (res) { - return res.item as FileStat | OpenEditor; + results.results.forEach(result => { + if (!result.success || result.source.scheme !== 'untitled') { + return; } - return void 0; + const untitledProps = mapUntitledToProperties[result.source.toString()]; + if (!untitledProps) { + return; + } + + // For each position where the untitled file was opened + untitledProps.indexInGroups.forEach((indexInGroup, index) => { + if (indexInGroup >= 0) { + untitledToReopen.push({ + input: { + resource: result.target, + encoding: untitledProps.encoding, + options: { + pinned: true, + index: indexInGroup, + preserveFocus: true, + inactive: !untitledProps.activeInGroups[index] + } + }, + position: index + }); + } + }); }); + + if (untitledToReopen.length) { + return editorService.openEditors(untitledToReopen).then(() => true); + } + + return void 0; }); } -export const renameFocusedFilesExplorerViewItemCommand = (accessor: ServicesAccessor) => { - runActionOnFocusedFilesExplorerViewItem(accessor, 'renameFile'); -}; +// Command registration -export const deleteFocusedFilesExplorerViewItemCommand = (accessor: ServicesAccessor) => { - runActionOnFocusedFilesExplorerViewItem(accessor, 'moveFileToTrash', { useTrash: false }); -}; +CommandsRegistry.registerCommand({ + id: REVERT_FILE_COMMAND_ID, + handler: (accessor, resource: URI) => { + const editorService = accessor.get(IWorkbenchEditorService); + const textFileService = accessor.get(ITextFileService); + const messageService = accessor.get(IMessageService); -export const moveFocusedFilesExplorerViewItemToTrashCommand = (accessor: ServicesAccessor) => { - runActionOnFocusedFilesExplorerViewItem(accessor, 'moveFileToTrash', { useTrash: true }); -}; - -export const copyFocusedFilesExplorerViewItem = (accessor: ServicesAccessor) => { - runActionOnFocusedFilesExplorerViewItem(accessor, 'filesExplorer.copy'); -}; - -export const copyPathOfFocusedExplorerItem = (accessor: ServicesAccessor) => { - withFocusedExplorerItem(accessor).then(item => { - const file = explorerItemToFileResource(item); - if (file) { - copyPathCommand(accessor, file.resource); + if (!resource) { + resource = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); } - }); -}; -export const openFocusedExplorerItemSideBySideCommand = (accessor: ServicesAccessor) => { - withFocusedExplorerItem(accessor).then(item => { - if (item instanceof FileStat) { - openFocusedFilesExplorerViewItem(accessor, true); + if (resource && resource.scheme !== 'untitled') { + return textFileService.revert(resource, { force: true }).then(null, error => { + messageService.show(Severity.Error, nls.localize('genericRevertError', "Failed to revert '{0}': {1}", basename(resource.fsPath), toErrorMessage(error, false))); + }); + } + + return TPromise.as(true); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ExplorerFocusCondition, + primary: KeyMod.CtrlCmd | KeyCode.Enter, + mac: { + primary: KeyMod.WinCtrl | KeyCode.Enter + }, + id: OPEN_TO_SIDE_COMMAND_ID, handler: (accessor, resource: URI) => { + const editorService = accessor.get(IWorkbenchEditorService); + const listService = accessor.get(IListService); + const tree = listService.lastFocusedList; + // Remove highlight + if (tree instanceof Tree) { + tree.clearHighlight(); + } + + // Set side input + return editorService.openEditor({ + resource, + options: { + preserveFocus: false + } + }, true); + } +}); + +const COMPARE_WITH_SAVED_SCHEMA = 'showModifications'; +let provider: FileOnDiskContentProvider; +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: COMPARE_WITH_SAVED_COMMAND_ID, + when: undefined, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_D), + handler: (accessor, resource: URI) => { + if (!provider) { + const instantiationService = accessor.get(IInstantiationService); + const textModelService = accessor.get(ITextModelService); + provider = instantiationService.createInstance(FileOnDiskContentProvider); + textModelService.registerTextModelContentProvider(COMPARE_WITH_SAVED_SCHEMA, provider); + } + + const editorService = accessor.get(IWorkbenchEditorService); + if (!resource) { + resource = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); + } + + if (resource && resource.scheme === 'file') { + const name = paths.basename(resource.fsPath); + const editorLabel = nls.localize('modifiedLabel', "{0} (on disk) ↔ {1}", name, name); + + return editorService.openEditor({ leftResource: URI.from({ scheme: COMPARE_WITH_SAVED_SCHEMA, path: resource.fsPath }), rightResource: resource, label: editorLabel }); + } + + return TPromise.as(true); + } +}); + +let globalResourceToCompare: URI; +let resourceSelectedForCompareContext: IContextKey; +CommandsRegistry.registerCommand({ + id: SELECT_FOR_COMPARE_COMMAND_ID, + handler: (accessor, resource: URI) => { + const listService = accessor.get(IListService); + const tree = listService.lastFocusedList; + // Remove highlight + if (tree instanceof Tree) { + tree.clearHighlight(); + tree.DOMFocus(); + } + + globalResourceToCompare = resource; + if (!resourceSelectedForCompareContext) { + resourceSelectedForCompareContext = ResourceSelectedForCompareContext.bindTo(accessor.get(IContextKeyService)); + } + resourceSelectedForCompareContext.set(true); + } +}); + +CommandsRegistry.registerCommand({ + id: COMPARE_RESOURCE_COMMAND_ID, + handler: (accessor, resource: URI) => { + const editorService = accessor.get(IWorkbenchEditorService); + const listService = accessor.get(IListService); + const tree = listService.lastFocusedList; + // Remove highlight + if (tree instanceof Tree) { + tree.clearHighlight(); + } + + return editorService.openEditor({ + leftResource: globalResourceToCompare, + rightResource: resource + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: REVEAL_IN_OS_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ExplorerFocusCondition, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R + }, + handler: (accessor, resource: URI) => { + // Without resource, try to look at the active editor + if (!resource) { + const editorService = accessor.get(IWorkbenchEditorService); + resource = toResource(editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' }); + } + + if (resource) { + const windowsService = accessor.get(IWindowsService); + windowsService.showItemInFolder(paths.normalize(resource.fsPath, true)); } else { - openFocusedOpenedEditorsViewItem(accessor, true); + const messageService = accessor.get(IMessageService); + messageService.show(severity.Info, nls.localize('openFileToReveal', "Open a file first to reveal")); } - }); -}; + } +}); -export const revealInOSFocusedFilesExplorerItem = (accessor: ServicesAccessor) => { - withFocusedExplorerItem(accessor).then(item => { - const file = explorerItemToFileResource(item); - if (file) { - revealInOSCommand(accessor, file.resource); +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: ExplorerFocusCondition, + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_C, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_C + }, + id: COPY_PATH_COMMAND_ID, + handler: (accessor, resource: URI) => { + // Without resource, try to look at the active editor + if (!resource) { + const editorGroupService = accessor.get(IEditorGroupService); + const editorService = accessor.get(IWorkbenchEditorService); + const activeEditor = editorService.getActiveEditor(); + + resource = activeEditor ? toResource(activeEditor.input, { supportSideBySide: true }) : void 0; + if (activeEditor) { + editorGroupService.focusGroup(activeEditor.position); // focus back to active editor group + } } - }); -}; + + if (resource) { + const clipboardService = accessor.get(IClipboardService); + clipboardService.writeText(resource.scheme === 'file' ? labels.getPathLabel(resource) : resource.toString()); + } else { + const messageService = accessor.get(IMessageService); + messageService.show(severity.Info, nls.localize('openFileToCopy', "Open a file first to copy its path")); + } + } +}); + +CommandsRegistry.registerCommand({ + id: REVEAL_IN_EXPLORER_COMMAND_ID, + handler: (accessor, resource: URI) => { + const viewletService = accessor.get(IViewletService); + const contextService = accessor.get(IWorkspaceContextService); + + viewletService.openViewlet(VIEWLET_ID, false).then((viewlet: ExplorerViewlet) => { + const isInsideWorkspace = contextService.isInsideWorkspace(resource); + if (isInsideWorkspace) { + const explorerView = viewlet.getExplorerView(); + if (explorerView) { + explorerView.setExpanded(true); + explorerView.select(resource, true); + } + } else { + const openEditorsView = viewlet.getOpenEditorsView(); + if (openEditorsView) { + openEditorsView.setExpanded(true); + } + } + }); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: SAVE_FILE_AS_COMMAND_ID, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + when: undefined, + primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S, + handler: (accessor, resource: URI) => { + return save(resource, true, accessor.get(IWorkbenchEditorService), accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupService)); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + when: undefined, + weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), + primary: KeyMod.CtrlCmd | KeyCode.KEY_S, + id: SAVE_FILE_COMMAND_ID, + handler: (accessor, resource: URI) => { + return save(resource, false, accessor.get(IWorkbenchEditorService), accessor.get(IFileService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupService)); + } +}); + +CommandsRegistry.registerCommand({ + id: SAVE_ALL_COMMAND_ID, + handler: (accessor) => { + return saveAll(true, accessor.get(IWorkbenchEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupService)); + } +}); + +CommandsRegistry.registerCommand({ + id: SAVE_ALL_IN_GROUP_COMMAND_ID, + handler: (accessor, resource: URI, editorContext: IEditorContext) => { + let saveAllArg: any; + if (!editorContext) { + saveAllArg = true; + } else { + const fileService = accessor.get(IFileService); + const editorGroup = editorContext.group; + saveAllArg = []; + editorGroup.getEditors().forEach(editor => { + const resource = toResource(editor, { supportSideBySide: true }); + if (resource && (resource.scheme === 'untitled' || fileService.canHandleResource(resource))) { + saveAllArg.push(resource); + } + }); + } + + return saveAll(saveAllArg, accessor.get(IWorkbenchEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupService)); + } +}); + +CommandsRegistry.registerCommand({ + id: SAVE_FILES_COMMAND_ID, + handler: (accessor) => { + return saveAll(false, accessor.get(IWorkbenchEditorService), accessor.get(IUntitledEditorService), accessor.get(ITextFileService), accessor.get(IEditorGroupService)); + } +}); diff --git a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts index 586fd2cbc05..379a2a53d81 100644 --- a/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/electron-browser/saveErrorHandler.ts @@ -11,7 +11,6 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import paths = require('vs/base/common/paths'); import { Action } from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; -import { SaveFileAsAction, RevertFileAction, SaveFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -30,6 +29,8 @@ import { IContextKeyService, IContextKey, RawContextKey } from 'vs/platform/cont import { FileOnDiskContentProvider } from 'vs/workbench/parts/files/common/files'; import { FileEditorInput } from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { ICommandService } from 'vs/platform/commands/common/commands'; +import { SAVE_FILE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL } from 'vs/workbench/parts/files/electron-browser/fileCommands'; export const CONFLICT_RESOLUTION_CONTEXT = 'saveConflictResolutionContext'; export const CONFLICT_RESOLUTION_SCHEME = 'conflictResolution'; @@ -50,7 +51,8 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi @IEditorGroupService private editorGroupService: IEditorGroupService, @IContextKeyService contextKeyService: IContextKeyService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @ITextModelService textModelService: ITextModelService + @ITextModelService textModelService: ITextModelService, + @ICommandService private commandService: ICommandService ) { this.toUnbind = []; this.messages = new ResourceMap<() => void>(); @@ -101,10 +103,12 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi public onSaveError(error: any, model: ITextFileEditorModel): void { let message: IMessageWithAction | string; + + const fileOperationError = error as FileOperationError; const resource = model.getResource(); // Dirty write prevention - if ((error).fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { + if (fileOperationError.fileOperationResult === FileOperationResult.FILE_MODIFIED_SINCE) { // If the user tried to save from the opened conflict editor, show its message again // Otherwise show the message that will lead the user into the save conflict editor. @@ -117,29 +121,28 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi // Any other save error else { - const isReadonly = (error).fileOperationResult === FileOperationResult.FILE_READ_ONLY; const actions: Action[] = []; - // Save As - actions.push(new Action('workbench.files.action.saveAs', SaveFileAsAction.LABEL, null, true, () => { - const saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); - saveAsAction.setResource(resource); - saveAsAction.run().done(() => saveAsAction.dispose(), errors.onUnexpectedError); + const isReadonly = fileOperationError.fileOperationResult === FileOperationResult.FILE_READ_ONLY; + const triedToMakeWriteable = isReadonly && fileOperationError.options && fileOperationError.options.overwriteReadonly; + const isPermissionDenied = fileOperationError.fileOperationResult === FileOperationResult.FILE_PERMISSION_DENIED; - return TPromise.as(true); - })); + // Save Elevated + if (isPermissionDenied || triedToMakeWriteable) { + actions.push(new Action('workbench.files.action.saveElevated', triedToMakeWriteable ? nls.localize('overwriteElevated', "Overwrite as Admin...") : nls.localize('saveElevated', "Retry as Admin..."), null, true, () => { + if (!model.isDisposed()) { + model.save({ + writeElevated: true, + overwriteReadonly: triedToMakeWriteable + }).done(null, errors.onUnexpectedError); + } - // Discard - actions.push(new Action('workbench.files.action.discard', nls.localize('discard', "Discard"), null, true, () => { - const revertFileAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); - revertFileAction.setResource(resource); - revertFileAction.run().done(() => revertFileAction.dispose(), errors.onUnexpectedError); + return TPromise.as(true); + })); + } - return TPromise.as(true); - })); - - // Retry - if (isReadonly) { + // Overwrite + else if (isReadonly) { actions.push(new Action('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"), null, true, () => { if (!model.isDisposed()) { model.save({ overwriteReadonly: true }).done(null, errors.onUnexpectedError); @@ -147,22 +150,37 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi return TPromise.as(true); })); - } else { - actions.push(new Action('workbench.files.action.retry', nls.localize('retry', "Retry"), null, true, () => { - const saveFileAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); - saveFileAction.setResource(resource); - saveFileAction.run().done(() => saveFileAction.dispose(), errors.onUnexpectedError); + } - return TPromise.as(true); + // Retry + else { + actions.push(new Action('workbench.files.action.retry', nls.localize('retry', "Retry"), null, true, () => { + return this.commandService.executeCommand(SAVE_FILE_COMMAND_ID, resource); })); } + // Save As + actions.push(new Action('workbench.files.action.saveAs', SAVE_FILE_AS_LABEL, null, true, () => { + return this.commandService.executeCommand(SAVE_FILE_AS_COMMAND_ID, resource); + })); + + // Discard + actions.push(new Action('workbench.files.action.discard', nls.localize('discard', "Discard"), null, true, () => { + return this.commandService.executeCommand(REVERT_FILE_COMMAND_ID, resource); + })); + // Cancel actions.push(CancelAction); let errorMessage: string; if (isReadonly) { - errorMessage = nls.localize('readonlySaveError', "Failed to save '{0}': File is write protected. Select 'Overwrite' to remove protection.", paths.basename(resource.fsPath)); + if (triedToMakeWriteable) { + errorMessage = nls.localize('readonlySaveErrorAdmin', "Failed to save '{0}': File is write protected. Select 'Overwrite as Admin' to retry as administrator.", paths.basename(resource.fsPath)); + } else { + errorMessage = nls.localize('readonlySaveError', "Failed to save '{0}': File is write protected. Select 'Overwrite' to attempt to remove protection.", paths.basename(resource.fsPath)); + } + } else if (isPermissionDenied) { + errorMessage = nls.localize('permissionDeniedSaveError', "Failed to save '{0}': Insufficient permissions. Select 'Retry as Admin' to retry as administrator.", paths.basename(resource.fsPath)); } else { errorMessage = nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(resource.fsPath), toErrorMessage(error, false)); } 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 7823cae29c3..9d30ed78228 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerView.ts @@ -16,7 +16,7 @@ import glob = require('vs/base/common/glob'); import { Action, IAction } from 'vs/base/common/actions'; import { prepareActions } from 'vs/workbench/browser/actions'; import { memoize } from 'vs/base/common/decorators'; -import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, SortOrderConfiguration, SortOrder, IExplorerView } from 'vs/workbench/parts/files/common/files'; +import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, SortOrderConfiguration, SortOrder, IExplorerView, ExplorerRootContext } from 'vs/workbench/parts/files/common/files'; import { FileOperation, FileOperationEvent, IResolveFileOptions, FileChangeType, FileChangesEvent, IFileService, FILES_EXCLUDE_CONFIG } from 'vs/platform/files/common/files'; import { RefreshViewExplorerAction, NewFolderAction, NewFileAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { FileDragAndDrop, FileFilter, FileSorter, FileController, FileRenderer, FileDataSource, FileViewletState, FileAccessibilityProvider } from 'vs/workbench/parts/files/electron-browser/views/explorerViewer'; @@ -68,6 +68,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView private resourceContext: ResourceContextKey; private folderContext: IContextKey; + private rootContext: IContextKey; private fileEventsFilter: ResourceGlobMatcher; @@ -104,6 +105,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView this.resourceContext = instantiationService.createInstance(ResourceContextKey); this.folderContext = ExplorerFolderContext.bindTo(contextKeyService); + this.rootContext = ExplorerRootContext.bindTo(contextKeyService); this.fileEventsFilter = instantiationService.createInstance( ResourceGlobMatcher, @@ -401,6 +403,7 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView const dataSource = this.instantiationService.createInstance(FileDataSource); const renderer = this.instantiationService.createInstance(FileRenderer, this.viewletState); const controller = this.instantiationService.createInstance(FileController, this.viewletState); + this.disposables.push(controller); const sorter = this.instantiationService.createInstance(FileSorter); this.disposables.push(sorter); this.filter = this.instantiationService.createInstance(FileFilter); @@ -440,8 +443,11 @@ export class ExplorerView extends TreeViewsViewletPanel implements IExplorerView // Update resource context based on focused element this.disposables.push(this.explorerViewer.onDidChangeFocus((e: { focus: FileStat }) => { - this.resourceContext.set(e.focus && e.focus.resource); - this.folderContext.set(e.focus && e.focus.isDirectory); + const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER; + const resource = e.focus ? e.focus.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : undefined; + this.resourceContext.set(resource); + this.folderContext.set((isSingleFolder && !e.focus) || e.focus && e.focus.isDirectory); + this.rootContext.set(!e.focus || (e.focus && e.focus.isRoot)); })); // Open when selecting via keyboard 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 d41113e034d..631831a7d9b 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/explorerViewer.ts @@ -15,7 +15,6 @@ import { once } from 'vs/base/common/functional'; import paths = require('vs/base/common/paths'); import resources = require('vs/base/common/resources'); import errors = require('vs/base/common/errors'); -import { isString } from 'vs/base/common/types'; import { IAction, ActionRunner as BaseActionRunner, IActionRunner } from 'vs/base/common/actions'; import comparers = require('vs/base/common/comparers'); import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; @@ -23,7 +22,6 @@ import { isMacintosh, isLinux } from 'vs/base/common/platform'; import glob = require('vs/base/common/glob'); import { FileLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { IFilesConfiguration, SortOrder } from 'vs/workbench/parts/files/common/files'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { FileOperationError, FileOperationResult, IFileService, FileKind } from 'vs/platform/files/common/files'; @@ -56,6 +54,7 @@ import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common import { getPathLabel } from 'vs/base/common/labels'; import { extractResources } from 'vs/workbench/browser/editor'; import { relative } from 'path'; +import { DataTransfers } from 'vs/base/browser/dnd'; export class FileDataSource implements IDataSource { constructor( @@ -139,102 +138,13 @@ export class FileDataSource implements IDataSource { } } -export class FileActionProvider extends ContributableActionProvider { - private state: FileViewletState; - - constructor(state: any) { - super(); - - this.state = state; - } - - public hasActions(tree: ITree, stat: FileStat): boolean { - if (stat instanceof NewStatPlaceholder) { - return false; - } - - return super.hasActions(tree, stat); - } - - public getActions(tree: ITree, stat: FileStat): TPromise { - if (stat instanceof NewStatPlaceholder) { - return TPromise.as([]); - } - - return super.getActions(tree, stat); - } - - public hasSecondaryActions(tree: ITree, stat: FileStat | Model): boolean { - if (stat instanceof NewStatPlaceholder) { - return false; - } - - return super.hasSecondaryActions(tree, stat); - } - - public getSecondaryActions(tree: ITree, stat: FileStat | Model): TPromise { - if (stat instanceof NewStatPlaceholder) { - return TPromise.as([]); - } - - return super.getSecondaryActions(tree, stat); - } - - public runAction(tree: ITree, stat: FileStat, action: IAction, context?: any): TPromise; - public runAction(tree: ITree, stat: FileStat, actionID: string, context?: any): TPromise; - public runAction(tree: ITree, stat: FileStat, arg: any, context: any = {}): TPromise { - context = objects.mixin({ - viewletState: this.state, - stat - }, context); - - if (!isString(arg)) { - const action = arg; - if (action.enabled) { - return action.run(context); - } - - return null; - } - - const id = arg; - let promise = this.hasActions(tree, stat) ? this.getActions(tree, stat) : TPromise.as([]); - - return promise.then((actions: IAction[]) => { - for (let i = 0, len = actions.length; i < len; i++) { - if (actions[i].id === id && actions[i].enabled) { - return actions[i].run(context); - } - } - - promise = this.hasSecondaryActions(tree, stat) ? this.getSecondaryActions(tree, stat) : TPromise.as([]); - - return promise.then((actions: IAction[]) => { - for (let i = 0, len = actions.length; i < len; i++) { - if (actions[i].id === id && actions[i].enabled) { - return actions[i].run(context); - } - } - - return null; - }); - }); - } -} - export class FileViewletState implements IFileViewletState { - private _actionProvider: FileActionProvider; private editableStats: ResourceMap; constructor() { - this._actionProvider = new FileActionProvider(this); this.editableStats = new ResourceMap(); } - public get actionProvider(): FileActionProvider { - return this._actionProvider; - } - public getEditableData(stat: FileStat): IEditableData { return this.editableStats.get(stat.resource); } @@ -373,7 +283,8 @@ export class FileRenderer implements IRenderer { tree.clearHighlight(); if (commit && inputBox.value) { - this.state.actionProvider.runAction(tree, stat, editableData.action, { value: inputBox.value }); + // TODO@Isidor check the context + editableData.action.run({ value: inputBox.value }); } const restoreFocus = document.activeElement === inputBox.inputElement; // https://github.com/Microsoft/vscode/issues/20269 @@ -415,10 +326,11 @@ export class FileAccessibilityProvider implements IAccessibilityProvider { } // Explorer Controller -export class FileController extends DefaultController { +export class FileController extends DefaultController implements IDisposable { private state: FileViewletState; private contributedContextMenu: IMenu; + private toDispose: IDisposable[]; constructor(state: FileViewletState, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @@ -429,7 +341,9 @@ export class FileController extends DefaultController { ) { super({ clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, keyboardSupport: false /* handled via IListService */ }); + this.toDispose = []; this.contributedContextMenu = menuService.createMenu(MenuId.ExplorerContext, contextKeyService); + this.toDispose.push(this.contributedContextMenu); this.state = state; } @@ -508,20 +422,14 @@ export class FileController extends DefaultController { tree.setFocus(stat); - if (!this.state.actionProvider.hasSecondaryActions(tree, stat)) { - return true; - } - const anchor = { x: event.posx, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => { - return this.state.actionProvider.getSecondaryActions(tree, stat).then(actions => { - fillInActions(this.contributedContextMenu, stat instanceof FileStat ? { arg: stat.resource } : null, actions); - return actions; - }); + const actions = []; + fillInActions(this.contributedContextMenu, { arg: stat instanceof FileStat ? stat.resource : undefined, shouldForwardArgs: true }, actions); + return TPromise.as(actions); }, - getActionItem: this.state.actionProvider.getActionItem.bind(this.state.actionProvider, tree, stat), getActionsContext: (event) => { return { viewletState: this.state, @@ -552,6 +460,10 @@ export class FileController extends DefaultController { this.editorService.openEditor({ resource: stat.resource, options }, options.sideBySide).done(null, errors.onUnexpectedError); } } + + public dispose(): void { + this.toDispose = dispose(this.toDispose); + } } // Explorer Sorter @@ -793,10 +705,10 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { // Apply some datatransfer types to allow for dragging the element outside of the application if (source) { if (!source.isDirectory) { - originalEvent.dataTransfer.setData('DownloadURL', [MIME_BINARY, source.name, source.resource.toString()].join(':')); + originalEvent.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, source.name, source.resource.toString()].join(':')); } - originalEvent.dataTransfer.setData('text/plain', getPathLabel(source.resource)); + originalEvent.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(source.resource)); } } @@ -913,18 +825,22 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { if (folders.length > 0) { // If we are in no-workspace context, ask for confirmation to create a workspace - let confirmed = true; + let confirmedPromise = TPromise.wrap(true); if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) { - confirmed = this.messageService.confirm({ + confirmedPromise = this.messageService.confirm({ message: folders.length > 1 ? nls.localize('dropFolders', "Do you want to add the folders to the workspace?") : nls.localize('dropFolder', "Do you want to add the folder to the workspace?"), type: 'question', primaryButton: folders.length > 1 ? nls.localize('addFolders', "&&Add Folders") : nls.localize('addFolder', "&&Add Folder") }); } - if (confirmed) { - return this.workspaceEditingService.addFolders(folders); - } + return confirmedPromise.then(confirmed => { + if (confirmed) { + return this.workspaceEditingService.addFolders(folders); + } + + return void 0; + }); } // Handle dropped files (only support FileStat as target) @@ -1039,18 +955,20 @@ export class FileDragAndDrop extends SimpleFileResourceDragAndDrop { }; // Move with overwrite if the user confirms - if (this.messageService.confirm(confirm)) { - const targetDirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, targetResource, !isLinux /* ignorecase */)); + return this.messageService.confirm(confirm).then(confirmed => { + if (confirmed) { + const targetDirty = this.textFileService.getDirty().filter(d => resources.isEqualOrParent(d, targetResource, !isLinux /* ignorecase */)); - // Make sure to revert all dirty in target first to be able to overwrite properly - return this.textFileService.revertAll(targetDirty, { soft: true /* do not attempt to load content from disk */ }).then(() => { + // Make sure to revert all dirty in target first to be able to overwrite properly + return this.textFileService.revertAll(targetDirty, { soft: true /* do not attempt to load content from disk */ }).then(() => { - // Then continue to do the move operation - return this.fileService.moveFile(source.resource, targetResource, true).then(onSuccess, error => onError(error, true)); - }); - } + // Then continue to do the move operation + return this.fileService.moveFile(source.resource, targetResource, true).then(onSuccess, error => onError(error, true)); + }); + } - return onError(); + return onError(); + }); } return onError(error, true); diff --git a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts index b9fb56c5d8b..b12fc2d7493 100644 --- a/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts +++ b/src/vs/workbench/parts/files/electron-browser/views/openEditorsView.ts @@ -15,15 +15,15 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Position, IEditorInput } from 'vs/platform/editor/common/editor'; import { IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup } from 'vs/workbench/common/editor'; -import { SaveAllAction, SaveAllInGroupAction, OpenToSideAction, SaveFileAction, RevertFileAction, SaveFileAsAction, CompareWithSavedAction, CompareResourcesAction, SelectResourceForCompareAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; +import { SaveAllAction, SaveAllInGroupAction } from 'vs/workbench/parts/files/electron-browser/fileActions'; import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet'; import { OpenEditorsFocusedContext, ExplorerFocusedContext, IFilesConfiguration } from 'vs/workbench/parts/files/common/files'; import { ITextFileService, AutoSaveMode } from 'vs/workbench/services/textfile/common/textfiles'; import { OpenEditor } from 'vs/workbench/parts/files/common/explorerModel'; import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { CloseAllEditorsAction, CloseUnmodifiedEditorsInGroupAction, CloseEditorsInGroupAction, CloseOtherEditorsInGroupAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; +import { CloseAllEditorsAction, CloseUnmodifiedEditorsInGroupAction, CloseEditorsInGroupAction, CloseEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { ToggleEditorLayoutAction } from 'vs/workbench/browser/actions/toggleEditorLayout'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { EditorGroup } from 'vs/workbench/common/editor/editorStacksModel'; import { attachStylerCallback } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; @@ -31,15 +31,20 @@ import { badgeBackground, badgeForeground, contrastBorder } from 'vs/platform/th import { IListService, WorkbenchList } from 'vs/platform/list/browser/listService'; import { IDelegate, IRenderer, IListContextMenuEvent, IListMouseEvent } from 'vs/base/browser/ui/list/list'; import { EditorLabel } from 'vs/workbench/browser/labels'; -import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar'; +import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { ContributableActionProvider } from 'vs/workbench/browser/actions'; -import { memoize } from 'vs/base/common/decorators'; +import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem'; +import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions'; +import { OpenEditorsGroupContext } from 'vs/workbench/parts/files/electron-browser/fileCommands'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { DataTransfers } from 'vs/base/browser/dnd'; +import { getPathLabel, getBaseLabel } from 'vs/base/common/labels'; +import { MIME_BINARY } from 'vs/base/common/mime'; const $ = dom.$; @@ -47,7 +52,7 @@ export class OpenEditorsView extends ViewsViewletPanel { private static readonly DEFAULT_VISIBLE_OPEN_EDITORS = 9; private static readonly DEFAULT_DYNAMIC_HEIGHT = true; - static ID = 'workbench.explorer.openEditorsView'; + static readonly ID = 'workbench.explorer.openEditorsView'; static NAME = nls.localize({ key: 'openEditors', comment: ['Open is an adjective'] }, "Open Editors"); private model: IEditorStacksModel; @@ -55,7 +60,10 @@ export class OpenEditorsView extends ViewsViewletPanel { private listRefreshScheduler: RunOnceScheduler; private structuralRefreshDelay: number; private list: WorkbenchList; + private contributedContextMenu: IMenu; private needsRefresh: boolean; + private resourceContext: ResourceContextKey; + private groupFocusedContext: IContextKey; constructor( options: IViewletViewOptions, @@ -70,7 +78,8 @@ export class OpenEditorsView extends ViewsViewletPanel { @IUntitledEditorService private untitledEditorService: IUntitledEditorService, @IContextKeyService private contextKeyService: IContextKeyService, @IThemeService private themeService: IThemeService, - @ITelemetryService private telemetryService: ITelemetryService + @ITelemetryService private telemetryService: ITelemetryService, + @IMenuService menuService: IMenuService ) { super({ ...(options as IViewOptions), @@ -89,6 +98,8 @@ export class OpenEditorsView extends ViewsViewletPanel { } this.needsRefresh = false; }, this.structuralRefreshDelay); + this.contributedContextMenu = menuService.createMenu(MenuId.OpenEditorsContext, contextKeyService); + this.disposables.push(this.contributedContextMenu); // update on model changes this.disposables.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e))); @@ -141,11 +152,25 @@ export class OpenEditorsView extends ViewsViewletPanel { }, this.contextKeyService, this.listService, this.themeService); this.updateSize(); + // Bind context keys OpenEditorsFocusedContext.bindTo(this.list.contextKeyService); ExplorerFocusedContext.bindTo(this.list.contextKeyService); + this.resourceContext = this.instantiationService.createInstance(ResourceContextKey); + this.groupFocusedContext = OpenEditorsGroupContext.bindTo(this.contextKeyService); + this.disposables.push(this.list.onContextMenu(e => this.onListContextMenu(e))); + this.list.onFocusChange(e => { + this.resourceContext.reset(); + this.groupFocusedContext.reset(); + const element = e.elements.length ? e.elements[0] : undefined; + if (element instanceof OpenEditor) { + this.resourceContext.set(element.getResource()); + } else if (!!element) { + this.groupFocusedContext.set(true); + } + }); // Open when selecting via keyboard this.disposables.push(this.list.onMouseClick(e => this.onMouseClick(e, false))); @@ -197,11 +222,6 @@ export class OpenEditorsView extends ViewsViewletPanel { } } - @memoize - private get actionProvider(): ActionProvider { - return new ActionProvider(this.instantiationService, this.textFileService, this.untitledEditorService); - } - private get elements(): (IEditorGroup | OpenEditor)[] { const result: (IEditorGroup | OpenEditor)[] = []; this.model.groups.forEach(g => { @@ -268,7 +288,11 @@ export class OpenEditorsView extends ViewsViewletPanel { const element = e.element; this.contextMenuService.showContextMenu({ getAnchor: () => e.anchor, - getActions: () => this.actionProvider.getSecondaryActions(element), + getActions: () => { + const actions = []; + fillInActions(this.contributedContextMenu, { shouldForwardArgs: true, arg: element instanceof OpenEditor ? element.editorInput.getResource() : undefined }, actions); + return TPromise.as(actions); + }, getActionsContext: () => element instanceof OpenEditor ? { group: element.editorGroup, editor: element.editorInput } : { group: element } }); } @@ -279,7 +303,7 @@ export class OpenEditorsView extends ViewsViewletPanel { return; } - // Do a minimal tree update based on if the change is structural or not #6670 + // Do a minimal list update based on if the change is structural or not #6670 if (e.structural) { this.listRefreshScheduler.schedule(this.structuralRefreshDelay); } else if (!this.listRefreshScheduler.isScheduled()) { @@ -318,7 +342,8 @@ export class OpenEditorsView extends ViewsViewletPanel { private updateSize(): void { // Adjust expanded body size - this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize(this.model); + this.minimumBodySize = this.getMinExpandedBodySize(); + this.maximumBodySize = this.getMaxExpandedBodySize(); } private updateDirtyIndicator(): void { @@ -332,7 +357,16 @@ export class OpenEditorsView extends ViewsViewletPanel { } } - private getExpandedBodySize(model: IEditorStacksModel): number { + private get elementCount(): number { + return this.model.groups.map(g => g.count) + .reduce((first, second) => first + second, this.model.groups.length > 1 ? this.model.groups.length : 0); + } + + private getMaxExpandedBodySize(): number { + return this.elementCount * OpenEditorsDelegate.ITEM_HEIGHT; + } + + private getMinExpandedBodySize(): number { let visibleOpenEditors = this.configurationService.getValue('explorer.openEditors.visible'); if (typeof visibleOpenEditors !== 'number') { visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS; @@ -343,15 +377,13 @@ export class OpenEditorsView extends ViewsViewletPanel { dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT; } - return this.computeExpandedBodySize(visibleOpenEditors, dynamicHeight); + return this.computeMinExpandedBodySize(visibleOpenEditors, dynamicHeight); } - private computeExpandedBodySize(visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS, dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT): number { + private computeMinExpandedBodySize(visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS, dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT): number { let itemsToShow: number; if (dynamicHeight) { - const elementCount = this.model.groups.map(g => g.count) - .reduce((first, second) => first + second, this.model.groups.length > 1 ? this.model.groups.length : 0); - itemsToShow = Math.min(Math.max(visibleOpenEditors, 1), elementCount); + itemsToShow = Math.min(Math.max(visibleOpenEditors, 1), this.elementCount); } else { itemsToShow = Math.max(visibleOpenEditors, 1); } @@ -405,7 +437,7 @@ class OpenEditorsDelegate implements IDelegate { } class EditorGroupRenderer implements IRenderer { - static ID = 'editorgroup'; + static readonly ID = 'editorgroup'; constructor( private keybindingService: IKeybindingService, @@ -449,7 +481,8 @@ class EditorGroupRenderer implements IRenderer { - static ID = 'openeditor'; + static readonly ID = 'openeditor'; public static DRAGGED_OPEN_EDITOR: OpenEditor; constructor( @@ -499,8 +532,31 @@ class OpenEditorRenderer implements IRenderer { + editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_START, (e: DragEvent) => { + + const dragImage = document.createElement('div'); + e.dataTransfer.effectAllowed = 'copyMove'; + dragImage.className = 'monaco-tree-drag-image'; + dragImage.textContent = editorTemplate.openEditor.editorInput.getName(); + document.body.appendChild(dragImage); + e.dataTransfer.setDragImage(dragImage, -10, -10); + setTimeout(() => document.body.removeChild(dragImage), 0); + OpenEditorRenderer.DRAGGED_OPEN_EDITOR = editorTemplate.openEditor; + + if (editorTemplate.openEditor && editorTemplate.openEditor.editorInput) { + const resource = editorTemplate.openEditor.editorInput.getResource(); + if (resource) { + const resourceStr = resource.toString(); + + e.dataTransfer.setData(DataTransfers.URL, resource.toString()); // enables dropping editor into editor area + e.dataTransfer.setData(DataTransfers.TEXT, getPathLabel(resource)); // enables dropping editor resource path into text controls + + if (resource.scheme === 'file') { + e.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [MIME_BINARY, getBaseLabel(resource), resourceStr].join(':')); // enables support to drag an editor as file to desktop + } + } + } })); editorTemplate.toDispose.push(dom.addDisposableListener(container, dom.EventType.DRAG_OVER, () => { if (OpenEditorRenderer.DRAGGED_OPEN_EDITOR) { @@ -518,7 +574,8 @@ class OpenEditorRenderer implements IRenderer { @@ -545,97 +602,3 @@ class OpenEditorRenderer implements IRenderer { - return super.getSecondaryActions(undefined, element).then(result => { - const autoSaveEnabled = this.textFileService.getAutoSaveMode() === AutoSaveMode.AFTER_SHORT_DELAY; - - if (element instanceof EditorGroup) { - if (!autoSaveEnabled) { - result.push(this.instantiationService.createInstance(SaveAllInGroupAction, SaveAllInGroupAction.ID, nls.localize('saveAll', "Save All"))); - result.push(new Separator()); - } - - result.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); - result.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); - } else { - const openEditor = element; - const resource = openEditor.getResource(); - if (resource) { - // Open to side - result.unshift(this.instantiationService.createInstance(OpenToSideAction, undefined, resource, false)); - - if (!openEditor.isUntitled()) { - - // Files: Save / Revert - if (!autoSaveEnabled) { - result.push(new Separator()); - - const saveAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); - saveAction.setResource(resource); - saveAction.enabled = openEditor.isDirty(); - result.push(saveAction); - - const revertAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); - revertAction.setResource(resource); - revertAction.enabled = openEditor.isDirty(); - result.push(revertAction); - } - } - - // Untitled: Save / Save As - if (openEditor.isUntitled()) { - result.push(new Separator()); - - if (this.untitledEditorService.hasAssociatedFilePath(resource)) { - let saveUntitledAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); - saveUntitledAction.setResource(resource); - result.push(saveUntitledAction); - } - - let saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); - saveAsAction.setResource(resource); - result.push(saveAsAction); - } - - // Compare Actions - result.push(new Separator()); - - if (!openEditor.isUntitled()) { - const compareWithSavedAction = this.instantiationService.createInstance(CompareWithSavedAction, CompareWithSavedAction.ID, nls.localize('compareWithSaved', "Compare with Saved")); - compareWithSavedAction.setResource(resource); - compareWithSavedAction.enabled = openEditor.isDirty(); - result.push(compareWithSavedAction); - } - - const runCompareAction = this.instantiationService.createInstance(CompareResourcesAction, resource, undefined); - if (runCompareAction._isEnabled()) { - result.push(runCompareAction); - } - result.push(this.instantiationService.createInstance(SelectResourceForCompareAction, resource, undefined)); - - result.push(new Separator()); - } - - result.push(this.instantiationService.createInstance(CloseEditorAction, CloseEditorAction.ID, nls.localize('close', "Close"))); - const closeOtherEditorsInGroupAction = this.instantiationService.createInstance(CloseOtherEditorsInGroupAction, CloseOtherEditorsInGroupAction.ID, nls.localize('closeOthers', "Close Others")); - closeOtherEditorsInGroupAction.enabled = openEditor.editorGroup.count > 1; - result.push(closeOtherEditorsInGroupAction); - result.push(this.instantiationService.createInstance(CloseUnmodifiedEditorsInGroupAction, CloseUnmodifiedEditorsInGroupAction.ID, nls.localize('closeAllUnmodified', "Close Unmodified"))); - result.push(this.instantiationService.createInstance(CloseEditorsInGroupAction, CloseEditorsInGroupAction.ID, nls.localize('closeAll', "Close All"))); - } - - return result; - }); - } -} diff --git a/src/vs/workbench/parts/html/browser/webview-pre.js b/src/vs/workbench/parts/html/browser/webview-pre.js index 3cfc357897a..ebb69c9bd46 100644 --- a/src/vs/workbench/parts/html/browser/webview-pre.js +++ b/src/vs/workbench/parts/html/browser/webview-pre.js @@ -46,11 +46,12 @@ if (!event || !event.view || !event.view.document) { return; } + + var baseElement = event.view.document.getElementsByTagName('base')[0]; /** @type {any} */ var node = event.target; while (node) { if (node.tagName && node.tagName.toLowerCase() === 'a' && node.href) { - var baseElement = event.view.document.getElementsByTagName('base')[0]; if (node.getAttribute('href') === '#') { event.view.scrollTo(0, 0); } else if (node.hash && (node.getAttribute('href') === node.hash || (baseElement && node.href.indexOf(baseElement.href) >= 0))) { @@ -108,8 +109,8 @@ styleBody(body[0]); // iframe - Object.keys(variables).forEach(function(variable) { - target.contentDocument.documentElement.style.setProperty(`--${variable}`,variables[variable]); + Object.keys(variables).forEach(function (variable) { + target.contentDocument.documentElement.style.setProperty(`--${variable}`, variables[variable]); }); }); @@ -127,6 +128,12 @@ const text = data.contents.join('\n'); const newDocument = new DOMParser().parseFromString(text, 'text/html'); + newDocument.querySelectorAll('a').forEach(a => { + if (!a.title) { + a.title = a.href; + } + }); + // set base-url if applicable if (initData.baseUrl && newDocument.head.getElementsByTagName('base').length === 0) { const baseElement = newDocument.createElement('base'); @@ -138,7 +145,7 @@ const defaultStyles = newDocument.createElement('style'); defaultStyles.id = '_defaultStyles'; - const vars = Object.keys(initData.styles).map(function(variable) { + const vars = Object.keys(initData.styles).map(function (variable) { return `--${variable}: ${initData.styles[variable]};`; }); defaultStyles.innerHTML = ` @@ -158,6 +165,11 @@ max-width: 100%; max-height: 100%; } + + body a { + color: var(--link-color); + } + a:focus, input:focus, select:focus, @@ -251,7 +263,7 @@ newFrame.style.visibility = 'visible'; contentWindow.addEventListener('scroll', handleInnerScroll); - pendingMessages.forEach(function(data) { + pendingMessages.forEach(function (data) { contentWindow.postMessage(data, document.location.origin); }); pendingMessages = []; diff --git a/src/vs/workbench/parts/html/browser/webview.ts b/src/vs/workbench/parts/html/browser/webview.ts index 4e207e563b9..c4954e1f376 100644 --- a/src/vs/workbench/parts/html/browser/webview.ts +++ b/src/vs/workbench/parts/html/browser/webview.ts @@ -6,11 +6,10 @@ 'use strict'; import URI from 'vs/base/common/uri'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; import { addDisposableListener, addClass } from 'vs/base/browser/dom'; -import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; +import { editorBackground, editorForeground, textLinkForeground } from 'vs/platform/theme/common/colorRegistry'; import { ITheme, LIGHT, DARK } from 'vs/platform/theme/common/themeService'; import { WebviewFindWidget } from './webviewFindWidget'; import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; @@ -43,7 +42,7 @@ export default class Webview { private static index: number = 0; private readonly _webview: Electron.WebviewTag; - private _ready: TPromise; + private _ready: Promise; private _disposables: IDisposable[] = []; private _onDidClickLink = new Emitter(); @@ -78,7 +77,7 @@ export default class Webview { this._webview.preload = require.toUrl('./webview-pre.js'); this._webview.src = require.toUrl('./webview.html'); - this._ready = new TPromise(resolve => { + this._ready = new Promise(resolve => { const subscription = addDisposableListener(this._webview, 'ipc-message', (event) => { if (event.channel === 'webview-ready') { // console.info('[PID Webview] ' event.args[0]); @@ -197,6 +196,10 @@ export default class Webview { this._onDidClickLink.dispose(); this._disposables = dispose(this._disposables); + if (this._contextKey) { + this._contextKey.reset(); + } + if (this._webview.parentElement) { this._webview.parentElement.removeChild(this._webview); const findWidgetDomNode = this._webviewFindWidget.getDomNode(); @@ -219,7 +222,7 @@ export default class Webview { private _send(channel: string, ...args: any[]): void { this._ready .then(() => this._webview.send(channel, ...args)) - .done(void 0, console.error); + .catch(err => console.error(err)); } set initialScrollProgress(value: number) { @@ -265,6 +268,7 @@ export default class Webview { 'font-family': fontFamily, 'font-weight': fontWeight, 'font-size': fontSize, + 'link-color': theme.getColor(textLinkForeground).toString() }; let activeTheme: ApiThemeClassName; diff --git a/src/vs/workbench/parts/logs/common/logConstants.ts b/src/vs/workbench/parts/logs/common/logConstants.ts new file mode 100644 index 00000000000..08ac75b8e96 --- /dev/null +++ b/src/vs/workbench/parts/logs/common/logConstants.ts @@ -0,0 +1,9 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export const mainLogChannelId = 'mainLog'; +export const sharedLogChannelId = 'sharedLog'; +export const rendererLogChannelId = 'rendererLog'; +export const extHostLogChannelId = 'extHostLog'; \ No newline at end of file diff --git a/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts new file mode 100644 index 00000000000..3ed42b36111 --- /dev/null +++ b/src/vs/workbench/parts/logs/electron-browser/logs.contribution.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { join } from 'vs/base/common/paths'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/parts/output/common/output'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { Disposable } from 'vs/base/common/lifecycle'; +import URI from 'vs/base/common/uri'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import * as Constants from 'vs/workbench/parts/logs/common/logConstants'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { ShowLogsAction, OpenLogsFolderAction, SetLogLevelAction, OpenLogFileAction } from 'vs/workbench/parts/logs/electron-browser/logsActions'; + +class LogOutputChannels extends Disposable implements IWorkbenchContribution { + + constructor( + @IWindowService private windowService: IWindowService, + @IEnvironmentService private environmentService: IEnvironmentService, + @IInstantiationService instantiationService: IInstantiationService + ) { + super(); + let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + outputChannelRegistry.registerChannel(Constants.mainLogChannelId, nls.localize('mainLog', "Log (Main)"), URI.file(join(this.environmentService.logsPath, `main.log`))); + outputChannelRegistry.registerChannel(Constants.sharedLogChannelId, nls.localize('sharedLog', "Log (Shared)"), URI.file(join(this.environmentService.logsPath, `sharedprocess.log`))); + outputChannelRegistry.registerChannel(Constants.rendererLogChannelId, nls.localize('rendererLog', "Log (Window)"), URI.file(join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`))); + outputChannelRegistry.registerChannel(Constants.extHostLogChannelId, nls.localize('extensionsLog', "Log (Extension Host)"), URI.file(join(this.environmentService.logsPath, `extHost${this.windowService.getCurrentWindowId()}.log`))); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(LogOutputChannels, LifecyclePhase.Restoring); + +const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +const devCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowLogsAction, ShowLogsAction.ID, ShowLogsAction.LABEL), 'Developer: Show Logs...', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogFileAction, OpenLogFileAction.ID, OpenLogFileAction.LABEL), 'Developer: Open Log File...', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Log Folder', devCategory); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SetLogLevelAction, SetLogLevelAction.ID, SetLogLevelAction.LABEL), 'Developer: Set Log Level', devCategory); \ No newline at end of file diff --git a/src/vs/workbench/parts/logs/electron-browser/logsActions.ts b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts new file mode 100644 index 00000000000..e9bac087e2f --- /dev/null +++ b/src/vs/workbench/parts/logs/electron-browser/logsActions.ts @@ -0,0 +1,127 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +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 } 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'; + +export class OpenLogsFolderAction extends Action { + + static ID = 'workbench.action.openLogsFolder'; + static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); + + constructor(id: string, label: string, + @IEnvironmentService private environmentService: IEnvironmentService, + @IWindowsService private windowsService: IWindowsService, + ) { + super(id, label); + } + + run(): TPromise { + return this.windowsService.showItemInFolder(paths.join(this.environmentService.logsPath, 'main.log')); + } +} + +export class ShowLogsAction extends Action { + + static ID = 'workbench.action.showLogs'; + static LABEL = nls.localize('showLogs', "Show Logs..."); + + constructor(id: string, label: string, + @IQuickOpenService private quickOpenService: IQuickOpenService, + @IOutputService private outputService: IOutputService + ) { + super(id, label); + } + + run(): TPromise { + const entries: IPickOpenEntry[] = [ + { id: Constants.mainLogChannelId, label: nls.localize('mainProcess', "Main") }, + { id: Constants.sharedLogChannelId, label: nls.localize('sharedProcess', "Shared") }, + { id: Constants.rendererLogChannelId, label: nls.localize('rendererProcess', "Window") }, + { id: Constants.extHostLogChannelId, label: nls.localize('extensionHost', "Extension Host") } + ]; + + return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select process") }) + .then(entry => { + if (entry) { + return this.outputService.showChannel(entry.id); + } + return null; + }); + } +} + +export class OpenLogFileAction extends Action { + + static ID = 'workbench.action.openLogFile'; + static LABEL = nls.localize('openLogFile', "Open Log File..."); + + constructor(id: string, label: string, + @IQuickOpenService private quickOpenService: IQuickOpenService, + @IEnvironmentService private environmentService: IEnvironmentService, + @ICommandService private commandService: ICommandService, + @IWindowService private windowService: IWindowService + ) { + super(id, label); + } + + run(): TPromise { + const entries: IPickOpenEntry[] = [ + { id: URI.file(paths.join(this.environmentService.logsPath, `main.log`)).fsPath, label: nls.localize('mainProcess', "Main") }, + { id: URI.file(paths.join(this.environmentService.logsPath, `sharedprocess.log`)).fsPath, label: nls.localize('sharedProcess', "Shared") }, + { id: URI.file(paths.join(this.environmentService.logsPath, `renderer${this.windowService.getCurrentWindowId()}.log`)).fsPath, label: nls.localize('rendererProcess', "Window") }, + { id: URI.file(paths.join(this.environmentService.logsPath, `extHost${this.windowService.getCurrentWindowId()}.log`)).fsPath, label: nls.localize('extensionHost', "Extension Host") } + ]; + + return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectProcess', "Select process") }) + .then(entry => { + if (entry) { + return this.commandService.executeCommand(COMMAND_OPEN_LOG_VIEWER, URI.file(entry.id)); + } + return null; + }); + } +} + +export class SetLogLevelAction extends Action { + + static ID = 'workbench.action.setLogLevel'; + static LABEL = nls.localize('setLogLevel', "Set Log Level"); + + constructor(id: string, label: string, + @IQuickOpenService private quickOpenService: IQuickOpenService, + @ILogService private logService: ILogService + ) { + super(id, label); + } + + run(): TPromise { + const entries = [ + { label: nls.localize('trace', "Trace"), level: LogLevel.Trace }, + { label: nls.localize('debug', "Debug"), level: LogLevel.Debug }, + { label: nls.localize('info', "Info"), level: LogLevel.Info }, + { label: nls.localize('warn', "Warning"), level: LogLevel.Warning }, + { label: nls.localize('err', "Error"), level: LogLevel.Error }, + { label: nls.localize('critical', "Critical"), level: LogLevel.Critical }, + { label: nls.localize('off', "Off"), level: LogLevel.Off } + ]; + + return this.quickOpenService.pick(entries, { placeHolder: nls.localize('selectLogLevel', "Select log level"), autoFocus: { autoFocusIndex: this.logService.getLevel() } }).then(entry => { + if (entry) { + this.logService.setLevel(entry.level); + } + }); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/output/browser/logViewer.ts b/src/vs/workbench/parts/output/browser/logViewer.ts new file mode 100644 index 00000000000..204cda19d60 --- /dev/null +++ b/src/vs/workbench/parts/output/browser/logViewer.ts @@ -0,0 +1,68 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as paths from 'vs/base/common/paths'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import URI from 'vs/base/common/uri'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { LOG_SCHEME } from 'vs/workbench/parts/output/common/output'; + + +export class LogViewerInput extends ResourceEditorInput { + + public static readonly ID = 'workbench.editorinputs.output'; + + constructor(private file: URI, + @ITextModelService textModelResolverService: ITextModelService, + @IHashService hashService: IHashService + ) { + super(paths.basename(file.fsPath), paths.dirname(file.fsPath), file.with({ scheme: LOG_SCHEME }), textModelResolverService, hashService); + } + + public getTypeId(): string { + return LogViewerInput.ID; + } + + public getResource(): URI { + return this.file; + } +} + +export class LogViewer extends AbstractTextResourceEditor { + + static readonly LOG_VIEWER_EDITOR_ID = 'workbench.editors.logViewer'; + + constructor( + @ITelemetryService telemetryService: ITelemetryService, + @IInstantiationService instantiationService: IInstantiationService, + @IStorageService storageService: IStorageService, + @IConfigurationService baseConfigurationService: IConfigurationService, + @ITextResourceConfigurationService textResourceConfigurationService: ITextResourceConfigurationService, + @IThemeService themeService: IThemeService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @ITextFileService textFileService: ITextFileService + ) { + super(LogViewer.LOG_VIEWER_EDITOR_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService); + } + + protected getConfigurationOverrides(): IEditorOptions { + const options = super.getConfigurationOverrides(); + options.wordWrap = 'off'; // all log viewers do not wrap + options.folding = false; + options.scrollBeyondLastLine = false; + return options; + } +} diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/parts/output/browser/outputActions.ts index 8fdbcd9698f..81c9db909cd 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/parts/output/browser/outputActions.ts @@ -7,7 +7,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import { IAction, Action } from 'vs/base/common/actions'; -import { IOutputService, OUTPUT_PANEL_ID } from 'vs/workbench/parts/output/common/output'; +import { IOutputService, OUTPUT_PANEL_ID, IOutputChannelRegistry, Extensions as OutputExt } from 'vs/workbench/parts/output/common/output'; import { SelectActionItem } from 'vs/base/browser/ui/actionbar/actionbar'; import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; @@ -15,6 +15,8 @@ import { TogglePanelAction } from 'vs/workbench/browser/panel'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { Registry } from 'vs/platform/registry/common/platform'; export class ToggleOutputAction extends TogglePanelAction { @@ -99,7 +101,7 @@ export class SwitchOutputAction extends Action { } public run(channelId?: string): TPromise { - return this.outputService.getChannel(channelId).show(); + return this.outputService.showChannel(channelId); } } @@ -108,14 +110,14 @@ export class SwitchOutputActionItem extends SelectActionItem { constructor( action: IAction, @IOutputService private outputService: IOutputService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService ) { - super(null, action, [], 0); + super(null, action, [], 0, contextViewService); - this.toDispose.push(this.outputService.onOutputChannel(() => { - const activeChannelIndex = this.getSelected(this.outputService.getActiveChannel().id); - this.setOptions(this.getOptions(), activeChannelIndex); - })); + let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); + this.toDispose.push(outputChannelRegistry.onDidRegisterChannel(() => this.updateOtions())); + this.toDispose.push(outputChannelRegistry.onDidRemoveChannel(() => this.updateOtions())); this.toDispose.push(this.outputService.onActiveOutputChannel(activeChannelId => this.setOptions(this.getOptions(), this.getSelected(activeChannelId)))); this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService)); @@ -132,6 +134,11 @@ export class SwitchOutputActionItem extends SelectActionItem { return this.outputService.getChannels().map(c => c.label); } + private updateOtions(): void { + const activeChannelIndex = this.getSelected(this.outputService.getActiveChannel().id); + this.setOptions(this.getOptions(), activeChannelIndex); + } + private getSelected(outputId: string): number { if (!outputId) { return undefined; diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index aeb4863884f..6ce402cc3df 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -17,15 +17,15 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; -import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; -import { OutputEditors, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; +import { AbstractTextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; +import { OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; import { SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction, ToggleOutputScrollLockAction } from 'vs/workbench/parts/output/browser/outputActions'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -export class OutputPanel extends TextResourceEditor { +export class OutputPanel extends AbstractTextResourceEditor { private actions: IAction[]; private scopedInstantiationService: IInstantiationService; @@ -41,7 +41,7 @@ export class OutputPanel extends TextResourceEditor { @IEditorGroupService editorGroupService: IEditorGroupService, @ITextFileService textFileService: ITextFileService ) { - super(telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService); + super(OUTPUT_PANEL_ID, telemetryService, instantiationService, storageService, textResourceConfigurationService, themeService, editorGroupService, textFileService); this.scopedInstantiationService = instantiationService; } @@ -101,11 +101,26 @@ export class OutputPanel extends TextResourceEditor { } public setInput(input: EditorInput, options?: EditorOptions): TPromise { + if (input.matches(this.input)) { + return TPromise.as(null); + } + + if (this.input) { + // Dispose previous input (Output panel is not a workbench editor) + this.input.dispose(); + } return super.setInput(input, options).then(() => this.revealLastLine()); } - protected createEditor(parent: Builder): void { + public clearInput(): void { + if (this.input) { + // Dispose current input (Output panel is not a workbench editor) + this.input.dispose(); + } + super.clearInput(); + } + protected createEditor(parent: Builder): void { // First create the scoped instantation service and only then construct the editor using the scoped service const scopedContextKeyService = this.contextKeyService.createScoped(parent.getHTMLElement()); this.toUnbind.push(scopedContextKeyService); @@ -113,7 +128,6 @@ export class OutputPanel extends TextResourceEditor { super.createEditor(parent); CONTEXT_IN_OUTPUT.bindTo(scopedContextKeyService).set(true); - this.setInput(OutputEditors.getInstance(this.instantiationService, this.outputService.getActiveChannel()), null); } public get instantiationService(): IInstantiationService { diff --git a/src/vs/workbench/parts/output/browser/outputServices.ts b/src/vs/workbench/parts/output/browser/outputServices.ts deleted file mode 100644 index f89599c5d41..00000000000 --- a/src/vs/workbench/parts/output/browser/outputServices.ts +++ /dev/null @@ -1,382 +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 { TPromise } from 'vs/base/common/winjs.base'; -import strings = require('vs/base/common/strings'); -import Event, { Emitter } from 'vs/base/common/event'; -import { binarySearch } from 'vs/base/common/arrays'; -import URI from 'vs/base/common/uri'; -import { IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { IEditor } from 'vs/platform/editor/common/editor'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; -import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorOptions } from 'vs/workbench/common/editor'; -import { IOutputChannelIdentifier, OutputEditors, IOutputEvent, IOutputChannel, IOutputService, IOutputDelta, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, MAX_OUTPUT_LENGTH, OUTPUT_SCHEME, OUTPUT_MIME } from 'vs/workbench/parts/output/common/output'; -import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; -import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { OutputLinkProvider } from 'vs/workbench/parts/output/common/outputLinkProvider'; -import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; -import { IModel } from 'vs/editor/common/editorCommon'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { RunOnceScheduler } from 'vs/base/common/async'; -import { EditOperation } from 'vs/editor/common/core/editOperation'; -import { Position } from 'vs/editor/common/core/position'; - -const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; - -export class BufferedContent { - - private data: string[] = []; - private dataIds: number[] = []; - private idPool = 0; - private length = 0; - - public append(content: string): void { - this.data.push(content); - this.dataIds.push(++this.idPool); - this.length += content.length; - this.trim(); - } - - public clear(): void { - this.data.length = 0; - this.dataIds.length = 0; - this.length = 0; - } - - private trim(): void { - if (this.length < MAX_OUTPUT_LENGTH * 1.2) { - return; - } - - while (this.length > MAX_OUTPUT_LENGTH) { - this.dataIds.shift(); - const removed = this.data.shift(); - this.length -= removed.length; - } - } - - public getDelta(previousDelta?: IOutputDelta): IOutputDelta { - let idx = -1; - if (previousDelta) { - idx = binarySearch(this.dataIds, previousDelta.id, (a, b) => a - b); - } - - const id = this.idPool; - if (idx >= 0) { - const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join('')); - return { value, id, append: true }; - } else { - const value = strings.removeAnsiEscapeCodes(this.data.join('')); - return { value, id }; - } - } -} - -export class OutputService implements IOutputService { - - public _serviceBrand: any; - - private receivedOutput: Map = new Map(); - private channels: Map = new Map(); - - private activeChannelId: string; - - private _onOutput: Emitter; - private _onOutputChannel: Emitter; - private _onActiveOutputChannel: Emitter; - - private _outputContentProvider: OutputContentProvider; - private _outputPanel: OutputPanel; - - constructor( - @IStorageService private storageService: IStorageService, - @IInstantiationService private instantiationService: IInstantiationService, - @IPanelService private panelService: IPanelService, - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IModelService modelService: IModelService, - @ITextModelService textModelResolverService: ITextModelService - ) { - this._onOutput = new Emitter(); - this._onOutputChannel = new Emitter(); - this._onActiveOutputChannel = new Emitter(); - - const channels = this.getChannels(); - this.activeChannelId = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, channels && channels.length > 0 ? channels[0].id : null); - - instantiationService.createInstance(OutputLinkProvider); - - this._outputContentProvider = instantiationService.createInstance(OutputContentProvider, this); - - // Register as text model content provider for output - textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this._outputContentProvider); - } - - public get onOutput(): Event { - return this._onOutput.event; - } - - public get onOutputChannel(): Event { - return this._onOutputChannel.event; - } - - public get onActiveOutputChannel(): Event { - return this._onActiveOutputChannel.event; - } - - public getChannel(id: string): IOutputChannel { - if (!this.channels.has(id)) { - const channelData = Registry.as(Extensions.OutputChannels).getChannel(id); - - const self = this; - this.channels.set(id, { - id, - label: channelData ? channelData.label : id, - getOutput(before?: IOutputDelta) { - return self.getOutput(id, before); - }, - get scrollLock() { - return self._outputContentProvider.scrollLock(id); - }, - set scrollLock(value: boolean) { - self._outputContentProvider.setScrollLock(id, value); - }, - append: (output: string) => this.append(id, output), - show: (preserveFocus: boolean) => this.showOutput(id, preserveFocus), - clear: () => this.clearOutput(id), - dispose: () => this.removeOutput(id) - }); - } - - return this.channels.get(id); - } - - public getChannels(): IOutputChannelIdentifier[] { - return Registry.as(Extensions.OutputChannels).getChannels(); - } - - private append(channelId: string, output: string): void { - - // Initialize - if (!this.receivedOutput.has(channelId)) { - this.receivedOutput.set(channelId, new BufferedContent()); - - this._onOutputChannel.fire(channelId); // emit event that we have a new channel - } - - // Store - if (output) { - const channel = this.receivedOutput.get(channelId); - channel.append(output); - } - - this._onOutput.fire({ channelId: channelId, isClear: false }); - } - - public getActiveChannel(): IOutputChannel { - return this.getChannel(this.activeChannelId); - } - - private getOutput(channelId: string, previousDelta: IOutputDelta): IOutputDelta { - if (this.receivedOutput.has(channelId)) { - return this.receivedOutput.get(channelId).getDelta(previousDelta); - } - - return undefined; - } - - private clearOutput(channelId: string): void { - if (this.receivedOutput.has(channelId)) { - this.receivedOutput.get(channelId).clear(); - this._onOutput.fire({ channelId: channelId, isClear: true }); - } - } - - private removeOutput(channelId: string): void { - this.receivedOutput.delete(channelId); - Registry.as(Extensions.OutputChannels).removeChannel(channelId); - if (this.activeChannelId === channelId) { - const channels = this.getChannels(); - this.activeChannelId = channels.length ? channels[0].id : undefined; - if (this._outputPanel && this.activeChannelId) { - this._outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(this.activeChannelId)), EditorOptions.create({ preserveFocus: true })); - } - this._onActiveOutputChannel.fire(this.activeChannelId); - } - - this._onOutputChannel.fire(channelId); - } - - private showOutput(channelId: string, preserveFocus?: boolean): TPromise { - const panel = this.panelService.getActivePanel(); - if (this.activeChannelId === channelId && panel && panel.getId() === OUTPUT_PANEL_ID) { - return TPromise.as(panel); - } - - this.activeChannelId = channelId; - this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannelId, StorageScope.WORKSPACE); - this._onActiveOutputChannel.fire(channelId); // emit event that a new channel is active - - return this.panelService.openPanel(OUTPUT_PANEL_ID, !preserveFocus).then((outputPanel: OutputPanel) => { - this._outputPanel = outputPanel; - return outputPanel && outputPanel.setInput(OutputEditors.getInstance(this.instantiationService, this.getChannel(this.activeChannelId)), EditorOptions.create({ preserveFocus: preserveFocus })). - then(() => outputPanel); - }); - } -} - -class OutputContentProvider implements ITextModelContentProvider { - - private static readonly OUTPUT_DELAY = 300; - - private bufferedOutput = new Map(); - private appendOutputScheduler: { [channel: string]: RunOnceScheduler; }; - private channelIdsWithScrollLock: Set = new Set(); - private toDispose: IDisposable[]; - - constructor( - private outputService: IOutputService, - @IModelService private modelService: IModelService, - @IModeService private modeService: IModeService, - @IPanelService private panelService: IPanelService - ) { - this.appendOutputScheduler = Object.create(null); - this.toDispose = []; - - this.registerListeners(); - } - - private registerListeners(): void { - this.toDispose.push(this.outputService.onOutput(e => this.onOutputReceived(e))); - this.toDispose.push(this.outputService.onActiveOutputChannel(channel => this.scheduleOutputAppend(channel))); - this.toDispose.push(this.panelService.onDidPanelOpen(panel => { - if (panel.getId() === OUTPUT_PANEL_ID) { - this.appendOutput(); - } - })); - } - - private onOutputReceived(e: IOutputEvent): void { - const model = this.getModel(e.channelId); - if (!model) { - return; // only react if we have a known model - } - - // Append to model - if (e.isClear) { - model.setValue(''); - } else { - this.scheduleOutputAppend(e.channelId); - } - } - - private getModel(channel: string): IModel { - return this.modelService.getModel(URI.from({ scheme: OUTPUT_SCHEME, path: channel })); - } - - private scheduleOutputAppend(channel: string): void { - if (!this.isVisible(channel)) { - return; // only if the output channel is visible - } - - let scheduler = this.appendOutputScheduler[channel]; - if (!scheduler) { - scheduler = new RunOnceScheduler(() => { - if (this.isVisible(channel)) { - this.appendOutput(channel); - } - }, OutputContentProvider.OUTPUT_DELAY); - - this.appendOutputScheduler[channel] = scheduler; - this.toDispose.push(scheduler); - } - - if (scheduler.isScheduled()) { - return; // only if not already scheduled - } - - scheduler.schedule(); - } - - private appendOutput(channel?: string): void { - if (!channel) { - const activeChannel = this.outputService.getActiveChannel(); - channel = activeChannel && activeChannel.id; - } - - if (!channel) { - return; // return if we do not have a valid channel to append to - } - - const model = this.getModel(channel); - if (!model) { - return; // only react if we have a known model - } - - const bufferedOutput = this.bufferedOutput.get(channel); - const newOutput = this.outputService.getChannel(channel).getOutput(bufferedOutput); - if (!newOutput) { - model.setValue(''); - return; - } - this.bufferedOutput.set(channel, newOutput); - - // just fill in the full (trimmed) output if we exceed max length - if (!newOutput.append) { - model.setValue(newOutput.value); - } - - // otherwise append - else { - const lastLine = model.getLineCount(); - const lastLineMaxColumn = model.getLineMaxColumn(lastLine); - - model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), newOutput.value)]); - } - - if (!this.channelIdsWithScrollLock.has(channel)) { - // reveal last line - const panel = this.panelService.getActivePanel(); - (panel).revealLastLine(); - } - } - - private isVisible(channel: string): boolean { - const panel = this.panelService.getActivePanel(); - - return panel && panel.getId() === OUTPUT_PANEL_ID && this.outputService.getActiveChannel().id === channel; - } - - public scrollLock(channelId: string): boolean { - return this.channelIdsWithScrollLock.has(channelId); - } - - public setScrollLock(channelId: string, value: boolean): void { - if (value) { - this.channelIdsWithScrollLock.add(channelId); - } else { - this.channelIdsWithScrollLock.delete(channelId); - } - } - - public provideTextContent(resource: URI): TPromise { - const output = this.outputService.getChannel(resource.fsPath).getOutput(); - const content = output ? output.value : ''; - - let codeEditorModel = this.modelService.getModel(resource); - if (!codeEditorModel) { - codeEditorModel = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), resource); - } - - return TPromise.as(codeEditorModel); - } - - public dispose(): void { - this.toDispose = dispose(this.toDispose); - } -} diff --git a/src/vs/workbench/parts/output/common/output.ts b/src/vs/workbench/parts/output/common/output.ts index b4066e6e6be..16786934caa 100644 --- a/src/vs/workbench/parts/output/common/output.ts +++ b/src/vs/workbench/parts/output/common/output.ts @@ -5,13 +5,10 @@ 'use strict'; import { TPromise } from 'vs/base/common/winjs.base'; -import Event from 'vs/base/common/event'; +import Event, { Emitter } from 'vs/base/common/event'; import { Registry } from 'vs/platform/registry/common/platform'; -import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IEditor } from 'vs/platform/editor/common/editor'; +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; -import nls = require('vs/nls'); import URI from 'vs/base/common/uri'; /** @@ -24,6 +21,11 @@ export const OUTPUT_MIME = 'text/x-code-output'; */ export const OUTPUT_SCHEME = 'output'; +/** + * Output resource scheme. + */ +export const LOG_SCHEME = 'log'; + /** * Id used by the output editor. */ @@ -34,6 +36,11 @@ export const OUTPUT_MODE_ID = 'Log'; */ export const OUTPUT_PANEL_ID = 'workbench.panel.output'; +/** + * Open log viewer command id + */ +export const COMMAND_OPEN_LOG_VIEWER = 'workbench.action.openLogViewer'; + export const Extensions = { OutputChannels: 'workbench.contributions.outputChannels' }; @@ -44,14 +51,6 @@ export const MAX_OUTPUT_LENGTH = 10000 /* Max. number of output lines to show in export const CONTEXT_IN_OUTPUT = new RawContextKey('inOutput', false); -/** - * The output event informs when new output got received. - */ -export interface IOutputEvent { - channelId: string; - isClear: boolean; -} - export const IOutputService = createDecorator(OUTPUT_SERVICE_ID); /** @@ -78,14 +77,9 @@ export interface IOutputService { getActiveChannel(): IOutputChannel; /** - * Allows to register on Output events. + * Show the channel with the passed id. */ - onOutput: Event; - - /** - * Allows to register on a output channel being added or removed - */ - onOutputChannel: Event; + showChannel(id: string, preserveFocus?: boolean): TPromise; /** * Allows to register on active output channel change. @@ -93,12 +87,6 @@ export interface IOutputService { onActiveOutputChannel: Event; } -export interface IOutputDelta { - readonly value: string; - readonly id: number; - readonly append?: boolean; -} - export interface IOutputChannel { /** @@ -121,17 +109,6 @@ export interface IOutputChannel { */ append(output: string): void; - /** - * Returns the received output content. - * If a delta is passed, returns only the content that came after the passed delta. - */ - getOutput(previousDelta?: IOutputDelta): IOutputDelta; - - /** - * Opens the output for this channel. - */ - show(preserveFocus?: boolean): TPromise; - /** * Clears all received output for this channel. */ @@ -146,14 +123,18 @@ export interface IOutputChannel { export interface IOutputChannelIdentifier { id: string; label: string; + file?: URI; } export interface IOutputChannelRegistry { + readonly onDidRegisterChannel: Event; + readonly onDidRemoveChannel: Event; + /** * Make an output channel known to the output world. */ - registerChannel(id: string, name: string): void; + registerChannel(id: string, name: string, file?: URI): void; /** * Returns the list of channels known to the output world. @@ -174,9 +155,16 @@ export interface IOutputChannelRegistry { class OutputChannelRegistry implements IOutputChannelRegistry { private channels = new Map(); - public registerChannel(id: string, label: string): void { + private _onDidRegisterChannel: Emitter = new Emitter(); + readonly onDidRegisterChannel: Event = this._onDidRegisterChannel.event; + + private _onDidRemoveChannel: Emitter = new Emitter(); + readonly onDidRemoveChannel: Event = this._onDidRemoveChannel.event; + + public registerChannel(id: string, label: string, file?: URI): void { if (!this.channels.has(id)) { - this.channels.set(id, { id, label }); + this.channels.set(id, { id, label, file }); + this._onDidRegisterChannel.fire(id); } } @@ -192,24 +180,8 @@ class OutputChannelRegistry implements IOutputChannelRegistry { public removeChannel(id: string): void { this.channels.delete(id); + this._onDidRemoveChannel.fire(id); } } -Registry.add(Extensions.OutputChannels, new OutputChannelRegistry()); - -export class OutputEditors { - - private static instances: { [channel: string]: ResourceEditorInput; } = Object.create(null); - - public static getInstance(instantiationService: IInstantiationService, channel: IOutputChannel): ResourceEditorInput { - if (OutputEditors.instances[channel.id]) { - return OutputEditors.instances[channel.id]; - } - - const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); - - OutputEditors.instances[channel.id] = instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "Output"), channel ? nls.localize('channel', "for '{0}'", channel.label) : '', resource); - - return OutputEditors.instances[channel.id]; - } -} \ No newline at end of file +Registry.add(Extensions.OutputChannels, new OutputChannelRegistry()); \ No newline at end of file diff --git a/src/vs/workbench/parts/output/browser/output.contribution.ts b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts similarity index 66% rename from src/vs/workbench/parts/output/browser/output.contribution.ts rename to src/vs/workbench/parts/output/electron-browser/output.contribution.ts index 2809a152fc1..d73919d5137 100644 --- a/src/vs/workbench/parts/output/browser/output.contribution.ts +++ b/src/vs/workbench/parts/output/electron-browser/output.contribution.ts @@ -11,13 +11,22 @@ import { MenuId, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/ import { KeybindingsRegistry, IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { OutputService } from 'vs/workbench/parts/output/browser/outputServices'; +import { OutputService, LogContentProvider } from 'vs/workbench/parts/output/electron-browser/outputServices'; import { ToggleOutputAction, ClearOutputAction } from 'vs/workbench/parts/output/browser/outputActions'; -import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT } from 'vs/workbench/parts/output/common/output'; +import { OUTPUT_MODE_ID, OUTPUT_MIME, OUTPUT_PANEL_ID, IOutputService, CONTEXT_IN_OUTPUT, LOG_SCHEME, COMMAND_OPEN_LOG_VIEWER } from 'vs/workbench/parts/output/common/output'; import { PanelRegistry, Extensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; +import { IEditorRegistry, Extensions as EditorExtensions, EditorDescriptor } from 'vs/workbench/browser/editor'; +import { LogViewer, LogViewerInput } from 'vs/workbench/parts/output/browser/logViewer'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import URI from 'vs/base/common/uri'; +import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; // Register Service registerSingleton(IOutputService, OutputService); @@ -40,6 +49,28 @@ Registry.as(Extensions.Panels).registerPanel(new PanelDescriptor( ToggleOutputAction.ID )); +Registry.as(EditorExtensions.Editors).registerEditor( + new EditorDescriptor( + LogViewer, + LogViewer.LOG_VIEWER_EDITOR_ID, + nls.localize('logViewer', "Log Viewer") + ), + [ + new SyncDescriptor(LogViewerInput) + ] +); + +class OutputContribution implements IWorkbenchContribution { + constructor( + @IInstantiationService instantiationService: IInstantiationService, + @ITextModelService textModelService: ITextModelService + ) { + textModelService.registerTextModelContentProvider(LOG_SCHEME, instantiationService.createInstance(LogContentProvider)); + } +} + +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(OutputContribution, LifecyclePhase.Running); + // register toggle output action globally const actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleOutputAction, ToggleOutputAction.ID, ToggleOutputAction.LABEL, { @@ -129,3 +160,11 @@ registerAction({ accessor.get(IOutputService).getActiveChannel().clear(); } }); + +CommandsRegistry.registerCommand(COMMAND_OPEN_LOG_VIEWER, function (accessor: ServicesAccessor, file: URI) { + if (file) { + const editorService = accessor.get(IWorkbenchEditorService); + return editorService.openEditor(accessor.get(IInstantiationService).createInstance(LogViewerInput, file)); + } + return null; +}); diff --git a/src/vs/workbench/parts/output/electron-browser/outputServices.ts b/src/vs/workbench/parts/output/electron-browser/outputServices.ts new file mode 100644 index 00000000000..d699f68bc36 --- /dev/null +++ b/src/vs/workbench/parts/output/electron-browser/outputServices.ts @@ -0,0 +1,763 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import * as paths from 'vs/base/common/paths'; +import * as strings from 'vs/base/common/strings'; +import * as extfs from 'vs/base/node/extfs'; +import * as fs from 'fs'; +import { TPromise } from 'vs/base/common/winjs.base'; +import Event, { Emitter } from 'vs/base/common/event'; +import URI from 'vs/base/common/uri'; +import { IDisposable, dispose, Disposable, toDisposable } from 'vs/base/common/lifecycle'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorOptions } from 'vs/workbench/common/editor'; +import { IOutputChannelIdentifier, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_ID, IOutputChannelRegistry, OUTPUT_SCHEME, OUTPUT_MIME, MAX_OUTPUT_LENGTH, LOG_SCHEME } from 'vs/workbench/parts/output/common/output'; +import { OutputPanel } from 'vs/workbench/parts/output/browser/outputPanel'; +import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { OutputLinkProvider } from 'vs/workbench/parts/output/common/outputLinkProvider'; +import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService'; +import { IModel } from 'vs/editor/common/editorCommon'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { RunOnceScheduler, ThrottledDelayer } from 'vs/base/common/async'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { Position } from 'vs/editor/common/core/position'; +import { IFileService } from 'vs/platform/files/common/files'; +import { IPanel } from 'vs/workbench/common/panel'; +import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { RotatingLogger } from 'spdlog'; +import { toLocalISOString } from 'vs/base/common/date'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { ILogService } from 'vs/platform/log/common/log'; +import { binarySearch } from 'vs/base/common/arrays'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { Schemas } from 'vs/base/common/network'; +import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle'; + +const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; + +let watchingOutputDir = false; +let callbacks = []; +function watchOutputDirectory(outputDir: string, logService: ILogService, onChange: (eventType: string, fileName: string) => void): IDisposable { + callbacks.push(onChange); + if (!watchingOutputDir) { + try { + const watcher = extfs.watch(outputDir, (eventType, fileName) => { + for (const callback of callbacks) { + callback(eventType, fileName); + } + }); + watcher.on('error', (code: number, signal: string) => logService.error(`Error watching ${outputDir}: (${code}, ${signal})`)); + watchingOutputDir = true; + return toDisposable(() => { + callbacks = []; + watcher.removeAllListeners(); + watcher.close(); + }); + } catch (error) { + logService.error(`Error watching ${outputDir}: (${error.toString()})`); + } + } + return toDisposable(() => { }); +} + +const fileWatchers: Map = new Map(); +function watchFile(file: string, callback: () => void): IDisposable { + + const onFileChange = (file: string) => { + for (const callback of fileWatchers.get(file)) { + callback(); + } + }; + + let callbacks = fileWatchers.get(file); + if (!callbacks) { + callbacks = []; + fileWatchers.set(file, callbacks); + fs.watchFile(file, { interval: 1000 }, (current, previous) => { + if ((previous && !current) || (!previous && !current)) { + onFileChange(file); + return; + } + if (previous && current && previous.mtime !== current.mtime) { + onFileChange(file); + return; + } + }); + } + callbacks.push(callback); + return toDisposable(() => { + let allCallbacks = fileWatchers.get(file); + allCallbacks.splice(allCallbacks.indexOf(callback), 1); + if (!allCallbacks.length) { + fs.unwatchFile(file); + fileWatchers.delete(file); + } + }); +} + +function unWatchAllFiles(): void { + fileWatchers.forEach((value, file) => fs.unwatchFile(file)); + fileWatchers.clear(); +} + +interface OutputChannel extends IOutputChannel { + readonly file: URI; + readonly onDidAppendedContent: Event; + readonly onDispose: Event; + loadModel(): TPromise; +} + +abstract class AbstractFileOutputChannel extends Disposable { + + scrollLock: boolean = false; + + protected _onDidAppendedContent: Emitter = new Emitter(); + readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; + + protected _onDispose: Emitter = new Emitter(); + readonly onDispose: Event = this._onDispose.event; + + protected modelUpdater: RunOnceScheduler; + protected model: IModel; + readonly file: URI; + protected startOffset: number = 0; + protected endOffset: number = 0; + + constructor( + protected readonly outputChannelIdentifier: IOutputChannelIdentifier, + private readonly modelUri: URI, + protected fileService: IFileService, + protected modelService: IModelService, + protected modeService: IModeService, + ) { + super(); + this.file = this.outputChannelIdentifier.file; + this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); + this._register(toDisposable(() => this.modelUpdater.cancel())); + } + + get id(): string { + return this.outputChannelIdentifier.id; + } + + get label(): string { + return this.outputChannelIdentifier.label; + } + + clear(): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } + if (this.model) { + this.model.setValue(''); + } + this.startOffset = this.endOffset; + } + + loadModel(): TPromise { + return this.fileService.resolveContent(this.file, { position: this.startOffset }) + .then(content => { + if (this.model) { + this.model.setValue(content.value); + } else { + this.model = this.createModel(content.value); + } + this.endOffset = this.startOffset + new Buffer(this.model.getValueLength()).byteLength; + return this.model; + }); + } + + resetModel(): TPromise { + this.startOffset = 0; + this.endOffset = 0; + if (this.model) { + return this.loadModel() as TPromise; + } + return TPromise.as(null); + } + + private createModel(content: string): IModel { + const model = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), this.modelUri); + this.onModelCreated(model); + const disposables: IDisposable[] = []; + disposables.push(model.onWillDispose(() => { + this.onModelWillDispose(model); + this.model = null; + dispose(disposables); + })); + return model; + } + + appendToModel(content: string): void { + if (this.model && content) { + const lastLine = this.model.getLineCount(); + const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); + this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), content)]); + this.endOffset = this.endOffset + new Buffer(content).byteLength; + this._onDidAppendedContent.fire(); + } + } + + protected onModelCreated(model: IModel) { } + protected onModelWillDispose(model: IModel) { } + protected updateModel() { } + + dispose(): void { + this._onDispose.fire(); + super.dispose(); + } +} + +/** + * An output channel that stores appended messages in a backup file. + */ +class OutputChannelBackedByFile extends AbstractFileOutputChannel implements OutputChannel { + + private outputWriter: RotatingLogger; + private appendedMessage = ''; + private loadingFromFileInProgress: boolean = false; + private resettingDelayer: ThrottledDelayer; + + constructor( + outputChannelIdentifier: IOutputChannelIdentifier, + modelUri: URI, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, + @ILogService logService: ILogService + ) { + super(outputChannelIdentifier, modelUri, fileService, modelService, modeService); + + // Use one rotating file to check for main file reset + this.outputWriter = new RotatingLogger(this.id, this.file.fsPath, 1024 * 1024 * 30, 1); + this.outputWriter.clearFormatters(); + this._register(watchOutputDirectory(paths.dirname(this.file.fsPath), logService, (eventType, file) => this.onFileChangedInOutputDirector(eventType, file))); + + this.resettingDelayer = new ThrottledDelayer(50); + } + + append(message: string): void { + if (this.loadingFromFileInProgress) { + this.appendedMessage += message; + } else { + this.outputWriter.critical(message); + if (this.model) { + this.appendedMessage += message; + if (!this.modelUpdater.isScheduled()) { + this.modelUpdater.schedule(); + } + } + } + } + + clear(): void { + super.clear(); + this.appendedMessage = ''; + } + + loadModel(): TPromise { + this.startLoadingFromFile(); + return super.loadModel() + .then(model => { + this.finishedLoadingFromFile(); + return model; + }); + } + + protected updateModel(): void { + if (this.model && this.appendedMessage) { + this.appendToModel(this.appendedMessage); + this.appendedMessage = ''; + } + } + + private startLoadingFromFile(): void { + this.loadingFromFileInProgress = true; + this.outputWriter.flush(); + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } + this.appendedMessage = ''; + } + + private finishedLoadingFromFile(): void { + if (this.appendedMessage) { + this.outputWriter.critical(this.appendedMessage); + this.appendToModel(this.appendedMessage); + this.appendedMessage = ''; + } + this.loadingFromFileInProgress = false; + } + + private onFileChangedInOutputDirector(eventType: string, fileName: string): void { + // Check if rotating file has changed. It changes only when the main file exceeds its limit. + if (`${paths.basename(this.file.fsPath)}.1` === fileName) { + this.resettingDelayer.trigger(() => this.resetModel()); + } + } +} + +class OutputFileListener extends Disposable { + + private _onDidChange: Emitter = new Emitter(); + readonly onDidContentChange: Event = this._onDidChange.event; + + private watching: boolean = false; + private disposables: IDisposable[] = []; + + constructor( + private readonly file: URI, + ) { + super(); + } + + watch(): void { + if (!this.watching) { + this.disposables.push(watchFile(this.file.fsPath, () => this._onDidChange.fire())); + this.watching = true; + } + } + + unwatch(): void { + if (this.watching) { + this.disposables = dispose(this.disposables); + this.watching = false; + } + } + + dispose(): void { + this.unwatch(); + super.dispose(); + } +} + +/** + * An output channel driven by a file and does not support appending messages. + */ +class FileOutputChannel extends AbstractFileOutputChannel implements OutputChannel { + + private readonly fileHandler: OutputFileListener; + + private updateInProgress: boolean = false; + + constructor( + outputChannelIdentifier: IOutputChannelIdentifier, + modelUri: URI, + @IFileService fileService: IFileService, + @IModelService modelService: IModelService, + @IModeService modeService: IModeService, + @ILogService logService: ILogService, + ) { + super(outputChannelIdentifier, modelUri, fileService, modelService, modeService); + + this.fileHandler = this._register(new OutputFileListener(this.file)); + this._register(this.fileHandler.onDidContentChange(() => this.onDidContentChange())); + this._register(toDisposable(() => this.fileHandler.unwatch())); + } + + append(message: string): void { + throw new Error('Not supported'); + } + + protected updateModel(): void { + if (this.model) { + this.fileService.resolveContent(this.file, { position: this.endOffset }) + .then(content => { + this.appendToModel(content.value); + this.updateInProgress = false; + }, () => this.updateInProgress = false); + } else { + this.updateInProgress = false; + } + } + + protected onModelCreated(model: IModel): void { + this.fileHandler.watch(); + } + + protected onModelWillDispose(model: IModel): void { + this.fileHandler.unwatch(); + } + + private onDidContentChange(): void { + if (!this.updateInProgress) { + this.updateInProgress = true; + this.modelUpdater.schedule(); + } + } +} + +export class OutputService extends Disposable implements IOutputService, ITextModelContentProvider { + + public _serviceBrand: any; + + private channels: Map = new Map(); + private activeChannelIdInStorage: string; + private activeChannel: IOutputChannel; + private readonly outputDir: string; + + private _onActiveOutputChannel: Emitter = new Emitter(); + readonly onActiveOutputChannel: Event = this._onActiveOutputChannel.event; + + private _outputPanel: OutputPanel; + + constructor( + @IStorageService private storageService: IStorageService, + @IInstantiationService private instantiationService: IInstantiationService, + @IPanelService private panelService: IPanelService, + @IWorkspaceContextService contextService: IWorkspaceContextService, + @ITextModelService textModelResolverService: ITextModelService, + @IEnvironmentService environmentService: IEnvironmentService, + @IWindowService windowService: IWindowService, + @ITelemetryService private telemetryService: ITelemetryService, + @ILogService private logService: ILogService, + @ILifecycleService private lifecycleService: ILifecycleService, + ) { + super(); + this.activeChannelIdInStorage = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, null); + this.outputDir = paths.join(environmentService.logsPath, `output_${windowService.getCurrentWindowId()}_${toLocalISOString(new Date()).replace(/-|:|\.\d+Z$/g, '')}`); + + // Register as text model content provider for output + textModelResolverService.registerTextModelContentProvider(OUTPUT_SCHEME, this); + instantiationService.createInstance(OutputLinkProvider); + + // Create output channels for already registered channels + const registry = Registry.as(Extensions.OutputChannels); + for (const channelIdentifier of registry.getChannels()) { + this.onDidRegisterChannel(channelIdentifier.id); + } + this._register(registry.onDidRegisterChannel(this.onDidRegisterChannel, this)); + + panelService.onDidPanelOpen(this.onDidPanelOpen, this); + panelService.onDidPanelClose(this.onDidPanelClose, this); + + this._register(toDisposable(() => unWatchAllFiles())); + + // Set active channel to first channel if not set + if (!this.activeChannel) { + const channels = this.getChannels(); + this.activeChannel = channels && channels.length > 0 ? this.getChannel(channels[0].id) : null; + } + + this.lifecycleService.onShutdown(() => this.onShutdown()); + } + + provideTextContent(resource: URI): TPromise { + const channel = this.getChannel(resource.fsPath); + if (channel) { + return channel.loadModel(); + } + return TPromise.as(null); + } + + showChannel(id: string, preserveFocus?: boolean): TPromise { + const channel = this.getChannel(id); + if (!channel || this.isChannelShown(channel)) { + return TPromise.as(null); + } + + this.activeChannel = channel; + let promise = TPromise.as(null); + if (this.isPanelShown()) { + this.doShowChannel(channel, preserveFocus); + } else { + promise = this.panelService.openPanel(OUTPUT_PANEL_ID) as TPromise; + } + return promise.then(() => this._onActiveOutputChannel.fire(id)); + } + + getChannel(id: string): IOutputChannel { + return this.channels.get(id); + } + + getChannels(): IOutputChannelIdentifier[] { + return Registry.as(Extensions.OutputChannels).getChannels(); + } + + getActiveChannel(): IOutputChannel { + return this.activeChannel; + } + + private onDidRegisterChannel(channelId: string): void { + const channel = this.createChannel(channelId); + this.channels.set(channelId, channel); + if (this.activeChannelIdInStorage === channelId) { + this.activeChannel = channel; + this.onDidPanelOpen(this.panelService.getActivePanel()); + } + } + + private onDidPanelOpen(panel: IPanel): void { + if (panel && panel.getId() === OUTPUT_PANEL_ID) { + this._outputPanel = this.panelService.getActivePanel(); + if (this.activeChannel) { + this.doShowChannel(this.activeChannel, true); + } + } + } + + private onDidPanelClose(panel: IPanel): void { + if (this._outputPanel && panel.getId() === OUTPUT_PANEL_ID) { + this._outputPanel.clearInput(); + } + } + + private createChannel(id: string): OutputChannel { + const channelDisposables = []; + const channel = this.instantiateChannel(id); + channel.onDidAppendedContent(() => { + if (!channel.scrollLock) { + const panel = this.panelService.getActivePanel(); + if (panel && panel.getId() === OUTPUT_PANEL_ID && this.isChannelShown(channel)) { + (panel).revealLastLine(); + } + } + }, channelDisposables); + channel.onDispose(() => { + Registry.as(Extensions.OutputChannels).removeChannel(id); + if (this.activeChannel === channel) { + const channels = this.getChannels(); + if (this._outputPanel && channels.length) { + this.showChannel(channels[0].id); + } else { + this._onActiveOutputChannel.fire(void 0); + } + } + dispose(channelDisposables); + }, channelDisposables); + + return channel; + } + + private instantiateChannel(id: string): OutputChannel { + const channelData = Registry.as(Extensions.OutputChannels).getChannel(id); + if (!channelData) { + this.logService.error(`Channel '${id}' is not registered yet`); + throw new Error(`Channel '${id}' is not registered yet`); + } + + const uri = URI.from({ scheme: OUTPUT_SCHEME, path: id }); + if (channelData && channelData.file) { + return this.instantiationService.createInstance(FileOutputChannel, channelData, uri); + } + const file = URI.file(paths.join(this.outputDir, `${id}.log`)); + try { + return this.instantiationService.createInstance(OutputChannelBackedByFile, { id, label: channelData ? channelData.label : '', file }, uri); + } catch (e) { + this.logService.error(e); + this.telemetryService.publicLog('output.used.bufferedChannel'); + return this.instantiationService.createInstance(BufferredOutputChannel, { id, label: channelData ? channelData.label : '' }); + } + } + + private doShowChannel(channel: IOutputChannel, preserveFocus: boolean): void { + if (this._outputPanel) { + this._outputPanel.setInput(this.createInput(channel), EditorOptions.create({ preserveFocus: preserveFocus })); + if (!preserveFocus) { + this._outputPanel.focus(); + } + } + } + + private isChannelShown(channel: IOutputChannel): boolean { + return this.isPanelShown() && this.activeChannel === channel; + } + + private isPanelShown(): boolean { + const panel = this.panelService.getActivePanel(); + return panel && panel.getId() === OUTPUT_PANEL_ID; + } + + private createInput(channel: IOutputChannel): ResourceEditorInput { + const resource = URI.from({ scheme: OUTPUT_SCHEME, path: channel.id }); + return this.instantiationService.createInstance(ResourceEditorInput, nls.localize('output', "{0} - Output", channel.label), nls.localize('channel', "Output channel for '{0}'", channel.label), resource); + } + + onShutdown(): void { + if (this.activeChannel) { + this.storageService.store(OUTPUT_ACTIVE_CHANNEL_KEY, this.activeChannel.id, StorageScope.WORKSPACE); + } + this.dispose(); + } +} + +export class LogContentProvider { + + private channels: Map = new Map(); + + constructor( + @IInstantiationService private instantiationService: IInstantiationService + ) { + } + + provideTextContent(resource: URI): TPromise { + if (resource.scheme === LOG_SCHEME) { + let channel = this.getChannel(resource); + if (channel) { + return channel.loadModel(); + } + } + return TPromise.as(null); + } + + private getChannel(resource: URI): OutputChannel { + const id = resource.path; + let channel = this.channels.get(id); + if (!channel) { + const channelDisposables = []; + channel = this.instantiationService.createInstance(FileOutputChannel, { id, label: '', file: resource.with({ scheme: Schemas.file }) }, resource); + channel.onDispose(() => dispose(channelDisposables), channelDisposables); + this.channels.set(id, channel); + } + return channel; + } +} + +// Remove this channel when there are no issues using Output channel backed by file +class BufferredOutputChannel extends Disposable implements OutputChannel { + + readonly id: string; + readonly label: string; + readonly file: URI = null; + scrollLock: boolean = false; + + protected _onDidAppendedContent: Emitter = new Emitter(); + readonly onDidAppendedContent: Event = this._onDidAppendedContent.event; + + private _onDispose: Emitter = new Emitter(); + readonly onDispose: Event = this._onDispose.event; + + private modelUpdater: RunOnceScheduler; + private model: IModel; + private readonly bufferredContent: BufferedContent; + private lastReadId: number = void 0; + + constructor( + protected readonly outputChannelIdentifier: IOutputChannelIdentifier, + @IModelService private modelService: IModelService, + @IModeService private modeService: IModeService + ) { + super(); + + this.id = outputChannelIdentifier.id; + this.label = outputChannelIdentifier.label; + + this.modelUpdater = new RunOnceScheduler(() => this.updateModel(), 300); + this._register(toDisposable(() => this.modelUpdater.cancel())); + + this.bufferredContent = new BufferedContent(); + this._register(toDisposable(() => this.bufferredContent.clear())); + } + + append(output: string) { + this.bufferredContent.append(output); + if (!this.modelUpdater.isScheduled()) { + this.modelUpdater.schedule(); + } + } + + clear(): void { + if (this.modelUpdater.isScheduled()) { + this.modelUpdater.cancel(); + } + if (this.model) { + this.model.setValue(''); + } + this.bufferredContent.clear(); + this.lastReadId = void 0; + } + + loadModel(): TPromise { + const { value, id } = this.bufferredContent.getDelta(this.lastReadId); + if (this.model) { + this.model.setValue(value); + } else { + this.model = this.createModel(value); + } + this.lastReadId = id; + return TPromise.as(this.model); + } + + private createModel(content: string): IModel { + const model = this.modelService.createModel(content, this.modeService.getOrCreateMode(OUTPUT_MIME), URI.from({ scheme: OUTPUT_SCHEME, path: this.id })); + const disposables: IDisposable[] = []; + disposables.push(model.onWillDispose(() => { + this.model = null; + dispose(disposables); + })); + return model; + } + + private updateModel(): void { + if (this.model) { + const { value, id } = this.bufferredContent.getDelta(this.lastReadId); + this.lastReadId = id; + const lastLine = this.model.getLineCount(); + const lastLineMaxColumn = this.model.getLineMaxColumn(lastLine); + this.model.applyEdits([EditOperation.insert(new Position(lastLine, lastLineMaxColumn), value)]); + this._onDidAppendedContent.fire(); + } + } + + dispose(): void { + this._onDispose.fire(); + super.dispose(); + } +} + +class BufferedContent { + + private data: string[] = []; + private dataIds: number[] = []; + private idPool = 0; + private length = 0; + + public append(content: string): void { + this.data.push(content); + this.dataIds.push(++this.idPool); + this.length += content.length; + this.trim(); + } + + public clear(): void { + this.data.length = 0; + this.dataIds.length = 0; + this.length = 0; + } + + private trim(): void { + if (this.length < MAX_OUTPUT_LENGTH * 1.2) { + return; + } + + while (this.length > MAX_OUTPUT_LENGTH) { + this.dataIds.shift(); + const removed = this.data.shift(); + this.length -= removed.length; + } + } + + public getDelta(previousId?: number): { value: string, id: number } { + let idx = -1; + if (previousId !== void 0) { + idx = binarySearch(this.dataIds, previousId, (a, b) => a - b); + } + + const id = this.idPool; + if (idx >= 0) { + const value = strings.removeAnsiEscapeCodes(this.data.slice(idx + 1).join('')); + return { value, id }; + } else { + const value = strings.removeAnsiEscapeCodes(this.data.join('')); + return { value, id }; + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/output/test/bufferedContent.test.ts b/src/vs/workbench/parts/output/test/bufferedContent.test.ts deleted file mode 100644 index 29f0dc5141a..00000000000 --- a/src/vs/workbench/parts/output/test/bufferedContent.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import { BufferedContent } from 'vs/workbench/parts/output/browser/outputServices'; - -suite('Workbench - Output Buffered Content', () => { - - test('Buffered Content - Simple', () => { - const bufferedContent = new BufferedContent(); - bufferedContent.append('first'); - bufferedContent.append('second'); - bufferedContent.append('third'); - const delta = bufferedContent.getDelta(); - assert.equal(bufferedContent.getDelta().value, 'firstsecondthird'); - bufferedContent.clear(); - assert.equal(bufferedContent.getDelta().value, ''); - assert.equal(bufferedContent.getDelta(delta).value, ''); - }); - - test('Buffered Content - Appending Output', () => { - const bufferedContent = new BufferedContent(); - bufferedContent.append('first'); - const firstDelta = bufferedContent.getDelta(); - bufferedContent.append('second'); - bufferedContent.append('third'); - const secondDelta = bufferedContent.getDelta(firstDelta); - assert.equal(secondDelta.append, true); - assert.equal(secondDelta.value, 'secondthird'); - bufferedContent.append('fourth'); - bufferedContent.append('fifth'); - assert.equal(bufferedContent.getDelta(firstDelta).value, 'secondthirdfourthfifth'); - assert.equal(bufferedContent.getDelta(secondDelta).value, 'fourthfifth'); - }); - - test('Buffered Content - Lots of Output', function () { - this.timeout(10000); - const bufferedContent = new BufferedContent(); - bufferedContent.append('first line'); - const firstDelta = bufferedContent.getDelta(); - let longString = ''; - for (let i = 0; i < 5000; i++) { - bufferedContent.append(i.toString()); - longString += i.toString(); - } - const secondDelta = bufferedContent.getDelta(firstDelta); - assert.equal(secondDelta.append, true); - assert.equal(secondDelta.value.substr(secondDelta.value.length - 4), '4999'); - longString = longString + longString + longString + longString; - bufferedContent.append(longString); - bufferedContent.append(longString); - const thirdDelta = bufferedContent.getDelta(firstDelta); - assert.equal(!!thirdDelta.append, true); - assert.equal(thirdDelta.value.substr(thirdDelta.value.length - 4), '4999'); - - bufferedContent.clear(); - assert.equal(bufferedContent.getDelta().value, ''); - }); -}); diff --git a/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts index 242c7c0e1c8..e8108e6af6c 100644 --- a/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/parts/performance/electron-browser/startupProfiler.ts @@ -59,36 +59,37 @@ class StartupProfiler implements IWorkbenchContribution { }).then(files => { const profileFiles = files.reduce((prev, cur) => `${prev}${join(dir, cur)}\n`, '\n'); - const primaryButton = this._messageService.confirm({ + return this._messageService.confirm({ type: 'info', message: localize('prof.message', "Successfully created profiles."), detail: localize('prof.detail', "Please create an issue and manually attach the following files:\n{0}", profileFiles), primaryButton: localize('prof.restartAndFileIssue', "Create Issue and Restart"), secondaryButton: localize('prof.restart', "Restart") - }); - - if (primaryButton) { - const action = this._instantiationService.createInstance(ReportPerformanceIssueAction, ReportPerformanceIssueAction.ID, ReportPerformanceIssueAction.LABEL); - TPromise.join([ - this._windowsService.showItemInFolder(join(dir, files[0])), - action.run(`:warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}`) - ]).then(() => { - // keep window stable until restart is selected - this._messageService.confirm({ - type: 'info', - message: localize('prof.thanks', "Thanks for helping us."), - detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong), - primaryButton: localize('prof.restart', "Restart"), - secondaryButton: null + }).then(primaryButton => { + if (primaryButton) { + const action = this._instantiationService.createInstance(ReportPerformanceIssueAction, ReportPerformanceIssueAction.ID, ReportPerformanceIssueAction.LABEL); + TPromise.join([ + this._windowsService.showItemInFolder(join(dir, files[0])), + action.run(`:warning: Make sure to **attach** these files from your *home*-directory: :warning:\n${files.map(file => `-\`${file}\``).join('\n')}`) + ]).then(() => { + // keep window stable until restart is selected + return this._messageService.confirm({ + type: 'info', + message: localize('prof.thanks', "Thanks for helping us."), + detail: localize('prof.detail.restart', "A final restart is required to continue to use '{0}'. Again, thank you for your contribution.", this._environmentService.appNameLong), + primaryButton: localize('prof.restart', "Restart"), + secondaryButton: null + }).then(() => { + // now we are ready to restart + this._windowsService.relaunch({ removeArgs }); + }); }); - // now we are ready to restart - this._windowsService.relaunch({ removeArgs }); - }); - } else { - // simply restart - this._windowsService.relaunch({ removeArgs }); - } + } else { + // simply restart + this._windowsService.relaunch({ removeArgs }); + } + }); }); } } diff --git a/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts b/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts index c6ab6b76c58..0d636ad01ed 100644 --- a/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts +++ b/src/vs/workbench/parts/preferences/browser/keybindingsEditorContribution.ts @@ -356,7 +356,7 @@ export class KeybindingEditorDecorationsRenderer extends Disposable { class DefineKeybindingCommand extends EditorCommand { - static ID = 'editor.action.defineKeybinding'; + static readonly ID = 'editor.action.defineKeybinding'; constructor() { super({ diff --git a/src/vs/workbench/parts/preferences/browser/media/preferences.css b/src/vs/workbench/parts/preferences/browser/media/preferences.css index 4faa43a06fc..04251e95105 100644 --- a/src/vs/workbench/parts/preferences/browser/media/preferences.css +++ b/src/vs/workbench/parts/preferences/browser/media/preferences.css @@ -48,6 +48,10 @@ text-overflow: ellipsis; } +.settings-tabs-widget > .monaco-action-bar .action-item .action-label:not([aria-haspopup]) { + display: block; +} + .settings-tabs-widget > .monaco-action-bar .actions-container { justify-content: flex-start; } @@ -309,4 +313,4 @@ .vs-dark .title-actions .action-item .icon.collapseAll, .vs-dark .editor-actions .action-item .icon.collapseAll { background: url('collapseAll_inverse.svg') center center no-repeat; -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts index e2c9bd976f7..0399b803ed0 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesActions.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesActions.ts @@ -14,7 +14,7 @@ import { IQuickOpenService, IPickOpenEntry, IFilePickOpenEntry } from 'vs/platfo import { IPreferencesService } from 'vs/workbench/parts/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 } from 'vs/workbench/browser/actions/workspaceActions'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; export class OpenRawDefaultSettingsAction extends Action { @@ -121,10 +121,11 @@ export class OpenWorkspaceSettingsAction extends Action { } export const OPEN_FOLDER_SETTINGS_COMMAND = '_workbench.action.openFolderSettings'; +export const OPEN_FOLDER_SETTINGS_LABEL = nls.localize('openFolderSettings', "Open Folder Settings"); export class OpenFolderSettingsAction extends Action { public static readonly ID = 'workbench.action.openFolderSettings'; - public static readonly LABEL = nls.localize('openFolderSettings', "Open Folder Settings"); + public static readonly LABEL = OPEN_FOLDER_SETTINGS_LABEL; private disposables: IDisposable[] = []; @@ -146,10 +147,10 @@ export class OpenFolderSettingsAction extends Action { } public run(): TPromise { - return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND) + return this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID) .then(workspaceFolder => { if (workspaceFolder) { - return this.commandService.executeCommand(OPEN_FOLDER_SETTINGS_COMMAND, workspaceFolder); + return this.commandService.executeCommand(OPEN_FOLDER_SETTINGS_COMMAND, workspaceFolder.uri); } return null; }); @@ -208,4 +209,4 @@ export class ConfigureLanguageBasedSettingsAction extends Action { }); } -} \ No newline at end of file +} diff --git a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts index 60ea35dc725..6420118a688 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesRenderers.ts @@ -28,7 +28,7 @@ import { IMarkerService, IMarkerData } from 'vs/platform/markers/common/markers' import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { overrideIdentifierFromKey, IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; diff --git a/src/vs/workbench/parts/preferences/common/preferencesModels.ts b/src/vs/workbench/parts/preferences/common/preferencesModels.ts index d4ec0090dce..463df86d951 100644 --- a/src/vs/workbench/parts/preferences/common/preferencesModels.ts +++ b/src/vs/workbench/parts/preferences/common/preferencesModels.ts @@ -401,6 +401,10 @@ export class DefaultSettings extends Disposable { return DefaultSettings._RAW; } + getSettingByName(name: string): ISetting { + return this._settingsByName && this._settingsByName.get(name); + } + private getRegisteredGroups(): ISettingsGroup[] { const configurations = Registry.as(Extensions.Configuration).getConfigurations().slice(); return this.removeEmptySettingsGroups(configurations.sort(this.compareConfigurationNodes) @@ -557,7 +561,6 @@ export class DefaultSettings extends Disposable { export class DefaultSettingsEditorModel extends AbstractSettingsModel implements ISettingsEditorModel { private _model: IModel; - private _settingsByName: Map; private _onDidChangeGroups: Emitter = this._register(new Emitter()); readonly onDidChangeGroups: Event = this._onDidChangeGroups.event; @@ -573,8 +576,6 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements this._register(defaultSettings.onDidChange(() => this._onDidChangeGroups.fire())); this._model = reference.object.textEditorModel; this._register(this.onDispose(() => reference.dispose())); - - this.initAllSettingsMap(); } public get uri(): URI { @@ -649,20 +650,9 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements return null; } - private initAllSettingsMap(): void { - this._settingsByName = new Map(); - for (const group of this.settingsGroups) { - for (const section of group.sections) { - for (const setting of section.settings) { - this._settingsByName.set(setting.key, setting); - } - } - } - } - private getMostRelevantSettings(rankedSettingNames: string[]): ISettingsGroup { const settings = rankedSettingNames.map(key => { - const setting = this._settingsByName.get(key); + const setting = this.defaultSettings.getSettingByName(key); if (setting) { return { description: setting.description, 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 c448009c688..24b637b2cea 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferences.contribution.ts @@ -28,7 +28,6 @@ import { PreferencesContribution } from 'vs/workbench/parts/preferences/common/p import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IEditorRegistry, EditorDescriptor, Extensions as EditorExtensions } from 'vs/workbench/browser/editor'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { PreferencesSearchService } from 'vs/workbench/parts/preferences/electron-browser/preferencesSearch'; @@ -263,7 +262,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution(PreferencesContribution, LifecyclePhase.Starting); -CommandsRegistry.registerCommand(OPEN_FOLDER_SETTINGS_COMMAND, function (accessor: ServicesAccessor, args?: IWorkspaceFolder) { +CommandsRegistry.registerCommand(OPEN_FOLDER_SETTINGS_COMMAND, function (accessor: ServicesAccessor, resource: URI) { const preferencesService = accessor.get(IPreferencesService); - return preferencesService.openFolderSettings(args.uri); -}); \ No newline at end of file + return preferencesService.openFolderSettings(resource); +}); diff --git a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts index 149102c0a87..f99d62999fd 100644 --- a/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts +++ b/src/vs/workbench/parts/preferences/electron-browser/preferencesSearch.ts @@ -99,12 +99,14 @@ export class PreferencesSearchModel implements IPreferencesSearchModel { return this._remoteProvider.filterPreferences(preferencesModel).then(null, err => { const message = errors.getErrorMessage(err); - /* __GDPR__ - "defaultSettings.searchError" : { - "message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.telemetryService.publicLog('defaultSettings.searchError', { message }); + if (message.toLowerCase() !== 'canceled') { + /* __GDPR__ + "defaultSettings.searchError" : { + "message": { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + this.telemetryService.publicLog('defaultSettings.searchError', { message }); + } return this._localProvider.filterPreferences(preferencesModel); }); @@ -154,7 +156,8 @@ class RemoteSearchProvider { let sortedNames = Object.keys(remoteResult.scoredResults).sort((a, b) => remoteResult.scoredResults[b] - remoteResult.scoredResults[a]); if (sortedNames.length) { const highScore = remoteResult.scoredResults[sortedNames[0]]; - sortedNames = sortedNames.filter(name => remoteResult.scoredResults[name] >= highScore / 2); + const minScore = highScore / 5; + sortedNames = sortedNames.filter(name => remoteResult.scoredResults[name] >= minScore); } const settingMatcher = this.getRemoteSettingMatcher(sortedNames, preferencesModel); diff --git a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts index 6b499c85130..bcfefc9f74f 100644 --- a/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/viewPickerHandler.ts @@ -161,7 +161,7 @@ export class ViewPickerHandler extends QuickOpenHandler { const channels = this.outputService.getChannels(); channels.forEach((channel, index) => { const outputCategory = nls.localize('channels', "Output"); - const entry = new ViewEntry(channel.label, outputCategory, () => this.outputService.getChannel(channel.id).show().done(null, errors.onUnexpectedError)); + const entry = new ViewEntry(channel.label, outputCategory, () => this.outputService.showChannel(channel.id).done(null, errors.onUnexpectedError)); viewEntries.push(entry); }); diff --git a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts index 0c923fea0fb..cf097396b36 100644 --- a/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts +++ b/src/vs/workbench/parts/relauncher/electron-browser/relauncher.contribution.ts @@ -24,6 +24,7 @@ import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; interface IConfiguration extends IWindowsConfiguration { update: { channel: string; }; telemetry: { enableCrashReporter: boolean }; + keyboard: { touchbar: { enabled: boolean } }; } export class SettingsChangeRelauncher implements IWorkbenchContribution { @@ -34,6 +35,7 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution { private nativeTabs: boolean; private updateChannel: string; private enableCrashReporter: boolean; + private touchbarEnabled: boolean; private firstFolderResource: URI; private extensionHostRestarter: RunOnceScheduler; @@ -91,6 +93,12 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution { changed = true; } + // Touchbar config + if (config.keyboard && typeof config.keyboard.touchbar.enabled === 'boolean' && config.keyboard.touchbar.enabled !== this.touchbarEnabled) { + this.touchbarEnabled = config.keyboard.touchbar.enabled; + changed = true; + } + // Notify only when changed and we are the focused window (avoids notification spam across windows) if (notify && changed) { this.doConfirm( @@ -138,17 +146,19 @@ export class SettingsChangeRelauncher implements IWorkbenchContribution { private doConfirm(message: string, detail: string, primaryButton: string, confirmed: () => void): void { this.windowService.isFocused().then(focused => { if (focused) { - const confirm = this.messageService.confirm({ + return this.messageService.confirm({ type: 'info', message, detail, primaryButton + }).then(confirm => { + if (confirm) { + confirmed(); + } }); - - if (confirm) { - confirmed(); - } } + + return void 0; }); } diff --git a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts index fb28a9e4c3f..302c958fb0a 100644 --- a/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/parts/scm/electron-browser/dirtydiffDecorator.ts @@ -19,10 +19,11 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import URI from 'vs/base/common/uri'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { ISCMService, ISCMRepository } from 'vs/workbench/services/scm/common/scm'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { registerThemingParticipant, ITheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService'; import { registerColor } from 'vs/platform/theme/common/colorRegistry'; import { localize } from 'vs/nls'; @@ -45,7 +46,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { basename } from 'vs/base/common/paths'; import { MenuId, IMenuService, IMenu, MenuItemAction } from 'vs/platform/actions/common/actions'; import { fillInActions, MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem'; -import { IChange, IEditorModel, ScrollType, IEditorContribution, OverviewRulerLane, IModel } from 'vs/editor/common/editorCommon'; +import { IChange, IEditorModel, ScrollType, IEditorContribution, OverviewRulerLane, IModel, IModelDecorationOptions } from 'vs/editor/common/editorCommon'; import { sortedDiff, firstIndex } from 'vs/base/common/arrays'; import { IMarginData } from 'vs/editor/browser/controller/mouseTarget'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; @@ -728,43 +729,46 @@ export const overviewRulerDeletedForeground = registerColor('editorOverviewRuler class DirtyDiffDecorator { - static MODIFIED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-modified', - isWholeLine: true, - overviewRuler: { - color: themeColorFromId(overviewRulerModifiedForeground), - darkColor: themeColorFromId(overviewRulerModifiedForeground), - position: OverviewRulerLane.Left - } - }); + static createDecoration(className: string, foregroundColor: string, options: { gutter: boolean, overview: boolean }): ModelDecorationOptions { + const decorationOptions: IModelDecorationOptions = { + isWholeLine: true, + }; - static ADDED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-added', - isWholeLine: true, - overviewRuler: { - color: themeColorFromId(overviewRulerAddedForeground), - darkColor: themeColorFromId(overviewRulerAddedForeground), - position: OverviewRulerLane.Left + if (options.gutter) { + decorationOptions.linesDecorationsClassName = `dirty-diff-glyph ${className}`; } - }); - static DELETED_DECORATION_OPTIONS = ModelDecorationOptions.register({ - linesDecorationsClassName: 'dirty-diff-glyph dirty-diff-deleted', - isWholeLine: true, - overviewRuler: { - color: themeColorFromId(overviewRulerDeletedForeground), - darkColor: themeColorFromId(overviewRulerDeletedForeground), - position: OverviewRulerLane.Left + if (options.overview) { + decorationOptions.overviewRuler = { + color: themeColorFromId(overviewRulerModifiedForeground), + darkColor: themeColorFromId(overviewRulerModifiedForeground), + position: OverviewRulerLane.Left + }; } - }); + return ModelDecorationOptions.createDynamic(decorationOptions); + } + + private modifiedOptions: ModelDecorationOptions; + private addedOptions: ModelDecorationOptions; + private deletedOptions: ModelDecorationOptions; private decorations: string[] = []; private disposables: IDisposable[] = []; constructor( private editorModel: IModel, - private model: DirtyDiffModel + private model: DirtyDiffModel, + @IConfigurationService configurationService: IConfigurationService ) { + const decorations = configurationService.getValue('scm.diffDecorations'); + const gutter = decorations === 'all' || decorations === 'gutter'; + const overview = decorations === 'all' || decorations === 'overview'; + const options = { gutter, overview }; + + this.modifiedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-modified', overviewRulerModifiedForeground, options); + this.addedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-added', overviewRulerAddedForeground, options); + this.deletedOptions = DirtyDiffDecorator.createDecoration('dirty-diff-deleted', overviewRulerDeletedForeground, options); + model.onDidChange(this.onDidChange, this, this.disposables); } @@ -781,7 +785,7 @@ class DirtyDiffDecorator { startLineNumber: startLineNumber, startColumn: 1, endLineNumber: endLineNumber, endColumn: 1 }, - options: DirtyDiffDecorator.ADDED_DECORATION_OPTIONS + options: this.addedOptions }; case ChangeType.Delete: return { @@ -789,7 +793,7 @@ class DirtyDiffDecorator { startLineNumber: startLineNumber, startColumn: 1, endLineNumber: startLineNumber, endColumn: 1 }, - options: DirtyDiffDecorator.DELETED_DECORATION_OPTIONS + options: this.deletedOptions }; case ChangeType.Modify: return { @@ -797,7 +801,7 @@ class DirtyDiffDecorator { startLineNumber: startLineNumber, startColumn: 1, endLineNumber: endLineNumber, endColumn: 1 }, - options: DirtyDiffDecorator.MODIFIED_DECORATION_OPTIONS + options: this.modifiedOptions }; } }); @@ -862,7 +866,8 @@ export class DirtyDiffModel { private _editorModel: IModel, @ISCMService private scmService: ISCMService, @IEditorWorkerService private editorWorkerService: IEditorWorkerService, - @ITextModelService private textModelResolverService: ITextModelService + @ITextModelService private textModelResolverService: ITextModelService, + @IConfigurationService private configurationService: IConfigurationService ) { this.diffDelayer = new ThrottledDelayer(200); @@ -923,7 +928,9 @@ export class DirtyDiffModel { return TPromise.as([]); // Files too large } - return this.editorWorkerService.computeDirtyDiff(originalURI, this._editorModel.uri, true); + const ignoreTrimWhitespace = this.configurationService.getValue('diffEditor.ignoreTrimWhitespace'); + + return this.editorWorkerService.computeDirtyDiff(originalURI, this._editorModel.uri, ignoreTrimWhitespace); }); } @@ -999,23 +1006,59 @@ class DirtyDiffItem { export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, IModelRegistry { + private enabled = false; private models: IModel[] = []; private items: { [modelId: string]: DirtyDiffItem; } = Object.create(null); + private transientDisposables: IDisposable[] = []; private disposables: IDisposable[] = []; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, - @IInstantiationService private instantiationService: IInstantiationService + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IInstantiationService private instantiationService: IInstantiationService, + @IConfigurationService private configurationService: IConfigurationService ) { - this.disposables.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + const onDidChangeConfiguration = filterEvent(configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.diffDecorations')); + onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables); + this.onDidChangeConfiguration(); } - private onEditorsChanged(): void { - // HACK: This is the best current way of figuring out whether to draw these decorations - // or not. Needs context from the editor, to know whether it is a diff editor, in place editor - // etc. + private onDidChangeConfiguration(): void { + const enabled = this.configurationService.getValue('scm.diffDecorations') !== 'none'; + if (enabled) { + this.enable(); + } else { + this.disable(); + } + } + + private enable(): void { + if (this.enabled) { + this.disable(); + } + + this.transientDisposables.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + this.onEditorsChanged(); + this.enabled = true; + } + + private disable(): void { + if (!this.enabled) { + return; + } + + this.transientDisposables = dispose(this.transientDisposables); + this.models.forEach(m => this.items[m.id].dispose()); + this.models = []; + this.items = Object.create(null); + this.enabled = false; + } + + // HACK: This is the best current way of figuring out whether to draw these decorations + // or not. Needs context from the editor, to know whether it is a diff editor, in place editor + // etc. + private onEditorsChanged(): void { const models = this.editorService.getVisibleEditors() // map to the editor controls @@ -1046,7 +1089,7 @@ export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, private onModelVisible(editorModel: IModel): void { const model = this.instantiationService.createInstance(DirtyDiffModel, editorModel); - const decorator = new DirtyDiffDecorator(editorModel, model); + const decorator = new DirtyDiffDecorator(editorModel, model, this.configurationService); this.items[editorModel.id] = new DirtyDiffItem(model, decorator); } @@ -1067,11 +1110,8 @@ export class DirtyDiffWorkbenchController implements ext.IWorkbenchContribution, } dispose(): void { + this.disable(); this.disposables = dispose(this.disposables); - this.models.forEach(m => this.items[m.id].dispose()); - - this.models = null; - this.items = null; } } diff --git a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css index fa1b724f9fc..f6e3ec0c18a 100644 --- a/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css +++ b/src/vs/workbench/parts/scm/electron-browser/media/scmViewlet.css @@ -25,6 +25,7 @@ .scm-viewlet .scm-status { height: 100%; + position: relative; } .scm-viewlet .monaco-list-row > .scm-provider { 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 2dc55faba6d..c8079172f45 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scm.contribution.ts @@ -19,10 +19,11 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { StatusUpdater, StatusBarController } from './scmActivity'; import { SCMViewlet } from 'vs/workbench/parts/scm/electron-browser/scmViewlet'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; class OpenSCMViewletAction extends ToggleViewletAction { - static ID = VIEWLET_ID; + static readonly ID = VIEWLET_ID; static LABEL = localize('toggleGitViewlet', "Show Git"); constructor(id: string, label: string, @IViewletService viewletService: IViewletService, @IWorkbenchEditorService editorService: IWorkbenchEditorService) { @@ -61,3 +62,29 @@ Registry.as(WorkbenchActionExtensions.WorkbenchActions 'View: Show SCM', localize('view', "View") ); + +Registry.as(ConfigurationExtensions.Configuration).registerConfiguration({ + id: 'scm', + order: 5, + title: localize('scmConfigurationTitle', "SCM"), + type: 'object', + properties: { + 'scm.alwaysShowProviders': { + type: 'boolean', + description: localize('alwaysShowProviders', "Whether to always show the Source Control Provider section."), + default: false + }, + 'scm.diffDecorations': { + type: 'string', + enum: ['all', 'gutter', 'overview', 'none'], + default: 'all', + description: localize('diffDecorations', "Controls diff decorations in the editor.") + }, + 'scm.inputCounter': { + type: 'string', + enum: ['always', 'warn', 'off'], + default: 'warn', + description: localize('inputCounter', "Controls when to display the input counter.") + } + } +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts index dd70573afa9..18c4877eae2 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmMenus.ts @@ -88,7 +88,7 @@ export class SCMMenus implements IDisposable { const primary: IAction[] = []; const secondary: IAction[] = []; const result = { primary, secondary }; - fillInActions(menu, { shouldForwardArgs: true }, result, g => g === 'inline'); + fillInActions(menu, { shouldForwardArgs: true }, result, g => /^inline/.test(g)); menu.dispose(); contextKeyService.dispose(); diff --git a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts index 4530cff6727..84b6588527b 100644 --- a/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts +++ b/src/vs/workbench/parts/scm/electron-browser/scmViewlet.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/scmViewlet'; import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; -import Event, { Emitter, chain, mapEvent } from 'vs/base/common/event'; +import Event, { Emitter, chain, mapEvent, anyEvent, filterEvent } from 'vs/base/common/event'; import { domEvent, stop } from 'vs/base/browser/event'; import { basename } from 'vs/base/common/paths'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -45,7 +45,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IStorageService } from 'vs/platform/storage/common/storage'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions'; -import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; +import { IMessage, InputBox, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { Command } from 'vs/editor/common/modes'; @@ -56,6 +56,7 @@ import { format } from 'vs/base/common/strings'; import { ISpliceable, ISequence, ISplice } from 'vs/base/common/sequence'; import { firstIndex } from 'vs/base/common/arrays'; import { WorkbenchList, IListService } from 'vs/platform/list/browser/listService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; // TODO@Joao // Need to subclass MenuItemActionItem in order to respect @@ -695,7 +696,8 @@ export class RepositoryPanel extends ViewletPanel { @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IEditorGroupService protected editorGroupService: IEditorGroupService, @IContextKeyService protected contextKeyService: IContextKeyService, - @IInstantiationService protected instantiationService: IInstantiationService + @IInstantiationService protected instantiationService: IInstantiationService, + @IConfigurationService protected configurationService: IConfigurationService ) { super(repository.provider.label, {}, keybindingService, contextMenuService); this.menus = instantiationService.createInstance(SCMMenus, repository.provider); @@ -753,10 +755,63 @@ export class RepositoryPanel extends ViewletPanel { this.inputBox.setPlaceHolder(placeholder); }; - this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { flexibleHeight: true }); + const validation = (text: string): IMessage => { + const setting = this.configurationService.getValue<'always' | 'warn' | 'off'>('scm.inputCounter'); + + if (setting === 'off') { + return null; + } + + let position = this.inputBox.inputElement.selectionStart; + let start = 0, end; + let match: RegExpExecArray; + const regex = /\r?\n/g; + + while ((match = regex.exec(text)) && position > match.index) { + start = match.index + match[0].length; + } + + end = match ? match.index : text.length; + + const line = text.substring(start, end); + + const lineWarningLength = this.repository.input.lineWarningLength; + + if (lineWarningLength === undefined) { + return { + content: localize('commitMessageInfo', "{0} characters in current line", text.length), + type: MessageType.INFO + }; + } + + if (line.length <= lineWarningLength) { + if (setting !== 'always') { + return null; + } + + return { + content: localize('commitMessageCountdown', "{0} characters left in current line", lineWarningLength - line.length), + type: MessageType.INFO + }; + } else { + return { + content: localize('commitMessageWarning', "{0} characters over {1} in current line", line.length - lineWarningLength, lineWarningLength), + type: MessageType.WARNING + }; + } + }; + + this.inputBox = new InputBox(this.inputBoxContainer, this.contextViewService, { + flexibleHeight: true, + validationOptions: { validation: validation } + }); this.disposables.push(attachInputBoxStyler(this.inputBox, this.themeService)); this.disposables.push(this.inputBox); + const onKeyUp = domEvent(this.inputBox.inputElement, 'keyup'); + const onMouseUp = domEvent(this.inputBox.inputElement, 'mouseup'); + anyEvent(onKeyUp, onMouseUp)(() => this.inputBox.validate(), null, this.disposables); + this.inputBox.value = this.repository.input.value; this.inputBox.onDidChange(value => this.repository.input.value = value, null, this.disposables); this.repository.input.onDidChange(value => this.inputBox.value = value, null, this.disposables); @@ -980,7 +1035,8 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IStorageService storageService: IStorageService, - @IExtensionService extensionService: IExtensionService + @IExtensionService extensionService: IExtensionService, + @IConfigurationService private configurationService: IConfigurationService ) { super(VIEWLET_ID, { showHeaderInTitleWhenSingleView: true }, telemetryService, themeService); @@ -999,6 +1055,10 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables); this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables); this.scmService.repositories.forEach(r => this.onDidAddRepository(r)); + + const onDidUpdateConfiguration = filterEvent(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowProviders')); + onDidUpdateConfiguration(this.onDidChangeRepositories, this, this.disposables); + this.onDidChangeRepositories(); } @@ -1032,7 +1092,8 @@ export class SCMViewlet extends PanelViewlet implements IViewModel { private onDidChangeRepositories(): void { toggleClass(this.el, 'empty', this.scmService.repositories.length === 0); - const shouldMainPanelBeVisible = this.scmService.repositories.length > 1; + const shouldMainPanelAlwaysBeVisible = this.configurationService.getValue('scm.alwaysShowProviders'); + const shouldMainPanelBeVisible = shouldMainPanelAlwaysBeVisible || this.scmService.repositories.length > 1; if (!!this.mainPanel === shouldMainPanelBeVisible) { return; diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index 3a85e381b07..04e8cad5150 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -88,8 +88,8 @@ export class FileEntry extends EditorQuickOpenEntry { this.range = range; } - public isFile(): boolean { - return true; // TODO@Ben debt with editor history merging + public mergeWithEditorHistory(): boolean { + return true; } public getInput(): IResourceInput | EditorInput { diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index 5641193f2b1..95a1fec354b 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -242,7 +242,7 @@ export class SearchRenderer extends Disposable implements IRenderer { private renderFolderMatch(tree: ITree, folderMatch: FolderMatch, templateData: IFolderMatchTemplate): void { if (folderMatch.hasRoot()) { - templateData.label.setFile(folderMatch.resource(), { fileKind: FileKind.ROOT_FOLDER }); + templateData.label.setFile(folderMatch.resource(), { fileKind: FileKind.FOLDER }); } else { templateData.label.setValue(nls.localize('searchFolderMatch.other.label', "Other files")); } diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 329d78f48fb..510ecc9faa6 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -398,18 +398,20 @@ export class SearchViewlet extends Viewlet { type: 'question' }; - if (this.messageService.confirm(confirmation)) { - this.searchWidget.setReplaceAllActionState(false); - this.viewModel.searchResult.replaceAll(progressRunner).then(() => { - progressRunner.done(); - this.clearMessage() - .p({ text: afterReplaceAllMessage }); - }, (error) => { - progressRunner.done(); - errors.isPromiseCanceledError(error); - this.messageService.show(Severity.Error, error); - }); - } + this.messageService.confirm(confirmation).then(confirmed => { + if (confirmed) { + this.searchWidget.setReplaceAllActionState(false); + this.viewModel.searchResult.replaceAll(progressRunner).then(() => { + progressRunner.done(); + this.clearMessage() + .p({ text: afterReplaceAllMessage }); + }, (error) => { + progressRunner.done(); + errors.isPromiseCanceledError(error); + this.messageService.show(Severity.Error, error); + }); + } + }); } private buildAfterReplaceAllMessage(occurrences: number, fileCount: number, replaceValue?: string) { @@ -670,15 +672,17 @@ export class SearchViewlet extends Viewlet { public focus(): void { super.focus(); + let updatedText = false; const seedSearchStringFromSelection = this.configurationService.getValue('editor').find.seedSearchStringFromSelection; if (seedSearchStringFromSelection) { const selectedText = this.getSearchTextFromEditor(); if (selectedText) { this.searchWidget.searchInput.setValue(selectedText); + updatedText = true; } } - this.searchWidget.focus(); + this.searchWidget.focus(undefined, undefined, updatedText); } public focusNextInputBox(): void { @@ -1059,7 +1063,7 @@ export class SearchViewlet extends Viewlet { this.showEmptyStage(); let isDone = false; - const outputChannel = this.outputService.getChannel('search'); + const outputChannel = this.outputService.getChannel(Constants.SEARCH_OUTPUT_CHANNEL_ID); let onComplete = (completed?: ISearchComplete) => { if (query.useRipgrep) { outputChannel.append('\n'); diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 1231f3f464c..7536317df8a 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -29,6 +29,9 @@ import { attachInputBoxStyler, attachFindInputBoxStyler, attachButtonStyler } fr import { IThemeService } from 'vs/platform/theme/common/themeService'; import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/findModel'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; export interface ISearchWidgetOptions { value?: string; @@ -92,6 +95,8 @@ export class SearchWidget extends Widget { private replaceActionBar: ActionBar; private searchHistory: HistoryNavigator; + private ignoreGlobalFindBufferOnNextFocus = false; + private previousGlobalFindBufferValue: string; private _onSearchSubmit = this._register(new Emitter()); public onSearchSubmit: Event = this._onSearchSubmit.event; @@ -118,6 +123,8 @@ export class SearchWidget extends Widget { @IThemeService private themeService: IThemeService, @IContextKeyService private keyBindingService: IContextKeyService, @IKeybindingService private keyBindingService2: IKeybindingService, + @IClipboardService private clipboardServce: IClipboardService, + @IConfigurationService private configurationService: IConfigurationService ) { super(); this.searchHistory = new HistoryNavigator(options.history); @@ -127,7 +134,9 @@ export class SearchWidget extends Widget { this.render(container, options); } - public focus(select: boolean = true, focusReplace: boolean = false): void { + public focus(select: boolean = true, focusReplace: boolean = false, suppressGlobalSearchBuffer = false): void { + this.ignoreGlobalFindBufferOnNextFocus = suppressGlobalSearchBuffer; + if (focusReplace && this.isReplaceShown()) { this.replaceInput.focus(); if (select) { @@ -241,7 +250,22 @@ export class SearchWidget extends Widget { })); this.searchInputFocusTracker = this._register(dom.trackFocus(this.searchInput.inputBox.inputElement)); - this._register(this.searchInputFocusTracker.onDidFocus(() => this.searchInputBoxFocused.set(true))); + this._register(this.searchInputFocusTracker.onDidFocus(() => { + this.searchInputBoxFocused.set(true); + + const useGlobalFindBuffer = this.configurationService.getValue('editor').find.globalFindClipboard; + if (!this.ignoreGlobalFindBufferOnNextFocus && useGlobalFindBuffer) { + const globalBufferText = this.clipboardServce.readFindText(); + if (this.previousGlobalFindBufferValue !== globalBufferText) { + this.searchInput.setValue(globalBufferText); + this.searchInput.select(); + } + + this.previousGlobalFindBufferValue = globalBufferText; + } + + this.ignoreGlobalFindBufferOnNextFocus = false; + })); this._register(this.searchInputFocusTracker.onDidBlur(() => this.searchInputBoxFocused.set(false))); } diff --git a/src/vs/workbench/parts/search/common/constants.ts b/src/vs/workbench/parts/search/common/constants.ts index d68fafac729..e15486d6ca5 100644 --- a/src/vs/workbench/parts/search/common/constants.ts +++ b/src/vs/workbench/parts/search/common/constants.ts @@ -6,6 +6,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; export const VIEWLET_ID = 'workbench.view.search'; +export const SEARCH_OUTPUT_CHANNEL_ID = 'search'; export const FindInFilesActionId = 'workbench.action.findInFiles'; export const FocusActiveEditorCommandId = 'search.action.focusActiveEditor'; diff --git a/src/vs/workbench/parts/search/common/queryBuilder.ts b/src/vs/workbench/parts/search/common/queryBuilder.ts index 5c8e0d65015..ee4da90d56b 100644 --- a/src/vs/workbench/parts/search/common/queryBuilder.ts +++ b/src/vs/workbench/parts/search/common/queryBuilder.ts @@ -10,11 +10,12 @@ import * as objects from 'vs/base/common/objects'; import * as collections from 'vs/base/common/collections'; import * as glob from 'vs/base/common/glob'; import * as paths from 'vs/base/common/paths'; -import * as strings from 'vs/base/common/strings'; import uri from 'vs/base/common/uri'; +import { untildify } from 'vs/base/common/labels'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IPatternInfo, IQueryOptions, IFolderQuery, ISearchQuery, QueryType, ISearchConfiguration, getExcludes, pathIncludedInQuery } from 'vs/platform/search/common/search'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; export interface ISearchPathPattern { searchPath: uri; @@ -30,8 +31,9 @@ export class QueryBuilder { constructor( @IConfigurationService private configurationService: IConfigurationService, - @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService) { - } + @IWorkspaceContextService private workspaceContextService: IWorkspaceContextService, + @IEnvironmentService private environmentService: IEnvironmentService + ) { } public text(contentPattern: IPatternInfo, folderResources?: uri[], options?: IQueryOptions): ISearchQuery { return this.query(QueryType.Text, contentPattern, folderResources, options); @@ -101,11 +103,12 @@ export class QueryBuilder { */ public parseSearchPaths(pattern: string): ISearchPathsResult { const isSearchPath = (segment: string) => { - // A segment is a search path if it is an absolute path or starts with ./ - return paths.isAbsolute(segment) || strings.startsWith(segment, './') || strings.startsWith(segment, '.\\'); + // A segment is a search path if it is an absolute path or starts with ./, ../, .\, or ..\ + return paths.isAbsolute(segment) || /^\.\.?[\/\\]/.test(segment); }; - const segments = splitGlobPattern(pattern); + const segments = splitGlobPattern(pattern) + .map(segment => untildify(segment, this.environmentService.userHome)); const groups = collections.groupBy(segments, segment => isSearchPath(segment) ? 'searchPaths' : 'exprSegments'); diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index 75071492b86..17ab1b6121e 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -21,7 +21,7 @@ import { IInstantiationService, createDecorator } from 'vs/platform/instantiatio import { IModelService } from 'vs/editor/common/services/modelService'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import { IProgressRunner } from 'vs/platform/progress/common/progress'; -import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDecorations'; +import { ModelDecorationOptions } from 'vs/editor/common/model/model'; import { overviewRulerFindMatchForeground } from 'vs/platform/theme/common/colorRegistry'; import { themeColorFromId } from 'vs/platform/theme/common/themeService'; import { getBaseLabel } from 'vs/base/common/labels'; 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 7ec9eebcb5e..bbee04d3204 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -12,17 +12,14 @@ import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } f import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry'; import nls = require('vs/nls'); import { TPromise } from 'vs/base/common/winjs.base'; -import { IAction, Action } from 'vs/base/common/actions'; +import { Action } from 'vs/base/common/actions'; import * as objects from 'vs/base/common/objects'; -import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; -import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; -import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions'; +import { explorerItemToFileResource, ExplorerFolderContext, ExplorerRootContext } from 'vs/workbench/parts/files/common/files'; +import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { getSelectionSearchString } from 'vs/editor/contrib/find/findController'; @@ -30,7 +27,6 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { KeyMod, KeyCode } from 'vs/base/common/keyCodes'; import { ITree } from 'vs/base/parts/tree/browser/tree'; import * as searchActions from 'vs/workbench/parts/search/browser/searchActions'; -import { Model } from 'vs/workbench/parts/files/common/explorerModel'; import * as Constants from 'vs/workbench/parts/search/common/constants'; import { registerContributions as replaceContributions } from 'vs/workbench/parts/search/browser/replaceContributions'; import { registerContributions as searchWidgetContributions } from 'vs/workbench/parts/search/browser/searchWidget'; @@ -45,9 +41,12 @@ import { OpenSymbolHandler } from 'vs/workbench/parts/search/browser/openSymbolH import { OpenAnythingHandler } from 'vs/workbench/parts/search/browser/openAnythingHandler'; import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions'; import { getWorkspaceSymbols } from 'vs/workbench/parts/search/common/search'; -import { illegalArgument } from 'vs/base/common/errors'; -import { FindInFolderAction, findInFolderCommand, FindInWorkspaceAction } from 'vs/workbench/parts/search/electron-browser/searchActions'; -import { WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService'; +import { illegalArgument, onUnexpectedError } from 'vs/base/common/errors'; +import { WorkbenchListFocusContextKey, IListService } from 'vs/platform/list/browser/listService'; +import URI from 'vs/base/common/uri'; +import { relative } from 'path'; +import { dirname } from 'vs/base/common/resources'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; registerSingleton(ISearchWorkbenchService, SearchWorkbenchService); replaceContributions(); @@ -183,60 +182,68 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -CommandsRegistry.registerCommand(FindInFolderAction.ID, findInFolderCommand); +const FIND_IN_FOLDER_ID = 'filesExplorer.findInFolder'; +CommandsRegistry.registerCommand({ + id: FIND_IN_FOLDER_ID, + handler: (accessor, resource?: URI) => { + const listService = accessor.get(IListService); + const viewletService = accessor.get(IViewletService); -class ExplorerViewerActionContributor extends ActionBarContributor { - private _instantiationService: IInstantiationService; - - constructor( @IInstantiationService instantiationService: IInstantiationService, @IWorkspaceContextService contextService: IWorkspaceContextService) { - super(); - - this._instantiationService = instantiationService; - } - - public hasSecondaryActions(context: any): boolean { - let element = context.element; - - // Contribute only on file resources and model (context menu for multi root) - if (element instanceof Model) { - return true; - } - - let fileResource = explorerItemToFileResource(element); - if (!fileResource) { - return false; - } - - return fileResource.isDirectory && fileResource.resource.scheme === 'file'; - } - - public getSecondaryActions(context: any): IAction[] { - let actions: IAction[] = []; - - if (this.hasSecondaryActions(context)) { - let action: Action; - if (context.element instanceof Model) { - action = this._instantiationService.createInstance(FindInWorkspaceAction); - } else { - let fileResource = explorerItemToFileResource(context.element); - action = this._instantiationService.createInstance(FindInFolderAction, fileResource.resource); + if (!URI.isUri(resource)) { + const lastFocusedList = listService.lastFocusedList; + const focus = lastFocusedList ? lastFocusedList.getFocus() : void 0; + if (focus) { + const file = explorerItemToFileResource(focus); + if (file) { + resource = file.isDirectory ? file.resource : dirname(file.resource); + } } - - action.order = 55; - actions.push(action); - - actions.push(new Separator('', 56)); } - return actions; + viewletService.openViewlet(Constants.VIEWLET_ID, true).then(viewlet => { + if (resource) { + (viewlet as SearchViewlet).searchInFolder(resource, (from, to) => relative(from, to)); + } + }).done(null, onUnexpectedError); } -} +}); + +const FIND_IN_WORKSPACE_ID = 'filesExplorer.findInWorkspace'; +CommandsRegistry.registerCommand({ + id: FIND_IN_WORKSPACE_ID, + handler: (accessor, ) => { + const viewletService = accessor.get(IViewletService); + return viewletService.openViewlet(Constants.VIEWLET_ID, true).then(viewlet => { + (viewlet as SearchViewlet).searchInFolder(null, (from, to) => relative(from, to)); + }); + } +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 50, + command: { + id: FIND_IN_FOLDER_ID, + title: nls.localize('findInFolder', "Find in Folder...") + }, + when: ContextKeyExpr.and(ExplorerFolderContext, ResourceContextKey.Scheme.isEqualTo('file')) +}); + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: { + id: FIND_IN_WORKSPACE_ID, + title: nls.localize('findInWorkspace', "Find in Workspace...") + }, + when: ContextKeyExpr.and(ExplorerRootContext, ExplorerFolderContext.toNegated()) +}); -const ACTION_ID = 'workbench.action.showAllSymbols'; -const ACTION_LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace..."); -const ALL_SYMBOLS_PREFIX = '#'; class ShowAllSymbolsAction extends Action { + static readonly ID = 'workbench.action.showAllSymbols'; + static readonly LABEL = nls.localize('showTriggerActions', "Go to Symbol in Workspace..."); + static readonly ALL_SYMBOLS_PREFIX = '#'; constructor( actionId: string, actionLabel: string, @@ -248,7 +255,7 @@ class ShowAllSymbolsAction extends Action { public run(context?: any): TPromise { - let prefix = ALL_SYMBOLS_PREFIX; + let prefix = ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX; let inputSelection: { start: number; end: number; } = void 0; let editor = this.editorService.getFocusedCodeEditor(); const word = editor && getSelectionSearchString(editor); @@ -325,11 +332,10 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.ShowPrev registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.ShowNextSearchExcludeAction, searchActions.ShowNextSearchExcludeAction.ID, searchActions.ShowNextSearchExcludeAction.LABEL, ShowNextFindTermKeybinding, searchActions.ShowNextSearchExcludeAction.CONTEXT_KEY_EXPRESSION), 'Search: Show Next Search Exclude Pattern', category); registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.ShowPreviousSearchExcludeAction, searchActions.ShowPreviousSearchExcludeAction.ID, searchActions.ShowPreviousSearchExcludeAction.LABEL, ShowPreviousFindTermKeybinding, searchActions.ShowPreviousSearchExcludeAction.CONTEXT_KEY_EXPRESSION), 'Search: Show Previous Search Exclude Pattern', category); -registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ACTION_ID, ACTION_LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); +registry.registerWorkbenchAction(new SyncActionDescriptor(searchActions.CollapseDeepestExpandedLevelAction, searchActions.CollapseDeepestExpandedLevelAction.ID, searchActions.CollapseDeepestExpandedLevelAction.LABEL), 'Search: Collapse All', category); + +registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction, ShowAllSymbolsAction.ID, ShowAllSymbolsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_T }), 'Go to Symbol in Workspace...'); -// Contribute to Explorer Viewer -const actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); -actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActionContributor); // Register Quick Open Handler Registry.as(QuickOpenExtensions.Quickopen).registerDefaultQuickOpenHandler( @@ -346,11 +352,11 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen new QuickOpenHandlerDescriptor( OpenSymbolHandler, OpenSymbolHandler.ID, - ALL_SYMBOLS_PREFIX, + ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, 'inWorkspaceSymbolsPicker', [ { - prefix: ALL_SYMBOLS_PREFIX, + prefix: ShowAllSymbolsAction.ALL_SYMBOLS_PREFIX, needsEditor: false, description: nls.localize('openSymbolDescriptionNormal', "Go to Symbol in Workspace") } @@ -360,7 +366,7 @@ Registry.as(QuickOpenExtensions.Quickopen).registerQuickOpen // Search output channel const outputChannelRegistry = Registry.as(OutputExt.OutputChannels); -outputChannelRegistry.registerChannel('search', nls.localize('searchOutputChannelTitle', "Search")); +outputChannelRegistry.registerChannel(Constants.SEARCH_OUTPUT_CHANNEL_ID, nls.localize('searchOutputChannelTitle', "Search")); // Configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); diff --git a/src/vs/workbench/parts/search/electron-browser/searchActions.ts b/src/vs/workbench/parts/search/electron-browser/searchActions.ts deleted file mode 100644 index ad6bc5404c4..00000000000 --- a/src/vs/workbench/parts/search/electron-browser/searchActions.ts +++ /dev/null @@ -1,72 +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 nls = require('vs/nls'); -import errors = require('vs/base/common/errors'); -import resources = require('vs/base/common/resources'); -import { TPromise } from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; -import { Action } from 'vs/base/common/actions'; -import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; -import { SearchViewlet } from 'vs/workbench/parts/search/browser/searchViewlet'; -import * as Constants from 'vs/workbench/parts/search/common/constants'; -import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IListService } from 'vs/platform/list/browser/listService'; -import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files'; -import { relative } from 'path'; - -export class FindInFolderAction extends Action { - - public static readonly ID = 'filesExplorer.findInFolder'; - - private resource: URI; - - constructor(resource: URI, @IInstantiationService private instantiationService: IInstantiationService) { - super(FindInFolderAction.ID, nls.localize('findInFolder', "Find in Folder...")); - - this.resource = resource; - } - - public run(event?: any): TPromise { - return this.instantiationService.invokeFunction.apply(this.instantiationService, [findInFolderCommand, this.resource]); - } -} - -export const findInFolderCommand = (accessor: ServicesAccessor, resource?: URI) => { - const listService = accessor.get(IListService); - const viewletService = accessor.get(IViewletService); - - if (!URI.isUri(resource)) { - const lastFocusedList = listService.lastFocusedList; - const focus = lastFocusedList ? lastFocusedList.getFocus() : void 0; - if (focus) { - const file = explorerItemToFileResource(focus); - if (file) { - resource = file.isDirectory ? file.resource : resources.dirname(file.resource); - } - } - } - - viewletService.openViewlet(Constants.VIEWLET_ID, true).then(viewlet => { - if (resource) { - (viewlet as SearchViewlet).searchInFolder(resource, (from, to) => relative(from, to)); - } - }).done(null, errors.onUnexpectedError); -}; - -export class FindInWorkspaceAction extends Action { - - public static readonly ID = 'filesExplorer.findInWorkspace'; - - constructor( @IViewletService private viewletService: IViewletService) { - super(FindInWorkspaceAction.ID, nls.localize('findInWorkspace', "Find in Workspace...")); - } - - public run(event?: any): TPromise { - return this.viewletService.openViewlet(Constants.VIEWLET_ID, true).then(viewlet => { - (viewlet as SearchViewlet).searchInFolder(null, (from, to) => relative(from, to)); - }); - } -} \ 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 ac6d1e3d763..0530c08b20f 100644 --- a/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts +++ b/src/vs/workbench/parts/search/test/common/queryBuilder.test.ts @@ -14,7 +14,9 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IWorkspaceContextService, Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { QueryBuilder, ISearchPathsResult } from 'vs/workbench/parts/search/common/queryBuilder'; -import { TestContextService } from 'vs/workbench/test/workbenchTestServices'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { TestContextService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices'; + import { ISearchQuery, QueryType, IPatternInfo, IFolderQuery } from 'vs/platform/search/common/search'; @@ -44,6 +46,8 @@ suite('QueryBuilder', () => { mockContextService.setWorkspace(mockWorkspace); instantiationService.stub(IWorkspaceContextService, mockContextService); + instantiationService.stub(IEnvironmentService, TestEnvironmentService); + queryBuilder = instantiationService.createInstance(QueryBuilder); }); @@ -473,6 +477,14 @@ suite('QueryBuilder', () => { }] } ], + [ + '../', + { + searchPaths: [{ + searchPath: getUri('foo/') + }] + } + ] ]; cases.forEach(testIncludesDataItem); }); diff --git a/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts b/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts index 47a5caff9bf..d2d0305b4dc 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/insertSnippet.ts @@ -81,6 +81,7 @@ class InsertSnippetAction extends EditorAction { undefined, undefined, undefined, + undefined, snippet, undefined )); diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts index fa0e7cd8a3a..e296217693a 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippets.contribution.ts @@ -20,7 +20,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { LanguageId } from 'vs/editor/common/modes'; import { TPromise } from 'vs/base/common/winjs.base'; import { SnippetParser, Variable, Placeholder, Text } from 'vs/editor/contrib/snippet/snippetParser'; -import { EditorSnippetVariableResolver } from 'vs/editor/contrib/snippet/snippetVariables'; +import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/snippetVariables'; export const ISnippetsService = createDecorator('snippetService'); @@ -40,6 +40,7 @@ export class Snippet { private _isBogous: boolean; constructor( + readonly scopes: string[], readonly name: string, readonly prefix: string, readonly description: string, @@ -107,7 +108,7 @@ export class Snippet { if ( marker instanceof Variable && marker.children.length === 0 - && !EditorSnippetVariableResolver.VariableNames[marker.name] + && !KnownSnippetVariableNames[marker.name] ) { // a 'variable' without a default value and not being one of our supported // variables is automatically turned into a placeholder. This is to restore diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts index 3ee4e82d60b..8b65020fcdd 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsFile.ts @@ -9,9 +9,13 @@ import { readFile } from 'vs/base/node/pfs'; import { parse as jsonParse } from 'vs/base/common/json'; import { forEach } from 'vs/base/common/collections'; import { Snippet } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; +import { endsWith } from 'vs/base/common/strings'; +import { basename } from 'path'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; interface JsonSerializedSnippet { body: string; + scope: string; prefix: string | string[]; description: string; } @@ -26,13 +30,35 @@ interface JsonSerializedSnippets { export class SnippetFile { - private constructor( + constructor( readonly filepath: string, readonly data: Snippet[] ) { // } + select(selector: string, bucket: Snippet[]): void { + for (const snippet of this.data) { + if (isFalsyOrEmpty(snippet.scopes)) { + // always accept + bucket.push(snippet); + } else { + // match + for (const scope of snippet.scopes) { + if (scope === selector) { + bucket.push(snippet); + break; // match only once! + } + } + } + } + + let idx = selector.lastIndexOf('.'); + if (idx >= 0) { + this.select(selector.substring(0, idx), bucket); + } + } + static fromFile(filepath: string, source: string, isFromExtension?: boolean): Promise { return Promise.resolve(readFile(filepath)).then(value => { const data = jsonParse(value.toString()); @@ -41,11 +67,11 @@ export class SnippetFile { forEach(data, entry => { const { key: name, value: scopeOrTemplate } = entry; if (isJsonSerilziedSnippet(scopeOrTemplate)) { - SnippetFile._parseSnippet(name, scopeOrTemplate, source, isFromExtension, snippets); + SnippetFile._parseSnippet(filepath, name, scopeOrTemplate, source, isFromExtension, snippets); } else { forEach(scopeOrTemplate, entry => { const { key: name, value: template } = entry; - SnippetFile._parseSnippet(name, template, source, isFromExtension, snippets); + SnippetFile._parseSnippet(filepath, name, template, source, isFromExtension, snippets); }); } }); @@ -54,7 +80,7 @@ export class SnippetFile { }); } - private static _parseSnippet(name: string, snippet: JsonSerializedSnippet, source: string, isFromExtension: boolean, bucket: Snippet[]): void { + private static _parseSnippet(filepath: string, name: string, snippet: JsonSerializedSnippet, source: string, isFromExtension: boolean, bucket: Snippet[]): void { let { prefix, body, description } = snippet; @@ -66,7 +92,17 @@ export class SnippetFile { return; } + let scopes: string[]; + if (endsWith(filepath, '.json')) { + scopes = [basename(filepath, '.json')]; + } else if (typeof snippet.scope === 'string') { + scopes = snippet.scope.split(','); + } else { + scopes = []; + } + bucket.push(new Snippet( + scopes, name, prefix, description, diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts index 004b30292b8..65615189a2b 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsService.ts @@ -378,8 +378,6 @@ export class SnippetSuggestProvider implements ISuggestSupport { } return languageId; } - - } export function getNonWhitespacePrefix(model: ISimpleModel, position: Position): string { diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts b/src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts new file mode 100644 index 00000000000..1a208ec715f --- /dev/null +++ b/src/vs/workbench/parts/snippets/test/electron-browser/snippetFile.test.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import { Snippet } from 'vs/workbench/parts/snippets/electron-browser/snippets.contribution'; +import { SnippetFile } from 'vs/workbench/parts/snippets/electron-browser/snippetsFile'; + +suite('Snippets', function () { + + test('SnippetFile#select', function () { + let file = new SnippetFile('somepath/foo.json', []); + let bucket: Snippet[] = []; + file.select('', bucket); + assert.equal(bucket.length, 0); + + file = new SnippetFile('somepath/foo.json', [ + new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test'), + new Snippet(['foo'], 'FooSnippet2', 'foo', '', 'snippet', 'test'), + new Snippet(['bar'], 'BarSnippet1', 'foo', '', 'snippet', 'test'), + new Snippet(['bar.comment'], 'BarSnippet2', 'foo', '', 'snippet', 'test'), + new Snippet(['bar.strings'], 'BarSnippet2', 'foo', '', 'snippet', 'test'), + new Snippet(['bazz', 'bazz'], 'BazzSnippet1', 'foo', '', 'snippet', 'test'), + ]); + + bucket = []; + file.select('foo', bucket); + assert.equal(bucket.length, 2); + + bucket = []; + file.select('fo', bucket); + assert.equal(bucket.length, 0); + + bucket = []; + file.select('bar', bucket); + assert.equal(bucket.length, 1); + + bucket = []; + file.select('bar.comment', bucket); + assert.equal(bucket.length, 2); + + bucket = []; + file.select('bazz', bucket); + assert.equal(bucket.length, 1); + }); + + test('SnippetFile#select - any scope', function () { + + let file = new SnippetFile('somepath/foo.json', [ + new Snippet([], 'AnySnippet1', 'foo', '', 'snippet', 'test'), + new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test'), + ]); + + let bucket: Snippet[] = []; + file.select('foo', bucket); + assert.equal(bucket.length, 2); + + }); + +}); diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts index ab171d24f38..0ecb2906d6b 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts +++ b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsRewrite.test.ts @@ -45,7 +45,7 @@ suite('SnippetRewrite', function () { }); test('lazy bogous variable rewrite', function () { - const snippet = new Snippet('foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source'); + const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source'); assert.equal(snippet.body, 'This is ${bogous} because it is a ${var}'); assert.equal(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}'); assert.equal(snippet.isBogous, true); diff --git a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts index 6093d376ea2..6ea4413c7c8 100644 --- a/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts +++ b/src/vs/workbench/parts/snippets/test/electron-browser/snippetsService.test.ts @@ -40,12 +40,14 @@ suite('SnippetsService', function () { setup(function () { modeService = new ModeServiceImpl(); snippetService = new SimpleSnippetService([new Snippet( + ['fooLang'], 'barTest', 'bar', '', 'barCodeSnippet', '' ), new Snippet( + ['fooLang'], 'bazzTest', 'bazz', '', @@ -81,6 +83,7 @@ suite('SnippetsService', function () { test('Cannot use " { - let success = true; - let code: number = undefined; - for (let response of responses) { - success = success && response.success; - // We only have a code in the old output runner which only has one task - // So we can use the first code. - if (code === void 0 && response.code !== void 0) { - code = response.code; - } - } - if (success) { - this._taskSystem = null; - this.disposeTaskSystemListeners(); - return false; // no veto - } else if (code && code === TerminateResponseCode.ProcessNotFound) { - return !this.messageService.confirm({ - message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'), - primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"), - type: 'info' - }); - } - return true; // veto - }, (err) => { - return true; // veto - }); + + let terminatePromise: TPromise; + if (this._taskSystem.canAutoTerminate()) { + terminatePromise = TPromise.wrap(true); } else { - return true; // veto + terminatePromise = this.messageService.confirm({ + message: nls.localize('TaskSystem.runningTask', 'There is a task running. Do you want to terminate it?'), + primaryButton: nls.localize({ key: 'TaskSystem.terminateTask', comment: ['&& denotes a mnemonic'] }, "&&Terminate Task"), + type: 'question' + }); } + + return terminatePromise.then(terminate => { + if (terminate) { + return this._taskSystem.terminateAll().then((responses) => { + let success = true; + let code: number = undefined; + for (let response of responses) { + success = success && response.success; + // We only have a code in the old output runner which only has one task + // So we can use the first code. + if (code === void 0 && response.code !== void 0) { + code = response.code; + } + } + if (success) { + this._taskSystem = null; + this.disposeTaskSystemListeners(); + return false; // no veto + } else if (code && code === TerminateResponseCode.ProcessNotFound) { + return this.messageService.confirm({ + message: nls.localize('TaskSystem.noProcess', 'The launched task doesn\'t exist anymore. If the task spawned background processes exiting VS Code might result in orphaned processes. To avoid this start the last background process with a wait flag.'), + primaryButton: nls.localize({ key: 'TaskSystem.exitAnyways', comment: ['&& denotes a mnemonic'] }, "&&Exit Anyways"), + type: 'info' + }).then(confirmed => !confirmed); + } + return true; // veto + }, (err) => { + return true; // veto + }); + } + + return true; // veto + }); } private getConfigureAction(code: TaskErrors): Action { @@ -1738,7 +1748,7 @@ class TaskService implements ITaskService { this.messageService.show(Severity.Error, nls.localize('TaskSystem.unknownError', 'An error has occurred while running a task. See task log for details.')); } if (showOutput) { - this._outputChannel.show(true); + this.outputService.showChannel(this._outputChannel.id, true); } } @@ -2348,6 +2358,10 @@ MenuRegistry.addCommand({ id: 'workbench.action.tasks.configureDefaultTestTask', // MenuRegistry.addCommand( { id: 'workbench.action.tasks.rebuild', title: nls.localize('RebuildAction.label', 'Run Rebuild Task'), category: tasksCategory }); // MenuRegistry.addCommand( { id: 'workbench.action.tasks.clean', title: nls.localize('CleanAction.label', 'Run Clean Task'), category: tasksCategory }); +// Tasks Output channel. Register it before using it in Task Service. +let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); +outputChannelRegistry.registerChannel(TaskService.OutputChannelId, TaskService.OutputChannelLabel); + // Task Service registerSingleton(ITaskService, TaskService); @@ -2373,10 +2387,6 @@ let statusbarRegistry = Registry.as(StatusbarExtensions.Stat statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(BuildStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); statusbarRegistry.registerStatusbarItem(new StatusbarItemDescriptor(TaskStatusBarItem, StatusbarAlignment.LEFT, 50 /* Medium Priority */)); -// Output channel -let outputChannelRegistry = Registry.as(OutputExt.OutputChannels); -outputChannelRegistry.registerChannel(TaskService.OutputChannelId, TaskService.OutputChannelLabel); - // tasks.json validation let schemaId = 'vscode://schemas/tasks'; let schema: IJSONSchema = { diff --git a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts index 55f852198c0..e6c5d2bc254 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/terminalTaskSystem.ts @@ -88,7 +88,7 @@ export class TerminalTaskSystem implements ITaskSystem { } protected showOutput(): void { - this.outputChannel.show(true); + this.outputService.showChannel(this.outputChannel.id, true); } public run(task: Task, resolver: ITaskResolver, trigger: string = Triggers.command): ITaskExecuteResult { diff --git a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts index 0d96212034e..70321d0cc64 100644 --- a/src/vs/workbench/parts/tasks/node/processTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processTaskSystem.ts @@ -417,7 +417,7 @@ export class ProcessTaskSystem implements ITaskSystem { } private showOutput(): void { - this.outputChannel.show(true); + this.outputService.showChannel(this.outputChannel.id, true); } private clearOutput(): void { diff --git a/src/vs/workbench/parts/terminal/common/terminal.ts b/src/vs/workbench/parts/terminal/common/terminal.ts index 68add6effcc..cd837dd9be5 100644 --- a/src/vs/workbench/parts/terminal/common/terminal.ts +++ b/src/vs/workbench/parts/terminal/common/terminal.ts @@ -71,11 +71,13 @@ export interface ITerminalConfiguration { commandsToSkipShell: string[]; cwd: string; confirmOnExit: boolean; + enableBell: boolean; env: { linux: { [key: string]: string }; osx: { [key: string]: string }; windows: { [key: string]: string }; }; + showExitAlert: boolean; } export interface ITerminalConfigHelper { @@ -335,6 +337,12 @@ export interface ITerminalInstance { */ updateConfig(): void; + /** + * Updates the accessibility support state of the terminal instance. + * @param isEnabled Whether it's enabled. + */ + updateAccessibilitySupport(isEnabled: boolean): void; + /** * Configure the dimensions of the terminal instance. * @@ -349,16 +357,6 @@ export interface ITerminalInstance { */ setVisible(visible: boolean): void; - /** - * Attach a listener to the data stream from the terminal's pty process. - * - * @param listener The listener function which takes the processes' data stream (including - * ANSI escape sequences). - * - * @deprecated onLineData will replace this. - */ - onData(listener: (data: string) => void): IDisposable; - /** * Attach a listener to listen for new lines added to this terminal instance. * diff --git a/src/vs/workbench/parts/terminal/common/terminalService.ts b/src/vs/workbench/parts/terminal/common/terminalService.ts index 55e392cb988..4683d7a6250 100644 --- a/src/vs/workbench/parts/terminal/common/terminalService.ts +++ b/src/vs/workbench/parts/terminal/common/terminalService.ts @@ -12,6 +12,7 @@ import { IPartService } from 'vs/workbench/services/part/common/partService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ITerminalService, ITerminalInstance, IShellLaunchConfig, ITerminalConfigHelper, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE, TERMINAL_PANEL_ID } from 'vs/workbench/parts/terminal/common/terminal'; import { TPromise } from 'vs/base/common/winjs.base'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; export abstract class TerminalService implements ITerminalService { public _serviceBrand: any; @@ -63,14 +64,18 @@ export abstract class TerminalService implements ITerminalService { if (e.affectsConfiguration('terminal.integrated')) { this.updateConfig(); } + if (e.affectsConfiguration('editor.accessibilitySupport')) { + this.updateAccessibilitySupport(); + } }); lifecycleService.onWillShutdown(event => event.veto(this._onWillShutdown())); + lifecycleService.onShutdown(() => this._onShutdown()); this._terminalFocusContextKey = KEYBINDING_CONTEXT_TERMINAL_FOCUS.bindTo(this._contextKeyService); this._findWidgetVisible = KEYBINDING_CONTEXT_TERMINAL_FIND_WIDGET_VISIBLE.bindTo(this._contextKeyService); this.onInstanceDisposed((terminalInstance) => { this._removeInstance(terminalInstance); }); } - protected abstract _showTerminalCloseConfirmation(): boolean; + protected abstract _showTerminalCloseConfirmation(): TPromise; public abstract createInstance(shell?: IShellLaunchConfig, wasNewTerminalAction?: boolean): ITerminalInstance; public abstract getActiveOrCreateInstance(wasNewTerminalAction?: boolean): ITerminalInstance; public abstract selectDefaultWindowsShell(): TPromise; @@ -89,12 +94,15 @@ export abstract class TerminalService implements ITerminalService { } } - // Dispose all terminal instances and don't veto this._isShuttingDown = true; + + return false; + } + + private _onShutdown(): void { this.terminalInstances.forEach(instance => { instance.dispose(); }); - return false; } public getInstanceLabels(): string[] { @@ -236,6 +244,11 @@ export abstract class TerminalService implements ITerminalService { this.terminalInstances.forEach(instance => instance.updateConfig()); } + public updateAccessibilitySupport(): void { + const isEnabled = this._configurationService.getValue('editor').accessibilitySupport === 'on'; + this.terminalInstances.forEach(instance => instance.updateAccessibilitySupport(isEnabled)); + } + public setWorkspaceShellAllowed(isAllowed: boolean): void { this.configHelper.setWorkspaceShellAllowed(isAllowed); } diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css b/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css index 0bff305aae4..70af76ab868 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/scrollbar.css @@ -26,6 +26,7 @@ .monaco-workbench .panel.integrated-terminal .xterm:focus .xterm-viewport, .monaco-workbench .panel.integrated-terminal .xterm:hover .xterm-viewport { transition: opacity 100ms linear; + cursor: default; } .monaco-workbench .panel.integrated-terminal .xterm .xterm-viewport::-webkit-scrollbar-thumb:hover { diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css index 31206060c2b..aadbf436fc4 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/xterm.css @@ -126,3 +126,24 @@ .xterm:not(.enable-mouse-events) { cursor: text; } + +.xterm .accessibility { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 100; +} + +.xterm .accessibility-tree { + color: transparent; +} + +.xterm .live-region { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} 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 822ef798261..e4b726a6c83 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -18,7 +18,7 @@ import { TERMINAL_DEFAULT_SHELL_UNIX_LIKE, TERMINAL_DEFAULT_SHELL_WINDOWS } from import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; +import { KillTerminalAction, CopyTerminalSelectionAction, CreateNewTerminalAction, CreateNewInActiveWorkspaceTerminalAction, FocusActiveTerminalAction, FocusNextTerminalAction, FocusPreviousTerminalAction, SelectDefaultShellWindowsTerminalAction, RunSelectedTextInTerminalAction, RunActiveFileInTerminalAction, ScrollDownTerminalAction, ScrollDownPageTerminalAction, ScrollToBottomTerminalAction, ScrollUpTerminalAction, ScrollUpPageTerminalAction, ScrollToTopTerminalAction, TerminalPasteAction, ToggleTerminalAction, ClearTerminalAction, AllowWorkspaceShellTerminalCommand, DisallowWorkspaceShellTerminalCommand, RenameTerminalAction, SelectAllTerminalAction, FocusTerminalFindWidgetAction, HideTerminalFindWidgetAction, ShowNextFindTermTerminalFindWidgetAction, ShowPreviousFindTermTerminalFindWidgetAction, DeleteWordLeftTerminalAction, DeleteWordRightTerminalAction, QuickOpenActionTermContributor, QuickOpenTermAction, TERMINAL_PICKER_PREFIX } from 'vs/workbench/parts/terminal/electron-browser/terminalActions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ShowAllCommandsAction } from 'vs/workbench/parts/quickopen/browser/commandsHandler'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; @@ -120,6 +120,11 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': TERMINAL_DEFAULT_RIGHT_CLICK_COPY_PASTE }, + 'terminal.integrated.copyOnSelection': { + 'description': nls.localize('terminal.integrated.copyOnSelection', "When set, text selected in the terminal will be copied to the clipboard."), + 'type': 'boolean', + 'default': false + }, 'terminal.integrated.fontFamily': { 'description': nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to editor.fontFamily's value."), 'type': 'string' @@ -175,6 +180,11 @@ configurationRegistry.registerConfiguration({ 'type': 'boolean', 'default': false }, + 'terminal.integrated.enableBell': { + 'description': nls.localize('terminal.integrated.enableBell', "Whether the terminal bell is enabled on not."), + 'type': 'boolean', + 'default': false + }, 'terminal.integrated.commandsToSkipShell': { 'description': nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open."), 'type': 'array', @@ -188,6 +198,7 @@ configurationRegistry.registerConfiguration({ QUICKOPEN_FOCUS_SECONDARY_ACTION_ID, ShowAllCommandsAction.ID, CreateNewTerminalAction.ID, + CreateNewInActiveWorkspaceTerminalAction.ID, CopyTerminalSelectionAction.ID, KillTerminalAction.ID, FocusActiveTerminalAction.ID, @@ -226,6 +237,9 @@ configurationRegistry.registerConfiguration({ debugActions.RestartAction.ID, debugActions.ContinueAction.ID, debugActions.PauseAction.ID, + debugActions.StepIntoAction.ID, + debugActions.StepOutAction.ID, + debugActions.StepOverAction.ID, OpenNextRecentlyUsedEditorInGroupAction.ID, OpenPreviousRecentlyUsedEditorInGroupAction.ID, FocusFirstGroupAction.ID, @@ -260,7 +274,12 @@ configurationRegistry.registerConfiguration({ '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"), 'type': 'object', 'default': {} - } + }, + 'terminal.integrated.showExitAlert': { + 'description': nls.localize('terminal.integrated.showExitAlert', "Show alert `The terminal process terminated with exit code` when exit code is non-zero."), + 'type': 'boolean', + 'default': true + }, } }); @@ -287,6 +306,7 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTermina primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } }), 'Terminal: Create New Integrated Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewInActiveWorkspaceTerminalAction, CreateNewInActiveWorkspaceTerminalAction.ID, CreateNewInActiveWorkspaceTerminalAction.LABEL), 'Terminal: Create New Integrated Terminal (In Active Workspace)', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusActiveTerminalAction, FocusActiveTerminalAction.ID, FocusActiveTerminalAction.LABEL), 'Terminal: Focus Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index 9297b40c9bd..8a941327053 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -17,13 +17,14 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen'; +import { IQuickOpenService, IPickOptions } from 'vs/platform/quickOpen/common/quickOpen'; import { ActionBarContributor } from 'vs/workbench/browser/actions'; import { TerminalEntry } from 'vs/workbench/parts/terminal/browser/terminalQuickOpen'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { ICommandService } from 'vs/platform/commands/common/commands'; -import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; export const TERMINAL_PICKER_PREFIX = 'term '; @@ -220,7 +221,10 @@ export class CreateNewTerminalAction extends Action { // single root instancePromise = TPromise.as(this.terminalService.createInstance(undefined, true)); } else { - instancePromise = this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND).then(workspace => { + const options: IPickOptions = { + placeHolder: nls.localize('workbench.action.terminal.newWorkspacePlaceholder', "Select current working directory for new terminal") + }; + instancePromise = this.commandService.executeCommand(PICK_WORKSPACE_FOLDER_COMMAND_ID, [options]).then(workspace => { if (!workspace) { // Don't create the instance if the workspace picker was canceled return null; @@ -239,6 +243,28 @@ export class CreateNewTerminalAction extends Action { } } +export class CreateNewInActiveWorkspaceTerminalAction extends Action { + + public static readonly ID = 'workbench.action.terminal.newInActiveWorkspace'; + public static readonly LABEL = nls.localize('workbench.action.terminal.newInActiveWorkspace', "Create New Integrated Terminal (In Active Workspace)"); + + constructor( + id: string, label: string, + @ITerminalService private terminalService: ITerminalService + ) { + super(id, label); + } + + public run(event?: any): TPromise { + const instance = this.terminalService.createInstance(undefined, true); + if (!instance) { + return TPromise.as(void 0); + } + this.terminalService.setActiveInstance(instance); + return this.terminalService.showPanel(true); + } +} + export class FocusActiveTerminalAction extends Action { public static readonly ID = 'workbench.action.terminal.focus'; @@ -430,9 +456,10 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { constructor( action: IAction, @ITerminalService private terminalService: ITerminalService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IContextViewService contextViewService: IContextViewService ) { - super(null, action, terminalService.getInstanceLabels(), terminalService.activeTerminalInstanceIndex); + super(null, action, terminalService.getInstanceLabels(), terminalService.activeTerminalInstanceIndex, contextViewService); this.toDispose.push(terminalService.onInstancesChanged(this._updateItems, this)); this.toDispose.push(terminalService.onActiveInstanceChanged(this._updateItems, this)); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalColorRegistry.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalColorRegistry.ts index d80978e1489..58e125b9f86 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalColorRegistry.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalColorRegistry.ts @@ -162,7 +162,7 @@ export function registerColors(): void { for (let id in ansiColorMap) { let entry = ansiColorMap[id]; let colorName = id.substring(13); - ansiColorIdentifiers[entry.index] = registerColor(id, entry.defaults, nls.localize('terminal.ansiColor', '\'{0}\' ansi color in the terminal.', colorName)); + ansiColorIdentifiers[entry.index] = registerColor(id, entry.defaults, nls.localize('terminal.ansiColor', '\'{0}\' ANSI color in the terminal.', colorName)); } } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 46cc64e1c2f..d9b982340fb 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -13,7 +13,7 @@ import * as dom from 'vs/base/browser/dom'; import Event, { Emitter } from 'vs/base/common/event'; import Uri from 'vs/base/common/uri'; import { WindowsShellHelper } from 'vs/workbench/parts/terminal/electron-browser/windowsShellHelper'; -import { Terminal as XTermTerminal } from 'xterm'; +import { Terminal as XTermTerminal } from 'vscode-xterm'; import { Dimension } from 'vs/base/browser/builder'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; @@ -35,14 +35,15 @@ import { IHistoryService } from 'vs/workbench/services/history/common/history'; import pkg from 'vs/platform/node/package'; import { ansiColorIdentifiers, TERMINAL_BACKGROUND_COLOR, TERMINAL_FOREGROUND_COLOR, TERMINAL_CURSOR_FOREGROUND_COLOR, TERMINAL_CURSOR_BACKGROUND_COLOR, TERMINAL_SELECTION_BACKGROUND_COLOR } from 'vs/workbench/parts/terminal/electron-browser/terminalColorRegistry'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; +import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; +import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; /** The amount of time to consider terminal errors to be related to the launch */ const LAUNCHING_DURATION = 500; -// Enable search functionality in xterm.js instance -XTermTerminal.loadAddon('search'); -// Enable the winpty compatibility addon which will simulate wraparound mode -XTermTerminal.loadAddon('winptyCompat'); +let Terminal: typeof XTermTerminal; enum ProcessState { // The process has not been initialized yet. @@ -97,6 +98,7 @@ export class TerminalInstance implements ITerminalInstance { private _initialCwd: string; private _windowsShellHelper: WindowsShellHelper; private _onLineDataListeners: ((lineData: string) => void)[]; + private _xtermReadyPromise: TPromise; private _widgetManager: TerminalWidgetManager; private _linkHandler: TerminalLinkHandler; @@ -123,7 +125,10 @@ export class TerminalInstance implements ITerminalInstance { @IInstantiationService private _instantiationService: IInstantiationService, @IClipboardService private _clipboardService: IClipboardService, @IHistoryService private _historyService: IHistoryService, - @IThemeService private _themeService: IThemeService + @IThemeService private _themeService: IThemeService, + @IConfigurationResolverService private _configurationResolverService: IConfigurationResolverService, + @IWorkspaceContextService private _workspaceContextService: IWorkspaceContextService, + @IConfigurationService private _configurationService: IConfigurationService ) { this._instanceDisposables = []; this._processDisposables = []; @@ -150,20 +155,22 @@ export class TerminalInstance implements ITerminalInstance { this._initDimensions(); this._createProcess(); - this._createXterm(); - if (platform.isWindows) { - this._processReady.then(() => { - if (!this._isDisposed) { - this._windowsShellHelper = new WindowsShellHelper(this._processId, this, this._xterm); - } - }); - } + this._xtermReadyPromise = this._createXterm(); + this._xtermReadyPromise.then(() => { + if (platform.isWindows) { + this._processReady.then(() => { + if (!this._isDisposed) { + this._windowsShellHelper = new WindowsShellHelper(this._processId, this, this._xterm); + } + }); + } - // Only attach xterm.js to the DOM if the terminal panel has been opened before. - if (_container) { - this.attachToElement(_container); - } + // Only attach xterm.js to the DOM if the terminal panel has been opened before. + if (_container) { + this.attachToElement(_container); + } + }); } public addDisposable(disposable: lifecycle.IDisposable): void { @@ -248,21 +255,31 @@ export class TerminalInstance implements ITerminalInstance { /** * Create xterm.js instance and attach data listeners. */ - protected _createXterm(): void { + protected async _createXterm(): TPromise { + if (!Terminal) { + Terminal = (await import('vscode-xterm')).Terminal; + // Enable search functionality in xterm.js instance + Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/search/search')); + // Enable the winpty compatibility addon which will simulate wraparound mode + Terminal.applyAddon(require.__$__nodeRequire('vscode-xterm/lib/addons/winptyCompat/winptyCompat')); + } + const accessibilitySupport = this._configurationService.getValue('editor').accessibilitySupport; const font = this._configHelper.getFont(true); - this._xterm = new XTermTerminal({ + this._xterm = new Terminal({ scrollback: this._configHelper.config.scrollback, theme: this._getXtermTheme(), fontFamily: font.fontFamily, fontSize: font.fontSize, lineHeight: font.lineHeight, - enableBold: this._configHelper.config.enableBold + enableBold: this._configHelper.config.enableBold, + bellStyle: this._configHelper.config.enableBell ? 'sound' : 'none', + screenReaderMode: accessibilitySupport === 'on' }); if (this._shellLaunchConfig.initialText) { this._xterm.writeln(this._shellLaunchConfig.initialText); } this._xterm.winptyCompatInit(); - this._xterm.on('lineFeed', () => this._onLineFeed()); + this._xterm.on('linefeed', () => this._onLineFeed()); this._process.on('message', (message) => this._sendPtyDataToXterm(message)); this._xterm.on('data', (data) => { if (this._processId) { @@ -284,100 +301,112 @@ export class TerminalInstance implements ITerminalInstance { } public attachToElement(container: HTMLElement): void { - if (this._wrapperElement) { - throw new Error('The terminal instance has already been attached to a container'); - } - - this._container = container; - this._wrapperElement = document.createElement('div'); - dom.addClass(this._wrapperElement, 'terminal-wrapper'); - this._xtermElement = document.createElement('div'); - - // Attach the xterm object to the DOM, exposing it to the smoke tests - (this._wrapperElement).xterm = this._xterm; - - this._xterm.open(this._xtermElement); - this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => { - // Disable all input if the terminal is exiting - if (this._isExiting) { - return false; + this._xtermReadyPromise.then(() => { + if (this._wrapperElement) { + throw new Error('The terminal instance has already been attached to a container'); } - // Skip processing by xterm.js of keyboard events that resolve to commands described - // within commandsToSkipShell - const standardKeyboardEvent = new StandardKeyboardEvent(event); - const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); - if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { - event.preventDefault(); - return false; - } + this._container = container; + this._wrapperElement = document.createElement('div'); + dom.addClass(this._wrapperElement, 'terminal-wrapper'); + this._xtermElement = document.createElement('div'); - // If tab focus mode is on, tab is not passed to the terminal - if (TabFocus.getTabFocusMode() && event.keyCode === 9) { - return false; - } + // Attach the xterm object to the DOM, exposing it to the smoke tests + (this._wrapperElement).xterm = this._xterm; - return undefined; + this._xterm.open(this._xtermElement); + this._xterm.attachCustomKeyEventHandler((event: KeyboardEvent) => { + // Disable all input if the terminal is exiting + if (this._isExiting) { + return false; + } + + // Skip processing by xterm.js of keyboard events that resolve to commands described + // within commandsToSkipShell + const standardKeyboardEvent = new StandardKeyboardEvent(event); + const resolveResult = this._keybindingService.softDispatch(standardKeyboardEvent, standardKeyboardEvent.target); + if (resolveResult && this._skipTerminalCommands.some(k => k === resolveResult.commandId)) { + event.preventDefault(); + return false; + } + + // If tab focus mode is on, tab is not passed to the terminal + if (TabFocus.getTabFocusMode() && event.keyCode === 9) { + return false; + } + // Always have alt+F4 skip the terminal on Windows and allow it to be handled by the + // system + if (platform.isWindows && event.altKey && event.key === 'F4' && !event.ctrlKey) { + return false; + } + + return undefined; + }); + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mousedown', (event: KeyboardEvent) => { + // We need to listen to the mouseup event on the document since the user may release + // the mouse button anywhere outside of _xterm.element. + const listener = dom.addDisposableListener(document, 'mouseup', (event: KeyboardEvent) => { + // Delay with a setTimeout to allow the mouseup to propagate through the DOM + // before evaluating the new selection state. + setTimeout(() => this._refreshSelectionContextKey(), 0); + listener.dispose(); + }); + })); + + // xterm.js currently drops selection on keyup as we need to handle this case. + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'keyup', (event: KeyboardEvent) => { + // Wait until keyup has propagated through the DOM before evaluating + // the new selection state. + setTimeout(() => this._refreshSelectionContextKey(), 0); + })); + + const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); + const focusTrap: HTMLElement = document.createElement('div'); + focusTrap.setAttribute('tabindex', '0'); + dom.addClass(focusTrap, 'focus-trap'); + this._instanceDisposables.push(dom.addDisposableListener(focusTrap, 'focus', (event: FocusEvent) => { + let currentElement = focusTrap; + while (!dom.hasClass(currentElement, 'part')) { + currentElement = currentElement.parentElement; + } + const hidePanelElement = currentElement.querySelector('.hide-panel-action'); + hidePanelElement.focus(); + })); + xtermHelper.insertBefore(focusTrap, this._xterm.textarea); + + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'focus', (event: KeyboardEvent) => { + this._terminalFocusContextKey.set(true); + })); + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'blur', (event: KeyboardEvent) => { + this._terminalFocusContextKey.reset(); + this._refreshSelectionContextKey(); + })); + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'focus', (event: KeyboardEvent) => { + this._terminalFocusContextKey.set(true); + })); + this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'blur', (event: KeyboardEvent) => { + this._terminalFocusContextKey.reset(); + this._refreshSelectionContextKey(); + })); + + this._wrapperElement.appendChild(this._xtermElement); + this._widgetManager = new TerminalWidgetManager(this._wrapperElement); + this._linkHandler.setWidgetManager(this._widgetManager); + this._container.appendChild(this._wrapperElement); + + const computedStyle = window.getComputedStyle(this._container); + const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); + const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); + this.layout(new Dimension(width, height)); + this.setVisible(this._isVisible); + this.updateConfig(); + + // If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal + // panel was initialized. + if (this._xterm.getOption('disableStdin')) { + this._attachPressAnyKeyToCloseListener(); + } }); - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'mouseup', (event: KeyboardEvent) => { - // Wait until mouseup has propagated through the DOM before - // evaluating the new selection state. - setTimeout(() => this._refreshSelectionContextKey(), 0); - })); - - // xterm.js currently drops selection on keyup as we need to handle this case. - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'keyup', (event: KeyboardEvent) => { - // Wait until keyup has propagated through the DOM before evaluating - // the new selection state. - setTimeout(() => this._refreshSelectionContextKey(), 0); - })); - - const xtermHelper: HTMLElement = this._xterm.element.querySelector('.xterm-helpers'); - const focusTrap: HTMLElement = document.createElement('div'); - focusTrap.setAttribute('tabindex', '0'); - dom.addClass(focusTrap, 'focus-trap'); - this._instanceDisposables.push(dom.addDisposableListener(focusTrap, 'focus', (event: FocusEvent) => { - let currentElement = focusTrap; - while (!dom.hasClass(currentElement, 'part')) { - currentElement = currentElement.parentElement; - } - const hidePanelElement = currentElement.querySelector('.hide-panel-action'); - hidePanelElement.focus(); - })); - xtermHelper.insertBefore(focusTrap, this._xterm.textarea); - - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'focus', (event: KeyboardEvent) => { - this._terminalFocusContextKey.set(true); - })); - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'blur', (event: KeyboardEvent) => { - this._terminalFocusContextKey.reset(); - this._refreshSelectionContextKey(); - })); - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'focus', (event: KeyboardEvent) => { - this._terminalFocusContextKey.set(true); - })); - this._instanceDisposables.push(dom.addDisposableListener(this._xterm.element, 'blur', (event: KeyboardEvent) => { - this._terminalFocusContextKey.reset(); - this._refreshSelectionContextKey(); - })); - - this._wrapperElement.appendChild(this._xtermElement); - this._widgetManager = new TerminalWidgetManager(this._wrapperElement); - this._linkHandler.setWidgetManager(this._widgetManager); - this._container.appendChild(this._wrapperElement); - - const computedStyle = window.getComputedStyle(this._container); - const width = parseInt(computedStyle.getPropertyValue('width').replace('px', ''), 10); - const height = parseInt(computedStyle.getPropertyValue('height').replace('px', ''), 10); - this.layout(new Dimension(width, height)); - this.setVisible(this._isVisible); - this.updateConfig(); - - // If IShellLaunchConfig.waitOnExit was true and the process finished before the terminal - // panel was initialized. - if (this._xterm.getOption('disableStdin')) { - this._attachPressAnyKeyToCloseListener(); - } } public registerLinkMatcher(regex: RegExp, handler: (url: string) => void, matchIndex?: number, validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void): number { @@ -549,6 +578,7 @@ export class TerminalInstance implements ITerminalInstance { private _refreshSelectionContextKey() { const activePanel = this._panelService.getActivePanel(); const isActive = activePanel && activePanel.getId() === TERMINAL_PANEL_ID; + this._terminalHasTextContextKey.set(isActive && this.hasSelection()); } @@ -585,16 +615,24 @@ export class TerminalInstance implements ITerminalInstance { if (!this._shellLaunchConfig.executable) { this._configHelper.mergeDefaultShellPathAndArgs(this._shellLaunchConfig); } - this._initialCwd = this._getCwd(this._shellLaunchConfig, this._historyService.getLastActiveWorkspaceRoot('file')); + + const lastActiveWorkspaceRootUri = this._historyService.getLastActiveWorkspaceRoot('file'); + this._initialCwd = this._getCwd(this._shellLaunchConfig, lastActiveWorkspaceRootUri); + + // Resolve env vars from config and shell + const lastActiveWorkspaceRoot = this._workspaceContextService.getWorkspaceFolder(lastActiveWorkspaceRootUri); + const platformKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); + const envFromConfig = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._configHelper.config.env[platformKey] }, lastActiveWorkspaceRoot); + const envFromShell = TerminalInstance.resolveConfigurationVariables(this._configurationResolverService, { ...this._shellLaunchConfig.env }, lastActiveWorkspaceRoot); + this._shellLaunchConfig.env = envFromShell; // Merge process env with the env from config - const envFromConfig = { ...process.env }; - const envSettingKey = platform.isWindows ? 'windows' : (platform.isMacintosh ? 'osx' : 'linux'); - TerminalInstance.mergeEnvironments(envFromConfig, this._configHelper.config.env[envSettingKey]); + const parentEnv = { ...process.env }; + TerminalInstance.mergeEnvironments(parentEnv, envFromConfig); // Continue env initialization, merging in the env from the launch // config and adding keys that are needed to create the process - const env = TerminalInstance.createTerminalEnv(envFromConfig, this._shellLaunchConfig, this._initialCwd, locale, this._cols, this._rows); + const env = TerminalInstance.createTerminalEnv(parentEnv, this._shellLaunchConfig, this._initialCwd, locale, this._cols, this._rows); this._process = cp.fork(Uri.parse(require.toUrl('bootstrap')).fsPath, ['--type=terminal'], { env, cwd: Uri.parse(path.dirname(require.toUrl('../node/terminalProcess'))).fsPath @@ -636,6 +674,16 @@ export class TerminalInstance implements ITerminalInstance { }, LAUNCHING_DURATION); } + // TODO: Should be protected + private static resolveConfigurationVariables(configurationResolverService: IConfigurationResolverService, env: IStringDictionary, lastActiveWorkspaceRoot: IWorkspaceFolder): IStringDictionary { + Object.keys(env).forEach((key) => { + if (typeof env[key] === 'string') { + env[key] = configurationResolverService.resolve(lastActiveWorkspaceRoot, env[key]); + } + }); + return env; + } + private _sendPtyDataToXterm(message: { type: string, content: string }): void { if (message.type === 'data') { if (this._widgetManager) { @@ -656,6 +704,7 @@ export class TerminalInstance implements ITerminalInstance { this._isExiting = true; this._process = null; let exitCodeMessage: string; + if (exitCode) { exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); } @@ -707,14 +756,18 @@ export class TerminalInstance implements ITerminalInstance { } this._messageService.show(Severity.Error, nls.localize('terminal.integrated.launchFailed', 'The terminal process command `{0}{1}` failed to launch (exit code: {2})', this._shellLaunchConfig.executable, args, exitCode)); } else { - this._messageService.show(Severity.Error, exitCodeMessage); + if (this._configHelper.config.showExitAlert) { + this._messageService.show(Severity.Error, exitCodeMessage); + } else { + console.warn(exitCodeMessage); + } } } } } private _attachPressAnyKeyToCloseListener() { - this._processDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'keypress', (event: KeyboardEvent) => { + this._processDisposables.push(dom.addDisposableListener(this._xterm.textarea, 'keydown', (event: KeyboardEvent) => { this.dispose(); event.preventDefault(); })); @@ -795,8 +848,7 @@ export class TerminalInstance implements ITerminalInstance { } // TODO: This should be private/protected - // TODO: locale should not be optional - public static createTerminalEnv(parentEnv: IStringDictionary, shell: IShellLaunchConfig, cwd: string, locale?: string, cols?: number, rows?: number): IStringDictionary { + public static createTerminalEnv(parentEnv: IStringDictionary, shell: IShellLaunchConfig, cwd: string, locale: string, cols?: number, rows?: number): IStringDictionary { const env = { ...parentEnv }; if (shell.env) { TerminalInstance.mergeEnvironments(env, shell.env); @@ -823,22 +875,6 @@ export class TerminalInstance implements ITerminalInstance { return env; } - public onData(listener: (data: string) => void): lifecycle.IDisposable { - let callback = (message) => { - if (message.type === 'data') { - listener(message.content); - } - }; - this._process.on('message', callback); - return { - dispose: () => { - if (this._process) { - this._process.removeListener('message', callback); - } - } - }; - } - public onLineData(listener: (lineData: string) => void): lifecycle.IDisposable { this._onLineDataListeners.push(listener); return { @@ -928,6 +964,11 @@ export class TerminalInstance implements ITerminalInstance { this._setCursorStyle(this._configHelper.config.cursorStyle); this._setCommandsToSkipShell(this._configHelper.config.commandsToSkipShell); this._setScrollback(this._configHelper.config.scrollback); + this._setEnableBell(this._configHelper.config.enableBell); + } + + public updateAccessibilitySupport(isEnabled: boolean): void { + this._xterm.setOption('screenReaderMode', isEnabled); } private _setCursorBlink(blink: boolean): void { @@ -955,6 +996,20 @@ export class TerminalInstance implements ITerminalInstance { } } + private _setEnableBell(isEnabled: boolean): void { + if (this._xterm) { + if (this._xterm.getOption('bellStyle') === 'sound') { + if (!this._configHelper.config.enableBell) { + this._xterm.setOption('bellStyle', 'none'); + } + } else { + if (this._configHelper.config.enableBell) { + this._xterm.setOption('bellStyle', 'sound'); + } + } + } + } + public layout(dimension: Dimension): void { const terminalWidth = this._evaluateColsAndRows(dimension.width, dimension.height); if (!terminalWidth) { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts index b34fb730079..c8b79cc6291 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalLinkHandler.ts @@ -33,6 +33,7 @@ const winLocalLinkClause = '((' + winPathPrefix + '|(' + winExcludedPathCharacte /** As xterm reads from DOM, space in that case is nonbreaking char ASCII code - 160, replacing space with nonBreakningSpace or space ASCII code - 32. */ const lineAndColumnClause = [ + '((\\S*)", line ((\\d+)( column (\\d+))?))', // "(file path)", line 45 [see #40468] '((\\S*) on line ((\\d+)(, column (\\d+))?))', // (file path) on line 8, column 13 '((\\S*):line ((\\d+)(, column (\\d+))?))', // (file path):line 8, column 13 '(([^\\s\\(\\)]*)(\\s?[\\(\\[](\\d+)(,\\s?(\\d+))?)[\\)\\]])', // (file path)(45), (file path) (45), (file path)(45,18), (file path) (45,18), (file path)(45, 18), (file path) (45, 18), also with [] @@ -41,7 +42,7 @@ const lineAndColumnClause = [ // Changing any regex may effect this value, hence changes this as well if required. const winLineAndColumnMatchIndex = 12; -const unixLineAndColumnMatchIndex = 23; +const unixLineAndColumnMatchIndex = 11; // Each line and column clause have 6 groups (ie no. of expressions in round brackets) const lineAndColumnClauseGroupCount = 6; diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 2dd16ba5775..d12de43f06d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -26,6 +26,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; import { TERMINAL_BACKGROUND_COLOR } from 'vs/workbench/parts/terminal/electron-browser/terminalColorRegistry'; +import { DataTransfers } from 'vs/base/browser/dnd'; export class TerminalPanel extends Panel { @@ -221,6 +222,20 @@ export class TerminalPanel extends Panel { } } })); + this._register(dom.addDisposableListener(this._parentDomElement, 'mouseup', (event: MouseEvent) => { + if (this._configurationService.getValue('terminal.integrated.copyOnSelection')) { + if (this._terminalService.terminalInstances.length === 0) { + return; + } + + if (event.which === 1) { + let terminal = this._terminalService.getActiveInstance(); + if (terminal.hasSelection()) { + terminal.copySelection(); + } + } + } + })); this._register(dom.addDisposableListener(this._parentDomElement, 'contextmenu', (event: MouseEvent) => { if (!this._cancelContextMenu) { const standardEvent = new StandardMouseEvent(event); @@ -256,7 +271,7 @@ export class TerminalPanel extends Panel { } // Check if the file was dragged from the tree explorer - let uri = e.dataTransfer.getData('URL'); + let uri = e.dataTransfer.getData(DataTransfers.URL); if (uri) { uri = URI.parse(uri).path; } else if (e.dataTransfer.files.length > 0) { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 4afb673317d..24eeeb6e16b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -180,6 +180,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina `${process.env['ProgramW6432']}\\Git\\usr\\bin\\bash.exe`, `${process.env['ProgramFiles']}\\Git\\bin\\bash.exe`, `${process.env['ProgramFiles']}\\Git\\usr\\bin\\bash.exe`, + `${process.env['LocalAppData']}\\Programs\\Git\\bin\\bash.exe`, ] }; const promises: TPromise<[string, string]>[] = []; @@ -212,7 +213,7 @@ export class TerminalService extends AbstractTerminalService implements ITermina return activeInstance ? activeInstance : this.createInstance(undefined, wasNewTerminalAction); } - protected _showTerminalCloseConfirmation(): boolean { + protected _showTerminalCloseConfirmation(): TPromise { let message; if (this.terminalInstances.length === 1) { message = nls.localize('terminalService.terminalCloseConfirmationSingular', "There is an active terminal session, do you want to kill it?"); @@ -220,10 +221,10 @@ export class TerminalService extends AbstractTerminalService implements ITermina message = nls.localize('terminalService.terminalCloseConfirmationPlural', "There are {0} active terminal sessions, do you want to kill them?", this.terminalInstances.length); } - return !this._messageService.confirm({ + return this._messageService.confirm({ message, type: 'warning', - }); + }).then(confirmed => !confirmed); } public setContainers(panelContainer: HTMLElement, terminalContainer: HTMLElement): void { diff --git a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts index da9a2b515ea..56930e6cd3d 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/windowsShellHelper.ts @@ -7,7 +7,7 @@ import * as platform from 'vs/base/common/platform'; import { TPromise } from 'vs/base/common/winjs.base'; import { Emitter, debounceEvent } from 'vs/base/common/event'; import { ITerminalInstance } from 'vs/workbench/parts/terminal/common/terminal'; -import { Terminal as XTermTerminal } from 'xterm'; +import { Terminal as XTermTerminal } from 'vscode-xterm'; const SHELL_EXECUTABLES = ['cmd.exe', 'powershell.exe', 'bash.exe']; @@ -42,12 +42,12 @@ export class WindowsShellHelper { }, 50); }); - // We want to fire a new check for the shell on a lineFeed, but only + // We want to fire a new check for the shell on a linefeed, but only // when parsing has finished which is indicated by the cursormove event. - // If this is done on every lineFeed, parsing ends up taking + // If this is done on every linefeed, parsing ends up taking // significantly longer due to resetting timers. Note that this is // private API. - this._xterm.on('lineFeed', () => this._newLineFeed = true); + this._xterm.on('linefeed', () => this._newLineFeed = true); this._xterm.on('cursormove', () => { if (this._newLineFeed) { this._onCheckShell.fire(); diff --git a/src/vs/workbench/parts/terminal/node/terminalProcess.ts b/src/vs/workbench/parts/terminal/node/terminalProcess.ts index c3ad10ecc9e..b46132c8d78 100644 --- a/src/vs/workbench/parts/terminal/node/terminalProcess.ts +++ b/src/vs/workbench/parts/terminal/node/terminalProcess.ts @@ -108,6 +108,7 @@ function cleanEnv() { var keys = [ 'AMD_ENTRYPOINT', 'ELECTRON_RUN_AS_NODE', + 'GOOGLE_API_KEY', 'PTYCWD', 'PTYPID', 'PTYSHELL', @@ -124,6 +125,7 @@ function cleanEnv() { var i = 0; while (process.env['PTYSHELLARG' + i]) { delete process.env['PTYSHELLARG' + i]; + i++; } } diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts index 7ef4320a57b..caef93152b9 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalConfigHelper.test.ts @@ -104,6 +104,21 @@ suite('Workbench - TerminalConfigHelper', () => { configHelper.panelContainer = fixture; assert.equal(configHelper.getFont().fontSize, 6, 'The minimum terminal font size should be used when terminal.integrated.fontSize less than it'); + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo' + }, + terminal: { + integrated: { + fontFamily: 0, + fontSize: 1500 + } + } + }); + configHelper = new TerminalConfigHelper(configurationService, null, null, null); + configHelper.panelContainer = fixture; + assert.equal(configHelper.getFont().fontSize, 25, 'The maximum terminal font size should be used when terminal.integrated.fontSize more than it'); + configurationService = new MockConfigurationService({ editor: { fontFamily: 'foo', diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts index 608a9e5f8bc..a7f066b25bd 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalInstance.test.ts @@ -20,6 +20,7 @@ import { MockContextKeyService, MockKeybindingService } from 'vs/platform/keybin import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { TPromise } from 'vs/base/common/winjs.base'; class TestTerminalInstance extends TerminalInstance { public _getCwd(shell: IShellLaunchConfig, root: Uri): string { @@ -27,7 +28,7 @@ class TestTerminalInstance extends TerminalInstance { } protected _createProcess(): void { } - protected _createXterm(): void { } + protected _createXterm(): TPromise { return TPromise.as(void 0); } } suite('Workbench - TerminalInstance', () => { diff --git a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts index a16322808aa..906b59e3037 100644 --- a/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts +++ b/src/vs/workbench/parts/terminal/test/electron-browser/terminalLinkHandler.test.ts @@ -134,10 +134,10 @@ suite('Workbench - TerminalLinkHandler', () => { const supportedLinkFormats: LinkFormatInfo[] = [ { urlFormat: '{0}' }, - // { urlFormat: '{0} on line {1}', line: '5' }, - // { urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' }, - // { urlFormat: '{0}:line {1}', line: '5' }, - // { urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' }, + { urlFormat: '{0} on line {1}', line: '5' }, + { urlFormat: '{0} on line {1}, column {2}', line: '5', column: '3' }, + { urlFormat: '{0}:line {1}', line: '5' }, + { urlFormat: '{0}:line {1}, column {2}', line: '5', column: '3' }, { urlFormat: '{0}({1})', line: '5' }, { urlFormat: '{0} ({1})', line: '5' }, { urlFormat: '{0}({1},{2})', line: '5', column: '3' }, diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index bcc8babb008..11334a526a4 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -28,7 +28,7 @@ import { LIGHT, DARK, HIGH_CONTRAST } from 'vs/platform/theme/common/themeServic export class SelectColorThemeAction extends Action { - static ID = 'workbench.action.selectTheme'; + static readonly ID = 'workbench.action.selectTheme'; static LABEL = localize('selectTheme.label', "Color Theme"); constructor( @@ -87,7 +87,7 @@ export class SelectColorThemeAction extends Action { class SelectIconThemeAction extends Action { - static ID = 'workbench.action.selectIconTheme'; + static readonly ID = 'workbench.action.selectIconTheme'; static LABEL = localize('selectIconTheme.label', "File Icon Theme"); constructor( @@ -171,7 +171,7 @@ function toEntries(themes: (IColorTheme | IFileIconTheme)[], label?: string, bor class GenerateColorThemeAction extends Action { - static ID = 'workbench.action.generateColorTheme'; + static readonly ID = 'workbench.action.generateColorTheme'; static LABEL = localize('generateColorTheme.label', "Generate Color Theme From Current Settings"); constructor( diff --git a/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css b/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css index b0a88187489..4e602f932a2 100644 --- a/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css +++ b/src/vs/workbench/parts/update/electron-browser/media/update.contribution.css @@ -8,7 +8,7 @@ -webkit-mask-size: 22px; } -/* HACK @bpasero @ben */ +/* TODO@Ben this is a hack to overwrite the icon for release notes eitor */ .file-icons-enabled .show-file-icons .release-notes-ext-file-icon.file-icon::before { content: ' '; background-image: url('code-icon.svg'); diff --git a/src/vs/workbench/parts/update/electron-browser/releaseNotesInput.ts b/src/vs/workbench/parts/update/electron-browser/releaseNotesInput.ts index 65be6da2398..7be415c613b 100644 --- a/src/vs/workbench/parts/update/electron-browser/releaseNotesInput.ts +++ b/src/vs/workbench/parts/update/electron-browser/releaseNotesInput.ts @@ -12,7 +12,7 @@ import URI from 'vs/base/common/uri'; export class ReleaseNotesInput extends EditorInput { - static get ID() { return 'workbench.releaseNotes.input'; } + static readonly ID = 'workbench.releaseNotes.input'; get version(): string { return this._version; } get text(): string { return this._text; } diff --git a/src/vs/workbench/parts/update/electron-browser/update.ts b/src/vs/workbench/parts/update/electron-browser/update.ts index 82166e122cd..fca8ccd73a6 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.ts @@ -165,7 +165,7 @@ export class ShowReleaseNotesAction extends AbstractShowReleaseNotesAction { export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesAction { - static ID = 'update.showCurrentReleaseNotes'; + static readonly ID = 'update.showCurrentReleaseNotes'; static LABEL = nls.localize('showReleaseNotes', "Show Release Notes"); constructor( @@ -308,6 +308,7 @@ export class UpdateContribution implements IGlobalActivity { private static readonly showCommandsId = 'workbench.action.showCommands'; private static readonly openSettingsId = 'workbench.action.openGlobalSettings'; private static readonly openKeybindingsId = 'workbench.action.openGlobalKeybindings'; + private static readonly openUserSnippets = 'workbench.action.openSnippets'; private static readonly selectColorThemeId = 'workbench.action.selectTheme'; private static readonly selectIconThemeId = 'workbench.action.selectIconTheme'; @@ -430,6 +431,8 @@ export class UpdateContribution implements IGlobalActivity { new CommandAction(UpdateContribution.openSettingsId, nls.localize('settings', "Settings"), this.commandService), new CommandAction(UpdateContribution.openKeybindingsId, nls.localize('keyboardShortcuts', "Keyboard Shortcuts"), this.commandService), new Separator(), + new CommandAction(UpdateContribution.openUserSnippets, nls.localize('userSnippets', "User Snippets"), this.commandService), + new Separator(), new CommandAction(UpdateContribution.selectColorThemeId, nls.localize('selectTheme.label', "Color Theme"), this.commandService), new CommandAction(UpdateContribution.selectIconThemeId, nls.localize('themes.selectIconTheme.label', "File Icon Theme"), this.commandService), new Separator(), diff --git a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts index 5be0939632b..56ccc4ee28c 100644 --- a/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts +++ b/src/vs/workbench/parts/welcome/page/electron-browser/welcomePage.ts @@ -585,7 +585,7 @@ function stripVersion(id: string): string { export class WelcomeInputFactory implements IEditorInputFactory { - static ID = welcomeInputTypeId; + static readonly ID = welcomeInputTypeId; public serialize(editorInput: EditorInput): string { return '{}'; diff --git a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts index 2cf12e99052..2db9e8bf0af 100644 --- a/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts +++ b/src/vs/workbench/parts/welcome/walkThrough/electron-browser/editor/editorWalkThrough.ts @@ -47,7 +47,7 @@ export class EditorWalkThroughAction extends Action { export class EditorWalkThroughInputFactory implements IEditorInputFactory { - static ID = typeId; + static readonly ID = typeId; public serialize(editorInput: EditorInput): string { return '{}'; diff --git a/src/vs/workbench/services/activity/browser/activityService.ts b/src/vs/workbench/services/activity/browser/activityService.ts index 4f328baa8ae..c45b8556c40 100644 --- a/src/vs/workbench/services/activity/browser/activityService.ts +++ b/src/vs/workbench/services/activity/browser/activityService.ts @@ -21,11 +21,11 @@ export class ActivityService implements IActivityService { @IPanelService private panelService: IPanelService ) { } - public showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string): IDisposable { + public showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable { if (this.panelService.getPanels().filter(p => p.id === compositeOrActionId).length) { return this.panelPart.showActivity(compositeOrActionId, badge, clazz); } - return this.activitybarPart.showActivity(compositeOrActionId, badge, clazz); + return this.activitybarPart.showActivity(compositeOrActionId, badge, clazz, priority); } } diff --git a/src/vs/workbench/services/activity/common/activity.ts b/src/vs/workbench/services/activity/common/activity.ts index e2f5286ba2e..6167a730512 100644 --- a/src/vs/workbench/services/activity/common/activity.ts +++ b/src/vs/workbench/services/activity/common/activity.ts @@ -66,5 +66,5 @@ export interface IActivityService { /** * Show activity in the panel for the given panel or in the activitybar for the given viewlet or global action. */ - showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string): IDisposable; + showActivity(compositeOrActionId: string, badge: IBadge, clazz?: string, priority?: number): IDisposable; } diff --git a/src/vs/workbench/services/backup/common/backup.ts b/src/vs/workbench/services/backup/common/backup.ts index c43688b8b48..25c3948dced 100644 --- a/src/vs/workbench/services/backup/common/backup.ts +++ b/src/vs/workbench/services/backup/common/backup.ts @@ -40,6 +40,14 @@ export interface IBackupFileService { */ loadBackupResource(resource: Uri): TPromise; + /** + * Given a resource, returns the associated backup resource. + * + * @param resource The resource to get the backup resource for. + * @return The backup resource. + */ + toBackupResource(resource: Uri): Uri; + /** * Backs up a resource. * diff --git a/src/vs/workbench/services/backup/node/backupFileService.ts b/src/vs/workbench/services/backup/node/backupFileService.ts index af5ca6bda2b..1a00d797b91 100644 --- a/src/vs/workbench/services/backup/node/backupFileService.ts +++ b/src/vs/workbench/services/backup/node/backupFileService.ts @@ -135,7 +135,7 @@ export class BackupFileService implements IBackupFileService { public loadBackupResource(resource: Uri): TPromise { return this.ready.then(model => { - const backupResource = this.getBackupResource(resource); + const backupResource = this.toBackupResource(resource); if (!backupResource) { return void 0; } @@ -155,7 +155,7 @@ export class BackupFileService implements IBackupFileService { } return this.ready.then(model => { - const backupResource = this.getBackupResource(resource); + const backupResource = this.toBackupResource(resource); if (!backupResource) { return void 0; } @@ -175,7 +175,7 @@ export class BackupFileService implements IBackupFileService { public discardResourceBackup(resource: Uri): TPromise { return this.ready.then(model => { - const backupResource = this.getBackupResource(resource); + const backupResource = this.toBackupResource(resource); if (!backupResource) { return void 0; } @@ -218,7 +218,7 @@ export class BackupFileService implements IBackupFileService { return textSource.lines.slice(1).join(textSource.EOL); // The first line of a backup text file is the file name } - protected getBackupResource(resource: Uri): Uri { + public toBackupResource(resource: Uri): Uri { if (!this.backupEnabled) { return null; } diff --git a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts index 5309017f4ea..66be679cdde 100644 --- a/src/vs/workbench/services/backup/test/node/backupFileService.test.ts +++ b/src/vs/workbench/services/backup/test/node/backupFileService.test.ts @@ -41,8 +41,8 @@ class TestBackupFileService extends BackupFileService { super(workspaceBackupPath, fileService); } - public getBackupResource(resource: Uri): Uri { - return super.getBackupResource(resource); + public toBackupResource(resource: Uri): Uri { + return super.toBackupResource(resource); } } @@ -73,7 +73,7 @@ suite('BackupFileService', () => { const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex'); const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex'); const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'file', filePathHash)).fsPath; - assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath); + assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); }); test('should get the correct backup path for untitled files', () => { @@ -82,7 +82,7 @@ suite('BackupFileService', () => { const workspaceHash = crypto.createHash('md5').update(workspaceResource.fsPath).digest('hex'); const filePathHash = crypto.createHash('md5').update(backupResource.fsPath).digest('hex'); const expectedPath = Uri.file(path.join(backupHome, workspaceHash, 'untitled', filePathHash)).fsPath; - assert.equal(service.getBackupResource(backupResource).fsPath, expectedPath); + assert.equal(service.toBackupResource(backupResource).fsPath, expectedPath); }); }); diff --git a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts index c0da65c8d9b..1a98e4493eb 100644 --- a/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-browser/configurationResolverService.ts @@ -18,6 +18,7 @@ import { toResource } from 'vs/workbench/common/editor'; import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { relative } from 'path'; +import { IProcessEnvironment, isWindows } from 'vs/base/common/platform'; export class ConfigurationResolverService implements IConfigurationResolverService { _serviceBrand: any; @@ -25,7 +26,7 @@ export class ConfigurationResolverService implements IConfigurationResolverServi private _lastWorkspaceFolder: IWorkspaceFolder; constructor( - envVariables: { [key: string]: string }, + envVariables: IProcessEnvironment, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEnvironmentService environmentService: IEnvironmentService, @IConfigurationService private configurationService: IConfigurationService, @@ -33,7 +34,8 @@ export class ConfigurationResolverService implements IConfigurationResolverServi ) { this._execPath = environmentService.execPath; Object.keys(envVariables).forEach(key => { - this[`env:${key}`] = envVariables[key]; + const name = isWindows ? key.toLowerCase() : key; + this[`env:${name}`] = envVariables[key]; }); } @@ -70,7 +72,7 @@ export class ConfigurationResolverService implements IConfigurationResolverServi } private get relativeFile(): string { - return (this.workspaceRoot) ? relative(this.workspaceRoot, this.file) : this.file; + return (this.workspaceRoot) ? paths.normalize(relative(this.workspaceRoot, this.file)) : this.file; } private get fileBasename(): string { @@ -103,6 +105,22 @@ export class ConfigurationResolverService implements IConfigurationResolverServi return ''; } + private get selectedText(): string { + const activeEditor = this.editorService.getActiveEditor(); + if (activeEditor) { + const editorControl = (activeEditor.getControl()); + if (editorControl) { + const editorModel = editorControl.getModel(); + const editorSelection = editorControl.getSelection(); + if (editorModel && editorSelection) { + return editorModel.getValueInRange(editorSelection); + } + } + } + + return ''; + } + private getFilePath(): string { let input = this.editorService.getActiveEditorInput(); if (input instanceof DiffEditorInput) { @@ -159,7 +177,8 @@ export class ConfigurationResolverService implements IConfigurationResolverServi let regexp = /\$\{(.*?)\}/g; const originalValue = value; const resolvedString = value.replace(regexp, (match: string, name: string) => { - let newValue = (this)[name]; + const key = (isWindows && match.indexOf('env:') > 0) ? name.toLowerCase() : name; + let newValue = (this)[key]; if (types.isString(newValue)) { return newValue; } else { diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index 069638e9d81..b77215ea758 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -17,7 +17,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ suite('Configuration Resolver Service', () => { let configurationResolverService: IConfigurationResolverService; - let envVariables: { [key: string]: string } = { key1: 'Value for Key1', key2: 'Value for Key2' }; + let envVariables: { [key: string]: string } = { key1: 'Value for key1', key2: 'Value for key2' }; let mockCommandService: MockCommandService; let editorService: TestEditorService; let workspace: IWorkspaceFolder; @@ -56,6 +56,10 @@ suite('Configuration Resolver Service', () => { assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${lineNumber} xyz'), `abc ${editorService.mockLineNumber} xyz`); }); + test('current selected text', () => { + assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${selectedText} xyz'), `abc ${editorService.mockSelectedText} xyz`); + }); + test('substitute many', () => { if (platform.isWindows) { assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation'); @@ -66,17 +70,25 @@ suite('Configuration Resolver Service', () => { test('substitute one env variable', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for Key1 xyz'); + assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc \\VSCode\\workspaceLocation Value for key1 xyz'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for Key1 xyz'); + assert.strictEqual(configurationResolverService.resolve(workspace, 'abc ${workspaceFolder} ${env:key1} xyz'), 'abc /VSCode/workspaceLocation Value for key1 xyz'); } }); test('substitute many env variable', () => { if (platform.isWindows) { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for Key1 - Value for Key2'); + assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '\\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { - assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for Key1 - Value for Key2'); + assert.strictEqual(configurationResolverService.resolve(workspace, '${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), '/VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); + } + }); + + test('substitute one env variable using platform case sensitivity', () => { + if (platform.isWindows) { + assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - Value for key1'); + } else { + assert.strictEqual(configurationResolverService.resolve(workspace, '${env:key1} - ${env:Key1}'), 'Value for key1 - '); } }); @@ -171,9 +183,9 @@ suite('Configuration Resolver Service', () => { let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService); if (platform.isWindows) { - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for Key1 xyz'); + assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { - assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for Key1 xyz'); + assert.strictEqual(service.resolve(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo /VSCode/workspaceLocation Value for key1 xyz'); } }); @@ -192,9 +204,9 @@ suite('Configuration Resolver Service', () => { let service = new ConfigurationResolverService(envVariables, new TestEditorService(), TestEnvironmentService, configurationService, mockCommandService); if (platform.isWindows) { - assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for Key1 - Value for Key2'); + assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { - assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for Key1 - Value for Key2'); + assert.strictEqual(service.resolve(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar /VSCode/workspaceLocation - /VSCode/workspaceLocation Value for key1 - Value for key2'); } }); diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 4e881ab0b2f..62474f4acbc 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -7,7 +7,6 @@ import * as nls from 'vs/nls'; import { toErrorMessage } from 'vs/base/common/errorMessage'; -import { stringify } from 'vs/base/common/marshalling'; import * as objects from 'vs/base/common/objects'; import URI from 'vs/base/common/uri'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -329,7 +328,7 @@ export class ExtensionHostProcessWorker { if (msg === 'ready') { // 1) Extension Host is ready to receive messages, initialize it - this._createExtHostInitData().then(data => protocol.send(stringify(data))); + this._createExtHostInitData().then(data => protocol.send(JSON.stringify(data))); return; } diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts b/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts index 9bc364ccc7c..b0b0f300177 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionPoints.ts @@ -9,7 +9,6 @@ import * as nls from 'vs/nls'; import * as pfs from 'vs/base/node/pfs'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { TPromise } from 'vs/base/common/winjs.base'; -import { groupBy, values } from 'vs/base/common/collections'; import { join, normalize, extname } from 'path'; import * as json from 'vs/base/common/json'; import * as types from 'vs/base/common/types'; @@ -17,6 +16,7 @@ import { isValidExtensionDescription } from 'vs/platform/extensions/node/extensi import * as semver from 'semver'; import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil'; import { getParseErrorMessage } from 'vs/base/common/jsonErrorMessages'; +import { groupByExtension } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; const MANIFEST_FILE = 'package.json'; @@ -340,7 +340,7 @@ export class ExtensionScanner { } else { // TODO: align with extensionsService const nonGallery: string[] = []; - const gallery: { folder: string; id: string; version: string; }[] = []; + const gallery: string[] = []; rawFolders.forEach(folder => { if (obsolete[folder]) { @@ -351,22 +351,24 @@ export class ExtensionScanner { if (!id || !version) { nonGallery.push(folder); - return; + } else { + gallery.push(folder); } - - gallery.push({ folder, id, version }); }); - const byId = values(groupBy(gallery, p => p.id)); - const latest = byId.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]) - .map(a => a.folder); - - folders = [...nonGallery, ...latest]; + folders = [...nonGallery, ...gallery]; } const nlsConfig = ExtensionScannerInput.createNLSConfig(input); let extensionDescriptions = await TPromise.join(folders.map(f => this.scanExtension(input.ourVersion, log, join(absoluteFolderPath, f), isBuiltin, nlsConfig))); extensionDescriptions = extensionDescriptions.filter(item => item !== null); + + if (!isBuiltin) { + // Filter out outdated extensions + const byExtension: IExtensionDescription[][] = groupByExtension(extensionDescriptions, e => ({ id: e.id, uuid: e.uuid })); + extensionDescriptions = byExtension.map(p => p.sort((a, b) => semver.rcompare(a.version, b.version))[0]); + } + extensionDescriptions.sort((a, b) => { if (a.extensionFolderPath < b.extensionFolderPath) { return -1; diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts index 01f086dea8d..c01e5b32c77 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionService.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionService.ts @@ -21,14 +21,13 @@ import { areSameExtensions, BetterMergeId, BetterMergeDisabledNowKey } from 'vs/ import { ExtensionsRegistry, ExtensionPoint, IExtensionPointUser, ExtensionMessageCollector, IExtensionPoint } from 'vs/platform/extensions/common/extensionsRegistry'; import { ExtensionScanner, ILog, ExtensionScannerInput } from 'vs/workbench/services/extensions/electron-browser/extensionPoints'; import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; -import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; +import { ProxyIdentifier } from 'vs/workbench/services/extensions/node/proxyIdentifier'; import { ExtHostContext, ExtHostExtensionServiceShape, IExtHostContext, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ExtensionHostProcessWorker } from 'vs/workbench/services/extensions/electron-browser/extensionHost'; -import { MainThreadService } from 'vs/workbench/services/thread/electron-browser/threadService'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { ExtHostCustomersRegistry } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { IWindowService } from 'vs/platform/windows/common/windows'; @@ -41,9 +40,14 @@ import Event, { Emitter } from 'vs/base/common/event'; import { ExtensionHostProfiler } from 'vs/workbench/services/extensions/electron-browser/extensionHostProfiler'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import product from 'vs/platform/node/product'; +import * as strings from 'vs/base/common/strings'; +import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; const SystemExtensionsRoot = path.normalize(path.join(URI.parse(require.toUrl('')).fsPath, '..', 'extensions')); +// Enable to see detailed message communication between window and extension host +const logExtensionHostCommunication = false; + function messageWithSource(msg: IMessage): string { return messageWithSource2(msg.source, msg.message); } @@ -81,7 +85,7 @@ export class ExtensionService extends Disposable implements IExtensionService { private _extensionHostProcessActivationTimes: { [id: string]: ActivationTimes; }; private _extensionHostExtensionRuntimeErrors: { [id: string]: Error[]; }; private _extensionHostProcessWorker: ExtensionHostProcessWorker; - private _extensionHostProcessThreadService: MainThreadService; + private _extensionHostProcessRPCProtocol: RPCProtocol; private _extensionHostProcessCustomers: IDisposable[]; /** * winjs believes a proxy is a promise because it has a `then` method, so wrap the result in an object. @@ -111,7 +115,7 @@ export class ExtensionService extends Disposable implements IExtensionService { this._extensionHostProcessActivationTimes = Object.create(null); this._extensionHostExtensionRuntimeErrors = Object.create(null); this._extensionHostProcessWorker = null; - this._extensionHostProcessThreadService = null; + this._extensionHostProcessRPCProtocol = null; this._extensionHostProcessCustomers = []; this._extensionHostProcessProxy = null; @@ -163,9 +167,9 @@ export class ExtensionService extends Disposable implements IExtensionService { this._extensionHostProcessWorker.dispose(); this._extensionHostProcessWorker = null; } - if (this._extensionHostProcessThreadService) { - this._extensionHostProcessThreadService.dispose(); - this._extensionHostProcessThreadService = null; + if (this._extensionHostProcessRPCProtocol) { + this._extensionHostProcessRPCProtocol.dispose(); + this._extensionHostProcessRPCProtocol = null; } for (let i = 0, len = this._extensionHostProcessCustomers.length; i < len; i++) { const customer = this._extensionHostProcessCustomers[i]; @@ -230,8 +234,12 @@ export class ExtensionService extends Disposable implements IExtensionService { private _createExtensionHostCustomers(protocol: IMessagePassingProtocol): ExtHostExtensionServiceShape { - this._extensionHostProcessThreadService = this._instantiationService.createInstance(MainThreadService, protocol); - const extHostContext: IExtHostContext = this._extensionHostProcessThreadService; + if (logExtensionHostCommunication || this._environmentService.logExtensionHostCommunication) { + protocol = asLoggingProtocol(protocol); + } + + this._extensionHostProcessRPCProtocol = new RPCProtocol(protocol); + const extHostContext: IExtHostContext = this._extensionHostProcessRPCProtocol; // Named customers const namedCustomers = ExtHostCustomersRegistry.getNamedCustomers(); @@ -239,7 +247,7 @@ export class ExtensionService extends Disposable implements IExtensionService { const [id, ctor] = namedCustomers[i]; const instance = this._instantiationService.createInstance(ctor, extHostContext); this._extensionHostProcessCustomers.push(instance); - this._extensionHostProcessThreadService.set(id, instance); + this._extensionHostProcessRPCProtocol.set(id, instance); } // Customers @@ -252,9 +260,9 @@ export class ExtensionService extends Disposable implements IExtensionService { // Check that no named customers are missing const expected: ProxyIdentifier[] = Object.keys(MainContext).map((key) => MainContext[key]); - this._extensionHostProcessThreadService.assertRegistered(expected); + this._extensionHostProcessRPCProtocol.assertRegistered(expected); - return this._extensionHostProcessThreadService.get(ExtHostContext.ExtHostExtensionService); + return this._extensionHostProcessRPCProtocol.getProxy(ExtHostContext.ExtHostExtensionService); } // ---- begin IExtensionService @@ -478,6 +486,10 @@ export class ExtensionService extends Disposable implements IExtensionService { const expected = await ExtensionScanner.scanExtensions(input, new NullLogger()); const cacheContents = await this._readExtensionCache(environmentService, cacheKey); + if (!cacheContents) { + // Cache has been deleted by someone else, which is perfectly fine... + return; + } const actual = cacheContents.result; if (objects.equals(expected, actual)) { @@ -695,6 +707,22 @@ export class ExtensionService extends Disposable implements IExtensionService { } } +function asLoggingProtocol(protocol: IMessagePassingProtocol): IMessagePassingProtocol { + + protocol.onMessage(msg => { + console.log('%c[Extension \u2192 Window]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg); + }); + + return { + onMessage: protocol.onMessage, + + send(msg: any) { + protocol.send(msg); + console.log('%c[Window \u2192 Extension]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg); + } + }; +} + interface IExtensionCacheData { input: ExtensionScannerInput; result: IExtensionDescription[]; diff --git a/src/vs/workbench/services/extensions/node/proxyIdentifier.ts b/src/vs/workbench/services/extensions/node/proxyIdentifier.ts new file mode 100644 index 00000000000..5d2c7484abd --- /dev/null +++ b/src/vs/workbench/services/extensions/node/proxyIdentifier.ts @@ -0,0 +1,55 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +export interface IRPCProtocol { + /** + * Returns a proxy to an object addressable/named in the extension host process or in the renderer process. + */ + getProxy(identifier: ProxyIdentifier): T; + + /** + * Register manually created instance. + */ + set(identifier: ProxyIdentifier, instance: R): R; + + /** + * Assert these identifiers are already registered via `.set`. + */ + assertRegistered(identifiers: ProxyIdentifier[]): void; +} + +export class ProxyIdentifier { + _proxyIdentifierBrand: void; + _suppressCompilerUnusedWarning: T; + + public readonly isMain: boolean; + public readonly id: string; + public readonly isFancy: boolean; + + constructor(isMain: boolean, id: string, isFancy: boolean) { + this.isMain = isMain; + this.id = id; + this.isFancy = isFancy; + } +} + +export const enum ProxyType { + NativeJSON = 0, + CustomMarshaller = 1 +} + +/** + * Using `isFancy` indicates that arguments or results of type `URI` or `RegExp` + * will be serialized/deserialized automatically, but this has a performance cost, + * as each argument/result must be visited. + */ +export function createMainContextProxyIdentifier(identifier: string, type: ProxyType = ProxyType.NativeJSON): ProxyIdentifier { + return new ProxyIdentifier(true, 'm' + identifier, type === ProxyType.CustomMarshaller); +} + +export function createExtHostContextProxyIdentifier(identifier: string, type: ProxyType = ProxyType.NativeJSON): ProxyIdentifier { + return new ProxyIdentifier(false, 'e' + identifier, type === ProxyType.CustomMarshaller); +} diff --git a/src/vs/workbench/services/extensions/node/rpcProtocol.ts b/src/vs/workbench/services/extensions/node/rpcProtocol.ts index 3fcd57403af..d738ab50737 100644 --- a/src/vs/workbench/services/extensions/node/rpcProtocol.ts +++ b/src/vs/workbench/services/extensions/node/rpcProtocol.ts @@ -9,15 +9,16 @@ import * as marshalling from 'vs/base/common/marshalling'; import * as errors from 'vs/base/common/errors'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { LazyPromise } from 'vs/workbench/services/extensions/node/lazyPromise'; +import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier'; +import { CharCode } from 'vs/base/common/charCode'; -export interface IDispatcher { - invoke(proxyId: string, methodName: string, args: any[]): any; -} +declare var Proxy: any; // TODO@TypeScript -export class RPCProtocol { +export class RPCProtocol implements IRPCProtocol { private _isDisposed: boolean; - private _bigHandler: IDispatcher; + private readonly _locals: { [id: string]: any; }; + private readonly _proxies: { [id: string]: any; }; private _lastMessageId: number; private readonly _invokedHandlers: { [req: string]: TPromise; }; private readonly _pendingRPCReplies: { [msgId: string]: LazyPromise; }; @@ -25,7 +26,8 @@ export class RPCProtocol { constructor(protocol: IMessagePassingProtocol) { this._isDisposed = false; - this._bigHandler = null; + this._locals = Object.create(null); + this._proxies = Object.create(null); this._lastMessageId = 0; this._invokedHandlers = Object.create(null); this._pendingRPCReplies = {}; @@ -42,96 +44,168 @@ export class RPCProtocol { }); } + public getProxy(identifier: ProxyIdentifier): T { + if (!this._proxies[identifier.id]) { + this._proxies[identifier.id] = this._createProxy(identifier.id, identifier.isFancy); + } + return this._proxies[identifier.id]; + } + + private _createProxy(proxyId: string, isFancy: boolean): T { + let handler = { + get: (target, name: string) => { + if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) { + target[name] = (...myArgs: any[]) => { + return this._remoteCall(proxyId, name, myArgs, isFancy); + }; + } + return target[name]; + } + }; + return new Proxy(Object.create(null), handler); + } + + public set(identifier: ProxyIdentifier, value: R): R { + this._locals[identifier.id] = value; + return value; + } + + public assertRegistered(identifiers: ProxyIdentifier[]): void { + for (let i = 0, len = identifiers.length; i < len; i++) { + const identifier = identifiers[i]; + if (!this._locals[identifier.id]) { + throw new Error(`Missing actor ${identifier.id} (isMain: ${identifier.isMain})`); + } + } + } + private _receiveOneMessage(rawmsg: string): void { if (this._isDisposed) { - console.warn('Received message after being shutdown: ', rawmsg); return; } - let msg = marshalling.parse(rawmsg); - if (msg.seq) { - if (!this._pendingRPCReplies.hasOwnProperty(msg.seq)) { - console.warn('Got reply to unknown seq'); - return; + let msg = JSON.parse(rawmsg); + + switch (msg.type) { + case MessageType.Request: + this._receiveRequest(msg); + break; + case MessageType.FancyRequest: + this._receiveRequest(marshalling.revive(msg, 0)); + break; + case MessageType.Cancel: + this._receiveCancel(msg); + break; + case MessageType.Reply: + this._receiveReply(msg); + break; + case MessageType.FancyReply: + this._receiveReply(marshalling.revive(msg, 0)); + break; + case MessageType.ReplyErr: + this._receiveReplyErr(msg); + break; + } + } + + private _receiveRequest(msg: RequestMessage | FancyRequestMessage): void { + const callId = msg.id; + const proxyId = msg.proxyId; + const isFancy = (msg.type === MessageType.FancyRequest); // a fancy request gets a fancy reply + + this._invokedHandlers[callId] = this._invokeHandler(proxyId, msg.method, msg.args); + + this._invokedHandlers[callId].then((r) => { + delete this._invokedHandlers[callId]; + if (isFancy) { + this._multiplexor.send(MessageFactory.fancyReplyOK(callId, r)); + } else { + this._multiplexor.send(MessageFactory.replyOK(callId, r)); } - let reply = this._pendingRPCReplies[msg.seq]; - delete this._pendingRPCReplies[msg.seq]; - - if (msg.err) { - let err = msg.err; - if (msg.err.$isError) { - err = new Error(); - err.name = msg.err.name; - err.message = msg.err.message; - err.stack = msg.err.stack; - } - reply.resolveErr(err); - return; - } - - reply.resolveOk(msg.res); - return; - } - - if (msg.cancel) { - if (this._invokedHandlers[msg.cancel]) { - this._invokedHandlers[msg.cancel].cancel(); - } - return; - } - - if (msg.err) { - console.error(msg.err); - return; - } - - let rpcId = msg.rpcId; - - if (!this._bigHandler) { - throw new Error('got message before big handler attached!'); - } - - let req = msg.req; - - this._invokedHandlers[req] = this._invokeHandler(rpcId, msg.method, msg.args); - - this._invokedHandlers[req].then((r) => { - delete this._invokedHandlers[req]; - this._multiplexor.send(MessageFactory.replyOK(req, r)); }, (err) => { - delete this._invokedHandlers[req]; - this._multiplexor.send(MessageFactory.replyErr(req, err)); + delete this._invokedHandlers[callId]; + this._multiplexor.send(MessageFactory.replyErr(callId, err)); }); } + private _receiveCancel(msg: CancelMessage): void { + const callId = msg.id; + if (this._invokedHandlers[callId]) { + this._invokedHandlers[callId].cancel(); + } + } + + private _receiveReply(msg: ReplyMessage | FancyReplyMessage): void { + const callId = msg.id; + if (!this._pendingRPCReplies.hasOwnProperty(callId)) { + return; + } + + const pendingReply = this._pendingRPCReplies[callId]; + delete this._pendingRPCReplies[callId]; + + pendingReply.resolveOk(msg.res); + } + + private _receiveReplyErr(msg: ReplyErrMessage): void { + const callId = msg.id; + if (!this._pendingRPCReplies.hasOwnProperty(callId)) { + return; + } + + const pendingReply = this._pendingRPCReplies[callId]; + delete this._pendingRPCReplies[callId]; + + let err: Error = null; + if (msg.err && msg.err.$isError) { + err = new Error(); + err.name = msg.err.name; + err.message = msg.err.message; + err.stack = msg.err.stack; + } + pendingReply.resolveErr(err); + } + private _invokeHandler(proxyId: string, methodName: string, args: any[]): TPromise { try { - return TPromise.as(this._bigHandler.invoke(proxyId, methodName, args)); + return TPromise.as(this._doInvokeHandler(proxyId, methodName, args)); } catch (err) { return TPromise.wrapError(err); } } - public callOnRemote(proxyId: string, methodName: string, args: any[]): TPromise { + private _doInvokeHandler(proxyId: string, methodName: string, args: any[]): any { + if (!this._locals[proxyId]) { + throw new Error('Unknown actor ' + proxyId); + } + let actor = this._locals[proxyId]; + let method = actor[methodName]; + if (typeof method !== 'function') { + throw new Error('Unknown method ' + methodName + ' on actor ' + proxyId); + } + return method.apply(actor, args); + } + + private _remoteCall(proxyId: string, methodName: string, args: any[], isFancy: boolean): TPromise { if (this._isDisposed) { return TPromise.wrapError(errors.canceled()); } - let req = String(++this._lastMessageId); - let result = new LazyPromise(() => { - this._multiplexor.send(MessageFactory.cancel(req)); + const callId = String(++this._lastMessageId); + const result = new LazyPromise(() => { + this._multiplexor.send(MessageFactory.cancel(callId)); }); - this._pendingRPCReplies[req] = result; + this._pendingRPCReplies[callId] = result; - this._multiplexor.send(MessageFactory.request(req, proxyId, methodName, args)); + if (isFancy) { + this._multiplexor.send(MessageFactory.fancyRequest(callId, proxyId, methodName, args)); + } else { + this._multiplexor.send(MessageFactory.request(callId, proxyId, methodName, args)); + } return result; } - - public setDispatcher(handler: IDispatcher): void { - this._bigHandler = handler; - } } /** @@ -175,24 +249,80 @@ class RPCMultiplexer { class MessageFactory { public static cancel(req: string): string { - return `{"cancel":"${req}"}`; + return `{"type":${MessageType.Cancel},"id":"${req}"}`; } public static request(req: string, rpcId: string, method: string, args: any[]): string { - return `{"req":"${req}","rpcId":"${rpcId}","method":"${method}","args":${marshalling.stringify(args)}}`; + return `{"type":${MessageType.Request},"id":"${req}","proxyId":"${rpcId}","method":"${method}","args":${JSON.stringify(args)}}`; + } + + public static fancyRequest(req: string, rpcId: string, method: string, args: any[]): string { + return `{"type":${MessageType.FancyRequest},"id":"${req}","proxyId":"${rpcId}","method":"${method}","args":${marshalling.stringify(args)}}`; } public static replyOK(req: string, res: any): string { if (typeof res === 'undefined') { - return `{"seq":"${req}"}`; + return `{"type":${MessageType.Reply},"id":"${req}"}`; } - return `{"seq":"${req}","res":${marshalling.stringify(res)}}`; + return `{"type":${MessageType.Reply},"id":"${req}","res":${JSON.stringify(res)}}`; + } + + public static fancyReplyOK(req: string, res: any): string { + if (typeof res === 'undefined') { + return `{"type":${MessageType.Reply},"id":"${req}"}`; + } + return `{"type":${MessageType.FancyReply},"id":"${req}","res":${marshalling.stringify(res)}}`; } public static replyErr(req: string, err: any): string { - if (typeof err === 'undefined') { - return `{"seq":"${req}","err":null}`; + if (err instanceof Error) { + return `{"type":${MessageType.ReplyErr},"id":"${req}","err":${JSON.stringify(errors.transformErrorForSerialization(err))}}`; } - return `{"seq":"${req}","err":${marshalling.stringify(errors.transformErrorForSerialization(err))}}`; + return `{"type":${MessageType.ReplyErr},"id":"${req}","err":null}`; } } + +const enum MessageType { + Request = 1, + FancyRequest = 2, + Cancel = 3, + Reply = 4, + FancyReply = 5, + ReplyErr = 6 +} + +class RequestMessage { + type: MessageType.Request; + id: string; + proxyId: string; + method: string; + args: any[]; +} +class FancyRequestMessage { + type: MessageType.FancyRequest; + id: string; + proxyId: string; + method: string; + args: any[]; +} +class CancelMessage { + type: MessageType.Cancel; + id: string; +} +class ReplyMessage { + type: MessageType.Reply; + id: string; + res: any; +} +class FancyReplyMessage { + type: MessageType.FancyReply; + id: string; + res: any; +} +class ReplyErrMessage { + type: MessageType.ReplyErr; + id: string; + err: errors.SerializedError; +} + +type RPCMessage = RequestMessage | FancyRequestMessage | CancelMessage | ReplyMessage | FancyReplyMessage | ReplyErrMessage; diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index 18987ccf7e0..bcd10448f92 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -24,6 +24,8 @@ import Event, { Emitter } from 'vs/base/common/event'; import { shell } from 'electron'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { isMacintosh } from 'vs/base/common/platform'; +import product from 'vs/platform/node/product'; export class FileService implements IFileService { @@ -70,7 +72,12 @@ export class FileService implements IFileService { encodingOverride: this.getEncodingOverrides(), watcherIgnoredPatterns, verboseLogging: environmentService.verbose, - useExperimentalFileWatcher: configuration.files.useExperimentalFileWatcher + useExperimentalFileWatcher: configuration.files.useExperimentalFileWatcher, + elevationSupport: { + cliPath: this.environmentService.cliPath, + promptTitle: this.environmentService.appNameLong, + promptIcnsPath: (isMacintosh && this.environmentService.isBuilt) ? paths.join(paths.dirname(this.environmentService.appRoot), `${product.nameShort}.icns`) : void 0 + } }; // create service diff --git a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts index 6de890c430f..c712f387483 100644 --- a/src/vs/workbench/services/files/electron-browser/remoteFileService.ts +++ b/src/vs/workbench/services/files/electron-browser/remoteFileService.ts @@ -249,7 +249,8 @@ export class RemoteFileService extends FileService { if (options.acceptTextOnly && detected.mimes.indexOf(MIME_BINARY) >= 0) { return TPromise.wrapError(new FileOperationError( localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), - FileOperationResult.FILE_IS_BINARY + FileOperationResult.FILE_IS_BINARY, + options )); } @@ -324,7 +325,7 @@ export class RemoteFileService extends FileService { return prepare.then(exists => { if (exists && options && !options.overwrite) { - return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE)); + return TPromise.wrapError(new FileOperationError('EEXIST', FileOperationResult.FILE_MODIFIED_SINCE, options)); } return this._doUpdateContent(provider, resource, content || '', {}); }).then(fileStat => { diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index c1c668826b1..81b3bf49b55 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -40,6 +40,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { getBaseLabel } from 'vs/base/common/labels'; +import { assign } from 'vs/base/common/objects'; export interface IEncodingOverride { resource: uri; @@ -54,6 +55,12 @@ export interface IFileServiceOptions { disableWatcher?: boolean; verboseLogging?: boolean; useExperimentalFileWatcher?: boolean; + writeElevated?: (source: string, target: string) => TPromise; + elevationSupport?: { + cliPath: string; + promptTitle: string; + promptIcnsPath?: string; + }; } function etag(stat: fs.Stats): string; @@ -220,7 +227,7 @@ export class FileService implements IFileService { public resolveFiles(toResolve: { resource: uri, options?: IResolveFileOptions }[]): TPromise { return TPromise.join(toResolve.map(resourceAndOptions => this.resolve(resourceAndOptions.resource, resourceAndOptions.options) - .then(stat => ({ stat, success: true }), error => ({ stat: undefined, success: false })))); + .then(stat => ({ stat, success: true }), error => ({ stat: void 0, success: false })))); } public existsFile(resource: uri): TPromise { @@ -255,17 +262,18 @@ export class FileService implements IFileService { if (resource.scheme !== 'file' || !resource.fsPath) { return TPromise.wrapError(new FileOperationError( nls.localize('fileInvalidPath', "Invalid file resource ({0})", resource.toString(true)), - FileOperationResult.FILE_INVALID_PATH + FileOperationResult.FILE_INVALID_PATH, + options )); } const result: IStreamContent = { - resource: undefined, - name: undefined, - mtime: undefined, - etag: undefined, - encoding: undefined, - value: undefined + resource: void 0, + name: void 0, + mtime: void 0, + etag: void 0, + encoding: void 0, + value: void 0 }; const contentResolverToken = new CancellationTokenSource(); @@ -291,7 +299,8 @@ export class FileService implements IFileService { if (stat.isDirectory) { return onStatError(new FileOperationError( nls.localize('fileIsDirectoryError', "File is directory"), - FileOperationResult.FILE_IS_DIRECTORY + FileOperationResult.FILE_IS_DIRECTORY, + options )); } @@ -299,7 +308,8 @@ export class FileService implements IFileService { if (options && options.etag && options.etag === stat.etag) { return onStatError(new FileOperationError( nls.localize('fileNotModifiedError', "File not modified since"), - FileOperationResult.FILE_NOT_MODIFIED_SINCE + FileOperationResult.FILE_NOT_MODIFIED_SINCE, + options )); } @@ -307,18 +317,20 @@ export class FileService implements IFileService { if (typeof stat.size === 'number' && stat.size > MAX_FILE_SIZE) { return onStatError(new FileOperationError( nls.localize('fileTooLargeError', "File too large to open"), - FileOperationResult.FILE_TOO_LARGE + FileOperationResult.FILE_TOO_LARGE, + options )); } - return undefined; + return void 0; }, err => { // Wrap file not found errors if (err.code === 'ENOENT') { return onStatError(new FileOperationError( nls.localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), - FileOperationResult.FILE_NOT_FOUND + FileOperationResult.FILE_NOT_FOUND, + options )); } @@ -351,16 +363,13 @@ export class FileService implements IFileService { }); } - //#region data stream - - private resolveFileData(resource: uri, options: IResolveContentOptions, token: CancellationToken): Thenable { const chunkBuffer = BufferPool._64K.acquire(); const result: IContentData = { - encoding: undefined, - stream: undefined, + encoding: void 0, + stream: void 0 }; return new Promise((resolve, reject) => { @@ -370,7 +379,8 @@ export class FileService implements IFileService { // Wrap file not found errors err = new FileOperationError( nls.localize('fileNotFoundError', "File not found ({0})", resource.toString(true)), - FileOperationResult.FILE_NOT_FOUND + FileOperationResult.FILE_NOT_FOUND, + options ); } @@ -387,7 +397,8 @@ export class FileService implements IFileService { // Wrap EISDIR errors (fs.open on a directory works, but you cannot read from it) err = new FileOperationError( nls.localize('fileIsDirectoryError', "File is directory"), - FileOperationResult.FILE_IS_DIRECTORY + FileOperationResult.FILE_IS_DIRECTORY, + options ); } if (decoder) { @@ -430,15 +441,24 @@ export class FileService implements IFileService { } }; + let currentPosition: number = (options && options.position) || null; + const readChunk = () => { - fs.read(fd, chunkBuffer, 0, chunkBuffer.length, null, (err, bytesRead) => { + fs.read(fd, chunkBuffer, 0, chunkBuffer.length, currentPosition, (err, bytesRead) => { totalBytesRead += bytesRead; + if (typeof currentPosition === 'number') { + // if we received a position argument as option we need to ensure that + // we advance the position by the number of bytesread + currentPosition += bytesRead; + } + if (totalBytesRead > MAX_FILE_SIZE) { // stop when reading too much finish(new FileOperationError( nls.localize('fileTooLargeError', "File too large to open"), - FileOperationResult.FILE_TOO_LARGE + FileOperationResult.FILE_TOO_LARGE, + options )); } else if (err) { // some error happened @@ -460,7 +480,8 @@ export class FileService implements IFileService { // Return error early if client only accepts text and this is not text finish(new FileOperationError( nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), - FileOperationResult.FILE_IS_BINARY + FileOperationResult.FILE_IS_BINARY, + options )); } else { @@ -470,7 +491,7 @@ export class FileService implements IFileService { handleChunk(bytesRead); } - }).then(undefined, err => { + }).then(void 0, err => { // failed to get encoding finish(err); }); @@ -484,9 +505,15 @@ export class FileService implements IFileService { }); } - //#endregion - public updateContent(resource: uri, value: string, options: IUpdateContentOptions = Object.create(null)): TPromise { + if (this.options.elevationSupport && options.writeElevated) { + return this.doUpdateContentElevated(resource, value, options); + } + + return this.doUpdateContent(resource, value, options); + } + + private doUpdateContent(resource: uri, value: string, options: IUpdateContentOptions = Object.create(null)): TPromise { const absolutePath = this.toAbsolutePath(resource); // 1.) check file @@ -521,7 +548,7 @@ export class FileService implements IFileService { return addBomPromise.then(addBom => { // 4.) set contents and resolve - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { mode: 0o666, flag: 'w' }).then(undefined, error => { + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite).then(void 0, error => { if (!exists || error.code !== 'EPERM' || !isWindows) { return TPromise.wrapError(error); } @@ -534,15 +561,25 @@ export class FileService implements IFileService { return pfs.truncate(absolutePath, 0).then(() => { // 6.) set contents (this time with r+ mode) and resolve again - return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { mode: 0o666, flag: 'r+' }); + return this.doSetContentsAndResolve(resource, absolutePath, value, addBom, encodingToWrite, { flag: 'r+' }); }); }); }); }); + }).then(null, error => { + if (error.code === 'EACCES' || error.code === 'EPERM') { + return TPromise.wrapError(new FileOperationError( + nls.localize('filePermission', "Permission denied writing to file ({0})", resource.toString(true)), + FileOperationResult.FILE_PERMISSION_DENIED, + options + )); + } + + return TPromise.wrapError(error); }); } - private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string, addBOM: boolean, encodingToWrite: string, options: { mode?: number; flag?: string; }): TPromise { + private doSetContentsAndResolve(resource: uri, absolutePath: string, value: string, addBOM: boolean, encodingToWrite: string, options?: { mode?: number; flag?: string; }): TPromise { let writeFilePromise: TPromise; // Write fast if we do UTF 8 without BOM @@ -564,6 +601,65 @@ export class FileService implements IFileService { }); } + private doUpdateContentElevated(resource: uri, value: string, options: IUpdateContentOptions = Object.create(null)): TPromise { + const absolutePath = this.toAbsolutePath(resource); + + // 1.) check file + return this.checkFile(absolutePath, options, options.overwriteReadonly /* ignore readonly if we overwrite readonly, this is handled via sudo later */).then(exists => { + const writeOptions: IUpdateContentOptions = assign(Object.create(null), options); + writeOptions.writeElevated = false; + writeOptions.encoding = this.getEncoding(resource, options.encoding); + + // 2.) write to a temporary file to be able to copy over later + const tmpPath = paths.join(this.tmpPath, `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`); + return this.updateContent(uri.file(tmpPath), value, writeOptions).then(() => { + + // 3.) invoke our CLI as super user + return (import('sudo-prompt')).then(sudoPrompt => { + return new TPromise((c, e) => { + const promptOptions = { name: this.options.elevationSupport.promptTitle.replace('-', ''), icns: this.options.elevationSupport.promptIcnsPath }; + + const sudoCommand: string[] = [`"${this.options.elevationSupport.cliPath}"`]; + if (options.overwriteReadonly) { + sudoCommand.push('--file-chmod'); + } + sudoCommand.push('--file-write', `"${tmpPath}"`, `"${absolutePath}"`); + + sudoPrompt.exec(sudoCommand.join(' '), promptOptions, (error: string, stdout: string, stderr: string) => { + if (error || stderr) { + e(error || stderr); + } else { + c(void 0); + } + }); + }); + }).then(() => { + + // 3.) delete temp file + return pfs.del(tmpPath, this.tmpPath).then(() => { + + // 4.) resolve again + return this.resolve(resource); + }); + }); + }); + }).then(null, error => { + if (this.options.verboseLogging) { + this.options.errorLogger(`Unable to write to file '${resource.toString(true)}' as elevated user (${error})`); + } + + if (!(error instanceof FileOperationError)) { + error = new FileOperationError( + nls.localize('filePermission', "Permission denied writing to file ({0})", resource.toString(true)), + FileOperationResult.FILE_PERMISSION_DENIED, + options + ); + } + + return TPromise.wrapError(error); + }); + } + public createFile(resource: uri, content: string = '', options: ICreateFileOptions = Object.create(null)): TPromise { const absolutePath = this.toAbsolutePath(resource); @@ -579,7 +675,8 @@ export class FileService implements IFileService { if (exists && !options.overwrite) { return TPromise.wrapError(new FileOperationError( nls.localize('fileExists', "File to create already exists ({0})", resource.toString(true)), - FileOperationResult.FILE_MODIFIED_SINCE + FileOperationResult.FILE_MODIFIED_SINCE, + options )); } @@ -835,7 +932,7 @@ export class FileService implements IFileService { return null; } - private checkFile(absolutePath: string, options: IUpdateContentOptions = Object.create(null)): TPromise { + private checkFile(absolutePath: string, options: IUpdateContentOptions = Object.create(null), ignoreReadonly?: boolean): TPromise { return pfs.exists(absolutePath).then(exists => { if (exists) { return pfs.stat(absolutePath).then(stat => { @@ -848,24 +945,30 @@ export class FileService implements IFileService { // Find out if content length has changed if (options.etag !== etag(stat.size, options.mtime)) { - return TPromise.wrapError(new FileOperationError(nls.localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE)); + return TPromise.wrapError(new FileOperationError(nls.localize('fileModifiedError', "File Modified Since"), FileOperationResult.FILE_MODIFIED_SINCE, options)); } } - let mode = stat.mode; - const readonly = !(mode & 128); - // Throw if file is readonly and we are not instructed to overwrite - if (readonly && !options.overwriteReadonly) { - return TPromise.wrapError(new FileOperationError( - nls.localize('fileReadOnlyError', "File is Read Only"), - FileOperationResult.FILE_READ_ONLY - )); - } + if (!ignoreReadonly && !(stat.mode & 128) /* readonly */) { + if (!options.overwriteReadonly) { + return this.readOnlyError(options); + } - if (readonly) { + // Try to change mode to writeable + let mode = stat.mode; mode = mode | 128; - return pfs.chmod(absolutePath, mode).then(() => exists); + return pfs.chmod(absolutePath, mode).then(() => { + + // Make sure to check the mode again, it could have failed + return pfs.stat(absolutePath).then(stat => { + if (!(stat.mode & 128) /* readonly */) { + return this.readOnlyError(options); + } + + return exists; + }); + }); } return TPromise.as(exists); @@ -876,6 +979,14 @@ export class FileService implements IFileService { }); } + private readOnlyError(options: IUpdateContentOptions): TPromise { + return TPromise.wrapError(new FileOperationError( + nls.localize('fileReadOnlyError', "File is Read Only"), + FileOperationResult.FILE_READ_ONLY, + options + )); + } + public watchFileChanges(resource: uri): void { assert.ok(resource && resource.scheme === 'file', `Invalid resource for watching: ${resource}`); @@ -1145,4 +1256,4 @@ export class StatResolver { }); }); } -} +} \ No newline at end of file diff --git a/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe b/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe index 4d4b6737825..3574c753fb0 100644 Binary files a/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe and b/src/vs/workbench/services/files/node/watcher/win32/CodeHelper.exe differ diff --git a/src/vs/workbench/services/files/test/node/fileService.test.ts b/src/vs/workbench/services/files/test/node/fileService.test.ts index 615f13379ba..3a97a2a5cec 100644 --- a/src/vs/workbench/services/files/test/node/fileService.test.ts +++ b/src/vs/workbench/services/files/test/node/fileService.test.ts @@ -524,7 +524,7 @@ suite('FileService', () => { test('resolveFile', function (done: () => void) { service.resolveFile(uri.file(testDir), { resolveTo: [uri.file(path.join(testDir, 'deep'))] }).done(r => { - assert.equal(r.children.length, 7); + assert.equal(r.children.length, 8); const deep = utils.getByName(r, 'deep'); assert.equal(deep.children.length, 4); @@ -540,7 +540,7 @@ suite('FileService', () => { ]).then(res => { const r1 = res[0].stat; - assert.equal(r1.children.length, 7); + assert.equal(r1.children.length, 8); const deep = utils.getByName(r1, 'deep'); assert.equal(deep.children.length, 4); @@ -879,4 +879,22 @@ suite('FileService', () => { }); }); }); + + test('resolveContent - from position (ASCII)', function (done: () => void) { + const resource = uri.file(path.join(testDir, 'small.txt')); + + service.resolveContent(resource, { position: 6 }).done(content => { + assert.equal(content.value, 'File'); + done(); + }, error => onError(error, done)); + }); + + test('resolveContent - from position (with umlaut)', function (done: () => void) { + const resource = uri.file(path.join(testDir, 'small_umlaut.txt')); + + service.resolveContent(resource, { position: new Buffer('Small File with Ü').length }).done(content => { + assert.equal(content.value, 'mlaut'); + done(); + }, error => onError(error, done)); + }); }); diff --git a/src/vs/workbench/services/files/test/node/fixtures/service/small_umlaut.txt b/src/vs/workbench/services/files/test/node/fixtures/service/small_umlaut.txt new file mode 100644 index 00000000000..a01c1626b30 --- /dev/null +++ b/src/vs/workbench/services/files/test/node/fixtures/service/small_umlaut.txt @@ -0,0 +1 @@ +Small File with Ümlaut \ No newline at end of file diff --git a/src/vs/workbench/services/history/electron-browser/history.ts b/src/vs/workbench/services/history/electron-browser/history.ts index e4659d11135..c4e2ebda591 100644 --- a/src/vs/workbench/services/history/electron-browser/history.ts +++ b/src/vs/workbench/services/history/electron-browser/history.ts @@ -89,22 +89,83 @@ interface IEditorIdentifier { position: GroupPosition; } -export abstract class BaseHistoryService { +interface IStackEntry { + input: IEditorInput | IResourceInput; + selection?: ITextEditorSelection; + timestamp: number; +} - protected toUnbind: IDisposable[]; +interface IRecentlyClosedFile { + resource: URI; + index: number; +} + +export class HistoryService implements IHistoryService { + + public _serviceBrand: any; + + private static readonly STORAGE_KEY = 'history.entries'; + private static readonly MAX_HISTORY_ITEMS = 200; + private static readonly MAX_STACK_ITEMS = 20; + private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20; + + private toUnbind: IDisposable[]; private activeEditorListeners: IDisposable[]; private lastActiveEditor: IEditorIdentifier; + private stack: IStackEntry[]; + private index: number; + private lastIndex: number; + private navigatingInStack: boolean; + private currentTextEditorState: TextEditorState; + + private history: (IEditorInput | IResourceInput)[]; + private recentlyClosedFiles: IRecentlyClosedFile[]; + private loaded: boolean; + private resourceFilter: ResourceGlobMatcher; + constructor( - protected editorGroupService: IEditorGroupService, - protected editorService: IWorkbenchEditorService + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IStorageService private storageService: IStorageService, + @IConfigurationService private configurationService: IConfigurationService, + @ILifecycleService private lifecycleService: ILifecycleService, + @IFileService private fileService: IFileService, + @IWindowsService private windowService: IWindowsService, + @IInstantiationService private instantiationService: IInstantiationService, ) { this.toUnbind = []; this.activeEditorListeners = []; - // Listeners + this.index = -1; + this.lastIndex = -1; + this.stack = []; + this.recentlyClosedFiles = []; + this.loaded = false; + this.resourceFilter = instantiationService.createInstance( + ResourceGlobMatcher, + (root: URI) => this.getExcludes(root), + (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') + ); + + this.registerListeners(); + } + + private getExcludes(root?: URI): IExpression { + const scope = root ? { resource: root } : void 0; + + return getExcludes(this.configurationService.getValue(scope)); + } + + private registerListeners(): void { this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + this.toUnbind.push(this.lifecycleService.onShutdown(reason => this.saveHistory())); + this.toUnbind.push(this.editorGroupService.onEditorOpenFail(editor => this.remove(editor))); + this.toUnbind.push(this.editorGroupService.getStacksModel().onEditorClosed(event => this.onEditorClosed(event))); + this.toUnbind.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); + this.toUnbind.push(this.resourceFilter.onExpressionChange(() => this.handleExcludesChange())); } private onEditorsChanged(): void { @@ -148,94 +209,6 @@ export abstract class BaseHistoryService { return identifier.editor.matches(editor.input); } - protected abstract handleExcludesChange(): void; - - protected abstract handleEditorSelectionChangeEvent(editor?: IBaseEditor, event?: ICursorPositionChangedEvent): void; - - protected abstract handleActiveEditorChange(editor?: IBaseEditor): void; - - public dispose(): void { - this.toUnbind = dispose(this.toUnbind); - } -} - -interface IStackEntry { - input: IEditorInput | IResourceInput; - selection?: ITextEditorSelection; - timestamp: number; -} - -interface IRecentlyClosedFile { - resource: URI; - index: number; -} - -export class HistoryService extends BaseHistoryService implements IHistoryService { - - public _serviceBrand: any; - - private static readonly STORAGE_KEY = 'history.entries'; - private static readonly MAX_HISTORY_ITEMS = 200; - private static readonly MAX_STACK_ITEMS = 20; - private static readonly MAX_RECENTLY_CLOSED_EDITORS = 20; - - private stack: IStackEntry[]; - private index: number; - private lastIndex: number; - private navigatingInStack: boolean; - private currentTextEditorState: TextEditorState; - - private history: (IEditorInput | IResourceInput)[]; - private recentlyClosedFiles: IRecentlyClosedFile[]; - private loaded: boolean; - private resourceFilter: ResourceGlobMatcher; - - constructor( - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEditorGroupService editorGroupService: IEditorGroupService, - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IStorageService private storageService: IStorageService, - @IConfigurationService private configurationService: IConfigurationService, - @ILifecycleService private lifecycleService: ILifecycleService, - @IFileService private fileService: IFileService, - @IWindowsService private windowService: IWindowsService, - @IInstantiationService private instantiationService: IInstantiationService, - ) { - super(editorGroupService, editorService); - - this.index = -1; - this.lastIndex = -1; - this.stack = []; - this.recentlyClosedFiles = []; - this.loaded = false; - this.resourceFilter = instantiationService.createInstance( - ResourceGlobMatcher, - (root: URI) => this.getExcludes(root), - (event: IConfigurationChangeEvent) => event.affectsConfiguration(FILES_EXCLUDE_CONFIG) || event.affectsConfiguration('search.exclude') - ); - - this.registerListeners(); - } - - private setIndex(value: number): void { - this.lastIndex = this.index; - this.index = value; - } - - private getExcludes(root?: URI): IExpression { - const scope = root ? { resource: root } : void 0; - - return getExcludes(this.configurationService.getValue(scope)); - } - - private registerListeners(): void { - this.toUnbind.push(this.lifecycleService.onShutdown(reason => this.saveHistory())); - this.toUnbind.push(this.editorGroupService.onEditorOpenFail(editor => this.remove(editor))); - this.toUnbind.push(this.editorGroupService.getStacksModel().onEditorClosed(event => this.onEditorClosed(event))); - this.toUnbind.push(this.fileService.onFileChanges(e => this.onFileChanges(e))); - this.toUnbind.push(this.resourceFilter.onExpressionChange(() => this.handleExcludesChange())); - } - private onFileChanges(e: FileChangesEvent): void { if (e.gotDeleted()) { this.remove(e); // remove from history files that got deleted or moved @@ -292,6 +265,11 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic this.navigate(); } + private setIndex(value: number): void { + this.lastIndex = this.index; + this.index = value; + } + private doForwardAcrossEditors(): void { let currentIndex = this.index; const currentEntry = this.stack[this.index]; @@ -840,4 +818,8 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic return void 0; } + + public dispose(): void { + this.toUnbind = dispose(this.toUnbind); + } } diff --git a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts index acb735b407f..f33d45cb29d 100644 --- a/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts +++ b/src/vs/workbench/services/keybinding/electron-browser/keybindingService.ts @@ -36,6 +36,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import { Extensions as ConfigExtensions, IConfigurationRegistry, IConfigurationNode } from 'vs/platform/configuration/common/configurationRegistry'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { release } from 'os'; export class KeyboardMapperFactory { public static readonly INSTANCE = new KeyboardMapperFactory(); @@ -583,6 +584,12 @@ const keyboardConfiguration: IConfigurationNode = { 'default': 'code', 'description': 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': { + 'type': 'boolean', + 'default': true, + 'description': nls.localize('touchbar.enabled', "Enables the macOS touchbar buttons on the keyboard if available."), + 'included': OS === OperatingSystem.Macintosh && parseFloat(release()) >= 16 // Minimum: macOS Sierra (10.12.x = darwin 16.x) } } }; diff --git a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts index 6b9bbd618b5..2d603d901f1 100644 --- a/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts +++ b/src/vs/workbench/services/keybinding/test/node/keybindingEditing.test.ts @@ -37,13 +37,14 @@ import { IModeService } from 'vs/editor/common/services/modeService'; import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; -import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsEditingService } from 'vs/workbench/services/keybinding/common/keybindingEditing'; import { IUserFriendlyKeybinding } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKeybindingItem'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { IHashService } from 'vs/workbench/services/hash/common/hashService'; import { mkdirp } from 'vs/base/node/pfs'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; interface Modifiers { metaKey?: boolean; @@ -73,6 +74,7 @@ suite('Keybindings Editing', () => { instantiationService.stub(IWorkspaceContextService, new TestContextService()); const lifecycleService = new TestLifecycleService(); instantiationService.stub(ILifecycleService, lifecycleService); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); instantiationService.stub(IHashService, new TestHashService()); instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); instantiationService.stub(ITelemetryService, NullTelemetryService); diff --git a/src/vs/workbench/services/message/browser/messageService.ts b/src/vs/workbench/services/message/browser/messageService.ts index 77a2a3ceb86..26b29d8e4c0 100644 --- a/src/vs/workbench/services/message/browser/messageService.ts +++ b/src/vs/workbench/services/message/browser/messageService.ts @@ -136,17 +136,22 @@ export class WorkbenchMessageService implements IMessageService { } } - public confirm(confirmation: IConfirmation): boolean { + public confirm(confirmation: IConfirmation): TPromise { let messageText = confirmation.message; if (confirmation.detail) { messageText = messageText + '\n\n' + confirmation.detail; } - return window.confirm(messageText); + return TPromise.wrap(window.confirm(messageText)); } public confirmWithCheckbox(confirmation: IConfirmation): TPromise { - return TPromise.as({ confirmed: this.confirm(confirmation) } as IConfirmationResult); + return this.confirm(confirmation).then(confirmed => { + return { + confirmed, + checkboxChecked: false // unsupported + } as IConfirmationResult; + }); } public dispose(): void { diff --git a/src/vs/workbench/services/message/electron-browser/messageService.ts b/src/vs/workbench/services/message/electron-browser/messageService.ts index 079ccb80cac..0c04c556de5 100644 --- a/src/vs/workbench/services/message/electron-browser/messageService.ts +++ b/src/vs/workbench/services/message/electron-browser/messageService.ts @@ -40,7 +40,7 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe private showMessageBoxWithCheckbox(opts: Electron.MessageBoxOptions): TPromise { opts = this.massageMessageBoxOptions(opts); - return this.windowService.showMessageBoxWithCheckbox(opts).then(result => { + return this.windowService.showMessageBox(opts).then(result => { return { button: isLinux ? opts.buttons.length - result.button - 1 : result.button, checkboxChecked: result.checkboxChecked @@ -48,12 +48,10 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe }); } - public confirm(confirmation: IConfirmation): boolean { + public confirm(confirmation: IConfirmation): TPromise { const opts = this.getConfirmOptions(confirmation); - const result = this.showMessageBox(opts); - - return result === 0 ? true : false; + return this.showMessageBox(opts).then(result => result === 0 ? true : false); } private getConfirmOptions(confirmation: IConfirmation): Electron.MessageBoxOptions { @@ -97,7 +95,8 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe public choose(severity: Severity, message: string, options: string[], cancelId: number, modal: boolean = false): TPromise { if (modal) { const type: 'none' | 'info' | 'error' | 'question' | 'warning' = severity === Severity.Info ? 'question' : severity === Severity.Error ? 'error' : severity === Severity.Warning ? 'warning' : 'none'; - return TPromise.wrap(this.showMessageBox({ message, buttons: options, type, cancelId })); + + return this.showMessageBox({ message, buttons: options, type, cancelId }); } let onCancel: () => void = null; @@ -116,11 +115,10 @@ export class MessageService extends WorkbenchMessageService implements IChoiceSe return promise; } - private showMessageBox(opts: Electron.MessageBoxOptions): number { + private showMessageBox(opts: Electron.MessageBoxOptions): TPromise { opts = this.massageMessageBoxOptions(opts); - const result = this.windowService.showMessageBox(opts); - return isLinux ? opts.buttons.length - result - 1 : result; + return this.windowService.showMessageBox(opts).then(result => isLinux ? opts.buttons.length - result.button - 1 : result.button); } private massageMessageBoxOptions(opts: Electron.MessageBoxOptions): Electron.MessageBoxOptions { diff --git a/src/vs/workbench/services/progress/browser/progressService2.ts b/src/vs/workbench/services/progress/browser/progressService2.ts index b6de990d858..8c87a033e1c 100644 --- a/src/vs/workbench/services/progress/browser/progressService2.ts +++ b/src/vs/workbench/services/progress/browser/progressService2.ts @@ -7,7 +7,7 @@ import 'vs/css!./media/progressService2'; import * as dom from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IProgressService2, IProgressOptions, ProgressLocation, IProgress, IProgressStep, Progress, emptyProgress } from 'vs/platform/progress/common/progress'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; @@ -15,6 +15,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { StatusbarAlignment, IStatusbarRegistry, StatusbarItemDescriptor, Extensions, IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar'; import { TPromise } from 'vs/base/common/winjs.base'; import { always } from 'vs/base/common/async'; +import { ProgressBadge, IActivityService } from 'vs/workbench/services/activity/common/activity'; class WindowProgressItem implements IStatusbarItem { @@ -60,27 +61,27 @@ export class ProgressService2 implements IProgressService2 { private _stack: [IProgressOptions, Progress][] = []; constructor( + @IActivityService private _activityBar: IActivityService, @IViewletService private _viewletService: IViewletService ) { // } - withProgress(options: IProgressOptions, task: (progress: IProgress<{ message?: string, percentage?: number }>) => TPromise): void { + withProgress

, R=any>(options: IProgressOptions, task: (progress: IProgress) => P): P { + const { location } = options; switch (location) { case ProgressLocation.Window: - this._withWindowProgress(options, task); - break; + return this._withWindowProgress(options, task); case ProgressLocation.Scm: - this._withViewletProgress('workbench.view.scm', task); - break; + return this._withViewletProgress('workbench.view.scm', task); default: console.warn(`Bad progress location: ${location}`); + return undefined; } } - - private _withWindowProgress(options: IProgressOptions, callback: (progress: IProgress<{ message?: string, percentage?: number }>) => TPromise): void { + private _withWindowProgress

, R=any>(options: IProgressOptions, callback: (progress: IProgress<{ message?: string, percentage?: number }>) => P): P { const task: [IProgressOptions, Progress] = [options, new Progress(() => this._updateWindowProgress())]; @@ -104,7 +105,8 @@ export class ProgressService2 implements IProgressService2 { }, 150); // cancel delay if promise finishes below 150ms - always(promise, () => clearTimeout(delayHandle)); + always(TPromise.wrap(promise), () => clearTimeout(delayHandle)); + return promise; } private _updateWindowProgress(idx: number = 0) { @@ -138,45 +140,49 @@ export class ProgressService2 implements IProgressService2 { } } - private _withViewletProgress(viewletId: string, task: (progress: IProgress<{ message?: string, percentage?: number }>) => TPromise): void { + private _withViewletProgress

, R=any>(viewletId: string, task: (progress: IProgress<{ message?: string, percentage?: number }>) => P): P { const promise = task(emptyProgress); // show in viewlet const viewletProgress = this._viewletService.getProgressIndicator(viewletId); if (viewletProgress) { - viewletProgress.showWhile(promise); + viewletProgress.showWhile(TPromise.wrap(promise)); } // show activity bar - // let activityProgress: IDisposable; - // let delayHandle = setTimeout(() => { - // delayHandle = undefined; - // const handle = this._activityBar.showActivity( - // viewletId, - // new ProgressBadge(() => ''), - // 'progress-badge' - // ); - // const startTimeVisible = Date.now(); - // const minTimeVisible = 300; - // activityProgress = { - // dispose() { - // const d = Date.now() - startTimeVisible; - // if (d < minTimeVisible) { - // // should at least show for Nms - // setTimeout(() => handle.dispose(), minTimeVisible - d); - // } else { - // // shown long enough - // handle.dispose(); - // } - // } - // }; - // }, 300); + let activityProgress: IDisposable; + let delayHandle = setTimeout(() => { + delayHandle = undefined; + const handle = this._activityBar.showActivity( + viewletId, + new ProgressBadge(() => ''), + 'progress-badge', + 100 + ); + const startTimeVisible = Date.now(); + const minTimeVisible = 300; + activityProgress = { + dispose() { + const d = Date.now() - startTimeVisible; + if (d < minTimeVisible) { + // should at least show for Nms + setTimeout(() => handle.dispose(), minTimeVisible - d); + } else { + // shown long enough + handle.dispose(); + } + } + }; + }, 300); - // always(promise, () => { - // clearTimeout(delayHandle); - // dispose(activityProgress); - // }); + const onDone = () => { + clearTimeout(delayHandle); + dispose(activityProgress); + }; + + promise.then(onDone, onDone); + return promise; } } diff --git a/src/vs/workbench/services/scm/common/scm.ts b/src/vs/workbench/services/scm/common/scm.ts index 78c1252f599..78014460542 100644 --- a/src/vs/workbench/services/scm/common/scm.ts +++ b/src/vs/workbench/services/scm/common/scm.ts @@ -74,6 +74,8 @@ export interface ISCMInput { placeholder: string; readonly onDidChangePlaceholder: Event; + + lineWarningLength: number | undefined; } export interface ISCMRepository extends IDisposable { diff --git a/src/vs/workbench/services/scm/common/scmService.ts b/src/vs/workbench/services/scm/common/scmService.ts index db569fc7c57..ad66331ddc1 100644 --- a/src/vs/workbench/services/scm/common/scmService.ts +++ b/src/vs/workbench/services/scm/common/scmService.ts @@ -39,6 +39,8 @@ class SCMInput implements ISCMInput { private _onDidChangePlaceholder = new Emitter(); get onDidChangePlaceholder(): Event { return this._onDidChangePlaceholder.event; } + + public lineWarningLength: number | undefined = undefined; } class SCMRepository implements ISCMRepository { @@ -106,4 +108,4 @@ export class SCMService implements ISCMService { return repository; } -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts index 939edf660cc..50fa1077c40 100644 --- a/src/vs/workbench/services/search/node/ripgrepFileSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepFileSearch.ts @@ -39,6 +39,8 @@ function getRgArgs(config: IRawSearch, folderQuery: IFolderSearch, includePatter if (folderQuery.disregardIgnoreFiles !== false) { // Don't use .gitignore or .ignore args.push('--no-ignore'); + } else { + args.push('--no-ignore-parent'); } // Follow symlinks diff --git a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts index ac82683de63..820d95f57f6 100644 --- a/src/vs/workbench/services/search/node/ripgrepTextSearch.ts +++ b/src/vs/workbench/services/search/node/ripgrepTextSearch.ts @@ -375,8 +375,13 @@ function globExprsToRgGlobs(patterns: glob.IExpression, folder?: string, exclude const value = patterns[key]; key = trimTrailingSlash(folder ? getAbsoluteGlob(folder, key) : key); - // glob.ts requires forward slashes - key = key.replace(/\\/g, '/'); + // glob.ts requires forward slashes, but a UNC path still must start with \\ + // #38165 and #38151 + if (strings.startsWith(key, '\\\\')) { + key = '\\\\' + key.substr(2).replace(/\\/g, '/'); + } else { + key = key.replace(/\\/g, '/'); + } if (typeof value === 'boolean' && value) { globArgs.push(fixDriveC(key)); @@ -446,6 +451,8 @@ function getRgArgs(config: IRawSearch): IRgGlobResult { if (config.disregardIgnoreFiles) { // Don't use .gitignore or .ignore args.push('--no-ignore'); + } else { + args.push('--no-ignore-parent'); } // Follow symlinks 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 cdc6ada67da..2a18075876d 100644 --- a/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts +++ b/src/vs/workbench/services/search/test/node/textSearch.integrationTest.ts @@ -306,5 +306,10 @@ suite('Search-integration', function () { }); function makeExpression(...patterns: string[]): glob.IExpression { - return patterns.reduce((glob, cur) => { glob[cur] = true; return glob; }, Object.create(null)); -} \ No newline at end of file + return patterns.reduce((glob, pattern) => { + // glob.ts needs forward slashes + pattern = pattern.replace(/\\/g, '/'); + glob[pattern] = true; + return glob; + }, Object.create(null)); +} diff --git a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts index 8d9985a8413..cb2dba8832e 100644 --- a/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts +++ b/src/vs/workbench/services/textMate/electron-browser/TMSyntax.ts @@ -14,7 +14,7 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionMessageCollector } from 'vs/platform/extensions/common/extensionsRegistry'; import { ITokenizationSupport, TokenizationRegistry, IState, LanguageId } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { INITIAL, StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2 } from 'vscode-textmate'; +import { StackElement, IGrammar, Registry, IEmbeddedLanguagesMap as IEmbeddedLanguagesMap2 } from 'vscode-textmate'; import { IWorkbenchThemeService, ITokenColorizationRule } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService'; import { grammarsExtPoint, IEmbeddedLanguagesMap, ITMSyntaxExtensionPoint } from 'vs/workbench/services/textMate/electron-browser/TMGrammars'; @@ -94,13 +94,14 @@ export class TMLanguageRegistration { interface ICreateGrammarResult { languageId: LanguageId; grammar: IGrammar; + initialState: StackElement; containsEmbeddedLanguages: boolean; } export class TextMateService implements ITextMateService { public _serviceBrand: any; - private _grammarRegistry: Registry; + private _grammarRegistry: TPromise<[Registry, StackElement]>; private _modeService: IModeService; private _themeService: IWorkbenchThemeService; private _scopeRegistry: TMScopeRegistry; @@ -128,16 +129,7 @@ export class TextMateService implements ITextMateService { this._injectedEmbeddedLanguages = {}; this._languageToScope = new Map(); - this._grammarRegistry = new Registry({ - getFilePath: (scopeName: string) => { - return this._scopeRegistry.getFilePath(scopeName); - }, - getInjections: (scopeName: string) => { - return this._injections[scopeName]; - } - }); - this._updateTheme(); - this._themeService.onDidColorThemeChange((e) => this._updateTheme()); + this._grammarRegistry = null; grammarsExtPoint.setHandler((extensions) => { for (let i = 0; i < extensions.length; i++) { @@ -148,6 +140,23 @@ export class TextMateService implements ITextMateService { } }); + // Generate some color map until the grammar registry is loaded + let colorTheme = this._themeService.getColorTheme(); + let defaultForeground: Color = Color.transparent; + let defaultBackground: Color = Color.transparent; + for (let i = 0, len = colorTheme.tokenColors.length; i < len; i++) { + let rule = colorTheme.tokenColors[i]; + if (!rule.scope) { + if (rule.settings.foreground) { + defaultForeground = Color.fromHex(rule.settings.foreground); + } + if (rule.settings.background) { + defaultBackground = Color.fromHex(rule.settings.background); + } + } + } + TokenizationRegistry.setColorMap([null, defaultForeground, defaultBackground]); + this._modeService.onDidCreateMode((mode) => { let modeId = mode.getId(); if (this._languageToScope.has(modeId)) { @@ -156,6 +165,26 @@ export class TextMateService implements ITextMateService { }); } + private _getOrCreateGrammarRegistry(): TPromise<[Registry, StackElement]> { + if (!this._grammarRegistry) { + this._grammarRegistry = TPromise.wrap(import('vscode-textmate')).then(({ Registry, INITIAL }) => { + const grammarRegistry = new Registry({ + getFilePath: (scopeName: string) => { + return this._scopeRegistry.getFilePath(scopeName); + }, + getInjections: (scopeName: string) => { + return this._injections[scopeName]; + } + }); + this._updateTheme(grammarRegistry); + this._themeService.onDidColorThemeChange((e) => this._updateTheme(grammarRegistry)); + return <[Registry, StackElement]>[grammarRegistry, INITIAL]; + }); + } + + return this._grammarRegistry; + } + private static _toColorMap(colorMap: string[]): Color[] { let result: Color[] = [null]; for (let i = 1, len = colorMap.length; i < len; i++) { @@ -164,13 +193,13 @@ export class TextMateService implements ITextMateService { return result; } - private _updateTheme(): void { + private _updateTheme(grammarRegistry: Registry): void { let colorTheme = this._themeService.getColorTheme(); if (!this.compareTokenRules(colorTheme.tokenColors)) { return; } - this._grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); - let colorMap = TextMateService._toColorMap(this._grammarRegistry.getColorMap()); + grammarRegistry.setTheme({ name: colorTheme.label, settings: colorTheme.tokenColors }); + let colorMap = TextMateService._toColorMap(grammarRegistry.getColorMap()); let cssRules = generateTokensCSSForColorMap(colorMap); this._styleElement.innerHTML = cssRules; TokenizationRegistry.setColorMap(colorMap); @@ -295,15 +324,19 @@ export class TextMateService implements ITextMateService { let languageId = this._modeService.getLanguageIdentifier(modeId).id; let containsEmbeddedLanguages = (Object.keys(embeddedLanguages).length > 0); - return new TPromise((c, e, p) => { - this._grammarRegistry.loadGrammarWithEmbeddedLanguages(scopeName, languageId, embeddedLanguages, (err, grammar) => { - if (err) { - return e(err); - } - c({ - languageId: languageId, - grammar: grammar, - containsEmbeddedLanguages: containsEmbeddedLanguages + return this._getOrCreateGrammarRegistry().then((_res) => { + const [grammarRegistry, initialState] = _res; + return new TPromise((c, e, p) => { + grammarRegistry.loadGrammarWithEmbeddedLanguages(scopeName, languageId, embeddedLanguages, (err, grammar) => { + if (err) { + return e(err); + } + c({ + languageId: languageId, + grammar: grammar, + initialState: initialState, + containsEmbeddedLanguages: containsEmbeddedLanguages + }); }); }); }); @@ -311,7 +344,7 @@ export class TextMateService implements ITextMateService { private registerDefinition(modeId: string): void { this._createGrammar(modeId).then((r) => { - TokenizationRegistry.register(modeId, new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.containsEmbeddedLanguages)); + TokenizationRegistry.register(modeId, new TMTokenization(this._scopeRegistry, r.languageId, r.grammar, r.initialState, r.containsEmbeddedLanguages)); }, onUnexpectedError); } } @@ -323,17 +356,19 @@ class TMTokenization implements ITokenizationSupport { private readonly _grammar: IGrammar; private readonly _containsEmbeddedLanguages: boolean; private readonly _seenLanguages: boolean[]; + private readonly _initialState: StackElement; - constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, containsEmbeddedLanguages: boolean) { + constructor(scopeRegistry: TMScopeRegistry, languageId: LanguageId, grammar: IGrammar, initialState: StackElement, containsEmbeddedLanguages: boolean) { this._scopeRegistry = scopeRegistry; this._languageId = languageId; this._grammar = grammar; + this._initialState = initialState; this._containsEmbeddedLanguages = containsEmbeddedLanguages; this._seenLanguages = []; } public getInitialState(): IState { - return INITIAL; + return this._initialState; } public tokenize(line: string, state: IState, offsetDelta: number): TokenizationResult { diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts index ee7360e86b3..edeb287757f 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModel.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModel.ts @@ -145,8 +145,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil // We have received reports of users seeing delete events even though the file still // exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665). // Since we do not want to mark the model as orphaned, we have to check if the - // file is really gone and not just a faulty file event (TODO@Ben revisit when we - // have a more stable file watcher in place for this scenario). + // file is really gone and not just a faulty file event. checkOrphanedPromise = TPromise.timeout(100).then(() => { if (this.disposed) { return true; @@ -713,7 +712,8 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil overwriteEncoding: options.overwriteEncoding, mtime: this.lastResolvedDiskStat.mtime, encoding: this.getEncoding(), - etag: this.lastResolvedDiskStat.etag + etag: this.lastResolvedDiskStat.etag, + writeElevated: options.writeElevated }).then(stat => { diag(`doSave(${versionId}) - after updateContent()`, this.resource, new Date()); diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts index 0ceca07a1f2..96e31476b70 100644 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ b/src/vs/workbench/services/textfile/common/textFileService.ts @@ -14,7 +14,7 @@ import Event, { Emitter } from 'vs/base/common/event'; import platform = require('vs/base/common/platform'); import { IWindowsService } from 'vs/platform/windows/common/windows'; import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IRevertOptions, IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions } from 'vs/workbench/services/textfile/common/textfiles'; +import { IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext } from 'vs/workbench/services/textfile/common/textfiles'; import { ConfirmResult } from 'vs/workbench/common/editor'; import { ILifecycleService, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; @@ -30,6 +30,8 @@ import { IMessageService, Severity } from 'vs/platform/message/common/message'; import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IRevertOptions } from 'vs/platform/editor/common/editor'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export interface IBackupResult { didBackup: boolean; @@ -55,6 +57,8 @@ export abstract class TextFileService implements ITextFileService { private configuredAutoSaveOnFocusChange: boolean; private configuredAutoSaveOnWindowChange: boolean; + private autoSaveContext: IContextKey; + private configuredHotExit: string; constructor( @@ -68,7 +72,8 @@ export abstract class TextFileService implements ITextFileService { protected environmentService: IEnvironmentService, private backupFileService: IBackupFileService, private windowsService: IWindowsService, - private historyService: IHistoryService + private historyService: IHistoryService, + contextKeyService: IContextKeyService ) { this.toUnbind = []; @@ -79,6 +84,7 @@ export abstract class TextFileService implements ITextFileService { this.toUnbind.push(this._onFilesAssociationChange); this._models = this.instantiationService.createInstance(TextFileEditorModelManager); + this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); const configuration = this.configurationService.getValue(); this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; @@ -94,9 +100,9 @@ export abstract class TextFileService implements ITextFileService { abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise; - abstract promptForPath(defaultPath: string): string; + abstract promptForPath(defaultPath: string): TPromise; - abstract confirmSave(resources?: URI[]): ConfirmResult; + abstract confirmSave(resources?: URI[]): TPromise; public get onAutoSaveConfigurationChange(): Event { return this._onAutoSaveConfigurationChange.event; @@ -253,35 +259,36 @@ export abstract class TextFileService implements ITextFileService { } private confirmBeforeShutdown(): boolean | TPromise { - const confirm = this.confirmSave(); + return this.confirmSave().then(confirm => { - // Save - if (confirm === ConfirmResult.SAVE) { - return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => { - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } + // Save + if (confirm === ConfirmResult.SAVE) { + return this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }).then(result => { + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed + } + + return this.noVeto({ cleanUpBackups: true }); + }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + + // Make sure to revert untitled so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + this.untitledEditorService.revertAll(); return this.noVeto({ cleanUpBackups: true }); - }); - } + } - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); - - return this.noVeto({ cleanUpBackups: true }); - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - - return void 0; + return void 0; + }); } private noVeto(options: { cleanUpBackups: boolean }): boolean | TPromise { @@ -304,6 +311,7 @@ export abstract class TextFileService implements ITextFileService { const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + this.autoSaveContext.set(autoSaveMode); switch (autoSaveMode) { case AutoSaveConfiguration.AFTER_DELAY: this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; @@ -422,7 +430,7 @@ export abstract class TextFileService implements ITextFileService { private doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): TPromise { // Handle files first that can just be saved - return this.doSaveAllFiles(fileResources, options).then(result => { + return this.doSaveAllFiles(fileResources, options).then(async result => { // Preflight for untitled to handle cancellation from the dialog const targetsForUntitled: URI[] = []; @@ -438,7 +446,7 @@ export abstract class TextFileService implements ITextFileService { // Otherwise ask user else { - targetPath = this.promptForPath(this.suggestFileName(untitled)); + targetPath = await this.promptForPath(this.suggestFileName(untitled)); if (!targetPath) { return TPromise.as({ results: [...fileResources, ...untitledResources].map(r => { @@ -525,35 +533,43 @@ export abstract class TextFileService implements ITextFileService { return this.getFileModels(arg1).filter(model => model.isDirty()); } - public saveAs(resource: URI, target?: URI): TPromise { + public saveAs(resource: URI, target?: URI, options?: ISaveOptions): TPromise { // Get to target resource - if (!target) { + let targetPromise: TPromise; + if (target) { + targetPromise = TPromise.wrap(target); + } else { let dialogPath = resource.fsPath; if (resource.scheme === UNTITLED_SCHEMA) { dialogPath = this.suggestFileName(resource); } - const pathRaw = this.promptForPath(dialogPath); - if (pathRaw) { - target = URI.file(pathRaw); + targetPromise = this.promptForPath(dialogPath).then(pathRaw => { + if (pathRaw) { + return URI.file(pathRaw); + } + + return void 0; + }); + } + + return targetPromise.then(target => { + if (!target) { + return TPromise.as(null); // user canceled } - } - if (!target) { - return TPromise.as(null); // user canceled - } + // Just save if target is same as models own resource + if (resource.toString() === target.toString()) { + return this.save(resource, options).then(() => resource); + } - // Just save if target is same as models own resource - if (resource.toString() === target.toString()) { - return this.save(resource).then(() => resource); - } - - // Do it - return this.doSaveAs(resource, target); + // Do it + return this.doSaveAs(resource, target, options); + }); } - private doSaveAs(resource: URI, target?: URI): TPromise { + private doSaveAs(resource: URI, target?: URI, options?: ISaveOptions): TPromise { // Retrieve text model from provided resource if any let modelPromise: TPromise = TPromise.as(null); @@ -567,7 +583,7 @@ export abstract class TextFileService implements ITextFileService { // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) if (model) { - return this.doSaveTextFileAs(model, resource, target); + return this.doSaveTextFileAs(model, resource, target, options); } // Otherwise we can only copy @@ -583,7 +599,7 @@ export abstract class TextFileService implements ITextFileService { }); } - private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise { + private doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): TPromise { let targetModelResolver: TPromise; // Prefer an existing model if it is already loaded for the given target resource @@ -606,12 +622,12 @@ export abstract class TextFileService implements ITextFileService { targetModel.textEditorModel.setValue(sourceModel.getValue()); // save model - return targetModel.save(); + return targetModel.save(options); }, error => { // binary model: delete the file and run the operation again if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { - return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target)); + return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target, options)); } return TPromise.wrapError(error); diff --git a/src/vs/workbench/services/textfile/common/textfiles.ts b/src/vs/workbench/services/textfile/common/textfiles.ts index fc5bb023221..69c3ac4e428 100644 --- a/src/vs/workbench/services/textfile/common/textfiles.ts +++ b/src/vs/workbench/services/textfile/common/textfiles.ts @@ -13,6 +13,8 @@ import { IBaseStat, IResolveContentOptions } from 'vs/platform/files/common/file import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { ITextEditorModel } from 'vs/editor/common/services/resolverService'; import { IRawTextSource } from 'vs/editor/common/model/textSource'; +import { IRevertOptions } from 'vs/platform/editor/common/editor'; +import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; /** * The save error handler can be installed on the text text file editor model to install code that executes when save errors occur. @@ -89,6 +91,7 @@ export class TextFileModelChangeEvent { } export const TEXT_FILE_SERVICE_ID = 'textFileService'; +export const AutoSaveContext = new RawContextKey('config.files.autoSave', undefined); export interface ITextFileOperationResult { results: IResult[]; @@ -178,6 +181,7 @@ export interface ISaveOptions { overwriteReadonly?: boolean; overwriteEncoding?: boolean; skipSaveParticipants?: boolean; + writeElevated?: boolean; } export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport { @@ -210,19 +214,6 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport isDisposed(): boolean; } -export interface IRevertOptions { - - /** - * Forces to load the contents from disk again even if the file is not dirty. - */ - force?: boolean; - - /** - * A soft revert will clear dirty state of a file but not attempt to load the contents from disk. - */ - soft?: boolean; -} - export interface ITextFileService extends IDisposable { _serviceBrand: any; onAutoSaveConfigurationChange: Event; @@ -258,17 +249,20 @@ export interface ITextFileService extends IDisposable { * Saves the resource. * * @param resource the resource to save + * @param options optional save options * @return true if the resource was saved. */ save(resource: URI, options?: ISaveOptions): TPromise; /** - * Saves the provided resource asking the user for a file name. + * Saves the provided resource asking the user for a file name or using the provided one. * * @param resource the resource to save as. + * @param targetResource the optional target to save to. + * @param options optional save options * @return true if the file was saved. */ - saveAs(resource: URI, targetResource?: URI): TPromise; + saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): TPromise; /** * Saves the set of resources and returns a promise with the operation result. @@ -298,7 +292,7 @@ export interface ITextFileService extends IDisposable { * @param resources the resources of the files to ask for confirmation or null if * confirming for all dirty resources. */ - confirmSave(resources?: URI[]): ConfirmResult; + confirmSave(resources?: URI[]): TPromise; /** * Convinient fast access to the current auto save mode. @@ -314,4 +308,4 @@ export interface ITextFileService extends IDisposable { * Convinient fast access to the hot exit file setting. */ isHotExitEnabled: boolean; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts index 58049539538..181c10d73da 100644 --- a/src/vs/workbench/services/textfile/electron-browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/textFileService.ts @@ -29,6 +29,7 @@ import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows'; import { IHistoryService } from 'vs/workbench/services/history/common/history'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class TextFileService extends AbstractTextFileService { @@ -47,9 +48,10 @@ export class TextFileService extends AbstractTextFileService { @IMessageService messageService: IMessageService, @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService, - @IHistoryService historyService: IHistoryService + @IHistoryService historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, messageService, environmentService, backupFileService, windowsService, historyService); + super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, messageService, environmentService, backupFileService, windowsService, historyService, contextKeyService); } public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { @@ -69,14 +71,14 @@ export class TextFileService extends AbstractTextFileService { }); } - public confirmSave(resources?: URI[]): ConfirmResult { + public confirmSave(resources?: URI[]): TPromise { if (this.environmentService.isExtensionDevelopment) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) + return TPromise.wrap(ConfirmResult.DONT_SAVE); // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) } const resourcesToConfirm = this.getDirty(resources); if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; + return TPromise.wrap(ConfirmResult.DONT_SAVE); } const message = [ @@ -130,12 +132,10 @@ export class TextFileService extends AbstractTextFileService { opts.defaultId = 2; } - const choice = this.windowService.showMessageBox(opts); - - return buttons[choice].result; + return this.windowService.showMessageBox(opts).then(result => buttons[result.button].result); } - public promptForPath(defaultPath: string): string { + public promptForPath(defaultPath: string): TPromise { return this.windowService.showSaveDialog(this.getSaveDialogOptions(defaultPath)); } diff --git a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts index a79b9fdba62..37e0498d121 100644 --- a/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/colorThemeData.ts @@ -14,7 +14,6 @@ import nls = require('vs/nls'); import * as types from 'vs/base/common/types'; import * as objects from 'vs/base/common/objects'; -import * as plist from 'fast-plist'; import pfs = require('vs/base/node/pfs'); import { Extensions, IColorRegistry, ColorIdentifier, editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry'; @@ -81,10 +80,11 @@ export class ColorThemeData implements IColorTheme { public setCustomColors(colors: IColorCustomizations) { this.customColorMap = {}; - for (let id in colors) { - let colorVal = colors[id]; - if (typeof colorVal === 'string') { - this.customColorMap[id] = Color.fromHex(colorVal); + this.overwriteCustomColors(colors); + if (`[${this.settingsId}]` in colors) { + const themeSpecificColors = (colors[`[${this.settingsId}]`] || {}) as IColorCustomizations; + if (types.isObject(themeSpecificColors)) { + this.overwriteCustomColors(themeSpecificColors); } } if (this.themeTokenColors && this.themeTokenColors.length) { @@ -92,7 +92,33 @@ export class ColorThemeData implements IColorTheme { } } + private overwriteCustomColors(colors: IColorCustomizations) { + for (let id in colors) { + let colorVal = colors[id]; + if (typeof colorVal === 'string') { + this.customColorMap[id] = Color.fromHex(colorVal); + } + } + } + public setCustomTokenColors(customTokenColors: ITokenColorCustomizations) { + this.customTokenColors = []; + let customTokenColorsWithoutThemeSpecific: ITokenColorCustomizations = {}; + for (let key in customTokenColors) { + if (key[0] !== '[') { + customTokenColorsWithoutThemeSpecific[key] = customTokenColors[key]; + } + } + this.addCustomTokenColors(customTokenColorsWithoutThemeSpecific); + if (`[${this.settingsId}]` in customTokenColors) { + const themeSpecificTokenColors: ITokenColorCustomizations = customTokenColors[`[${this.settingsId}]`]; + if (types.isObject(themeSpecificTokenColors)) { + this.addCustomTokenColors(themeSpecificTokenColors); + } + } + } + + private addCustomTokenColors(customTokenColors: ITokenColorCustomizations) { let generalRules: ITokenColorizationRule[] = []; Object.keys(tokenGroupToScopesMap).forEach(key => { @@ -113,7 +139,7 @@ export class ColorThemeData implements IColorTheme { // Put the general customizations such as comments, strings, etc. first so that // they can be overridden by specific customizations like "string.interpolated" - this.customTokenColors = generalRules.concat(textMateRules); + this.customTokenColors = this.customTokenColors.concat(generalRules, textMateRules); } public ensureLoaded(themeService: WorkbenchThemeService): TPromise { @@ -289,25 +315,31 @@ function _loadColorThemeFromFile(themePath: string, resultRules: ITokenColorizat } } +let pListParser: Thenable<{ parse(content: string) }>; +function getPListParser() { + return pListParser || import('fast-plist'); +} + function _loadSyntaxTokensFromFile(themePath: string, resultRules: ITokenColorizationRule[], resultColors: IColorMap): TPromise { return pfs.readFile(themePath).then(content => { - try { - let contentValue = plist.parse(content.toString()); - let settings: ITokenColorizationRule[] = contentValue.settings; - if (!Array.isArray(settings)) { - return TPromise.wrapError(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); + return getPListParser().then(parser => { + try { + let contentValue = parser.parse(content.toString()); + let settings: ITokenColorizationRule[] = contentValue.settings; + if (!Array.isArray(settings)) { + return TPromise.wrapError(new Error(nls.localize('error.plist.invalidformat', "Problem parsing tmTheme file: {0}. 'settings' is not array."))); + } + convertSettings(settings, resultRules, resultColors); + return TPromise.as(null); + } catch (e) { + return TPromise.wrapError(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); } - convertSettings(settings, resultRules, resultColors); - return TPromise.as(null); - } catch (e) { - return TPromise.wrapError(new Error(nls.localize('error.cannotparse', "Problems parsing tmTheme file: {0}", e.message))); - } + }); }, error => { return TPromise.wrapError(new Error(nls.localize('error.cannotload', "Problems loading tmTheme file {0}: {1}", themePath, error.message))); }); } - function updateDefaultRuleSettings(defaultRule: ITokenColorizationRule, theme: ColorThemeData): ITokenColorizationRule { let foreground = theme.getColor(editorForeground) || theme.getDefault(editorForeground); let background = theme.getColor(editorBackground) || theme.getDefault(editorBackground); diff --git a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts index 5c12079ecf1..513e42fe9dc 100644 --- a/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts +++ b/src/vs/workbench/services/themes/electron-browser/fileIconThemeData.ts @@ -244,6 +244,9 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic let languageIds = associations.languageIds; if (languageIds) { + if (!languageIds.jsonc && languageIds.json) { + languageIds.jsonc = languageIds.json; + } for (let languageId in languageIds) { addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); result.hasFileIcons = true; diff --git a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts index 4518ccc5c3a..44674eadfef 100644 --- a/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/workbenchThemeService.ts @@ -64,7 +64,7 @@ function validateThemeId(theme: string): string { } export interface IColorCustomizations { - [colorId: string]: string; + [colorIdOrThemeSettingsId: string]: string | IColorCustomizations; } export class WorkbenchThemeService implements IWorkbenchThemeService { @@ -83,11 +83,11 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { private _configurationWriter: ConfigurationWriter; private get colorCustomizations(): IColorCustomizations { - return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING); + return this.configurationService.getValue(CUSTOM_WORKBENCH_COLORS_SETTING) || {}; } private get tokenColorCustomizations(): ITokenColorCustomizations { - return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING); + return this.configurationService.getValue(CUSTOM_EDITOR_COLORS_SETTING) || {}; } constructor( @@ -151,9 +151,26 @@ export class WorkbenchThemeService implements IWorkbenchThemeService { // update settings schema setting this.colorThemeStore.onDidChange(themes => { - colorThemeSettingSchema.enum = themes.map(t => t.settingsId); - colorThemeSettingSchema.enumDescriptions = themes.map(t => themeData.description || ''); + const enumDescription = themeData.description || ''; + + colorCustomizationsSchema.properties = colorThemeSchema.colorsSchema.properties; + const copyColorCustomizationsSchema = { ...colorCustomizationsSchema }; + copyColorCustomizationsSchema.properties = { ...colorThemeSchema.colorsSchema.properties }; + + customEditorColorSchema.properties = customEditorColorConfigurationProperties; + const copyCustomEditorColorSchema = { ...customEditorColorSchema }; + copyCustomEditorColorSchema.properties = { ...customEditorColorConfigurationProperties }; + + themes.forEach(t => { + colorThemeSettingSchema.enum.push(t.settingsId); + colorThemeSettingSchema.enumDescriptions.push(enumDescription); + const themeId = `[${t.settingsId}]`; + colorCustomizationsSchema.properties[themeId] = copyColorCustomizationsSchema; + customEditorColorSchema.properties[themeId] = copyCustomEditorColorSchema; + }); + configurationRegistry.notifyConfigurationSchemaUpdated(themeSettingsConfiguration); + configurationRegistry.notifyConfigurationSchemaUpdated(customEditorColorConfiguration); }); this.iconThemeStore.onDidChange(themes => { iconThemeSettingSchema.enum = [null, ...themes.map(t => t.settingsId)]; @@ -483,9 +500,9 @@ const iconThemeSettingSchema: IConfigurationPropertySchema = { errorMessage: nls.localize('iconThemeError', "File icon theme is unknown or not installed.") }; const colorCustomizationsSchema: IConfigurationPropertySchema = { - type: ['object'], + type: 'object', description: nls.localize('workbenchColors', "Overrides colors from the currently selected color theme."), - properties: colorThemeSchema.colorsSchema.properties, + properties: {}, additionalProperties: false, default: {}, defaultSnippets: [{ @@ -523,26 +540,29 @@ function tokenGroupSettings(description: string) { }; } -configurationRegistry.registerConfiguration({ +const customEditorColorConfigurationProperties = { + comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), + strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), + keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), + numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), + types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), + functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), + variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), + [CUSTOM_EDITOR_SCOPE_COLORS_SETTING]: colorThemeSchema.tokenColorsSchema(nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).')) +}; +const customEditorColorSchema: IConfigurationPropertySchema = { + description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), + default: {}, + additionalProperties: false, + properties: {} +}; +const customEditorColorConfiguration: IConfigurationNode = { id: 'editor', order: 7.2, type: 'object', properties: { - [CUSTOM_EDITOR_COLORS_SETTING]: { - description: nls.localize('editorColors', "Overrides editor colors and font style from the currently selected color theme."), - default: {}, - additionalProperties: false, - properties: { - comments: tokenGroupSettings(nls.localize('editorColors.comments', "Sets the colors and styles for comments")), - strings: tokenGroupSettings(nls.localize('editorColors.strings', "Sets the colors and styles for strings literals.")), - keywords: tokenGroupSettings(nls.localize('editorColors.keywords', "Sets the colors and styles for keywords.")), - numbers: tokenGroupSettings(nls.localize('editorColors.numbers', "Sets the colors and styles for number literals.")), - types: tokenGroupSettings(nls.localize('editorColors.types', "Sets the colors and styles for type declarations and references.")), - functions: tokenGroupSettings(nls.localize('editorColors.functions', "Sets the colors and styles for functions declarations and references.")), - variables: tokenGroupSettings(nls.localize('editorColors.variables', "Sets the colors and styles for variables declarations and references.")), - [CUSTOM_EDITOR_SCOPE_COLORS_SETTING]: colorThemeSchema.tokenColorsSchema(nls.localize('editorColors.textMateRules', 'Sets colors and styles using textmate theming rules (advanced).')) - } - } + [CUSTOM_EDITOR_COLORS_SETTING]: customEditorColorSchema } -}); +}; +configurationRegistry.registerConfiguration(customEditorColorConfiguration); diff --git a/src/vs/workbench/services/thread/common/threadService.ts b/src/vs/workbench/services/thread/common/threadService.ts deleted file mode 100644 index a6530eee39f..00000000000 --- a/src/vs/workbench/services/thread/common/threadService.ts +++ /dev/null @@ -1,43 +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'; - -export interface IThreadService { - /** - * Always returns a proxy. - */ - get(identifier: ProxyIdentifier): T; - - /** - * Register instance. - */ - set(identifier: ProxyIdentifier, value: R): R; - - /** - * Assert these identifiers are already registered via `.set`. - */ - assertRegistered(identifiers: ProxyIdentifier[]): void; -} - -export class ProxyIdentifier { - _proxyIdentifierBrand: void; - _suppressCompilerUnusedWarning: T; - - isMain: boolean; - id: string; - - constructor(isMain: boolean, id: string) { - this.isMain = isMain; - this.id = id; - } -} - -export function createMainContextProxyIdentifier(identifier: string): ProxyIdentifier { - return new ProxyIdentifier(true, 'm' + identifier); -} - -export function createExtHostContextProxyIdentifier(identifier: string): ProxyIdentifier { - return new ProxyIdentifier(false, 'e' + identifier); -} diff --git a/src/vs/workbench/services/thread/electron-browser/threadService.ts b/src/vs/workbench/services/thread/electron-browser/threadService.ts deleted file mode 100644 index 9bc4ac23210..00000000000 --- a/src/vs/workbench/services/thread/electron-browser/threadService.ts +++ /dev/null @@ -1,44 +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 strings from 'vs/base/common/strings'; -import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; -import { AbstractThreadService } from 'vs/workbench/services/thread/node/abstractThreadService'; -import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; - -// Enable to see detailed message communication between window and extension host -const logExtensionHostCommunication = false; - - -function asLoggingProtocol(protocol: IMessagePassingProtocol): IMessagePassingProtocol { - - protocol.onMessage(msg => { - console.log('%c[Extension \u2192 Window]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg); - }); - - return { - onMessage: protocol.onMessage, - - send(msg: any) { - protocol.send(msg); - console.log('%c[Window \u2192 Extension]%c[len: ' + strings.pad(msg.length, 5, ' ') + ']', 'color: darkgreen', 'color: grey', msg); - } - }; -} - - -export class MainThreadService extends AbstractThreadService implements IThreadService { - constructor(protocol: IMessagePassingProtocol, @IEnvironmentService environmentService: IEnvironmentService) { - if (logExtensionHostCommunication || environmentService.logExtensionHostCommunication) { - protocol = asLoggingProtocol(protocol); - } - - super(new RPCProtocol(protocol), true); - } -} diff --git a/src/vs/workbench/services/thread/node/abstractThreadService.ts b/src/vs/workbench/services/thread/node/abstractThreadService.ts deleted file mode 100644 index 7bc4ba3b38d..00000000000 --- a/src/vs/workbench/services/thread/node/abstractThreadService.ts +++ /dev/null @@ -1,86 +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 { TPromise } from 'vs/base/common/winjs.base'; -import { IDispatcher, RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; -import { ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; -import { CharCode } from 'vs/base/common/charCode'; - -declare var Proxy: any; // TODO@TypeScript - -export abstract class AbstractThreadService implements IDispatcher { - - private readonly _rpcProtocol: RPCProtocol; - private readonly _isMain: boolean; - protected readonly _locals: { [id: string]: any; }; - private readonly _proxies: { [id: string]: any; } = Object.create(null); - - constructor(rpcProtocol: RPCProtocol, isMain: boolean) { - this._rpcProtocol = rpcProtocol; - this._isMain = isMain; - this._locals = Object.create(null); - this._proxies = Object.create(null); - this._rpcProtocol.setDispatcher(this); - } - - public dispose(): void { - this._rpcProtocol.dispose(); - } - - public invoke(proxyId: string, methodName: string, args: any[]): any { - if (!this._locals[proxyId]) { - throw new Error('Unknown actor ' + proxyId); - } - let actor = this._locals[proxyId]; - let method = actor[methodName]; - if (typeof method !== 'function') { - throw new Error('Unknown method ' + methodName + ' on actor ' + proxyId); - } - return method.apply(actor, args); - } - - get(identifier: ProxyIdentifier): T { - if (!this._proxies[identifier.id]) { - this._proxies[identifier.id] = this._createProxy(identifier.id); - } - return this._proxies[identifier.id]; - } - - private _createProxy(proxyId: string): T { - let handler = { - get: (target, name: string) => { - if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) { - target[name] = (...myArgs: any[]) => { - return this._callOnRemote(proxyId, name, myArgs); - }; - } - return target[name]; - } - }; - return new Proxy(Object.create(null), handler); - } - - set(identifier: ProxyIdentifier, value: R): R { - if (identifier.isMain !== this._isMain) { - throw new Error('Mismatch in object registration!'); - } - this._locals[identifier.id] = value; - return value; - } - - assertRegistered(identifiers: ProxyIdentifier[]): void { - for (let i = 0, len = identifiers.length; i < len; i++) { - const identifier = identifiers[i]; - if (!this._locals[identifier.id]) { - throw new Error(`Missing actor ${identifier.id} (isMain: ${identifier.isMain})`); - } - } - } - - private _callOnRemote(proxyId: string, methodName: string, args: any[]): TPromise { - return this._rpcProtocol.callOnRemote(proxyId, methodName, args); - } -} diff --git a/src/vs/workbench/services/thread/node/extHostThreadService.ts b/src/vs/workbench/services/thread/node/extHostThreadService.ts deleted file mode 100644 index 00e135d8d8c..00000000000 --- a/src/vs/workbench/services/thread/node/extHostThreadService.ts +++ /dev/null @@ -1,15 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import { RPCProtocol } from 'vs/workbench/services/extensions/node/rpcProtocol'; -import { AbstractThreadService } from 'vs/workbench/services/thread/node/abstractThreadService'; -import { IThreadService } from 'vs/workbench/services/thread/common/threadService'; - -export class ExtHostThreadService extends AbstractThreadService implements IThreadService { - constructor(rpcProtocol: RPCProtocol) { - super(rpcProtocol, false); - } -} diff --git a/src/vs/workbench/services/timer/common/timerService.ts b/src/vs/workbench/services/timer/common/timerService.ts index fb523a04dc4..f6ca71866cb 100644 --- a/src/vs/workbench/services/timer/common/timerService.ts +++ b/src/vs/workbench/services/timer/common/timerService.ts @@ -83,14 +83,7 @@ export interface IStartupMetrics { export interface IInitData { start: number; - - appReady: number; - windowLoad: number; - - beforeLoadWorkbenchMain: number; - afterLoadWorkbenchMain: number; - isInitialStartup: boolean; hasAccessibilitySupport: boolean; } @@ -98,17 +91,5 @@ export interface IInitData { export interface ITimerService extends IInitData { _serviceBrand: any; - beforeDOMContentLoaded: number; - afterDOMContentLoaded: number; - - beforeWorkbenchOpen: number; - workbenchStarted: number; - - beforeExtensionLoad: number; - afterExtensionLoad: number; - - restoreViewletDuration: number; - restoreEditorsDuration: number; - readonly startupMetrics: IStartupMetrics; } diff --git a/src/vs/workbench/services/timer/node/timerService.ts b/src/vs/workbench/services/timer/node/timerService.ts index 2aafef25729..7a49b00ba65 100644 --- a/src/vs/workbench/services/timer/node/timerService.ts +++ b/src/vs/workbench/services/timer/node/timerService.ts @@ -6,7 +6,7 @@ import { ITimerService, IStartupMetrics, IInitData, IMemoryInfo } from 'vs/workbench/services/timer/common/timerService'; import { virtualMachineHint } from 'vs/base/node/id'; - +import * as perf from 'vs/base/common/performance'; import * as os from 'os'; export class TimerService implements ITimerService { @@ -14,38 +14,17 @@ export class TimerService implements ITimerService { public _serviceBrand: any; public readonly start: number; - public readonly appReady: number; public readonly windowLoad: number; - public readonly beforeLoadWorkbenchMain: number; - public readonly afterLoadWorkbenchMain: number; - public readonly isInitialStartup: boolean; public readonly hasAccessibilitySupport: boolean; - public beforeDOMContentLoaded: number; - public afterDOMContentLoaded: number; - - public beforeWorkbenchOpen: number; - public workbenchStarted: number; - - public beforeExtensionLoad: number; - public afterExtensionLoad: number; - - public restoreViewletDuration: number; - public restoreEditorsDuration: number; - - private _startupMetrics: IStartupMetrics; constructor(initData: IInitData, private isEmptyWorkbench: boolean) { this.start = initData.start; - this.appReady = initData.appReady; this.windowLoad = initData.windowLoad; - this.beforeLoadWorkbenchMain = initData.beforeLoadWorkbenchMain; - this.afterLoadWorkbenchMain = initData.afterLoadWorkbenchMain; - this.isInitialStartup = initData.isInitialStartup; this.hasAccessibilitySupport = initData.hasAccessibilitySupport; } @@ -93,15 +72,15 @@ export class TimerService implements ITimerService { this._startupMetrics = { version: 1, - ellapsed: this.workbenchStarted - start, + ellapsed: perf.getEntry('mark', 'didStartWorkbench').startTime - start, timers: { - ellapsedExtensions: this.afterExtensionLoad - this.beforeExtensionLoad, - ellapsedExtensionsReady: this.afterExtensionLoad - start, - ellapsedRequire: this.afterLoadWorkbenchMain - this.beforeLoadWorkbenchMain, - ellapsedViewletRestore: this.restoreViewletDuration, - ellapsedEditorRestore: this.restoreEditorsDuration, - ellapsedWorkbench: this.workbenchStarted - this.beforeWorkbenchOpen, - ellapsedWindowLoadToRequire: this.beforeLoadWorkbenchMain - this.windowLoad, + ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'), + ellapsedExtensionsReady: perf.getEntry('mark', 'didLoadExtensions').startTime - start, + ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'), + ellapsedEditorRestore: perf.getDuration('willRestoreEditors', 'didRestoreEditors'), + ellapsedViewletRestore: perf.getDuration('willRestoreViewlet', 'didRestoreViewlet'), + ellapsedWorkbench: perf.getDuration('willStartWorkbench', 'didStartWorkbench'), + ellapsedWindowLoadToRequire: perf.getEntry('mark', 'willLoadWorkbenchMain').startTime - this.windowLoad, ellapsedTimersToTimersComputed: Date.now() - now }, platform, @@ -119,8 +98,8 @@ export class TimerService implements ITimerService { }; if (initialStartup) { - this._startupMetrics.timers.ellapsedAppReady = this.appReady - this.start; - this._startupMetrics.timers.ellapsedWindowLoad = this.windowLoad - this.appReady; + this._startupMetrics.timers.ellapsedAppReady = perf.getDuration('main:started', 'main:appReady'); + this._startupMetrics.timers.ellapsedWindowLoad = this.windowLoad - perf.getEntry('mark', 'main:appReady').startTime; } } } diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index 3a74b1efe28..6a5b10c7bd0 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -8,6 +8,11 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const ITitleService = createDecorator('titleService'); +export interface ITitleProperties { + isPure?: boolean; + isAdmin?: boolean; +} + export interface ITitleService { _serviceBrand: any; @@ -20,4 +25,9 @@ export interface ITitleService { * Set the represented file name to the title if any. */ setRepresentedFilename(path: string): void; + + /** + * Update some environmental title properties. + */ + updateProperties(properties: ITitleProperties): void; } \ No newline at end of file diff --git a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts index 5803459ade3..7bb21530f42 100644 --- a/src/vs/workbench/services/workspace/node/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/node/workspaceEditingService.ts @@ -16,7 +16,7 @@ import { IWorkspaceIdentifier, IWorkspaceFolderCreationData } from 'vs/platform/ import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; import { migrateStorageToMultiRootWorkspace } from 'vs/platform/storage/common/migration'; -import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; +import { IStorageService } from 'vs/platform/storage/common/storage'; import { StorageService } from 'vs/platform/storage/common/storageService'; import { ConfigurationScope, IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { Registry } from 'vs/platform/registry/common/platform'; @@ -28,15 +28,11 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { distinct } from 'vs/base/common/arrays'; import { isLinux } from 'vs/base/common/platform'; import { isEqual } from 'vs/base/common/resources'; -import { Action } from 'vs/base/common/actions'; -import product from 'vs/platform/node/product'; export class WorkspaceEditingService implements IWorkspaceEditingService { public _serviceBrand: any; - private static readonly INFO_MESSAGE_KEY = 'enterWorkspace.message'; - constructor( @IJSONEditingService private jsonEditingService: IJSONEditingService, @IWorkspaceContextService private contextService: WorkspaceService, @@ -145,17 +141,14 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { if (result) { return this.migrate(result.workspace).then(() => { - // Show message to user (once) if entering workspace state - if (this.contextService.getWorkbenchState() !== WorkbenchState.WORKSPACE) { - this.informUserOnce(); // TODO@Ben remove me after a couple of releases - } + // TODO@Ben TODO@Sandeep the following requires ugly casts and should probably have a service interface // Reinitialize backup service - const backupFileService = this.backupFileService as BackupFileService; // TODO@Ben ugly cast + const backupFileService = this.backupFileService as BackupFileService; backupFileService.initialize(result.backupPath); // Reinitialize configuration service - const workspaceImpl = this.contextService as WorkspaceService; // TODO@Ben TODO@Sandeep ugly cast + const workspaceImpl = this.contextService as WorkspaceService; return workspaceImpl.initialize(result.workspace); }); } @@ -168,53 +161,6 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { }); } - private informUserOnce(): void { - if (product.quality !== 'stable') { - return; // only for stable - } - - if (this.storageService.getBoolean(WorkspaceEditingService.INFO_MESSAGE_KEY)) { - return; // user does not want to see it again - } - - const closeAction = new Action( - 'enterWorkspace.close', - nls.localize('enterWorkspace.close', "Close"), - null, - true, - () => TPromise.as(true) - ); - - const dontShowAgainAction = new Action( - 'enterWorkspace.dontShowAgain', - nls.localize('enterWorkspace.dontShowAgain', "Don't Show Again"), - null, - true, - () => { - this.storageService.store(WorkspaceEditingService.INFO_MESSAGE_KEY, true, StorageScope.GLOBAL); - - return TPromise.as(true); - } - ); - const moreInfoAction = new Action( - 'enterWorkspace.moreInfo', - nls.localize('enterWorkspace.moreInfo', "More Information"), - null, - true, - () => { - const uri = URI.parse('https://go.microsoft.com/fwlink/?linkid=861970'); - window.open(uri.toString(true)); - - return TPromise.as(true); - } - ); - - this.messageService.show(Severity.Info, { - message: nls.localize('enterWorkspace.prompt', "Learn more about working with multiple folders in VS Code."), - actions: [moreInfoAction, dontShowAgainAction, closeAction] - }); - } - private migrate(toWorkspace: IWorkspaceIdentifier): TPromise { // Storage (UI State) migration diff --git a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts index b9fcbd97a82..728be6d38ec 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostApiCommands.test.ts @@ -13,7 +13,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import * as types from 'vs/workbench/api/node/extHostTypes'; import * as EditorCommon from 'vs/editor/common/editorCommon'; import { Model as EditorModel } from 'vs/editor/common/model/model'; -import { TestThreadService } from './testThreadService'; +import { TestRPCProtocol } from './testRPCProtocol'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -32,7 +32,7 @@ import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import * as vscode from 'vscode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import 'vs/workbench/parts/search/electron-browser/search.contribution'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; const defaultSelector = { scheme: 'far' }; const model: EditorCommon.IModel = EditorModel.createFromString( @@ -45,7 +45,7 @@ const model: EditorCommon.IModel = EditorModel.createFromString( undefined, URI.parse('far://testing/file.b')); -let threadService: TestThreadService; +let rpcProtocol: TestRPCProtocol; let extHost: ExtHostLanguageFeatures; let mainThread: MainThreadLanguageFeatures; let commands: ExtHostCommands; @@ -63,7 +63,7 @@ suite('ExtHostLanguageFeatureCommands', function () { let inst: IInstantiationService; { let instantiationService = new TestInstantiationService(); - threadService = new TestThreadService(); + rpcProtocol = new TestRPCProtocol(); instantiationService.stub(IHeapService, { _serviceBrand: undefined, trackRecursive(args) { @@ -98,36 +98,36 @@ suite('ExtHostLanguageFeatureCommands', function () { inst = instantiationService; } - const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(threadService); + const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol); extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: [{ isDirty: false, versionId: model.getVersionId(), modeId: model.getLanguageIdentifier().language, - url: model.uri, + uri: model.uri, lines: model.getValue().split(model.getEOL()), EOL: model.getEOL(), }] }); - const extHostDocuments = new ExtHostDocuments(threadService, extHostDocumentsAndEditors); - threadService.set(ExtHostContext.ExtHostDocuments, extHostDocuments); + const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + rpcProtocol.set(ExtHostContext.ExtHostDocuments, extHostDocuments); const heapService = new ExtHostHeapService(); - commands = new ExtHostCommands(threadService, heapService, new NoopLogService()); - threadService.set(ExtHostContext.ExtHostCommands, commands); - threadService.setTestInstance(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, threadService)); + commands = new ExtHostCommands(rpcProtocol, heapService, new NullLogService()); + rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); + rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); ExtHostApiCommands.register(commands); - const diagnostics = new ExtHostDiagnostics(threadService); - threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); + const diagnostics = new ExtHostDiagnostics(rpcProtocol); + rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, heapService, diagnostics); - threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); + extHost = new ExtHostLanguageFeatures(rpcProtocol, extHostDocuments, commands, heapService, diagnostics); + rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost); - mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, threadService)); + mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol)); - threadService.sync().then(done, done); + rpcProtocol.sync().then(done, done); }); suiteTeardown(() => { @@ -140,7 +140,7 @@ suite('ExtHostLanguageFeatureCommands', function () { while (disposables.length) { disposables.pop().dispose(); } - threadService.sync() + rpcProtocol.sync() .then(() => done(), err => done(err)); }); @@ -154,13 +154,11 @@ suite('ExtHostLanguageFeatureCommands', function () { commands.executeCommand('vscode.executeWorkspaceSymbolProvider', true) ]; - // threadService.sync().then(() => { TPromise.join(promises).then(undefined, (err: any[]) => { assert.equal(err.length, 4); done(); return []; }); - // }); }); test('WorkspaceSymbols, back and forth', function (done) { @@ -182,7 +180,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - threadService.sync().then(() => { + rpcProtocol.sync().then(() => { commands.executeCommand('vscode.executeWorkspaceSymbolProvider', 'testing').then(value => { for (let info of value) { @@ -204,11 +202,11 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - await threadService.sync(); + await rpcProtocol.sync(); let symbols = await commands.executeCommand('vscode.executeWorkspaceSymbolProvider', ''); assert.equal(symbols.length, 1); - await threadService.sync(); + await rpcProtocol.sync(); symbols = await commands.executeCommand('vscode.executeWorkspaceSymbolProvider', '*'); assert.equal(symbols.length, 1); }); @@ -223,13 +221,11 @@ suite('ExtHostLanguageFeatureCommands', function () { commands.executeCommand('vscode.executeDefinitionProvider', true, false) ]; - // threadService.sync().then(() => { TPromise.join(promises).then(undefined, (err: any[]) => { assert.equal(err.length, 4); done(); return []; }); - // }); }); test('Definition, back and forth', function () { @@ -249,7 +245,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeDefinitionProvider', model.uri, new types.Position(0, 0)).then(values => { assert.equal(values.length, 4); for (let v of values) { @@ -295,7 +291,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - threadService.sync().then(() => { + rpcProtocol.sync().then(() => { commands.executeCommand('vscode.executeDocumentSymbolProvider', model.uri).then(values => { assert.equal(values.length, 2); let [first, second] = values; @@ -325,7 +321,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)).then(list => { assert.ok(list instanceof types.CompletionList); @@ -375,7 +371,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } }, [])); - threadService.sync().then(() => { + rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCompletionItemProvider', model.uri, new types.Position(0, 4)).then(list => { assert.ok(list instanceof types.CompletionList); assert.equal(list.isIncomplete, true); @@ -393,7 +389,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCodeActionProvider', model.uri, new types.Range(0, 0, 1, 1)).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -420,7 +416,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeCodeLensProvider', model.uri).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -442,7 +438,7 @@ suite('ExtHostLanguageFeatureCommands', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return commands.executeCommand('vscode.executeLinkProvider', model.uri).then(value => { assert.equal(value.length, 1); let [first] = value; diff --git a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts index 9ea21cf75fa..701e96bc603 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostCommands.test.ts @@ -8,11 +8,10 @@ import * as assert from 'assert'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { MainThreadCommandsShape } from 'vs/workbench/api/node/extHost.protocol'; -import { TPromise } from 'vs/base/common/winjs.base'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { OneGetThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostCommands', function () { @@ -21,16 +20,15 @@ suite('ExtHostCommands', function () { let lastUnregister: string; const shape = new class extends mock() { - $registerCommand(id: string): TPromise { - return undefined; + $registerCommand(id: string): void { + // } - $unregisterCommand(id: string): TPromise { + $unregisterCommand(id: string): void { lastUnregister = id; - return undefined; } }; - const commands = new ExtHostCommands(OneGetThreadService(shape), undefined, new NoopLogService()); + const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), undefined, new NullLogService()); commands.registerCommand('foo', (): any => { }).dispose(); assert.equal(lastUnregister, 'foo'); assert.equal(CommandsRegistry.getCommand('foo'), undefined); @@ -42,16 +40,15 @@ suite('ExtHostCommands', function () { let unregisterCounter = 0; const shape = new class extends mock() { - $registerCommand(id: string): TPromise { - return undefined; + $registerCommand(id: string): void { + // } - $unregisterCommand(id: string): TPromise { + $unregisterCommand(id: string): void { unregisterCounter += 1; - return undefined; } }; - const commands = new ExtHostCommands(OneGetThreadService(shape), undefined, new NoopLogService()); + const commands = new ExtHostCommands(SingleProxyRPCProtocol(shape), undefined, new NullLogService()); const reg = commands.registerCommand('foo', (): any => { }); reg.dispose(); reg.dispose(); diff --git a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts index 0601b4b7f74..9449ad6c56a 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostConfiguration.test.ts @@ -12,7 +12,7 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration import { MainThreadConfigurationShape, IConfigurationInitData } from 'vs/workbench/api/node/extHost.protocol'; import { TPromise } from 'vs/base/common/winjs.base'; import { ConfigurationModel } from 'vs/platform/configuration/common/configurationModels'; -import { TestThreadService } from './testThreadService'; +import { TestRPCProtocol } from './testRPCProtocol'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { IWorkspaceFolder, WorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; @@ -31,7 +31,7 @@ suite('ExtHostConfiguration', function () { if (!shape) { shape = new class extends mock() { }; } - return new ExtHostConfiguration(shape, new ExtHostWorkspace(new TestThreadService(), null), createConfigurationData(contents)); + return new ExtHostConfiguration(shape, new ExtHostWorkspace(new TestRPCProtocol(), null), createConfigurationData(contents)); } function createConfigurationData(contents: any): IConfigurationInitData { @@ -135,7 +135,7 @@ suite('ExtHostConfiguration', function () { test('inspect in no workspace context', function () { const testObject = new ExtHostConfiguration( new class extends mock() { }, - new ExtHostWorkspace(new TestThreadService(), null), + new ExtHostWorkspace(new TestRPCProtocol(), null), { defaults: new ConfigurationModel({ 'editor': { @@ -177,7 +177,7 @@ suite('ExtHostConfiguration', function () { folders[workspaceUri.toString()] = workspace; const testObject = new ExtHostConfiguration( new class extends mock() { }, - new ExtHostWorkspace(new TestThreadService(), { + new ExtHostWorkspace(new TestRPCProtocol(), { 'id': 'foo', 'folders': [aWorkspaceFolder(URI.file('foo'), 0)], 'name': 'foo' @@ -250,7 +250,7 @@ suite('ExtHostConfiguration', function () { const testObject = new ExtHostConfiguration( new class extends mock() { }, - new ExtHostWorkspace(new TestThreadService(), { + new ExtHostWorkspace(new TestRPCProtocol(), { 'id': 'foo', 'folders': [aWorkspaceFolder(firstRoot, 0), aWorkspaceFolder(secondRoot, 1)], 'name': 'foo' @@ -458,7 +458,7 @@ suite('ExtHostConfiguration', function () { const workspaceFolder = aWorkspaceFolder(URI.file('folder1'), 0); const testObject = new ExtHostConfiguration( new class extends mock() { }, - new ExtHostWorkspace(new TestThreadService(), { + new ExtHostWorkspace(new TestRPCProtocol(), { 'id': 'foo', 'folders': [workspaceFolder], 'name': 'foo' diff --git a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts index b0fee510b50..95bee35a936 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDiagnostics.test.ts @@ -6,23 +6,22 @@ 'use strict'; import * as assert from 'assert'; -import URI from 'vs/base/common/uri'; +import URI, { UriComponents } from 'vs/base/common/uri'; import Severity from 'vs/base/common/severity'; import { DiagnosticCollection } from 'vs/workbench/api/node/extHostDiagnostics'; import { Diagnostic, DiagnosticSeverity, Range } from 'vs/workbench/api/node/extHostTypes'; import { MainThreadDiagnosticsShape } from 'vs/workbench/api/node/extHost.protocol'; -import { TPromise } from 'vs/base/common/winjs.base'; import { IMarkerData } from 'vs/platform/markers/common/markers'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; suite('ExtHostDiagnostics', () => { class DiagnosticsShape extends mock() { - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { - return TPromise.as(null); + $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { + // } - $clear(owner: string): TPromise { - return TPromise.as(null); + $clear(owner: string): void { + // } } @@ -163,9 +162,9 @@ suite('ExtHostDiagnostics', () => { test('diagnostics collection, set tuple overrides, #11547', function () { - let lastEntries: [URI, IMarkerData[]][]; + let lastEntries: [UriComponents, IMarkerData[]][]; let collection = new DiagnosticCollection('test', new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { + $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); } @@ -237,9 +236,9 @@ suite('ExtHostDiagnostics', () => { test('diagnostic capping', function () { - let lastEntries: [URI, IMarkerData[]][]; + let lastEntries: [UriComponents, IMarkerData[]][]; let collection = new DiagnosticCollection('test', new class extends DiagnosticsShape { - $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { + $changeMany(owner: string, entries: [UriComponents, IMarkerData[]][]): void { lastEntries = entries; return super.$changeMany(owner, entries); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts index 06167349726..a4c0366b6e4 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentSaveParticipant.test.ts @@ -12,42 +12,55 @@ import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumen import { TextDocumentSaveReason, TextEdit, Position, EndOfLine } from 'vs/workbench/api/node/extHostTypes'; import { MainThreadEditorsShape, IWorkspaceResourceEdit } from 'vs/workbench/api/node/extHost.protocol'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/node/extHostDocumentSaveParticipant'; -import { OneGetThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import * as vscode from 'vscode'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; +import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostDocumentSaveParticipant', () => { let resource = URI.parse('foo:bar'); let mainThreadEditors = new class extends mock() { }; let documents: ExtHostDocuments; + let nullLogService = new NullLogService(); + let nullExtensionDescription: IExtensionDescription = { + id: 'nullExtensionDescription', + name: 'Null Extension Description', + publisher: 'vscode', + enableProposedApi: false, + engines: undefined, + extensionFolderPath: undefined, + isBuiltin: false, + version: undefined + }; setup(() => { - const documentsAndEditors = new ExtHostDocumentsAndEditors(OneGetThreadService(null)); + const documentsAndEditors = new ExtHostDocumentsAndEditors(SingleProxyRPCProtocol(null)); documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: [{ isDirty: false, modeId: 'foo', - url: resource, + uri: resource, versionId: 1, lines: ['foo'], EOL: '\n', }] }); - documents = new ExtHostDocuments(OneGetThreadService(null), documentsAndEditors); + documents = new ExtHostDocuments(SingleProxyRPCProtocol(null), documentsAndEditors); }); test('no listeners, no problem', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => assert.ok(true)); }); test('event delivery', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); let event: vscode.TextDocumentWillSaveEvent; - let sub = participant.onWillSaveTextDocumentEvent(function (e) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { event = e; }); @@ -61,10 +74,10 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, immutable', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); let event: vscode.TextDocumentWillSaveEvent; - let sub = participant.onWillSaveTextDocumentEvent(function (e) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { event = e; }); @@ -77,9 +90,9 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, bad listener', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); - let sub = participant.onWillSaveTextDocumentEvent(function (e) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { throw new Error('💀'); }); @@ -92,13 +105,13 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, bad listener doesn\'t prevent more events', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); - let sub1 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { throw new Error('💀'); }); let event: vscode.TextDocumentWillSaveEvent; - let sub2 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { event = e; }); @@ -111,14 +124,14 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, in subscriber order', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); let counter = 0; - let sub1 = participant.onWillSaveTextDocumentEvent(function (event) { + let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { assert.equal(counter++, 0); }); - let sub2 = participant.onWillSaveTextDocumentEvent(function (event) { + let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { assert.equal(counter++, 1); }); @@ -128,42 +141,39 @@ suite('ExtHostDocumentSaveParticipant', () => { }); }); - test('event delivery, ignore bad listeners', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors, { timeout: 5, errors: 1 }); + test('event delivery, ignore bad listeners', async () => { + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 5, errors: 1 }); let callCount = 0; - let sub = participant.onWillSaveTextDocumentEvent(function (event) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { callCount += 1; throw new Error('boom'); }); - return TPromise.join([ - participant.$participateInSave(resource, SaveReason.EXPLICIT), - participant.$participateInSave(resource, SaveReason.EXPLICIT), - participant.$participateInSave(resource, SaveReason.EXPLICIT), - participant.$participateInSave(resource, SaveReason.EXPLICIT) + await participant.$participateInSave(resource, SaveReason.EXPLICIT); + await participant.$participateInSave(resource, SaveReason.EXPLICIT); + await participant.$participateInSave(resource, SaveReason.EXPLICIT); + await participant.$participateInSave(resource, SaveReason.EXPLICIT); - ]).then(values => { - sub.dispose(); - assert.equal(callCount, 2); - }); + sub.dispose(); + assert.equal(callCount, 2); }); test('event delivery, overall timeout', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors, { timeout: 20, errors: 5 }); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 20, errors: 5 }); let callCount = 0; - let sub1 = participant.onWillSaveTextDocumentEvent(function (event) { + let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { callCount += 1; event.waitUntil(TPromise.timeout(17)); }); - let sub2 = participant.onWillSaveTextDocumentEvent(function (event) { + let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { callCount += 1; event.waitUntil(TPromise.timeout(17)); }); - let sub3 = participant.onWillSaveTextDocumentEvent(function (event) { + let sub3 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { callCount += 1; }); @@ -178,9 +188,9 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); - let sub = participant.onWillSaveTextDocumentEvent(function (event) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { event.waitUntil(TPromise.timeout(10)); event.waitUntil(TPromise.timeout(10)); @@ -194,9 +204,9 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil must be called sync', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); - let sub = participant.onWillSaveTextDocumentEvent(function (event) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { event.waitUntil(new TPromise((resolve, reject) => { setTimeout(() => { @@ -217,9 +227,9 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil will timeout', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors, { timeout: 5, errors: 3 }); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors, { timeout: 5, errors: 3 }); - let sub = participant.onWillSaveTextDocumentEvent(function (event) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (event) { event.waitUntil(TPromise.timeout(15)); }); @@ -232,14 +242,14 @@ suite('ExtHostDocumentSaveParticipant', () => { }); test('event delivery, waitUntil failure handling', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, mainThreadEditors); + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, mainThreadEditors); - let sub1 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { e.waitUntil(TPromise.wrapError(new Error('dddd'))); }); let event: vscode.TextDocumentWillSaveEvent; - let sub2 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { event = e; }); @@ -253,14 +263,14 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, pushEdits sync', () => { let edits: IWorkspaceResourceEdit[]; - const participant = new ExtHostDocumentSaveParticipant(documents, new class extends mock() { + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { $tryApplyWorkspaceEdit(_edits: IWorkspaceResourceEdit[]) { edits = _edits; return TPromise.as(true); } }); - let sub = participant.onWillSaveTextDocumentEvent(function (e) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); e.waitUntil(TPromise.as([TextEdit.setEndOfLine(EndOfLine.CRLF)])); }); @@ -276,14 +286,14 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, concurrent change', () => { let edits: IWorkspaceResourceEdit[]; - const participant = new ExtHostDocumentSaveParticipant(documents, new class extends mock() { + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { $tryApplyWorkspaceEdit(_edits: IWorkspaceResourceEdit[]) { edits = _edits; return TPromise.as(true); } }); - let sub = participant.onWillSaveTextDocumentEvent(function (e) { + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { // concurrent change from somewhere documents.$acceptModelChanged(resource.toString(), { @@ -310,19 +320,20 @@ suite('ExtHostDocumentSaveParticipant', () => { test('event delivery, two listeners -> two document states', () => { - const participant = new ExtHostDocumentSaveParticipant(documents, new class extends mock() { + const participant = new ExtHostDocumentSaveParticipant(nullLogService, documents, new class extends mock() { $tryApplyWorkspaceEdit(_edits: IWorkspaceResourceEdit[]) { for (const { resource, edits } of _edits) { + const uri = URI.revive(resource); for (const { newText, range } of edits) { - documents.$acceptModelChanged(resource.toString(), { + documents.$acceptModelChanged(uri.toString(), { changes: [{ range, rangeLength: undefined, text: newText }], eol: undefined, - versionId: documents.getDocumentData(resource).version + 1 + versionId: documents.getDocumentData(uri).version + 1 }, true); } } @@ -333,7 +344,7 @@ suite('ExtHostDocumentSaveParticipant', () => { const document = documents.getDocumentData(resource).document; - let sub1 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub1 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { // the document state we started with assert.equal(document.version, 1); assert.equal(document.getText(), 'foo'); @@ -341,7 +352,7 @@ suite('ExtHostDocumentSaveParticipant', () => { e.waitUntil(TPromise.as([TextEdit.insert(new Position(0, 0), 'bar')])); }); - let sub2 = participant.onWillSaveTextDocumentEvent(function (e) { + let sub2 = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { // the document state AFTER the first listener kicked in assert.equal(document.version, 2); assert.equal(document.getText(), 'barfoo'); @@ -359,4 +370,23 @@ suite('ExtHostDocumentSaveParticipant', () => { }); }); + + test('Log failing listener', function () { + let didLogSomething = false; + let participant = new ExtHostDocumentSaveParticipant(new class extends NullLogService { + error(message: string | Error, ...args: any[]): void { + didLogSomething = true; + } + }, documents, mainThreadEditors); + + + let sub = participant.getOnWillSaveTextDocumentEvent(nullExtensionDescription)(function (e) { + throw new Error('boom'); + }); + + return participant.$participateInSave(resource, SaveReason.EXPLICIT).then(() => { + sub.dispose(); + assert.equal(didLogSomething, true); + }); + }); }); diff --git a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts index e7be9b92b3a..b171ace99f9 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostDocumentsAndEditors.test.ts @@ -8,8 +8,6 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; -import { TPromise } from 'vs/base/common/winjs.base'; - suite('ExtHostDocumentsAndEditors', () => { @@ -17,7 +15,9 @@ suite('ExtHostDocumentsAndEditors', () => { setup(function () { editors = new ExtHostDocumentsAndEditors({ - get() { return undefined; } + getProxy: () => { return undefined; }, + set: undefined, + assertRegistered: undefined }); }); @@ -28,7 +28,7 @@ suite('ExtHostDocumentsAndEditors', () => { EOL: '\n', isDirty: true, modeId: 'fooLang', - url: URI.parse('foo:bar'), + uri: URI.parse('foo:bar'), versionId: 1, lines: [ 'first', @@ -37,7 +37,7 @@ suite('ExtHostDocumentsAndEditors', () => { }] }); - return new TPromise((resolve, reject) => { + return new Promise((resolve, reject) => { editors.onDidRemoveDocuments(e => { try { 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 e1fb7ffbb5d..342f7ea9407 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostLanguageFeatures.test.ts @@ -14,7 +14,7 @@ import * as EditorCommon from 'vs/editor/common/editorCommon'; import { Model as EditorModel } from 'vs/editor/common/model/model'; import { Position as EditorPosition } from 'vs/editor/common/core/position'; import { Range as EditorRange } from 'vs/editor/common/core/range'; -import { TestThreadService } from './testThreadService'; +import { TestRPCProtocol } from './testRPCProtocol'; import { IMarkerService } from 'vs/platform/markers/common/markers'; import { MarkerService } from 'vs/platform/markers/common/markerService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures'; @@ -44,7 +44,7 @@ import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import * as vscode from 'vscode'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; const defaultSelector = { scheme: 'far' }; const model: EditorCommon.IModel = EditorModel.createFromString( @@ -60,14 +60,14 @@ const model: EditorCommon.IModel = EditorModel.createFromString( let extHost: ExtHostLanguageFeatures; let mainThread: MainThreadLanguageFeatures; let disposables: vscode.Disposable[] = []; -let threadService: TestThreadService; +let rpcProtocol: TestRPCProtocol; let originalErrorHandler: (e: any) => any; suite('ExtHostLanguageFeatures', function () { suiteSetup(() => { - threadService = new TestThreadService(); + rpcProtocol = new TestRPCProtocol(); // Use IInstantiationService to get typechecking when instantiating let inst: IInstantiationService; @@ -87,33 +87,33 @@ suite('ExtHostLanguageFeatures', function () { originalErrorHandler = errorHandler.getUnexpectedErrorHandler(); setUnexpectedErrorHandler(() => { }); - const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(threadService); + const extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol); extHostDocumentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: [{ isDirty: false, versionId: model.getVersionId(), modeId: model.getLanguageIdentifier().language, - url: model.uri, + uri: model.uri, lines: model.getValue().split(model.getEOL()), EOL: model.getEOL(), }] }); - const extHostDocuments = new ExtHostDocuments(threadService, extHostDocumentsAndEditors); - threadService.set(ExtHostContext.ExtHostDocuments, extHostDocuments); + const extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors); + rpcProtocol.set(ExtHostContext.ExtHostDocuments, extHostDocuments); const heapService = new ExtHostHeapService(); - const commands = new ExtHostCommands(threadService, heapService, new NoopLogService()); - threadService.set(ExtHostContext.ExtHostCommands, commands); - threadService.setTestInstance(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, threadService)); + const commands = new ExtHostCommands(rpcProtocol, heapService, new NullLogService()); + rpcProtocol.set(ExtHostContext.ExtHostCommands, commands); + rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); - const diagnostics = new ExtHostDiagnostics(threadService); - threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); + const diagnostics = new ExtHostDiagnostics(rpcProtocol); + rpcProtocol.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, heapService, diagnostics); - threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); + extHost = new ExtHostLanguageFeatures(rpcProtocol, extHostDocuments, commands, heapService, diagnostics); + rpcProtocol.set(ExtHostContext.ExtHostLanguageFeatures, extHost); - mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, threadService)); + mainThread = rpcProtocol.set(MainContext.MainThreadLanguageFeatures, inst.createInstance(MainThreadLanguageFeatures, rpcProtocol)); }); suiteTeardown(() => { @@ -126,7 +126,7 @@ suite('ExtHostLanguageFeatures', function () { while (disposables.length) { disposables.pop().dispose(); } - return threadService.sync(); + return rpcProtocol.sync(); }); // --- outline @@ -139,10 +139,10 @@ suite('ExtHostLanguageFeatures', function () { } }); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { assert.equal(DocumentSymbolProviderRegistry.all(model).length, 1); d1.dispose(); - return threadService.sync(); + return rpcProtocol.sync(); }); }); @@ -159,7 +159,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentSymbols(model).then(value => { assert.equal(value.entries.length, 1); @@ -174,7 +174,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentSymbols(model).then(value => { assert.equal(value.entries.length, 1); @@ -201,7 +201,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeLensData(model).then(value => { assert.equal(value.length, 1); }); @@ -221,7 +221,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeLensData(model).then(value => { assert.equal(value.length, 1); @@ -245,7 +245,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeLensData(model).then(value => { assert.equal(value.length, 1); @@ -272,7 +272,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDefinitionsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); @@ -296,7 +296,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDefinitionsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 2); @@ -318,7 +318,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDefinitionsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 2); @@ -343,7 +343,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDefinitionsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); @@ -361,7 +361,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getImplementationsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); let [entry] = value; @@ -381,7 +381,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getTypeDefinitionsAtPosition(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); let [entry] = value; @@ -401,7 +401,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { getHover(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); let [entry] = value; @@ -419,7 +419,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { getHover(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 1); @@ -444,7 +444,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getHover(model, new EditorPosition(1, 1)).then(value => { assert.equal(value.length, 2); let [first, second] = value as Hover[]; @@ -468,7 +468,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { getHover(model, new EditorPosition(1, 1)).then(value => { @@ -487,7 +487,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -511,7 +511,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -535,7 +535,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -560,7 +560,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getOccurrencesAtPosition(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -584,7 +584,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideReferences(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 2); @@ -604,7 +604,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideReferences(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -630,7 +630,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideReferences(model, new EditorPosition(1, 2)).then(value => { assert.equal(value.length, 1); @@ -652,7 +652,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeActions(model, model.getFullModelRange()).then(value => { assert.equal(value.length, 2); @@ -677,7 +677,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeActions(model, model.getFullModelRange()).then(value => { assert.equal(value.length, 1); }); @@ -697,7 +697,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getCodeActions(model, model.getFullModelRange()).then(value => { assert.equal(value.length, 1); }); @@ -720,7 +720,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getWorkspaceSymbols('').then(value => { assert.equal(value.length, 1); @@ -742,7 +742,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return rename(model, new EditorPosition(1, 1), 'newName').then(value => { throw Error(); @@ -760,7 +760,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return rename(model, new EditorPosition(1, 1), 'newName').then(value => { assert.equal(value.rejectReason, 'evil'); @@ -784,7 +784,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return rename(model, new EditorPosition(1, 1), 'newName').then(value => { assert.equal(value.edits.length, 1); @@ -809,7 +809,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return rename(model, new EditorPosition(1, 1), 'newName').then(value => { assert.equal(value.edits.length, 2); // least relevant renamer @@ -837,7 +837,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSignatureHelp(model, new EditorPosition(1, 1)).then(value => { assert.ok(value); @@ -852,7 +852,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSignatureHelp(model, new EditorPosition(1, 1)).then(value => { assert.equal(value, undefined); @@ -876,7 +876,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { assert.equal(value.length, 1); assert.equal(value[0].suggestion.insertText, 'testing2'); @@ -898,7 +898,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { assert.equal(value.length, 1); assert.equal(value[0].suggestion.insertText, 'weak-selector'); @@ -920,7 +920,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { assert.equal(value.length, 2); assert.equal(value[0].suggestion.insertText, 'strong-1'); // sort by label @@ -944,7 +944,7 @@ suite('ExtHostLanguageFeatures', function () { }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { assert.equal(value[0].container.incomplete, undefined); @@ -960,7 +960,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { provideSuggestionItems(model, new EditorPosition(1, 1), 'none').then(value => { assert.equal(value[0].container.incomplete, true); @@ -977,7 +977,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }).then(value => { assert.equal(value.length, 2); let [first, second] = value; @@ -998,7 +998,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }); }); }); @@ -1016,7 +1016,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentFormattingEdits(model, { insertSpaces: true, tabSize: 4 }).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1033,7 +1033,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1054,7 +1054,7 @@ suite('ExtHostLanguageFeatures', function () { return [new types.TextEdit(new types.Range(0, 0, 1, 1), 'doc')]; } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1070,7 +1070,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getDocumentRangeFormattingEdits(model, new EditorRange(1, 1, 1, 1), { insertSpaces: true, tabSize: 4 }); }); }); @@ -1083,7 +1083,7 @@ suite('ExtHostLanguageFeatures', function () { } }, [';'])); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getOnTypeFormattingEdits(model, new EditorPosition(1, 1), ';', { insertSpaces: true, tabSize: 2 }).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1102,7 +1102,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getLinks(model).then(value => { assert.equal(value.length, 1); let [first] = value; @@ -1127,7 +1127,7 @@ suite('ExtHostLanguageFeatures', function () { } })); - return threadService.sync().then(() => { + return rpcProtocol.sync().then(() => { return getLinks(model).then(value => { assert.equal(value.length, 1); let [first] = value; diff --git a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts index 4c012a03b62..b5ec3f55274 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTextEditors.test.ts @@ -11,7 +11,7 @@ import { MainContext, MainThreadEditorsShape, IWorkspaceResourceEdit } from 'vs/ import URI from 'vs/base/common/uri'; import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/node/extHostDocumentsAndEditors'; -import { OneGetThreadService, TestThreadService } from 'vs/workbench/test/electron-browser/api/testThreadService'; +import { SingleProxyRPCProtocol, TestRPCProtocol } from 'vs/workbench/test/electron-browser/api/testRPCProtocol'; import { ExtHostEditors } from 'vs/workbench/api/node/extHostTextEditors'; suite('ExtHostTextEditors.applyWorkspaceEdit', () => { @@ -23,25 +23,25 @@ suite('ExtHostTextEditors.applyWorkspaceEdit', () => { setup(() => { workspaceResourceEdits = null; - let threadService = new TestThreadService(); - threadService.setTestInstance(MainContext.MainThreadEditors, new class extends mock() { + let rpcProtocol = new TestRPCProtocol(); + rpcProtocol.set(MainContext.MainThreadEditors, new class extends mock() { $tryApplyWorkspaceEdit(_workspaceResourceEdits: IWorkspaceResourceEdit[]): TPromise { workspaceResourceEdits = _workspaceResourceEdits; return TPromise.as(true); } }); - const documentsAndEditors = new ExtHostDocumentsAndEditors(OneGetThreadService(null)); + const documentsAndEditors = new ExtHostDocumentsAndEditors(SingleProxyRPCProtocol(null)); documentsAndEditors.$acceptDocumentsAndEditorsDelta({ addedDocuments: [{ isDirty: false, modeId: 'foo', - url: resource, + uri: resource, versionId: 1337, lines: ['foo'], EOL: '\n', }] }); - editors = new ExtHostEditors(threadService, documentsAndEditors); + editors = new ExtHostEditors(rpcProtocol, documentsAndEditors); }); test('uses version id if document available', () => { diff --git a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts index 26c9913d5b6..b62cac7b7e0 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostTreeViews.test.ts @@ -11,7 +11,7 @@ import { ExtHostTreeViews } from 'vs/workbench/api/node/extHostTreeViews'; import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands'; import { MainThreadTreeViewsShape, MainContext } from 'vs/workbench/api/node/extHost.protocol'; import { TreeDataProvider, TreeItem } from 'vscode'; -import { TestThreadService } from './testThreadService'; +import { TestRPCProtocol } from './testRPCProtocol'; import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { MainThreadCommands } from 'vs/workbench/api/electron-browser/mainThreadCommands'; @@ -19,7 +19,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { mock } from 'vs/workbench/test/electron-browser/api/mock'; import { TPromise } from 'vs/base/common/winjs.base'; import { TreeItemCollapsibleState, ITreeItem } from 'vs/workbench/common/views'; -import { NoopLogService } from 'vs/platform/log/common/log'; +import { NullLogService } from 'vs/platform/log/common/log'; suite('ExtHostTreeView', function () { @@ -56,7 +56,7 @@ suite('ExtHostTreeView', function () { labels = {}; nodes = {}; - let threadService = new TestThreadService(); + let rpcProtocol = new TestRPCProtocol(); // Use IInstantiationService to get typechecking when instantiating let inst: IInstantiationService; { @@ -64,9 +64,9 @@ suite('ExtHostTreeView', function () { inst = instantiationService; } - threadService.setTestInstance(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, threadService)); + rpcProtocol.set(MainContext.MainThreadCommands, inst.createInstance(MainThreadCommands, rpcProtocol)); target = new RecordingShape(); - testObject = new ExtHostTreeViews(target, new ExtHostCommands(threadService, new ExtHostHeapService(), new NoopLogService())); + testObject = new ExtHostTreeViews(target, new ExtHostCommands(rpcProtocol, new ExtHostHeapService(), new NullLogService())); onDidChangeTreeNode = new Emitter<{ key: string }>(); onDidChangeTreeKey = new Emitter(); testObject.registerTreeDataProvider('testNodeTreeProvider', aNodeTreeDataProvider()); diff --git a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts index 73399a9bd37..75f5cb5effe 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostWorkspace.test.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import { basename } from 'path'; import { ExtHostWorkspace } from 'vs/workbench/api/node/extHostWorkspace'; -import { TestThreadService } from './testThreadService'; +import { TestRPCProtocol } from './testRPCProtocol'; import { normalize } from 'vs/base/common/paths'; import { IWorkspaceFolderData } from 'vs/platform/workspace/common/workspace'; @@ -26,7 +26,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }); + const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/Applications/NewsWoWBot'), 0)], name: 'Test' }); assertAsRelativePath(ws, '/Coding/Applications/NewsWoWBot/bernd/das/brot', 'bernd/das/brot'); assertAsRelativePath(ws, '/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart', @@ -40,7 +40,7 @@ suite('ExtHostWorkspace', function () { test('asRelativePath, same paths, #11402', function () { const root = '/home/aeschli/workspaces/samples/docker'; const input = '/home/aeschli/workspaces/samples/docker'; - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }); + const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file(root), 0)], name: 'Test' }); assertAsRelativePath(ws, (input), input); @@ -49,20 +49,20 @@ suite('ExtHostWorkspace', function () { }); test('asRelativePath, no workspace', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), null); + const ws = new ExtHostWorkspace(new TestRPCProtocol(), null); assertAsRelativePath(ws, (''), ''); assertAsRelativePath(ws, ('/foo/bar'), '/foo/bar'); }); test('asRelativePath, multiple folders', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); + const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); assertAsRelativePath(ws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(ws, '/Coding/Two/files/out.txt', 'Two/files/out.txt'); assertAsRelativePath(ws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt'); }); test('slightly inconsistent behaviour of asRelativePath and getWorkspaceFolder, #31553', function () { - const mrws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); + const mrws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt'); assertAsRelativePath(mrws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -74,7 +74,7 @@ suite('ExtHostWorkspace', function () { assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', true); assertAsRelativePath(mrws, '/Coding/Two2/files/out.txt', '/Coding/Two2/files/out.txt', false); - const srws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }); + const srws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0)], name: 'Test' }); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt'); assertAsRelativePath(srws, '/Coding/One/file.txt', 'file.txt', false); assertAsRelativePath(srws, '/Coding/One/file.txt', 'One/file.txt', true); @@ -84,24 +84,24 @@ suite('ExtHostWorkspace', function () { }); test('getPath, legacy', function () { - let ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', name: 'Test', folders: [] }); + let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestThreadService(), null); + ws = new ExtHostWorkspace(new TestRPCProtocol(), null); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestThreadService(), undefined); + ws = new ExtHostWorkspace(new TestRPCProtocol(), undefined); assert.equal(ws.getPath(), undefined); - ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }); + ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('Folder'), 0), aWorkspaceFolderData(URI.file('Another/Folder'), 1)] }); assert.equal(ws.getPath().replace(/\\/g, '/'), '/Folder'); - ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }); + ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [aWorkspaceFolderData(URI.file('/Folder'), 0)] }); assert.equal(ws.getPath().replace(/\\/g, '/'), '/Folder'); }); test('WorkspaceFolder has name and index', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); + const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', folders: [aWorkspaceFolderData(URI.file('/Coding/One'), 0), aWorkspaceFolderData(URI.file('/Coding/Two'), 1)], name: 'Test' }); const [one, two] = ws.getWorkspaceFolders(); @@ -112,7 +112,7 @@ suite('ExtHostWorkspace', function () { }); test('getContainingWorkspaceFolder', function () { - const ws = new ExtHostWorkspace(new TestThreadService(), { + const ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ @@ -160,7 +160,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change event should have a delta, #29641', function () { - let ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', name: 'Test', folders: [] }); + let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }); let sub = ws.onDidChangeWorkspace(e => { assert.deepEqual(e.added, []); @@ -199,7 +199,7 @@ suite('ExtHostWorkspace', function () { }); test('Multiroot change event is immutable', function () { - let ws = new ExtHostWorkspace(new TestThreadService(), { id: 'foo', name: 'Test', folders: [] }); + let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [] }); let sub = ws.onDidChangeWorkspace(e => { assert.throws(() => { (e).added = []; @@ -213,7 +213,7 @@ suite('ExtHostWorkspace', function () { }); test('`vscode.workspace.getWorkspaceFolder(file)` don\'t return workspace folder when file open from command line. #36221', function () { - let ws = new ExtHostWorkspace(new TestThreadService(), { + let ws = new ExtHostWorkspace(new TestRPCProtocol(), { id: 'foo', name: 'Test', folders: [ aWorkspaceFolderData(URI.file('c:/Users/marek/Desktop/vsc_test/'), 0) ] diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadCommands.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadCommands.test.ts index 144bc7665b8..bc0f4d213f3 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadCommands.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadCommands.test.ts @@ -8,13 +8,13 @@ import * as assert from 'assert'; import { MainThreadCommands } from 'vs/workbench/api/electron-browser/mainThreadCommands'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; -import { OneGetThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol } from './testRPCProtocol'; suite('MainThreadCommands', function () { test('dispose on unregister', function () { - const commands = new MainThreadCommands(OneGetThreadService(null), undefined); + const commands = new MainThreadCommands(SingleProxyRPCProtocol(null), undefined); assert.equal(CommandsRegistry.getCommand('foo'), undefined); // register @@ -28,7 +28,7 @@ suite('MainThreadCommands', function () { test('unregister all on dispose', function () { - const commands = new MainThreadCommands(OneGetThreadService(null), undefined); + const commands = new MainThreadCommands(SingleProxyRPCProtocol(null), undefined); assert.equal(CommandsRegistry.getCommand('foo'), undefined); commands.$registerCommand('foo'); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts index b91637eeeae..4cf96c8b3e8 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadConfiguration.test.ts @@ -13,7 +13,7 @@ import { Extensions, IConfigurationRegistry, ConfigurationScope } from 'vs/platf import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { MainThreadConfiguration } from 'vs/workbench/api/electron-browser/mainThreadConfiguration'; -import { OneGetThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; import { WorkspaceService } from 'vs/workbench/services/configuration/node/configurationService'; @@ -56,7 +56,7 @@ suite('MainThreadConfiguration', function () { test('update resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); @@ -65,7 +65,7 @@ suite('MainThreadConfiguration', function () { test('update resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); @@ -74,7 +74,7 @@ suite('MainThreadConfiguration', function () { test('update resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', null); @@ -83,7 +83,7 @@ suite('MainThreadConfiguration', function () { test('update window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); @@ -92,7 +92,7 @@ suite('MainThreadConfiguration', function () { test('update window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); @@ -101,7 +101,7 @@ suite('MainThreadConfiguration', function () { test('update window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', URI.file('abc')); @@ -110,7 +110,7 @@ suite('MainThreadConfiguration', function () { test('update window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.window', 'value', null); @@ -119,7 +119,7 @@ suite('MainThreadConfiguration', function () { test('update resource configuration without configuration target defaults to folder', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(null, 'extHostConfiguration.resource', 'value', URI.file('abc')); @@ -128,7 +128,7 @@ suite('MainThreadConfiguration', function () { test('update configuration with user configuration target', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(ConfigurationTarget.USER, 'extHostConfiguration.window', 'value', URI.file('abc')); @@ -137,7 +137,7 @@ suite('MainThreadConfiguration', function () { test('update configuration with workspace configuration target', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE, 'extHostConfiguration.window', 'value', URI.file('abc')); @@ -146,7 +146,7 @@ suite('MainThreadConfiguration', function () { test('update configuration with folder configuration target', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$updateConfigurationOption(ConfigurationTarget.WORKSPACE_FOLDER, 'extHostConfiguration.window', 'value', URI.file('abc')); @@ -155,7 +155,7 @@ suite('MainThreadConfiguration', function () { test('remove resource configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); @@ -164,7 +164,7 @@ suite('MainThreadConfiguration', function () { test('remove resource configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); @@ -173,7 +173,7 @@ suite('MainThreadConfiguration', function () { test('remove resource configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', null); @@ -182,7 +182,7 @@ suite('MainThreadConfiguration', function () { test('remove window configuration without configuration target defaults to workspace in multi root workspace when no resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); @@ -191,7 +191,7 @@ suite('MainThreadConfiguration', function () { test('remove window configuration without configuration target defaults to workspace in multi root workspace when resource is provided', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); @@ -200,7 +200,7 @@ suite('MainThreadConfiguration', function () { test('remove window configuration without configuration target defaults to workspace in folder workspace when resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', URI.file('abc')); @@ -209,7 +209,7 @@ suite('MainThreadConfiguration', function () { test('remove window configuration without configuration target defaults to workspace in folder workspace when no resource is provider', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.FOLDER }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.window', null); @@ -218,7 +218,7 @@ suite('MainThreadConfiguration', function () { test('remove configuration without configuration target defaults to folder', function () { instantiationService.stub(IWorkspaceContextService, { getWorkbenchState: () => WorkbenchState.WORKSPACE }); - const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, OneGetThreadService(null)); + const testObject: MainThreadConfiguration = instantiationService.createInstance(MainThreadConfiguration, SingleProxyRPCProtocol(null)); testObject.$removeConfigurationOption(null, 'extHostConfiguration.resource', URI.file('abc')); diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts index d8fe85cff6b..efe09ddbb8d 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadDocumentsAndEditors.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; -import { OneGetThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol } from './testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { TestCodeEditorService } from 'vs/editor/test/browser/testCodeEditorService'; @@ -53,7 +53,7 @@ suite('MainThreadDocumentsAndEditors', () => { /* tslint:disable */ new MainThreadDocumentsAndEditors( - OneGetThreadService(new class extends mock() { + SingleProxyRPCProtocol(new class extends mock() { $acceptDocumentsAndEditorsDelta(delta) { deltas.push(delta); } }), modelService, diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts index a0b70a56dc7..73887be8ea3 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadEditors.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { MainThreadDocumentsAndEditors } from 'vs/workbench/api/electron-browser/mainThreadDocumentsAndEditors'; -import { OneGetThreadService, TestThreadService } from './testThreadService'; +import { SingleProxyRPCProtocol, TestRPCProtocol } from './testRPCProtocol'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { TestCodeEditorService } from 'vs/editor/test/browser/testCodeEditorService'; @@ -52,18 +52,18 @@ suite('MainThreadEditors', () => { onEditorGroupMoved = Event.None; }; - const testThreadService = new TestThreadService(true); - testThreadService.setTestInstance(ExtHostContext.ExtHostDocuments, new class extends mock() { + const rpcProtocol = new TestRPCProtocol(); + rpcProtocol.set(ExtHostContext.ExtHostDocuments, new class extends mock() { $acceptModelChanged(): void { } }); - testThreadService.setTestInstance(ExtHostContext.ExtHostDocumentsAndEditors, new class extends mock() { + rpcProtocol.set(ExtHostContext.ExtHostDocumentsAndEditors, new class extends mock() { $acceptDocumentsAndEditorsDelta(): void { } }); const documentAndEditor = new MainThreadDocumentsAndEditors( - testThreadService, + rpcProtocol, modelService, textFileService, workbenchEditorService, @@ -77,7 +77,7 @@ suite('MainThreadEditors', () => { editors = new MainThreadEditors( documentAndEditor, - OneGetThreadService(null), + SingleProxyRPCProtocol(null), codeEditorService, workbenchEditorService, editorGroupService, diff --git a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts index e3e90a945e4..d6f3681d331 100644 --- a/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts +++ b/src/vs/workbench/test/electron-browser/api/mainThreadSaveParticipant.test.ts @@ -12,6 +12,8 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { workbenchInstantiationService, TestTextFileService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IModelService } from 'vs/editor/common/services/modelService'; +import { Range } from 'vs/editor/common/core/range'; +import { Selection } from 'vs/editor/common/core/selection'; import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel'; import { ITextFileService, SaveReason } from 'vs/workbench/services/textfile/common/textfiles'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; @@ -112,4 +114,33 @@ suite('MainThreadSaveParticipant', function () { done(); }); }); + + test('trim final new lines bug#39750', function (done) { + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, toResource.call(this, '/path/trim_final_new_line.txt'), 'utf8'); + + model.load().then(() => { + const configService = new TestConfigurationService(); + configService.setUserConfiguration('files', { 'trimFinalNewlines': true }); + + const participant = new TrimFinalNewLinesParticipant(configService, undefined); + + const textContent = 'Trim New Line'; + + // single line + let lineContent = `${textContent}`; + model.textEditorModel.setValue(lineContent); + // apply edits and push to undo stack. + let textEdits = [{ identifier: null, range: new Range(1, 14, 1, 14), text: '.', forceMoveMarkers: false }]; + model.textEditorModel.pushEditOperations([new Selection(1, 14, 1, 14)], textEdits, () => { return [new Selection(1, 15, 1, 15)]; }); + // undo + model.textEditorModel.undo(); + assert.equal(model.getValue(), `${textContent}`); + // trim final new lines should not mess the undo stack + participant.participate(model, { reason: SaveReason.EXPLICIT }); + model.textEditorModel.redo(); + assert.equal(model.getValue(), `${textContent}.`); + done(); + }); + }); + }); diff --git a/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts new file mode 100644 index 00000000000..f2effd9e861 --- /dev/null +++ b/src/vs/workbench/test/electron-browser/api/testRPCProtocol.ts @@ -0,0 +1,140 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { TPromise } from 'vs/base/common/winjs.base'; +import { ProxyIdentifier, IRPCProtocol } from 'vs/workbench/services/extensions/node/proxyIdentifier'; +import { CharCode } from 'vs/base/common/charCode'; +import * as marshalling from 'vs/base/common/marshalling'; + +export function SingleProxyRPCProtocol(thing: any): IRPCProtocol { + return { + getProxy(): T { + return thing; + }, + set(identifier: ProxyIdentifier, value: R): R { + return value; + }, + assertRegistered: undefined + }; +} + +declare var Proxy; // TODO@TypeScript + +export class TestRPCProtocol implements IRPCProtocol { + + private _callCountValue: number = 0; + private _idle: Promise; + private _completeIdle: Function; + + private readonly _locals: { [id: string]: any; }; + private readonly _proxies: { [id: string]: any; }; + + constructor() { + this._locals = Object.create(null); + this._proxies = Object.create(null); + } + + private get _callCount(): number { + return this._callCountValue; + } + + private set _callCount(value: number) { + this._callCountValue = value; + if (this._callCountValue === 0) { + if (this._completeIdle) { + this._completeIdle(); + } + this._idle = undefined; + } + } + + sync(): Promise { + return new Promise((c) => { + setTimeout(c, 0); + }).then(() => { + if (this._callCount === 0) { + return undefined; + } + if (!this._idle) { + this._idle = new Promise((c, e) => { + this._completeIdle = c; + }); + } + return this._idle; + }); + } + + public getProxy(identifier: ProxyIdentifier): T { + if (!this._proxies[identifier.id]) { + this._proxies[identifier.id] = this._createProxy(identifier.id, identifier.isFancy); + } + return this._proxies[identifier.id]; + } + + private _createProxy(proxyId: string, isFancy: boolean): T { + let handler = { + get: (target, name: string) => { + if (!target[name] && name.charCodeAt(0) === CharCode.DollarSign) { + target[name] = (...myArgs: any[]) => { + return this._remoteCall(proxyId, name, myArgs, isFancy); + }; + } + return target[name]; + } + }; + return new Proxy(Object.create(null), handler); + } + + public set(identifier: ProxyIdentifier, value: R): R { + this._locals[identifier.id] = value; + return value; + } + + protected _remoteCall(proxyId: string, path: string, args: any[], isFancy: boolean): TPromise { + this._callCount++; + + return new TPromise((c) => { + setTimeout(c, 0); + }).then(() => { + const instance = this._locals[proxyId]; + // pretend the args went over the wire... (invoke .toJSON on objects...) + const wireArgs = simulateWireTransfer(args, isFancy); + let p: Thenable; + try { + let result = (instance[path]).apply(instance, wireArgs); + p = TPromise.is(result) ? result : TPromise.as(result); + } catch (err) { + p = TPromise.wrapError(err); + } + + return p.then(result => { + this._callCount--; + // pretend the result went over the wire... (invoke .toJSON on objects...) + const wireResult = simulateWireTransfer(result, isFancy); + return wireResult; + }, err => { + this._callCount--; + return TPromise.wrapError(err); + }); + }); + } + + public assertRegistered(identifiers: ProxyIdentifier[]): void { + throw new Error('Not implemented!'); + } +} + +function simulateWireTransfer(obj: T, isFancy: boolean): T { + if (!obj) { + return obj; + } + return ( + isFancy + ? marshalling.parse(marshalling.stringify(obj)) + : JSON.parse(JSON.stringify(obj)) + ); +} diff --git a/src/vs/workbench/test/electron-browser/api/testThreadService.ts b/src/vs/workbench/test/electron-browser/api/testThreadService.ts deleted file mode 100644 index 901935ee7a9..00000000000 --- a/src/vs/workbench/test/electron-browser/api/testThreadService.ts +++ /dev/null @@ -1,161 +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 { TPromise } from 'vs/base/common/winjs.base'; -import { IThreadService, ProxyIdentifier } from 'vs/workbench/services/thread/common/threadService'; - -export function OneGetThreadService(thing: any): IThreadService { - return { - get(): T { - return thing; - }, - set(identifier: ProxyIdentifier, value: R): R { - return value; - }, - assertRegistered: undefined - }; -} - -declare var Proxy; // TODO@TypeScript - -export abstract class AbstractTestThreadService { - - private _isMain: boolean; - protected _locals: { [id: string]: any; }; - private _proxies: { [id: string]: any; } = Object.create(null); - - constructor(isMain: boolean) { - this._isMain = isMain; - this._locals = Object.create(null); - this._proxies = Object.create(null); - } - - public handle(rpcId: string, methodName: string, args: any[]): any { - if (!this._locals[rpcId]) { - throw new Error('Unknown actor ' + rpcId); - } - let actor = this._locals[rpcId]; - let method = actor[methodName]; - if (typeof method !== 'function') { - throw new Error('Unknown method ' + methodName + ' on actor ' + rpcId); - } - return method.apply(actor, args); - } - - get(identifier: ProxyIdentifier): T { - if (!this._proxies[identifier.id]) { - this._proxies[identifier.id] = this._createProxy(identifier.id); - } - return this._proxies[identifier.id]; - } - - private _createProxy(id: string): T { - let handler = { - get: (target, name) => { - return (...myArgs: any[]) => { - return this._callOnRemote(id, name, myArgs); - }; - } - }; - return new Proxy({}, handler); - } - - set(identifier: ProxyIdentifier, value: R): R { - if (identifier.isMain !== this._isMain) { - throw new Error('Mismatch in object registration!'); - } - this._locals[identifier.id] = value; - return value; - } - - protected abstract _callOnRemote(proxyId: string, path: string, args: any[]): TPromise; -} - -export class TestThreadService extends AbstractTestThreadService implements IThreadService { - constructor(isMainProcess: boolean = false) { - super(isMainProcess); - } - - private _callCountValue: number = 0; - private _idle: TPromise; - private _completeIdle: Function; - - private get _callCount(): number { - return this._callCountValue; - } - - private set _callCount(value: number) { - this._callCountValue = value; - if (this._callCountValue === 0) { - if (this._completeIdle) { - this._completeIdle(); - } - this._idle = undefined; - } - } - - sync(): TPromise { - return new TPromise((c) => { - setTimeout(c, 0); - }).then(() => { - if (this._callCount === 0) { - return undefined; - } - if (!this._idle) { - this._idle = new TPromise((c, e) => { - this._completeIdle = c; - }, function () { - // no cancel - }); - } - return this._idle; - }); - } - - private _testInstances: { [id: string]: any; } = Object.create(null); - setTestInstance(identifier: ProxyIdentifier, value: T): T { - this._testInstances[identifier.id] = value; - return value; - } - - get(identifier: ProxyIdentifier): T { - let id = identifier.id; - if (this._locals[id]) { - return this._locals[id]; - } - return super.get(identifier); - } - - protected _callOnRemote(proxyId: string, path: string, args: any[]): TPromise { - this._callCount++; - - return new TPromise((c) => { - setTimeout(c, 0); - }).then(() => { - const instance = this._testInstances[proxyId]; - let p: Thenable; - try { - let result = (instance[path]).apply(instance, args); - p = TPromise.is(result) ? result : TPromise.as(result); - } catch (err) { - p = TPromise.wrapError(err); - } - - return p.then(result => { - this._callCount--; - return result; - }, err => { - this._callCount--; - return TPromise.wrapError(err); - }); - }); - } - - public assertRegistered(identifiers: ProxyIdentifier[]): void { - throw new Error('Not implemented!'); - } -} diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index e243bc645dc..e55ac87907a 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -59,6 +59,8 @@ import { ITextResourceConfigurationService } from 'vs/editor/common/services/res import { IPosition, Position as EditorPosition } from 'vs/editor/common/core/position'; import { ICommandAction } from 'vs/platform/actions/common/actions'; import { IHashService } from 'vs/workbench/services/hash/common/hashService'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, void 0); @@ -175,9 +177,10 @@ export class TestTextFileService extends TextFileService { @IMessageService messageService: IMessageService, @IBackupFileService backupFileService: IBackupFileService, @IWindowsService windowsService: IWindowsService, - @IHistoryService historyService: IHistoryService + @IHistoryService historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService ) { - super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, messageService, TestEnvironmentService, backupFileService, windowsService, historyService); + super(lifecycleService, contextService, configurationService, fileService, untitledEditorService, instantiationService, messageService, TestEnvironmentService, backupFileService, windowsService, historyService, contextKeyService); } public setPromptPath(path: string): void { @@ -214,12 +217,12 @@ export class TestTextFileService extends TextFileService { }); } - public promptForPath(defaultPath: string): string { - return this.promptPath; + public promptForPath(defaultPath: string): TPromise { + return TPromise.wrap(this.promptPath); } - public confirmSave(resources?: URI[]): ConfirmResult { - return this.confirmResult; + public confirmSave(resources?: URI[]): TPromise { + return TPromise.wrap(this.confirmResult); } public onFilesConfigurationChange(configuration: any): void { @@ -234,6 +237,7 @@ export class TestTextFileService extends TextFileService { export function workbenchInstantiationService(): IInstantiationService { let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); + instantiationService.stub(IContextKeyService, instantiationService.createInstance(MockContextKeyService)); instantiationService.stub(IWorkspaceContextService, new TestContextService(TestWorkspace)); const configService = new TestConfigurationService(); instantiationService.stub(IConfigurationService, configService); @@ -328,8 +332,8 @@ export class TestMessageService implements IMessageService { // No-op } - public confirm(confirmation: IConfirmation): boolean { - return false; + public confirm(confirmation: IConfirmation): TPromise { + return TPromise.wrap(false); } public confirmWithCheckbox(confirmation: IConfirmation): Promise { @@ -584,12 +588,14 @@ export class TestEditorService implements IWorkbenchEditorService { public activeEditorOptions: IEditorOptions; public activeEditorPosition: Position; public mockLineNumber: number; + public mockSelectedText: string; private callback: (method: string) => void; constructor(callback?: (method: string) => void) { this.callback = callback || ((s: string) => { }); this.mockLineNumber = 15; + this.mockSelectedText = 'selected text'; } public openEditors(inputs: any[]): Promise { @@ -618,7 +624,8 @@ export class TestEditorService implements IWorkbenchEditorService { getId: () => { return null; }, getControl: () => { return { - getSelection: () => { return { positionLineNumber: this.mockLineNumber }; } + getSelection: () => { return { positionLineNumber: this.mockLineNumber }; }, + getModel: () => { return { getValueInRange: () => this.mockSelectedText }; } }; }, focus: () => { }, @@ -831,7 +838,7 @@ export class TestBackupFileService implements IBackupFileService { public loadBackupResource(resource: URI): TPromise { return this.hasBackup(resource).then(hasBackup => { if (hasBackup) { - return this.getBackupResource(resource); + return this.toBackupResource(resource); } return void 0; @@ -846,7 +853,7 @@ export class TestBackupFileService implements IBackupFileService { return TPromise.as(void 0); } - public getBackupResource(resource: URI): URI { + public toBackupResource(resource: URI): URI { return null; } @@ -961,20 +968,16 @@ export class TestWindowService implements IWindowService { return TPromise.as(void 0); } - showMessageBox(options: Electron.MessageBoxOptions): number { - return 0; + showMessageBox(options: Electron.MessageBoxOptions): TPromise { + return TPromise.wrap({ button: 0 }); } - showMessageBoxWithCheckbox(options: Electron.MessageBoxOptions): Promise { - return TPromise.as(void 0); + showSaveDialog(options: Electron.SaveDialogOptions): TPromise { + return TPromise.wrap(void 0); } - showSaveDialog(options: Electron.SaveDialogOptions): string { - return void 0; - } - - showOpenDialog(options: Electron.OpenDialogOptions): string[] { - return void 0; + showOpenDialog(options: Electron.OpenDialogOptions): TPromise { + return TPromise.wrap(void 0); } updateTouchBar(items: ICommandAction[][]): Promise { @@ -1198,6 +1201,18 @@ export class TestWindowsService implements IWindowsService { startCrashReporter(config: Electron.CrashReporterStartOptions): TPromise { return TPromise.as(void 0); } + + showMessageBox(windowId: number, options: Electron.MessageBoxOptions): TPromise { + return TPromise.as(void 0); + } + + showSaveDialog(windowId: number, options: Electron.SaveDialogOptions): TPromise { + return TPromise.as(void 0); + } + + showOpenDialog(windowId: number, options: Electron.OpenDialogOptions): TPromise { + return TPromise.as(void 0); + } } export class TestTextResourceConfigurationService implements ITextResourceConfigurationService { diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 8b75deae37f..b624558e852 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -31,6 +31,7 @@ import 'vs/workbench/browser/actions/toggleZenMode'; import 'vs/workbench/browser/actions/toggleTabsVisibility'; import 'vs/workbench/parts/preferences/electron-browser/preferences.contribution'; import 'vs/workbench/parts/preferences/browser/keybindingsEditorContribution'; +import 'vs/workbench/parts/logs/electron-browser/logs.contribution'; import 'vs/workbench/browser/parts/quickopen/quickopen.contribution'; import 'vs/workbench/parts/quickopen/browser/quickopen.contribution'; @@ -71,7 +72,7 @@ import 'vs/workbench/parts/extensions/electron-browser/extensionsViewlet'; // ca import 'vs/workbench/parts/welcome/page/electron-browser/welcomePage.contribution'; -import 'vs/workbench/parts/output/browser/output.contribution'; +import 'vs/workbench/parts/output/electron-browser/output.contribution'; import 'vs/workbench/parts/output/browser/outputPanel'; // can be packaged separately import 'vs/workbench/parts/terminal/electron-browser/terminal.contribution'; diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 41133e687b6..1d922106600 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -151,7 +151,7 @@ async function setup(): Promise { console.log('*** Test data:', testDataPath); console.log('*** Preparing smoketest setup...'); - const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/scripts/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`; + const keybindingsUrl = `https://raw.githubusercontent.com/Microsoft/vscode-docs/master/build/keybindings/doc.keybindings.${getKeybindingPlatform()}.json`; console.log('*** Fetching keybindings...'); await new Promise((c, e) => { diff --git a/yarn.lock b/yarn.lock index f95110eef08..42e334c53da 100644 --- a/yarn.lock +++ b/yarn.lock @@ -147,9 +147,9 @@ anymatch@^1.3.0: micromatch "^2.1.5" normalize-path "^2.0.0" -applicationinsights@0.17.1: - version "0.17.1" - resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.17.1.tgz#1c12501dbe9c1e9176423fce0ce8da611cccb9a8" +applicationinsights@0.18.0: + version "0.18.0" + resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.18.0.tgz#162ebb48a383408bc4de44db32b417307f45bbc1" archy@^1.0.0: version "1.0.0" @@ -3742,6 +3742,10 @@ nan@^2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.8.0.tgz#ed715f3fe9de02b57a5e6252d90a96675e1f085a" +native-is-elevated@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/native-is-elevated/-/native-is-elevated-0.2.1.tgz#70a2123a8575b9f624a3ef465d98cb74ae017385" + native-keymap@1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/native-keymap/-/native-keymap-1.2.5.tgz#1035a9417b9a9340cf8097763a43c76d588165a5" @@ -5038,9 +5042,9 @@ sparkles@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/sparkles/-/sparkles-1.0.0.tgz#1acbbfb592436d10bbe8f785b7cc6f82815012c3" -spdlog@0.3.7: - version "0.3.7" - resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.3.7.tgz#5f068efab0b7c85efa1aaed6eacd3da1d978fe24" +spdlog@0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/spdlog/-/spdlog-0.5.0.tgz#5ec92c34e59f29328f4e19dfab17a1ba51cc0573" dependencies: bindings "^1.3.0" mkdirp "^0.5.1" @@ -5205,6 +5209,10 @@ strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" +sudo-prompt@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-8.0.0.tgz#a7b4a1ca6cbcca0e705b90a89dfc81d11034cba9" + sumchecker@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/sumchecker/-/sumchecker-2.0.2.tgz#0f42c10e5d05da5d42eea3e56c3399a37d6c5b3e" @@ -5789,9 +5797,9 @@ vscode-nls-dev@^2.0.1: xml2js "^0.4.17" yargs "^3.32.0" -vscode-ripgrep@^0.6.0-patch.0.5: - version "0.6.0-patch.0.5" - resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-0.6.0-patch.0.5.tgz#afd62ed290481c4cca6bf8e376fdf5d1e3f8afcd" +vscode-ripgrep@^0.7.1-patch.0: + version "0.7.1-patch.0" + resolved "https://registry.yarnpkg.com/vscode-ripgrep/-/vscode-ripgrep-0.7.1-patch.0.tgz#738be8b6da5cb9a8807b528595a884b0dfcb60a5" vscode-textmate@^3.2.0: version "3.2.0" @@ -5800,6 +5808,10 @@ vscode-textmate@^3.2.0: fast-plist "^0.1.2" oniguruma "^6.0.1" +vscode-xterm@3.0.0-beta8: + version "3.0.0-beta8" + resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.0.0-beta8.tgz#7e7d5f399d76992e07e5099aea9a1732ad40f7c1" + vso-node-api@^6.1.2-preview: version "6.1.2-preview" resolved "https://registry.yarnpkg.com/vso-node-api/-/vso-node-api-6.1.2-preview.tgz#aab3546df2451ecd894e071bb99b5df19c5fa78f" @@ -5938,10 +5950,6 @@ xtend@~3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/xtend/-/xtend-3.0.0.tgz#5cce7407baf642cba7becda568111c493f59665a" -xterm@Tyriar/xterm.js#vscode-release/1.19: - version "2.9.1" - resolved "https://codeload.github.com/Tyriar/xterm.js/tar.gz/d242c552cb5c88125ac257ccaebed7fe336d9266" - y18n@^3.2.0: version "3.2.1" resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41"