diff --git a/ThirdPartyNotices-Repository.txt b/ThirdPartyNotices-Repository.txt index 8e7951914857..e5766366bd53 100644 --- a/ThirdPartyNotices-Repository.txt +++ b/ThirdPartyNotices-Repository.txt @@ -14,6 +14,7 @@ Microsoft Python extension for Visual Studio Code incorporates third party mater 11. pythonVSCode (https://github.com/DonJayamanne/pythonVSCode) 12. Sphinx (http://sphinx-doc.org/) 13. nteract (https://github.com/nteract/nteract) +14. less-plugin-inline-urls (https://github.com/less/less-plugin-inline-urls/) %% @@ -753,3 +754,210 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ========================================= END OF nteract NOTICES, INFORMATION, AND LICENSE + +%% less-plugin-inline-urls NOTICES, INFORMATION, AND LICENSE BEGIN HERE +========================================= +Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + +========================================= +END OF less-plugin-inline-urls NOTICES, INFORMATION, AND LICENSE diff --git a/build/webpack/plugins/less-plugin-base64.js b/build/webpack/plugins/less-plugin-base64.js new file mode 100644 index 000000000000..c05d9a2b5b71 --- /dev/null +++ b/build/webpack/plugins/less-plugin-base64.js @@ -0,0 +1,62 @@ +// Most of this was based on https://github.com/less/less-plugin-inline-urls +// License for this was included in the ThirdPartyNotices-Repository.txt +const less = require('less'); + +class Base64MimeTypeNode { + constructor() { + this.value = "image/svg+xml;base64"; + this.type = "Base64MimeTypeNode"; + } + + eval(context) { + return this; + } +} + +class Base64Visitor { + + constructor() { + this.visitor = new less.visitors.Visitor(this); + + // Set to a preEval visitor to make sure this runs before + // any evals + this.isPreEvalVisitor = true; + + // Make sure this is a replacing visitor so we remove the old data. + this.isReplacing = true; + } + + run(root) { + return this.visitor.visit(root); + } + + visitUrl(URLNode, visitArgs) { + // Return two new nodes in the call. One that has the mime type and other with the node. The data-uri + // evaluator will transform this into a base64 string + return new less.tree.Call("data-uri", [new Base64MimeTypeNode(), URLNode.value], URLNode.index || 0, URLNode.currentFileInfo); + } + +} +/* +* This was originally used to perform less on uris and turn them into base64 encoded so they can be loaded into +* a webpack html. There's one caveat though. Less and webpack don't play well together. It runs the less at the root dir. +* This means in order to use this in a less file, you need to qualify the urls as if they come from the root dir. +* Example: +* url("./foo.svg") +* becomes +* url("./src/datascience-ui/history-react/images/foo.svg") +*/ +class Base64Plugin { + constructor() { + } + + install(less, pluginManager) { + pluginManager.addVisitor(new Base64Visitor()); + } + + printUsage() { + console.log('Base64 Plugin. Add to your webpack.config.js as a plugin to convert URLs to base64 inline') + } +} + +module.exports = Base64Plugin; diff --git a/build/webpack/webpack.extension.dependencies.config.js b/build/webpack/webpack.extension.dependencies.config.js index bfcf81c111fe..5a87744bfcb3 100644 --- a/build/webpack/webpack.extension.dependencies.config.js +++ b/build/webpack/webpack.extension.dependencies.config.js @@ -41,6 +41,10 @@ const config = { // 'find' the calling extension. new copyWebpackPlugin([ { from: './package.json', to: '.' } + ]), + // onigasm requires our onigasm.wasm to be in node_modules + new copyWebpackPlugin([ + { from: './node_modules/onigasm/lib/onigasm.wasm', to: './node_modules/onigasm/lib/onigasm.wasm' } ]) ], resolve: { diff --git a/build/webpack/webpack.extension.dependencies.config.ts b/build/webpack/webpack.extension.dependencies.config.ts index db852debbb62..76c72bea9fba 100644 --- a/build/webpack/webpack.extension.dependencies.config.ts +++ b/build/webpack/webpack.extension.dependencies.config.ts @@ -45,6 +45,10 @@ const config: webpack.Configuration = { // 'find' the calling extension. new copyWebpackPlugin([ { from: './package.json', to: '.' } + ]), + // onigasm requires our onigasm.wasm to be in node_modules + new copyWebpackPlugin([ + { from: './node_modules/onigasm/lib/onigasm.wasm', to: './node_modules/onigasm/lib/onigasm.wasm' } ]) ], resolve: { diff --git a/news/1 Enhancements/5342.md b/news/1 Enhancements/5342.md new file mode 100644 index 000000000000..1a5337a65cc0 --- /dev/null +++ b/news/1 Enhancements/5342.md @@ -0,0 +1 @@ +Provide basic intellisense using the language server. \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0d8fa99f1bf1..d0a5be111446 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,32 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/cli": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.4.4.tgz", + "integrity": "sha512-XGr5YjQSjgTa6OzQZY57FAJsdeVSAKR/u/KA5exWIz66IKtv/zXtHy+fIZcMry/EgYegwuHE7vzGnrFhjdIAsQ==", + "dev": true, + "requires": { + "chokidar": "^2.0.4", + "commander": "^2.8.1", + "convert-source-map": "^1.1.0", + "fs-readdir-recursive": "^1.1.0", + "glob": "^7.0.0", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", + "output-file-sync": "^2.0.0", + "slash": "^2.0.0", + "source-map": "^0.5.0" + }, + "dependencies": { + "slash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "dev": true + } + } + }, "@babel/code-frame": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", @@ -14,41 +40,111 @@ } }, "@babel/core": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.1.0.tgz", - "integrity": "sha512-9EWmD0cQAbcXSc+31RIoYgEHx3KQ2CCSMDBhnXrShWvo45TMw+3/55KVxlhkG53kw9tl87DqINgHDgFVhZJV/Q==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.4.4.tgz", + "integrity": "sha512-lQgGX3FPRgbz2SKmhMtYgJvVzGZrmjaF4apZ2bLwofAKiSjxU0drPh4S/VasyYXwaTs+A1gvQ45BN8SQJzHsQQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@babel/generator": "^7.0.0", - "@babel/helpers": "^7.1.0", - "@babel/parser": "^7.1.0", - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helpers": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4", "convert-source-map": "^1.1.0", - "debug": "^3.1.0", - "json5": "^0.5.0", - "lodash": "^4.17.10", + "debug": "^4.1.0", + "json5": "^2.1.0", + "lodash": "^4.17.11", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", + "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", + "dev": true + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", + "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, "debug": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.5.tgz", - "integrity": "sha512-D61LaDQPQkxJ5AUM2mbSJRbPkNs/TmdmOeLAi1hgDkpDfIfetSrjmWhccwtuResSwMbACjx/xXQofvM9CE/aeg==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { "ms": "^2.1.1" } }, - "lodash": { - "version": "4.17.11", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", - "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", - "dev": true + "json5": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.0.tgz", + "integrity": "sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } }, "ms": { "version": "2.1.1", @@ -306,14 +402,98 @@ } }, "@babel/helpers": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.1.0.tgz", - "integrity": "sha512-V1jXUTNdTpBn37wqqN73U+eBpzlLHmxA4aDaghJBggmzly/FpIJMHXse9lgdzQQT4gs5jZ5NmYxOL8G3ROc29g==", + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.4.4.tgz", + "integrity": "sha512-igczbR/0SeuPR8RFfC7tGrbdTbFL3QTvH6D+Z6zNxnTe//GyqmtHmDkzrqDmyZ3eSwPqB/LhyKoU5DXsp+Vp2A==", "dev": true, "requires": { - "@babel/template": "^7.1.0", - "@babel/traverse": "^7.1.0", - "@babel/types": "^7.0.0" + "@babel/template": "^7.4.4", + "@babel/traverse": "^7.4.4", + "@babel/types": "^7.4.4" + }, + "dependencies": { + "@babel/generator": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.4.4.tgz", + "integrity": "sha512-53UOLK6TVNqKxf7RUh8NE851EHRxOOeVXKbK2bivdb+iziMyk03Sr4eaE9OELCbyZAAafAKPDwF2TPUES5QbxQ==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4", + "jsesc": "^2.5.1", + "lodash": "^4.17.11", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.4.4.tgz", + "integrity": "sha512-Ro/XkzLf3JFITkW6b+hNxzZ1n5OQ80NvIUdmHspih1XAhtN3vPTuUFT4eQnela+2MaZ5ulH+iyP513KJrxbN7Q==", + "dev": true, + "requires": { + "@babel/types": "^7.4.4" + } + }, + "@babel/parser": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.4.4.tgz", + "integrity": "sha512-5pCS4mOsL+ANsFZGdvNLybx4wtqAZJ0MJjMHxvzI3bvIsz6sQvzW8XX92EYIkiPtIvcfG3Aj+Ir5VNyjnZhP7w==", + "dev": true + }, + "@babel/template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.4.4.tgz", + "integrity": "sha512-CiGzLN9KgAvgZsnivND7rkA+AeJ9JB0ciPOD4U59GKbQP2iQl+olF1l76kJOupqidozfZ32ghwBEJDhnk9MEcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4" + } + }, + "@babel/traverse": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.4.4.tgz", + "integrity": "sha512-Gw6qqkw/e6AGzlyj9KnkabJX7VcubqPtkUQVAwkc0wUMldr3A/hezNB3Rc5eIvId95iSGkGIOe5hh1kMKf951A==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/generator": "^7.4.4", + "@babel/helper-function-name": "^7.1.0", + "@babel/helper-split-export-declaration": "^7.4.4", + "@babel/parser": "^7.4.4", + "@babel/types": "^7.4.4", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.11" + } + }, + "@babel/types": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.4.4.tgz", + "integrity": "sha512-dOllgYdnEFOebhkKCjzSVFqw/PmmB8pH6RGOWkY4GsboQNd47b1fBThBSwlHAq9alF9vc1M3+6oqR47R50L0tQ==", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "lodash": "^4.17.11", + "to-fast-properties": "^2.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } } }, "@babel/highlight": { @@ -732,6 +912,41 @@ "regenerator-transform": "^0.13.3" } }, + "@babel/plugin-transform-runtime": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.4.4.tgz", + "integrity": "sha512-aMVojEjPszvau3NRg+TIH14ynZLvPewH4xhlCW1w6A3rkxTS1m4uwzRclYR9oS+rl/dr+kT+pzbfHuAWP/lc7Q==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/helper-plugin-utils": "^7.0.0", + "resolve": "^1.8.1", + "semver": "^5.5.1" + }, + "dependencies": { + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.1.tgz", + "integrity": "sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, "@babel/plugin-transform-shorthand-properties": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.0.0.tgz", @@ -790,6 +1005,30 @@ "regexpu-core": "^4.1.3" } }, + "@babel/polyfill": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/polyfill/-/polyfill-7.4.4.tgz", + "integrity": "sha512-WlthFLfhQQhh+A2Gn5NSFl0Huxz36x86Jn+E9OW7ibK8edKPq+KLy4apM1yDpQ8kJOVi1OVjpP4vSDLdrI04dg==", + "dev": true, + "requires": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "core-js": { + "version": "2.6.5", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", + "integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", + "dev": true + } + } + }, "@babel/preset-env": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.1.0.tgz", @@ -852,33 +1091,130 @@ "@babel/plugin-transform-react-jsx-source": "^7.0.0" } }, - "@babel/runtime": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.1.2.tgz", - "integrity": "sha512-Y3SCjmhSupzFB6wcv1KmmFucH6gDVnI30WjOcicV10ju0cZjak3Jcs67YLIXBrmZYw1xCrVeJPbycFwrqNyxpg==", - "dev": true, - "requires": { - "regenerator-runtime": "^0.12.0" - } - }, - "@babel/runtime-corejs2": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz", - "integrity": "sha512-drxaPByExlcRDKW4ZLubUO4ZkI8/8ax9k9wve1aEthdLKFzjB7XRkOQ0xoTIWGxqdDnWDElkjYq77bt7yrcYJQ==", - "dev": true, - "requires": { - "core-js": "^2.5.7", - "regenerator-runtime": "^0.12.0" - } - }, - "@babel/template": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.0.tgz", - "integrity": "sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA==", + "@babel/register": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.4.4.tgz", + "integrity": "sha512-sn51H88GRa00+ZoMqCVgOphmswG4b7mhf9VOB0LUBAieykq2GnRFerlN+JQkO/ntT7wz4jaHNSRPg9IdMPEUkA==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.1.0", + "core-js": "^3.0.0", + "find-cache-dir": "^2.0.0", + "lodash": "^4.17.11", + "mkdirp": "^0.5.1", + "pirates": "^4.0.0", + "source-map-support": "^0.5.9" + }, + "dependencies": { + "core-js": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.0.1.tgz", + "integrity": "sha512-sco40rF+2KlE0ROMvydjkrVMMG1vYilP2ALoRXcYR4obqbYIuV3Bg+51GEDW+HF8n7NRA+iaA4qD0nD9lo9mew==", + "dev": true + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "@babel/runtime": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.4.tgz", + "integrity": "sha512-w0+uT71b6Yi7i5SE0co4NioIpSYS6lLiXvCzWzGSKvpK5vdQtCbICHMj+gbAKAOtxiV6HsVh/MBdaF9EQ6faSg==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.2" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", + "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==", + "dev": true + } + } + }, + "@babel/runtime-corejs2": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.1.2.tgz", + "integrity": "sha512-drxaPByExlcRDKW4ZLubUO4ZkI8/8ax9k9wve1aEthdLKFzjB7XRkOQ0xoTIWGxqdDnWDElkjYq77bt7yrcYJQ==", + "dev": true, + "requires": { + "core-js": "^2.5.7", + "regenerator-runtime": "^0.12.0" + } + }, + "@babel/template": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.1.0.tgz", + "integrity": "sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, @@ -1321,15 +1657,6 @@ "integrity": "sha1-AFLBNvUkgAJCjjY4s33ko5gYZB0=", "dev": true }, - "@types/codemirror": { - "version": "0.0.71", - "resolved": "https://registry.npmjs.org/@types/codemirror/-/codemirror-0.0.71.tgz", - "integrity": "sha512-b2oEEnno1LIGKMR7uBEsr40al1UijF1HEpRn0+Yf1xOLl24iQgB7DBpZVMM7y54G5wCNoclDrRO65E6KHPNO2w==", - "dev": true, - "requires": { - "@types/tern": "*" - } - }, "@types/commander": { "version": "2.12.2", "resolved": "https://registry.npmjs.org/@types/commander/-/commander-2.12.2.tgz", @@ -1403,12 +1730,6 @@ "@types/enzyme": "*" } }, - "@types/estree": { - "version": "0.0.39", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", - "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", - "dev": true - }, "@types/event-stream": { "version": "3.3.34", "resolved": "https://registry.npmjs.org/@types/event-stream/-/event-stream-3.3.34.tgz", @@ -1602,16 +1923,6 @@ "csstype": "^2.2.0" } }, - "@types/react-codemirror": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@types/react-codemirror/-/react-codemirror-1.0.2.tgz", - "integrity": "sha512-59UY5C5EVaFq/f+p1mkgLpNQELehdCNGTiLuFYacjptTzDDSyMKF4Zrs1z2WJWQSh53vIeuS8i2JrPFyfHjxBw==", - "dev": true, - "requires": { - "@types/codemirror": "*", - "@types/react": "*" - } - }, "@types/react-dom": { "version": "16.0.8", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-16.0.8.tgz", @@ -1694,15 +2005,6 @@ "@types/node": "*" } }, - "@types/tern": { - "version": "0.22.1", - "resolved": "https://registry.npmjs.org/@types/tern/-/tern-0.22.1.tgz", - "integrity": "sha512-CRzPRkg8hYLwunsj61r+rqPJQbiCIEQqlMMY/0k7krgIsoSaFgGg1ZH2f9qaR1YpenaMl6PnlTtUkCbNH/uo+A==", - "dev": true, - "requires": { - "@types/estree": "*" - } - }, "@types/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/@types/tmp/-/tmp-0.0.33.tgz", @@ -2041,9 +2343,9 @@ } }, "acorn-globals": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.0.tgz", - "integrity": "sha512-hMtHj3s5RnuhvHPowpBYvJVj3rAar82JiDQHvGs1zO0l10ocX/xEdBShNHTJaboucJUsScghp74pH3s7EnHHQw==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.2.tgz", + "integrity": "sha512-BbzvZhVtZP+Bs1J1HcwrQe8ycfO0wStkSGxuul3He3GkHOIZ6eTqOkPuw9IP1X3+IkOo4wiJmwkobzXYz4wewQ==", "dev": true, "requires": { "acorn": "^6.0.1", @@ -2051,17 +2353,17 @@ }, "dependencies": { "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true } } }, "acorn-walk": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.0.tgz", - "integrity": "sha512-ugTb7Lq7u4GfWSqqpwE0bGyoBZNMTok/zDBXxfEG0QM50jNlGhIWjRC1pPN7bvV1anhF+bs+/gNcRw+o55Evbg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.1.1.tgz", + "integrity": "sha512-OtUw6JUTgxA2QoqqmrmQ7F2NYqiBPi/L2jqHyFtllhOUvXYQXf0Z1CYUinIfyT4bTCGmrA7gX9FvHA81uzCoVw==", "dev": true }, "address": { @@ -2193,6 +2495,17 @@ "integrity": "sha1-qCJQ3bABXponyoLoLqYDu/pF768=", "dev": true }, + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "optional": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + } + }, "append-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", @@ -3415,175 +3728,763 @@ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", "dev": true, "requires": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + } + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", + "dev": true + }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30000887", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000887.tgz", + "integrity": "sha512-AHpONWuGFWO8yY9igdXH94tikM6ERS84286r0cAMAXYFtJBk76lhiMhtCxBJNBZsD6hzlvpWZ2AtbVFEkf4JQA==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "caw": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", + "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", + "dev": true, + "requires": { + "get-proxy": "^2.0.0", + "isurl": "^1.0.0-alpha5", + "tunnel-agent": "^0.6.0", + "url-to-options": "^1.0.1" + } + }, + "chai": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", + "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", + "dev": true, + "requires": { + "assertion-error": "^1.0.1", + "check-error": "^1.0.1", + "deep-eql": "^3.0.0", + "get-func-name": "^2.0.0", + "pathval": "^1.0.0", + "type-detect": "^4.0.0" + } + }, + "chai-arrays": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.0.0.tgz", + "integrity": "sha512-jWAvZu1BV8tL3pj0iosBECzzHEg+XB1zSnMjJGX83bGi/1GlGdDO7J/A0sbBBS6KJT0FVqZIzZW9C6WLiMkHpQ==", + "dev": true + }, + "chai-as-promised": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", + "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", + "dev": true, + "requires": { + "check-error": "^1.0.2" + } + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, + "character-entities": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", + "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==", + "dev": true + }, + "character-entities-legacy": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", + "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==", + "dev": true + }, + "character-reference-invalid": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", + "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==", + "dev": true + }, + "chardet": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", + "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", + "dev": true + }, + "charenc": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", + "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "check-types": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", + "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", + "dev": true + }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "chokidar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.5.tgz", + "integrity": "sha512-i0TprVWp+Kj4WRPtInjexJ8Q+BqTE909VpH8xVhXrJkoc5QC8VO9TryGOqTr+2hljzc1sC62t22h5tZePodM/A==", + "dev": true, + "optional": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.1" + }, + "dependencies": { + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "optional": true, + "requires": { + "is-extglob": "^2.1.1" } }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "optional": true }, - "semver": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", - "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", - "dev": true - } - } - }, - "callsite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", - "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=", - "dev": true - }, - "camel-case": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", - "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", - "dev": true, - "requires": { - "no-case": "^2.2.0", - "upper-case": "^1.1.1" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true - }, - "caniuse-lite": { - "version": "1.0.30000887", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000887.tgz", - "integrity": "sha512-AHpONWuGFWO8yY9igdXH94tikM6ERS84286r0cAMAXYFtJBk76lhiMhtCxBJNBZsD6hzlvpWZ2AtbVFEkf4JQA==", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" - }, - "caw": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/caw/-/caw-2.0.1.tgz", - "integrity": "sha512-Cg8/ZSBEa8ZVY9HspcGUYaK63d/bN7rqS3CYCzEGUxuYv6UlmcjzDUz2fCFFHyTvUW5Pk0I+3hkA3iXlIj6guA==", - "dev": true, - "requires": { - "get-proxy": "^2.0.0", - "isurl": "^1.0.0-alpha5", - "tunnel-agent": "^0.6.0", - "url-to-options": "^1.0.1" - } - }, - "chai": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.1.2.tgz", - "integrity": "sha1-D2RYS6ZC8PKs4oBiefTwbKI61zw=", - "dev": true, - "requires": { - "assertion-error": "^1.0.1", - "check-error": "^1.0.1", - "deep-eql": "^3.0.0", - "get-func-name": "^2.0.0", - "pathval": "^1.0.0", - "type-detect": "^4.0.0" - } - }, - "chai-arrays": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chai-arrays/-/chai-arrays-2.0.0.tgz", - "integrity": "sha512-jWAvZu1BV8tL3pj0iosBECzzHEg+XB1zSnMjJGX83bGi/1GlGdDO7J/A0sbBBS6KJT0FVqZIzZW9C6WLiMkHpQ==", - "dev": true - }, - "chai-as-promised": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.1.tgz", - "integrity": "sha512-azL6xMoi+uxu6z4rhWQ1jbdUhOMhis2PvscD/xjLqNMkv3BPPp2JyyuTHOrf9BOosGpNQ11v6BKv/g57RXbiaA==", - "dev": true, - "requires": { - "check-error": "^1.0.2" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" } + }, + "upath": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.2.tgz", + "integrity": "sha512-kXpym8nmDmlCBr7nKdIx8P2jNBa+pBpIUFRnKJ4dr8htyYGJFokkr2ZvERRtUN+9SY+JqXouNgUPtv6JQva/2Q==", + "dev": true, + "optional": true } } }, - "character-entities": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-1.2.2.tgz", - "integrity": "sha512-sMoHX6/nBiy3KKfC78dnEalnpn0Az0oSNvqUWYTtYrhRI5iUIYsROU48G+E+kMFQzqXaJ8kHJZ85n7y6/PHgwQ==", - "dev": true - }, - "character-entities-legacy": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-1.1.2.tgz", - "integrity": "sha512-9NB2VbXtXYWdXzqrvAHykE/f0QJxzaKIpZ5QzNZrrgQ7Iyxr2vnfS8fCBNVW9nUEZE0lo57nxKRqnzY/dKrwlA==", - "dev": true - }, - "character-reference-invalid": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/character-reference-invalid/-/character-reference-invalid-1.1.2.tgz", - "integrity": "sha512-7I/xceXfKyUJmSAn/jw8ve/9DyOP7XxufNYLI9Px7CmsKgEUaZLUTax6nZxGQtaoiZCjpu6cHPj20xC/vqRReQ==", - "dev": true - }, - "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=", - "dev": true - }, - "charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=" - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "check-types": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/check-types/-/check-types-7.4.0.tgz", - "integrity": "sha512-YbulWHdfP99UfZ73NcUDlNJhEIDgm9Doq9GhpyXbF+7Aegi3CVV7qqMCKTTqJxlvEvnQBp9IA+dxsGN6xK/nSg==", - "dev": true - }, - "cheerio": { - "version": "1.0.0-rc.2", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", - "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", - "dev": true, - "requires": { - "css-select": "~1.2.0", - "dom-serializer": "~0.1.0", - "entities": "~1.1.1", - "htmlparser2": "^3.9.1", - "lodash": "^4.15.0", - "parse5": "^3.0.1" - } - }, "chownr": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", @@ -3879,12 +4780,6 @@ "urlgrey": "0.4.4" } }, - "codemirror": { - "version": "5.42.2", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.42.2.tgz", - "integrity": "sha512-Tkv6im39VuhduFMsDA3MlXcC/kKas3Z0PI1/8N88QvFQbtOeiiwnfFJE4juGyC8/a4sb1BSxQlzsil8XLQdxRw==", - "dev": true - }, "collapse-white-space": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/collapse-white-space/-/collapse-white-space-1.0.4.tgz", @@ -4295,17 +5190,6 @@ "sha.js": "^2.4.8" } }, - "create-react-class": { - "version": "15.6.3", - "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz", - "integrity": "sha512-M+/3Q6E6DLO6Yx3OwrWjwHBnvfXXYA7W+dFjt/ZDBemHO1DDZhsalX/NUtnTYclN6GfnBDRh4qRHjcDHmlJBJg==", - "dev": true, - "requires": { - "fbjs": "^0.8.9", - "loose-envify": "^1.3.1", - "object-assign": "^4.1.1" - } - }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", @@ -4507,15 +5391,15 @@ } }, "cssom": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.4.tgz", - "integrity": "sha512-+7prCSORpXNeR4/fUP3rL+TzqtiFfhMvTd7uEqMdgPvLPt4+uzFUeufx5RHjGTACCargg/DiEt/moMQmvnfkog==", + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.6.tgz", + "integrity": "sha512-DtUeseGk9/GBW0hl0vVPpU22iHL6YB5BUX7ml1hB+GMpo0NX5G4voX3kdWiMSEguFtcW3Vh3djqNF4aIe6ne0A==", "dev": true }, "cssstyle": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.1.1.tgz", - "integrity": "sha512-364AI1l/M5TYcFH83JnOH/pSqgaNnKmYgKrm0didZMGKWjQB60dymwWy1rKUgL3J1ffdq9xVi2yGLHdSjjSNog==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.2.2.tgz", + "integrity": "sha512-43wY3kl1CVQSvL7wUY1qXkxVGkStjpkDmVjiIKX8R97uhajy8Bybay78uOtqvh7Q5GK75dNPfW0geWjE6qQQow==", "dev": true, "requires": { "cssom": "0.3.x" @@ -4777,13 +5661,13 @@ } }, "data-urls": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.1.tgz", - "integrity": "sha512-0HdcMZzK6ubMUnsMmQmG0AcLQPvbvb47R0+7CCZQCYgcd8OUWG91CG7sM6GoXgjz+WLl4ArFzHtBMy/QqSF4eg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", "dev": true, "requires": { "abab": "^2.0.0", - "whatwg-mimetype": "^2.1.0", + "whatwg-mimetype": "^2.2.0", "whatwg-url": "^7.0.0" } }, @@ -6089,6 +6973,11 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-plist": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/fast-plist/-/fast-plist-0.1.2.tgz", + "integrity": "sha1-pFr/NFGWAG1AbKbNzQX2kFHvNbg=" + }, "fastparse": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.2.tgz", @@ -6145,6 +7034,12 @@ "pend": "~1.2.0" } }, + "figgy-pudding": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", + "integrity": "sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w==", + "dev": true + }, "figures": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", @@ -6435,6 +7330,12 @@ "through2": "^2.0.3" } }, + "fs-readdir-recursive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", + "dev": true + }, "fs-walk": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/fs-walk/-/fs-walk-0.0.1.tgz", @@ -6463,10 +7364,12 @@ }, "fsevents": { "version": "1.2.4", - "resolved": "", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, "optional": true, "requires": { + "nan": "^2.9.2", "node-pre-gyp": "^0.10.0" }, "dependencies": { @@ -9415,44 +10318,57 @@ "optional": true }, "jsdom": { - "version": "12.2.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-12.2.0.tgz", - "integrity": "sha512-QPOggIJ8fquWPLaYYMoh+zqUmdphDtu1ju0QGTitZT1Yd8I5qenPpXM1etzUegu3MjVp8XPzgZxdn8Yj7e40ig==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.0.0.tgz", + "integrity": "sha512-rJnHm7CHyIj4tDyz9VaCt0f0P0nEh/wEmMfwp9mMixy+L/r8OW/BNcgmIlfZuBBnVQS3eRBpvd/qM3R7vr7e3A==", "dev": true, "requires": { "abab": "^2.0.0", - "acorn": "^6.0.2", + "acorn": "^6.0.4", "acorn-globals": "^4.3.0", "array-equal": "^1.0.0", "cssom": "^0.3.4", "cssstyle": "^1.1.1", - "data-urls": "^1.0.1", + "data-urls": "^1.1.0", "domexception": "^1.0.1", "escodegen": "^1.11.0", "html-encoding-sniffer": "^1.0.2", - "nwsapi": "^2.0.9", + "nwsapi": "^2.1.3", "parse5": "5.1.0", "pn": "^1.1.0", "request": "^2.88.0", "request-promise-native": "^1.0.5", - "saxes": "^3.1.3", + "saxes": "^3.1.9", "symbol-tree": "^3.2.2", - "tough-cookie": "^2.4.3", + "tough-cookie": "^2.5.0", "w3c-hr-time": "^1.0.1", + "w3c-xmlserializer": "^1.1.2", "webidl-conversions": "^4.0.2", "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.2.0", + "whatwg-mimetype": "^2.3.0", "whatwg-url": "^7.0.0", - "ws": "^6.1.0", + "ws": "^6.1.2", "xml-name-validator": "^3.0.0" }, "dependencies": { "acorn": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.2.tgz", - "integrity": "sha512-GXmKIvbrN3TV7aVqAzVFaMW8F8wzVX7voEBRO3bDA64+EX37YSayggRJP5Xig6HYHBkWKpFg9W5gg6orklubhg==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz", + "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==", "dev": true }, + "ajv": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.0.tgz", + "integrity": "sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "aws4": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", @@ -9460,9 +10376,9 @@ "dev": true }, "escodegen": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.0.tgz", - "integrity": "sha512-IeMV45ReixHS53K/OmfKAIztN/igDHzTJUhZM3k1jMhIZWjk45SMwAtBsEXiJp3vSPmTcu6CXn7mDvFHRN66fw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.11.1.tgz", + "integrity": "sha512-JwiqFD9KdGVVpeuRa68yU3zZnBEOcPs0nKW7wZzXky8Z7tffdYUHbe11bPCV5jYlK6DVdKLWLm0f5I/QlL0Kmw==", "dev": true, "requires": { "esprima": "^3.1.3", @@ -9490,29 +10406,41 @@ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "har-validator": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", - "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "ajv": "^5.3.0", + "ajv": "^6.5.5", "har-schema": "^2.0.0" } }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, "mime-db": { - "version": "1.36.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.36.0.tgz", - "integrity": "sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.40.0.tgz", + "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==", "dev": true }, "mime-types": { - "version": "2.1.20", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.20.tgz", - "integrity": "sha512-HrkrPaP9vGuWbLK1B1FfgAkbqNjIuy4eHlIYnFi7kamZyLLrGlo2mpcx0bBmNpKqBtYtAfGbodDddIgddSJC2A==", + "version": "2.1.24", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", + "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", "dev": true, "requires": { - "mime-db": "~1.36.0" + "mime-db": "1.40.0" } }, "oauth-sign": { @@ -9553,6 +10481,18 @@ "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" + }, + "dependencies": { + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + } } }, "source-map": { @@ -9563,25 +10503,27 @@ "optional": true }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } } }, - "uuid": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", - "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", - "dev": true - }, "ws": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz", - "integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", + "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { "async-limiter": "~1.0.0" @@ -9865,6 +10807,11 @@ } } }, + "less-plugin-inline-urls": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/less-plugin-inline-urls/-/less-plugin-inline-urls-1.2.0.tgz", + "integrity": "sha1-XdqwegwlcfGihVz5Kd3J78Hjisw=" + }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -10158,7 +11105,6 @@ "version": "4.1.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -10418,9 +11364,9 @@ } }, "mime": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.3.1.tgz", - "integrity": "sha512-OEUllcVoydBHGN1z84yfQDimn58pZNNNXgZlHXSboxMlFvgI6MXSWpWKpFRra7H1HxpVhHTkrghfRW49k6yjeg==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.2.tgz", + "integrity": "sha512-zJBfZDkwRu+j3Pdd2aHsR5GfH2jIWhmL1ZzBoc+X+3JEti2hbArWcyJ+1laC1D2/U/W1a/+Cegj0/OnEU2ybjg==", "dev": true }, "mime-db": { @@ -10837,6 +11783,34 @@ "resolved": "http://registry.npmjs.org/moment/-/moment-2.21.0.tgz", "integrity": "sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==" }, + "monaco-editor": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.16.2.tgz", + "integrity": "sha512-NtGrFzf54jADe7qsWh3lazhS7Kj0XHkJUGBq9fA/Jbwc+sgVcyfsYF6z2AQ7hPqDC+JmdOt/OwFjBnRwqXtx6w==", + "dev": true + }, + "monaco-editor-textmate": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/monaco-editor-textmate/-/monaco-editor-textmate-2.1.1.tgz", + "integrity": "sha512-7jbOpjHhjJn5BYNjBSTD/yVf+Pnd6gBqr69skvFw8n1gJaUvjlVBZBCc5nrF5E8Q/4s1nOKuvqH/OvE+loDebg==" + }, + "monaco-editor-webpack-plugin": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/monaco-editor-webpack-plugin/-/monaco-editor-webpack-plugin-1.7.0.tgz", + "integrity": "sha512-oItymcnlL14Sjd7EF7q+CMhucfwR/2BxsqrXIBrWL6LQplFfAfV+grLEQRmVHeGSBZ/Gk9ptzfueXnWcoEcFuA==", + "dev": true, + "requires": { + "@types/webpack": "^4.4.19" + } + }, + "monaco-textmate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/monaco-textmate/-/monaco-textmate-3.0.0.tgz", + "integrity": "sha512-llE/NasQkbAEDx/RPp0ili5ZEXH4e/UkYFMACvJrEY0aybq6FVW9qySt5C4kWwRXCJDL+4ewgoTt4XO3M+bfIg==", + "requires": { + "fast-plist": "^0.1.2" + } + }, "moo": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/moo/-/moo-0.4.3.tgz", @@ -10892,6 +11866,13 @@ "resolved": "https://registry.npmjs.org/named-js-regexp/-/named-js-regexp-1.3.3.tgz", "integrity": "sha512-zIUAXzGQOp16VR0Ct89SDstU62hzAPBluNUrUrsdD7MNSRbm/vyqGhEnp+4hnsMjmX3C2wh1cbIEP0joKMFLxw==" }, + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==", + "dev": true, + "optional": true + }, "nanomatch": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", @@ -11090,6 +12071,12 @@ } } }, + "node-modules-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", + "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", + "dev": true + }, "node-releases": { "version": "1.0.0-alpha.12", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.0.0-alpha.12.tgz", @@ -11230,9 +12217,9 @@ "dev": true }, "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz", + "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw==", "dev": true }, "nyc": { @@ -11654,6 +12641,14 @@ "mimic-fn": "^1.0.0" } }, + "onigasm": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/onigasm/-/onigasm-2.2.2.tgz", + "integrity": "sha512-TQTMk+RmPYx4sGzNAgV0q7At7PABDNHVqZBlC4aRXHg8hpCdemLOF0qq0gUCjwUbc7mhJMBOo3XpTRYwyr45Gw==", + "requires": { + "lru-cache": "^4.1.1" + } + }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", @@ -11754,6 +12749,17 @@ "os-tmpdir": "^1.0.0" } }, + "output-file-sync": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/output-file-sync/-/output-file-sync-2.0.1.tgz", + "integrity": "sha512-mDho4qm7WgIXIGf4eYU1RHN2UU5tPfVYVSRwDJw0uTmj35DQUt/eNp19N7v6T3SrR0ESTEf2up2CGO73qI35zQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "is-plain-obj": "^1.1.0", + "mkdirp": "^0.5.1" + } + }, "p-cancelable": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.4.1.tgz", @@ -12159,6 +13165,15 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", + "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", + "dev": true, + "requires": { + "node-modules-regexp": "^1.0.0" + } + }, "pkg-dir": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", @@ -12467,8 +13482,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", @@ -12705,20 +13719,6 @@ "pure-color": "^1.2.0" } }, - "react-codemirror": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/react-codemirror/-/react-codemirror-1.0.0.tgz", - "integrity": "sha1-kUZ7U7H12A2Rai/QtMetuFqQAbo=", - "dev": true, - "requires": { - "classnames": "^2.2.5", - "codemirror": "^5.18.2", - "create-react-class": "^15.5.1", - "lodash.debounce": "^4.0.8", - "lodash.isequal": "^4.5.0", - "prop-types": "^15.5.4" - } - }, "react-color": { "version": "2.14.1", "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.14.1.tgz", @@ -13341,23 +14341,23 @@ } }, "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz", + "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==", "dev": true, "requires": { - "lodash": "^4.13.1" + "lodash": "^4.17.11" } }, "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz", + "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==", "dev": true, "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" + "request-promise-core": "1.1.2", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" } }, "require-directory": { @@ -13617,9 +14617,9 @@ "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" }, "saxes": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.3.tgz", - "integrity": "sha512-Nc5DXc5A+m3rUDtkS+vHlBWKT7mCKjJPyia7f8YMW773hsXVv2wEHQZGE0zs4+5PLwz9U5Sbl/94Cnd9vHV7Bg==", + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.9.tgz", + "integrity": "sha512-FZeKhJglhJHk7eWG5YM0z46VHmI3KJpMBAQm3xa9meDvd+wevB5GuBB0wc0exPInZiBBHqi00DbS8AcvCGCFMw==", "dev": true, "requires": { "xmlchars": "^1.3.1" @@ -14885,6 +15885,224 @@ } } }, + "terser-webpack-plugin": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz", + "integrity": "sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA==", + "dev": true, + "requires": { + "cacache": "^11.0.2", + "find-cache-dir": "^2.0.0", + "schema-utils": "^1.0.0", + "serialize-javascript": "^1.4.0", + "source-map": "^0.6.1", + "terser": "^3.16.1", + "webpack-sources": "^1.1.0", + "worker-farm": "^1.5.2" + }, + "dependencies": { + "bluebird": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.4.tgz", + "integrity": "sha512-FG+nFEZChJrbQ9tIccIfZJBz3J7mLrAhxakAbnrJWn8d7aKOC+LWifa0G+p4ZqKp4y13T7juYvdhq9NzKdsrjw==", + "dev": true + }, + "cacache": { + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-11.3.2.tgz", + "integrity": "sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg==", + "dev": true, + "requires": { + "bluebird": "^3.5.3", + "chownr": "^1.1.1", + "figgy-pudding": "^3.5.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "lru-cache": "^5.1.1", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.1", + "unique-filename": "^1.1.1", + "y18n": "^4.0.0" + } + }, + "commander": { + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.0.tgz", + "integrity": "sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ==", + "dev": true + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz", + "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", + "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "mississippi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", + "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", + "dev": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "semver": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.12", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.12.tgz", + "integrity": "sha512-4h2Pbvyy15EE02G+JOZpUCmqWJuqrs+sEkzewTm++BPi7Hvn/HwcqLAcNxYAyI0x13CpPPn+kMjl+hplXMHITQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ssri": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", + "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", + "dev": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "terser": { + "version": "3.17.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.17.0.tgz", + "integrity": "sha512-/FQzzPJmCpjAH9Xvk2paiWrFq+5M6aVOf+2KRbwhByISDX/EujxsK+BAvrhb6H+2rtrLCHK9N01wO014vrIwVQ==", + "dev": true, + "requires": { + "commander": "^2.19.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.10" + } + }, + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", + "dev": true + } + } + }, "test-exclude": { "version": "5.2.3", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", @@ -16160,9 +17378,9 @@ "dev": true }, "url-loader": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.1.tgz", - "integrity": "sha512-vugEeXjyYFBCUOpX+ZuaunbK3QXMKaQ3zUnRfIpRBlGkY7QizCnzyyn2ASfcxsvyU3ef+CJppVywnl3Kgf13Gg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/url-loader/-/url-loader-1.1.2.tgz", + "integrity": "sha512-dXHkKmw8FhPqu8asTc1puBfe3TehOCo2+RmOOev5suNCIYBcT626kxiWg1NBVkwc4rO8BGa7gP70W7VXuqHrjg==", "dev": true, "requires": { "loader-utils": "^1.1.0", @@ -16886,6 +18104,17 @@ "browser-process-hrtime": "^0.1.2" } }, + "w3c-xmlserializer": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz", + "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==", + "dev": true, + "requires": { + "domexception": "^1.0.1", + "webidl-conversions": "^4.0.2", + "xml-name-validator": "^3.0.0" + } + }, "watchpack": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", @@ -17312,9 +18541,9 @@ "dev": true }, "whatwg-mimetype": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.2.0.tgz", - "integrity": "sha512-5YSO1nMd5D1hY3WzAQV3PzZL83W3YeyR1yW9PcH26Weh1t+Vzh9B6XkDh7aXm83HBZ4nSMvkjvN2H2ySWIvBgw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", "dev": true }, "whatwg-url": { @@ -17528,8 +18757,7 @@ "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "12.0.2", diff --git a/package.datascience-ui.dependencies.json b/package.datascience-ui.dependencies.json index c684e5579101..85f45ee3ff31 100644 --- a/package.datascience-ui.dependencies.json +++ b/package.datascience-ui.dependencies.json @@ -28,11 +28,9 @@ "character-entities-legacy", "character-reference-invalid", "classnames", - "codemirror", "collapse-white-space", "core-js", "create-emotion", - "create-react-class", "css-loader", "d3-array", "d3-bboxCollide", @@ -66,6 +64,7 @@ "entities", "escape-carriage", "extend", + "fast-plist", "fbjs", "flat", "immutable", @@ -83,31 +82,34 @@ "leaflet", "lodash.clonedeep", "lodash.curry", - "lodash.debounce", "lodash.flatten", "lodash.flow", "lodash.get", - "lodash.isequal", "lodash.set", "lodash.uniq", "lodash", + "lru-cache", "martinez-polygon-clipping", "markdown-escapes", "material-colors", "mdast-add-list-metadata", "memoize-one", + "monaco-editor", + "monaco-editor-textmate", + "monaco-textmate", "numeral", "object-assign", + "onigasm", "os-browserify", "parse-entities", "path-browserify", "polygon-offset", "process", "prop-types", + "pseudomap", "pure-color", "react-annotation", "react-base16-styling", - "react-codemirror", "react-color", "react-data-grid", "react-data-grid-addons", @@ -125,6 +127,7 @@ "schedule", "semiotic-mark", "semiotic", + "setimmediate", "state-toggle", "string-hash", "style-loader", @@ -133,6 +136,7 @@ "svg-inline-react", "svg-path-bounding-box", "svgpath", + "timers-browserify", "tinycolor2", "tinyqueue", "trim-trailing-lines", @@ -146,11 +150,13 @@ "unist-util-stringify-position", "unist-util-visit-parents", "unist-util-visit", + "util", "uuid", "vfile-location", "vfile-message", "vfile", "viz-annotation", "x-is-string", - "xtend" + "xtend", + "yallist" ] diff --git a/package.json b/package.json index 068d042c5b2e..6d885ebf7a64 100644 --- a/package.json +++ b/package.json @@ -2148,12 +2148,16 @@ "iconv-lite": "^0.4.21", "inversify": "^4.11.1", "jsonc-parser": "^2.0.3", + "less-plugin-inline-urls": "^1.2.0", "line-by-line": "^0.1.6", "lodash": "^4.17.11", "md5": "^2.2.1", "minimatch": "^3.0.4", + "monaco-editor-textmate": "^2.1.1", + "monaco-textmate": "^3.0.0", "named-js-regexp": "^1.3.3", "node-stream-zip": "^1.6.0", + "onigasm": "^2.2.2", "pidusage": "^1.2.0", "reflect-metadata": "^0.1.12", "request": "^2.87.0", @@ -2183,9 +2187,13 @@ "xml2js": "^0.4.19" }, "devDependencies": { - "@babel/core": "^7.1.0", + "@babel/cli": "^7.4.4", + "@babel/core": "^7.4.4", + "@babel/plugin-transform-runtime": "^7.4.4", + "@babel/polyfill": "^7.4.4", "@babel/preset-env": "^7.1.0", "@babel/preset-react": "^7.0.0", + "@babel/register": "^7.4.4", "@nteract/plotly": "^1.47.1", "@nteract/transform-dataresource": "^4.3.5", "@nteract/transform-geojson": "^3.2.3", @@ -2216,7 +2224,6 @@ "@types/node": "9.4.7", "@types/promisify-node": "^0.4.0", "@types/react": "^16.4.14", - "@types/react-codemirror": "^1.0.2", "@types/react-dom": "^16.0.8", "@types/react-json-tree": "^0.6.8", "@types/request": "^2.47.0", @@ -2270,7 +2277,7 @@ "immutable": "^4.0.0-rc.12", "is-running": "^2.1.0", "istanbul": "^0.4.5", - "jsdom": "^12.2.0", + "jsdom": "^15.0.0", "json-loader": "^0.5.7", "less": "^3.9.0", "less-loader": "^5.0.0", @@ -2278,12 +2285,13 @@ "mocha": "^6.1.4", "mocha-junit-reporter": "^1.17.0", "mocha-multi-reporters": "^1.1.7", + "monaco-editor": "0.16.2", + "monaco-editor-webpack-plugin": "^1.7.0", "node-has-native-dependencies": "^1.0.2", "node-html-parser": "^1.1.13", "nyc": "^14.1.0", "raw-loader": "^0.5.1", "react": "^16.5.2", - "react-codemirror": "^1.0.0", "react-data-grid": "^6.0.2-0", "react-data-grid-addons": "^6.0.2-0", "react-dev-utils": "^5.0.2", @@ -2299,6 +2307,7 @@ "styled-jsx": "^3.1.0", "svg-inline-loader": "^0.8.0", "svg-inline-react": "^3.1.0", + "terser-webpack-plugin": "^1.2.3", "ts-loader": "^5.3.0", "ts-mockito": "^2.3.1", "tsconfig-paths-webpack-plugin": "^3.2.0", @@ -2308,7 +2317,7 @@ "typemoq": "^2.1.0", "typescript": "^3.4.3", "typescript-formatter": "^7.1.0", - "url-loader": "^1.1.1", + "url-loader": "^1.1.2", "uuid": "^3.3.2", "vsce": "^1.59.0", "vscode": "^1.1.33", diff --git a/resources/MagicPython.tmLanguage.json b/resources/MagicPython.tmLanguage.json new file mode 100644 index 000000000000..e51fcce1a859 --- /dev/null +++ b/resources/MagicPython.tmLanguage.json @@ -0,0 +1,5279 @@ +{ + "information_for_contributors": [ + "This file has been converted from https://github.com/MagicStack/MagicPython/blob/master/grammars/MagicPython.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/MagicStack/MagicPython/commit/8ff35b3e5fcde471fae62a57ea1ae1c7cd34c9fc", + "name": "MagicPython", + "scopeName": "source.python", + "patterns": [ + { + "include": "#statement" + }, + { + "include": "#expression" + } + ], + "repository": { + "impossible": { + "comment": "This is a special rule that should be used where no match is desired. It is not a good idea to match something like '1{0}' because in some cases that can result in infinite loops in token generation. So the rule instead matches and impossible expression to allow a match to fail and move to the next token.", + "match": "$.^" + }, + "statement": { + "patterns": [ + { + "include": "#import" + }, + { + "include": "#class-declaration" + }, + { + "include": "#function-declaration" + }, + { + "include": "#statement-keyword" + }, + { + "include": "#assignment-operator" + }, + { + "include": "#decorator" + }, + { + "include": "#docstring-statement" + }, + { + "include": "#semicolon" + } + ] + }, + "semicolon": { + "patterns": [ + { + "name": "invalid.deprecated.semicolon.python", + "match": "\\;$" + } + ] + }, + "comments": { + "patterns": [ + { + "name": "comment.line.number-sign.python", + "contentName": "meta.typehint.comment.python", + "begin": "(?x)\n (?:\n \\# \\s* (type:)\n \\s*+ (?# we want `\\s*+` which is possessive quantifier since\n we do not actually want to backtrack when matching\n whitespace here)\n (?! $ | \\#)\n )\n", + "end": "(?:$|(?=\\#))", + "beginCaptures": { + "0": { + "name": "meta.typehint.comment.python" + }, + "1": { + "name": "comment.typehint.directive.notation.python" + } + }, + "patterns": [ + { + "name": "comment.typehint.ignore.notation.python", + "match": "(?x)\n \\G ignore\n (?= \\s* (?: $ | \\#))\n" + }, + { + "name": "comment.typehint.type.notation.python", + "match": "(?x)\n (?))" + }, + { + "name": "comment.typehint.variable.notation.python", + "match": "([[:alpha:]_]\\w*)" + } + ] + }, + { + "include": "#comments-base" + } + ] + }, + "docstring-statement": { + "begin": "^(?=\\s*[rR]?(\\'\\'\\'|\\\"\\\"\\\"|\\'|\\\"))", + "comment": "the string either terminates correctly or by the beginning of a new line (this is for single line docstrings that aren't terminated) AND it's not followed by another docstring", + "end": "((?<=\\1)|^)(?!\\s*[rR]?(\\'\\'\\'|\\\"\\\"\\\"|\\'|\\\"))", + "patterns": [ + { + "include": "#docstring" + } + ] + }, + "docstring": { + "patterns": [ + { + "name": "string.quoted.docstring.multi.python", + "begin": "(\\'\\'\\'|\\\"\\\"\\\")", + "end": "(\\1)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + } + }, + "patterns": [ + { + "include": "#docstring-prompt" + }, + { + "include": "#codetags" + }, + { + "include": "#docstring-guts-unicode" + } + ] + }, + { + "name": "string.quoted.docstring.raw.multi.python", + "begin": "([rR])(\\'\\'\\'|\\\"\\\"\\\")", + "end": "(\\2)", + "beginCaptures": { + "1": { + "name": "storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + } + }, + "patterns": [ + { + "include": "#string-consume-escape" + }, + { + "include": "#docstring-prompt" + }, + { + "include": "#codetags" + } + ] + }, + { + "name": "string.quoted.docstring.single.python", + "begin": "(\\'|\\\")", + "end": "(\\1)|(\\n)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + }, + { + "include": "#docstring-guts-unicode" + } + ] + }, + { + "name": "string.quoted.docstring.raw.single.python", + "begin": "([rR])(\\'|\\\")", + "end": "(\\2)|(\\n)", + "beginCaptures": { + "1": { + "name": "storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#string-consume-escape" + }, + { + "include": "#codetags" + } + ] + } + ] + }, + "docstring-guts-unicode": { + "patterns": [ + { + "include": "#escape-sequence-unicode" + }, + { + "include": "#escape-sequence" + }, + { + "include": "#string-line-continuation" + } + ] + }, + "docstring-prompt": { + "match": "(?x)\n (?:\n (?:^|\\G) \\s* (?# '\\G' is necessary for ST)\n ((?:>>>|\\.\\.\\.) \\s) (?=\\s*\\S)\n )\n", + "captures": { + "1": { + "name": "keyword.control.flow.python" + } + } + }, + "statement-keyword": { + "patterns": [ + { + "name": "storage.type.function.python", + "match": "\\b((async\\s+)?\\s*def)\\b" + }, + { + "name": "keyword.control.flow.python", + "comment": "if `as` is eventually followed by `:` or line continuation\nit's probably control flow like:\n with foo as bar, \\\n Foo as Bar:\n try:\n do_stuff()\n except Exception as e:\n pass\n", + "match": "\\b(?>= | //= | \\*\\*=\n | \\+= | -= | /= | @=\n | \\*= | %= | ~= | \\^= | &= | \\|=\n | =(?!=)\n" + }, + "operator": { + "match": "(?x)\n \\b(?> | & | \\| | \\^ | ~) (?# 3)\n\n | (\\*\\* | \\* | \\+ | - | % | // | / | @) (?# 4)\n\n | (!= | == | >= | <= | < | >) (?# 5)\n", + "captures": { + "1": { + "name": "keyword.operator.logical.python" + }, + "2": { + "name": "keyword.control.flow.python" + }, + "3": { + "name": "keyword.operator.bitwise.python" + }, + "4": { + "name": "keyword.operator.arithmetic.python" + }, + "5": { + "name": "keyword.operator.comparison.python" + } + } + }, + "punctuation": { + "patterns": [ + { + "name": "punctuation.separator.colon.python", + "match": ":" + }, + { + "name": "punctuation.separator.element.python", + "match": "," + } + ] + }, + "literal": { + "patterns": [ + { + "name": "constant.language.python", + "match": "\\b(True|False|None|NotImplemented|Ellipsis)\\b" + }, + { + "include": "#number" + } + ] + }, + "number": { + "name": "constant.numeric.python", + "patterns": [ + { + "include": "#number-float" + }, + { + "include": "#number-dec" + }, + { + "include": "#number-hex" + }, + { + "include": "#number-oct" + }, + { + "include": "#number-bin" + }, + { + "include": "#number-long" + }, + { + "name": "invalid.illegal.name.python", + "match": "\\b[0-9]+\\w+" + } + ] + }, + "number-float": { + "name": "constant.numeric.float.python", + "match": "(?x)\n (?=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )?\n })\n )\n", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "3": { + "name": "storage.type.format.python" + }, + "4": { + "name": "storage.type.format.python" + } + } + }, + { + "name": "meta.format.brace.python", + "match": "(?x)\n (\n {\n \\w* (\\.[[:alpha:]_]\\w* | \\[[^\\]'\"]+\\])*\n (![rsa])?\n (:)\n [^'\"{}\\n]* (?:\n \\{ [^'\"}\\n]*? \\} [^'\"{}\\n]*\n )*\n }\n )\n", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "3": { + "name": "storage.type.format.python" + }, + "4": { + "name": "storage.type.format.python" + } + } + } + ] + }, + "fstring-formatting": { + "patterns": [ + { + "include": "#fstring-formatting-braces" + }, + { + "include": "#fstring-formatting-singe-brace" + } + ] + }, + "fstring-formatting-singe-brace": { + "name": "invalid.illegal.brace.python", + "match": "(}(?!}))" + }, + "import": { + "comment": "Import statements used to correctly mark `from`, `import`, and `as`\n", + "patterns": [ + { + "begin": "\\b(?)", + "end": "(?=:)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.annotation.result.python" + } + }, + "patterns": [ + { + "include": "#expression" + } + ] + }, + "item-access": { + "patterns": [ + { + "name": "meta.item-access.python", + "begin": "(?x)\n \\b(?=\n [[:alpha:]_]\\w* \\s* \\[\n )\n", + "end": "(\\])", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#item-name" + }, + { + "include": "#item-index" + }, + { + "include": "#expression" + } + ] + } + ] + }, + "item-name": { + "patterns": [ + { + "include": "#special-variables" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#special-names" + }, + { + "match": "(?x)\n \\b ([[:alpha:]_]\\w*) \\b\n" + } + ] + }, + "item-index": { + "begin": "(\\[)", + "end": "(?=\\])", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.python" + } + }, + "contentName": "meta.item-access.arguments.python", + "patterns": [ + { + "name": "punctuation.separator.slice.python", + "match": ":" + }, + { + "include": "#expression" + } + ] + }, + "decorator": { + "name": "meta.function.decorator.python", + "begin": "(?x)\n ^\\s*\n ((@)) \\s* (?=[[:alpha:]_]\\w*)\n", + "end": "(?x)\n ( \\) )\n # trailing whitespace and comments are legal\n (?: (.*?) (?=\\s*(?:\\#|$)) )\n | (?=\\n|\\#)\n", + "beginCaptures": { + "1": { + "name": "entity.name.function.decorator.python" + }, + "2": { + "name": "punctuation.definition.decorator.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + }, + "2": { + "name": "invalid.illegal.decorator.python" + } + }, + "patterns": [ + { + "include": "#decorator-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "decorator-name": { + "patterns": [ + { + "include": "#builtin-callables" + }, + { + "include": "#illegal-object-name" + }, + { + "name": "entity.name.function.decorator.python", + "match": "(?x)\n ([[:alpha:]_]\\w*) | (\\.)\n", + "captures": { + "2": { + "name": "punctuation.separator.period.python" + } + } + }, + { + "include": "#line-continuation" + }, + { + "name": "invalid.illegal.decorator.python", + "match": "(?x)\n \\s* ([^([:alpha:]\\s_\\.#\\\\] .*?) (?=\\#|$)\n", + "captures": { + "1": { + "name": "invalid.illegal.decorator.python" + } + } + } + ] + }, + "call-wrapper-inheritance": { + "comment": "same as a function call, but in inheritance context", + "name": "meta.function-call.python", + "begin": "(?x)\n \\b(?=\n ([[:alpha:]_]\\w*) \\s* (\\()\n )\n", + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#inheritance-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "inheritance-name": { + "patterns": [ + { + "include": "#lambda-incomplete" + }, + { + "include": "#builtin-possible-callables" + }, + { + "include": "#inheritance-identifier" + } + ] + }, + "function-call": { + "name": "meta.function-call.python", + "begin": "(?x)\n \\b(?=\n ([[:alpha:]_]\\w*) \\s* (\\()\n )\n", + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + } + }, + "patterns": [ + { + "include": "#special-variables" + }, + { + "include": "#function-name" + }, + { + "include": "#function-arguments" + } + ] + }, + "function-name": { + "patterns": [ + { + "include": "#builtin-possible-callables" + }, + { + "comment": "Some color schemas support meta.function-call.generic scope", + "name": "meta.function-call.generic.python", + "match": "(?x)\n \\b ([[:alpha:]_]\\w*) \\b\n" + } + ] + }, + "function-arguments": { + "begin": "(\\()", + "end": "(?=\\))(?!\\)\\s*\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.arguments.begin.python" + } + }, + "contentName": "meta.function-call.arguments.python", + "patterns": [ + { + "name": "punctuation.separator.arguments.python", + "match": "(,)" + }, + { + "match": "(?x)\n (?:(?<=[,(])|^) \\s* (\\*{1,2})\n", + "captures": { + "1": { + "name": "keyword.operator.unpacking.arguments.python" + } + } + }, + { + "include": "#lambda-incomplete" + }, + { + "include": "#illegal-names" + }, + { + "match": "\\b([[:alpha:]_]\\w*)\\s*(=)(?!=)", + "captures": { + "1": { + "name": "variable.parameter.function-call.python" + }, + "2": { + "name": "keyword.operator.assignment.python" + } + } + }, + { + "name": "keyword.operator.assignment.python", + "match": "=(?!=)" + }, + { + "include": "#expression" + }, + { + "match": "\\s*(\\))\\s*(\\()", + "captures": { + "1": { + "name": "punctuation.definition.arguments.end.python" + }, + "2": { + "name": "punctuation.definition.arguments.begin.python" + } + } + } + ] + }, + "builtin-callables": { + "patterns": [ + { + "include": "#illegal-names" + }, + { + "include": "#illegal-object-name" + }, + { + "include": "#builtin-exceptions" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#builtin-types" + } + ] + }, + "builtin-possible-callables": { + "patterns": [ + { + "include": "#builtin-callables" + }, + { + "include": "#magic-names" + } + ] + }, + "builtin-exceptions": { + "name": "support.type.exception.python", + "match": "(?x) (?" + }, + "regexp-base-expression": { + "patterns": [ + { + "include": "#regexp-quantifier" + }, + { + "include": "#regexp-base-common" + } + ] + }, + "fregexp-base-expression": { + "patterns": [ + { + "include": "#fregexp-quantifier" + }, + { + "include": "#fstring-formatting-braces" + }, + { + "match": "\\{.*?\\}" + }, + { + "include": "#regexp-base-common" + } + ] + }, + "fstring-formatting-braces": { + "patterns": [ + { + "comment": "empty braces are illegal", + "match": "({)(\\s*?)(})", + "captures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + }, + "2": { + "name": "invalid.illegal.brace.python" + }, + "3": { + "name": "constant.character.format.placeholder.other.python" + } + } + }, + { + "name": "constant.character.escape.python", + "match": "({{|}})" + } + ] + }, + "regexp-base-common": { + "patterns": [ + { + "name": "support.other.match.any.regexp", + "match": "\\." + }, + { + "name": "support.other.match.begin.regexp", + "match": "\\^" + }, + { + "name": "support.other.match.end.regexp", + "match": "\\$" + }, + { + "name": "keyword.operator.quantifier.regexp", + "match": "[+*?]\\??" + }, + { + "name": "keyword.operator.disjunction.regexp", + "match": "\\|" + }, + { + "include": "#regexp-escape-sequence" + } + ] + }, + "regexp-quantifier": { + "name": "keyword.operator.quantifier.regexp", + "match": "(?x)\n \\{(\n \\d+ | \\d+,(\\d+)? | ,\\d+\n )\\}\n" + }, + "fregexp-quantifier": { + "name": "keyword.operator.quantifier.regexp", + "match": "(?x)\n \\{\\{(\n \\d+ | \\d+,(\\d+)? | ,\\d+\n )\\}\\}\n" + }, + "regexp-backreference-number": { + "name": "meta.backreference.regexp", + "match": "(\\\\[1-9]\\d?)", + "captures": { + "1": { + "name": "entity.name.tag.backreference.regexp" + } + } + }, + "regexp-backreference": { + "name": "meta.backreference.named.regexp", + "match": "(?x)\n (\\() (\\?P= \\w+(?:\\s+[[:alnum:]]+)?) (\\))\n", + "captures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.backreference.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.backreference.regexp" + }, + "3": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.backreference.named.end.regexp" + } + } + }, + "regexp-flags": { + "name": "storage.modifier.flag.regexp", + "match": "\\(\\?[aiLmsux]+\\)" + }, + "regexp-escape-special": { + "name": "support.other.escape.special.regexp", + "match": "\\\\([AbBdDsSwWZ])" + }, + "regexp-escape-character": { + "name": "constant.character.escape.regexp", + "match": "(?x)\n \\\\ (\n x[0-9A-Fa-f]{2}\n | 0[0-7]{1,2}\n | [0-7]{3}\n )\n" + }, + "regexp-escape-unicode": { + "name": "constant.character.unicode.regexp", + "match": "(?x)\n \\\\ (\n u[0-9A-Fa-f]{4}\n | U[0-9A-Fa-f]{8}\n )\n" + }, + "regexp-escape-catchall": { + "name": "constant.character.escape.regexp", + "match": "\\\\(.|\\n)" + }, + "regexp-escape-sequence": { + "patterns": [ + { + "include": "#regexp-escape-special" + }, + { + "include": "#regexp-escape-character" + }, + { + "include": "#regexp-escape-unicode" + }, + { + "include": "#regexp-backreference-number" + }, + { + "include": "#regexp-escape-catchall" + } + ] + }, + "regexp-charecter-set-escapes": { + "patterns": [ + { + "name": "constant.character.escape.regexp", + "match": "\\\\[abfnrtv\\\\]" + }, + { + "include": "#regexp-escape-special" + }, + { + "name": "constant.character.escape.regexp", + "match": "\\\\([0-7]{1,3})" + }, + { + "include": "#regexp-escape-character" + }, + { + "include": "#regexp-escape-unicode" + }, + { + "include": "#regexp-escape-catchall" + } + ] + }, + "codetags": { + "match": "(?:\\b(NOTE|XXX|HACK|FIXME|BUG|TODO)\\b)", + "captures": { + "1": { + "name": "keyword.codetag.notation.python" + } + } + }, + "comments-base": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($)", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "comments-string-single-three": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($|(?='''))", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "comments-string-double-three": { + "name": "comment.line.number-sign.python", + "begin": "(\\#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.comment.python" + } + }, + "end": "($|(?=\"\"\"))", + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "single-one-regexp-expression": { + "patterns": [ + { + "include": "#regexp-base-expression" + }, + { + "include": "#single-one-regexp-character-set" + }, + { + "include": "#single-one-regexp-comments" + }, + { + "include": "#regexp-flags" + }, + { + "include": "#single-one-regexp-named-group" + }, + { + "include": "#regexp-backreference" + }, + { + "include": "#single-one-regexp-lookahead" + }, + { + "include": "#single-one-regexp-lookahead-negative" + }, + { + "include": "#single-one-regexp-lookbehind" + }, + { + "include": "#single-one-regexp-lookbehind-negative" + }, + { + "include": "#single-one-regexp-conditional" + }, + { + "include": "#single-one-regexp-parentheses-non-capturing" + }, + { + "include": "#single-one-regexp-parentheses" + } + ] + }, + "single-one-regexp-character-set": { + "patterns": [ + { + "match": "(?x)\n \\[ \\^? \\] (?! .*?\\])\n" + }, + { + "name": "meta.character.set.regexp", + "begin": "(\\[)(\\^)?(\\])?", + "end": "(\\]|(?=\\'))|((?=(?)\n", + "end": "(\\)|(?=\\'))|((?=(?)\n", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-comments": { + "name": "comment.regexp", + "begin": "\\(\\?#", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "punctuation.comment.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.comment.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "single-three-regexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-regexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-regexp-lookbehind-negative": { + "begin": "(\\()\\?)\n", + "end": "(\\)|(?=\"))|((?=(?)\n", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-comments": { + "name": "comment.regexp", + "begin": "\\(\\?#", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "punctuation.comment.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.comment.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#codetags" + } + ] + }, + "double-three-regexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-regexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-regexp-lookbehind-negative": { + "begin": "(\\()\\?)\n", + "end": "(\\)|(?=\\'))|((?=(?)\n", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-fregexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-fregexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-fregexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-fregexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-fregexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-fregexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\\'\\'\\'))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#single-three-fregexp-expression" + }, + { + "include": "#comments-string-single-three" + } + ] + }, + "single-three-fregexp-lookbehind-negative": { + "begin": "(\\()\\?)\n", + "end": "(\\)|(?=\"))|((?=(?)\n", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.begin.regexp" + }, + "2": { + "name": "entity.name.tag.named.group.regexp" + } + }, + "endCaptures": { + "1": { + "name": "support.other.parenthesis.regexp punctuation.parenthesis.named.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-fregexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-fregexp-lookahead": { + "begin": "(\\()\\?=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-fregexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-fregexp-lookahead-negative": { + "begin": "(\\()\\?!", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookahead.negative.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookahead.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookahead.negative.regexp punctuation.parenthesis.lookahead.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-fregexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-fregexp-lookbehind": { + "begin": "(\\()\\?<=", + "end": "(\\)|(?=\"\"\"))", + "beginCaptures": { + "0": { + "name": "keyword.operator.lookbehind.regexp" + }, + "1": { + "name": "punctuation.parenthesis.lookbehind.begin.regexp" + } + }, + "endCaptures": { + "1": { + "name": "keyword.operator.lookbehind.regexp punctuation.parenthesis.lookbehind.end.regexp" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#double-three-fregexp-expression" + }, + { + "include": "#comments-string-double-three" + } + ] + }, + "double-three-fregexp-lookbehind-negative": { + "begin": "(\\()\\?=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "captures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + } + }, + { + "include": "#fstring-terminator-single-tail" + } + ] + }, + "fstring-terminator-single-tail": { + "begin": "(![rsa])?(:)(?=.*?{)", + "end": "(?=})|(?=\\n)", + "beginCaptures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + }, + "patterns": [ + { + "include": "#fstring-illegal-single-brace" + }, + { + "include": "#fstring-single-brace" + }, + { + "name": "storage.type.format.python", + "match": "([bcdeEfFgGnosxX%])(?=})" + }, + { + "name": "storage.type.format.python", + "match": "(\\.\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(,)" + }, + { + "name": "storage.type.format.python", + "match": "(\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(\\#)" + }, + { + "name": "storage.type.format.python", + "match": "([-+ ])" + }, + { + "name": "storage.type.format.python", + "match": "([<>=^])" + }, + { + "name": "storage.type.format.python", + "match": "(\\w)" + } + ] + }, + "fstring-fnorm-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b[fF])([bBuU])?('''|\"\"\")", + "end": "(\\3)", + "beginCaptures": { + "1": { + "name": "string.interpolated.python string.quoted.multi.python storage.type.string.python" + }, + "2": { + "name": "invalid.illegal.prefix.python" + }, + "3": { + "name": "punctuation.definition.string.begin.python string.interpolated.python string.quoted.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-multi-core" + } + ] + }, + "fstring-normf-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b[bBuU])([fF])('''|\"\"\")", + "end": "(\\3)", + "beginCaptures": { + "1": { + "name": "invalid.illegal.prefix.python" + }, + "2": { + "name": "string.interpolated.python string.quoted.multi.python storage.type.string.python" + }, + "3": { + "name": "punctuation.definition.string.begin.python string.quoted.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-multi-core" + } + ] + }, + "fstring-raw-quoted-multi-line": { + "name": "meta.fstring.python", + "begin": "(\\b(?:[R][fF]|[fF][R]))('''|\"\"\")", + "end": "(\\2)", + "beginCaptures": { + "1": { + "name": "string.interpolated.python string.quoted.raw.multi.python storage.type.string.python" + }, + "2": { + "name": "punctuation.definition.string.begin.python string.quoted.raw.multi.python" + } + }, + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.python string.interpolated.python string.quoted.raw.multi.python" + }, + "2": { + "name": "invalid.illegal.newline.python" + } + }, + "patterns": [ + { + "include": "#fstring-raw-guts" + }, + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "include": "#fstring-raw-multi-core" + } + ] + }, + "fstring-multi-core": { + "name": "string.interpolated.python string.quoted.multi.python", + "match": "(?x)\n (.+?)\n (\n (?# .* and .*? in multi-line match need special handling of\n newlines otherwise SublimeText and Atom will match slightly\n differently.\n\n The guard for newlines has to be separate from the\n lookahead because of special $ matching rule.)\n ($\\n?)\n |\n (?=[\\\\\\}\\{]|'''|\"\"\")\n )\n (?# due to how multiline regexps are matched we need a special case\n for matching a newline character)\n | \\n\n" + }, + "fstring-raw-multi-core": { + "name": "string.interpolated.python string.quoted.raw.multi.python", + "match": "(?x)\n (.+?)\n (\n (?# .* and .*? in multi-line match need special handling of\n newlines otherwise SublimeText and Atom will match slightly\n differently.\n\n The guard for newlines has to be separate from the\n lookahead because of special $ matching rule.)\n ($\\n?)\n |\n (?=[\\\\\\}\\{]|'''|\"\"\")\n )\n (?# due to how multiline regexps are matched we need a special case\n for matching a newline character)\n | \\n\n" + }, + "fstring-multi-brace": { + "comment": "value interpolation using { ... }", + "begin": "(\\{)", + "end": "(?x)\n (\\})\n", + "beginCaptures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + } + }, + "endCaptures": { + "1": { + "name": "constant.character.format.placeholder.other.python" + } + }, + "patterns": [ + { + "include": "#fstring-terminator-multi" + }, + { + "include": "#f-expression" + } + ] + }, + "fstring-terminator-multi": { + "patterns": [ + { + "name": "storage.type.format.python", + "match": "(![rsa])(?=})" + }, + { + "match": "(?x)\n (![rsa])?\n ( : \\w? [<>=^]? [-+ ]? \\#?\n \\d* ,? (\\.\\d+)? [bcdeEfFgGnosxX%]? )(?=})\n", + "captures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + } + }, + { + "include": "#fstring-terminator-multi-tail" + } + ] + }, + "fstring-terminator-multi-tail": { + "begin": "(![rsa])?(:)(?=.*?{)", + "end": "(?=})", + "beginCaptures": { + "1": { + "name": "storage.type.format.python" + }, + "2": { + "name": "storage.type.format.python" + } + }, + "patterns": [ + { + "include": "#fstring-illegal-multi-brace" + }, + { + "include": "#fstring-multi-brace" + }, + { + "name": "storage.type.format.python", + "match": "([bcdeEfFgGnosxX%])(?=})" + }, + { + "name": "storage.type.format.python", + "match": "(\\.\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(,)" + }, + { + "name": "storage.type.format.python", + "match": "(\\d+)" + }, + { + "name": "storage.type.format.python", + "match": "(\\#)" + }, + { + "name": "storage.type.format.python", + "match": "([-+ ])" + }, + { + "name": "storage.type.format.python", + "match": "([<>=^])" + }, + { + "name": "storage.type.format.python", + "match": "(\\w)" + } + ] + } + } +} \ No newline at end of file diff --git a/src/client/activation/languageServer/languageServer.ts b/src/client/activation/languageServer/languageServer.ts index e7621c7df920..7e3486b88e15 100644 --- a/src/client/activation/languageServer/languageServer.ts +++ b/src/client/activation/languageServer/languageServer.ts @@ -20,10 +20,9 @@ import { ProgressReporting } from './progress'; @injectable() export class LanguageServer implements ILanguageServer { - private readonly startupCompleted: Deferred; + public languageClient: LanguageClient | undefined; + private startupCompleted: Deferred; private readonly disposables: Disposable[] = []; - - private languageClient?: LanguageClient; private extensionLoadedArgs = new Set<{}>(); constructor( @@ -48,27 +47,32 @@ export class LanguageServer implements ILanguageServer { } if (this.startupCompleted.completed) { this.startupCompleted.reject(new Error('Disposed Language Server')); + this.startupCompleted = createDeferred(); } } @traceDecorators.error('Failed to start language server') @captureTelemetry(EventName.PYTHON_LANGUAGE_SERVER_ENABLED, undefined, true) public async start(resource: Resource, options: LanguageClientOptions): Promise { - this.languageClient = await this.factory.createLanguageClient(resource, options); - this.disposables.push(this.languageClient!.start()); - await this.serverReady(); - const progressReporting = new ProgressReporting(this.languageClient!); - this.disposables.push(progressReporting); + if (!this.languageClient) { + this.languageClient = await this.factory.createLanguageClient(resource, options); + this.disposables.push(this.languageClient!.start()); + await this.serverReady(); + const progressReporting = new ProgressReporting(this.languageClient!); + this.disposables.push(progressReporting); - const settings = this.configurationService.getSettings(resource); - if (settings.downloadLanguageServer) { - this.languageClient.onTelemetry(telemetryEvent => { - const eventName = telemetryEvent.EventName || EventName.PYTHON_LANGUAGE_SERVER_TELEMETRY; - sendTelemetryEvent(eventName, telemetryEvent.Measurements, telemetryEvent.Properties); - }); - } + const settings = this.configurationService.getSettings(resource); + if (settings.downloadLanguageServer) { + this.languageClient.onTelemetry(telemetryEvent => { + const eventName = telemetryEvent.EventName || EventName.PYTHON_LANGUAGE_SERVER_TELEMETRY; + sendTelemetryEvent(eventName, telemetryEvent.Measurements, telemetryEvent.Properties); + }); + } - await this.registerTestServices(); + await this.registerTestServices(); + } else { + await this.startupCompleted.promise; + } } @traceDecorators.error('Failed to load Language Server extension') public loadExtension(args?: {}) { diff --git a/src/client/activation/serviceRegistry.ts b/src/client/activation/serviceRegistry.ts index e15f7ab82554..ad19bd0541d5 100644 --- a/src/client/activation/serviceRegistry.ts +++ b/src/client/activation/serviceRegistry.ts @@ -53,6 +53,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(ILanguageServerDownloader, LanguageServerDownloader); serviceManager.addSingleton(IPlatformData, PlatformData); serviceManager.add(ILanguageServerAnalysisOptions, LanguageServerAnalysisOptions); - serviceManager.add(ILanguageServer, LanguageServer); + serviceManager.addSingleton(ILanguageServer, LanguageServer); serviceManager.add(ILanguageServerManager, LanguageServerManager); } diff --git a/src/client/activation/types.ts b/src/client/activation/types.ts index ecb227dca265..b948e236361b 100644 --- a/src/client/activation/types.ts +++ b/src/client/activation/types.ts @@ -103,6 +103,10 @@ export interface ILanguageServerExtension extends IDisposable { } export const ILanguageServer = Symbol('ILanguageServer'); export interface ILanguageServer extends IDisposable { + /** + * LanguageClient in use + */ + languageClient: LanguageClient | undefined; start(resource: Resource, options: LanguageClientOptions): Promise; /** * Sends a request to LS so as to load other extensions. diff --git a/src/client/datascience/codeCssGenerator.ts b/src/client/datascience/codeCssGenerator.ts index 47ffe44969b5..5112084cf2de 100644 --- a/src/client/datascience/codeCssGenerator.ts +++ b/src/client/datascience/codeCssGenerator.ts @@ -1,15 +1,16 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import { JSONArray, JSONObject, JSONValue } from '@phosphor/coreutils'; +import { JSONArray, JSONObject } from '@phosphor/coreutils'; import * as fs from 'fs-extra'; import { inject, injectable } from 'inversify'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as path from 'path'; import * as stripJsonComments from 'strip-json-comments'; import { IWorkspaceService } from '../common/application/types'; import { IConfigurationService, ILogger } from '../common/types'; -import { DefaultTheme, Identifiers } from './constants'; +import { DefaultTheme } from './constants'; import { ICodeCssGenerator, IThemeFinder } from './types'; // tslint:disable:no-any @@ -71,6 +72,15 @@ const DefaultColors: { [key: string] : string } = { 'dark.punctuation' : '#1e1e1e' }; +interface IApplyThemeArgs { + tokenColors?: JSONArray | null; + baseColors?: JSONObject | null; + fontFamily: string; + fontSize: number; + isDark: boolean; + defaultStyle: string | undefined; +} + // This class generates css using the current theme in order to colorize code. // // NOTE: This is all a big hack. It's relying on the theme json files to have a certain format @@ -86,31 +96,39 @@ export class CodeCssGenerator implements ICodeCssGenerator { @inject(ILogger) private logger: ILogger) { } - public async generateThemeCss(isDark: boolean, theme: string): Promise { - let css : string = ''; + public generateThemeCss(isDark: boolean, theme: string): Promise { + return this.applyThemeData(isDark, theme, '', this.generateCss.bind(this)); + } + + public generateMonacoTheme(isDark: boolean, theme: string) : Promise { + return this.applyThemeData(isDark, theme, {}, this.generateMonacoThemeObject.bind(this)); + } + + private async applyThemeData(isDark: boolean, theme: string, defaultT: T, applier: (args: IApplyThemeArgs) => T) : Promise { + let result = defaultT; try { // First compute our current theme. - const workbench = this.workspaceService.getConfiguration('workbench'); const ignoreTheme = this.configService.getSettings().datascience.ignoreVscodeTheme ? true : false; theme = ignoreTheme ? DefaultTheme : theme; - const terminalCursor = workbench ? workbench.get('terminal.integrated.cursorStyle', 'block') : 'block'; const editor = this.workspaceService.getConfiguration('editor', undefined); - const font = editor ? editor.get('fontFamily', 'Consolas, \'Courier New\', monospace') : 'Consolas, \'Courier New\', monospace'; + const fontFamily = editor ? editor.get('fontFamily', 'Consolas, \'Courier New\', monospace') : 'Consolas, \'Courier New\', monospace'; const fontSize = editor ? editor.get('fontSize', 14) : 14; + const isDarkUpdated = ignoreTheme ? false : isDark; // Then we have to find where the theme resources are loaded from if (theme) { this.logger.logInformation('Searching for token colors ...'); const tokenColors = await this.findTokenColors(theme); + const baseColors = await this.findBaseColors(theme); // The tokens object then contains the necessary data to generate our css - if (tokenColors && font && fontSize) { + if (tokenColors && fontFamily && fontSize) { this.logger.logInformation('Using colors to generate CSS ...'); - css = this.generateCss(theme, tokenColors, font, fontSize, terminalCursor, ignoreTheme ? LightTheme : undefined); - } else if (tokenColors === null && font && fontSize) { + result = applier({ tokenColors, baseColors, fontFamily, fontSize, isDark: isDarkUpdated, defaultStyle: ignoreTheme ? LightTheme : undefined }); + } else if (tokenColors === null && fontFamily && fontSize) { // No colors found. See if we can figure out what type of theme we have const style = isDark ? DarkTheme : LightTheme ; - css = this.generateCss(theme, null, font, fontSize, terminalCursor, style); + result = applier({ fontFamily, fontSize, isDark: isDarkUpdated, defaultStyle: style}); } } } catch (err) { @@ -118,26 +136,27 @@ export class CodeCssGenerator implements ICodeCssGenerator { this.logger.logError(err); } - return css; + return result; + } + + private getScopes(entry: any) : JSONArray { + if (entry && entry.scope) { + return Array.isArray(entry.scope) ? entry.scope as JSONArray : entry.scope.toString().split(','); + } + return []; } private matchTokenColor(tokenColors: JSONArray, scope: string) : number { return tokenColors.findIndex((entry: any) => { - if (entry) { - const scopes = entry.scope as JSONValue; - if (scopes) { - const scopeArray = Array.isArray(scope) ? scopes as JSONArray : scopes.toString().split(','); - if (scopeArray.find(v => v !== null && v !== undefined && v.toString().trim() === scope)) { - return true; - } - } + const scopeArray = this.getScopes(entry); + if (scopeArray.find(v => v !== null && v !== undefined && v.toString().trim() === scope)) { + return true; } - return false; }); } - private getScopeStyle = (tokenColors: JSONArray | null, scope: string, secondary: string, defaultStyle: string | undefined): { color: string; fontStyle: string } => { + private getScopeStyle = (tokenColors: JSONArray | null | undefined, scope: string, secondary: string, defaultStyle: string | undefined): { color: string; fontStyle: string } => { // Search through the scopes on the json object if (tokenColors) { let match = this.matchTokenColor(tokenColors, scope); @@ -165,28 +184,14 @@ export class CodeCssGenerator implements ICodeCssGenerator { } // tslint:disable-next-line:max-func-body-length - private generateCss(theme: string, tokenColors: JSONArray | null, fontFamily: string, fontSize: number, cursorType: string, defaultStyle: string | undefined): string { - const escapedThemeName = Identifiers.GeneratedThemeName; + private generateCss(args: IApplyThemeArgs): string { // There's a set of values that need to be found - const commentStyle = this.getScopeStyle(tokenColors, 'comment', 'comment', defaultStyle); - const numericStyle = this.getScopeStyle(tokenColors, 'constant.numeric', 'constant', defaultStyle); - const stringStyle = this.getScopeStyle(tokenColors, 'string', 'string', defaultStyle); - const keywordStyle = this.getScopeStyle(tokenColors, 'keyword.control', 'keyword', defaultStyle); - const operatorStyle = this.getScopeStyle(tokenColors, 'keyword.operator', 'keyword', defaultStyle); - const variableStyle = this.getScopeStyle(tokenColors, 'variable', 'variable', defaultStyle); - const entityTypeStyle = this.getScopeStyle(tokenColors, 'entity.name.type', 'entity.name.type', defaultStyle); - // const atomic = this.getScopeColor(tokenColors, 'atomic'); - const builtinStyle = this.getScopeStyle(tokenColors, 'support.function', 'support.function', defaultStyle); - const punctuationStyle = this.getScopeStyle(tokenColors, 'punctuation', 'punctuation', defaultStyle); - - const def = 'var(--vscode-editor-foreground)'; - - // Define our cursor style based on the cursor type - const cursorStyle = cursorType === 'block' ? - `{ border: 1px solid ${def}; background: ${def}; width: 5px; z-index=100; }` : cursorType === 'underline' ? - `{ border-bottom: 1px solid ${def}; z-index=100; width: 5px; }` : - `{ border-left: 1px solid ${def}; border-right: none; z-index=100; }`; + const commentStyle = this.getScopeStyle(args.tokenColors, 'comment', 'comment', args.defaultStyle); + const numericStyle = this.getScopeStyle(args.tokenColors, 'constant.numeric', 'constant', args.defaultStyle); + const stringStyle = this.getScopeStyle(args.tokenColors, 'string', 'string', args.defaultStyle); + const variableStyle = this.getScopeStyle(args.tokenColors, 'variable', 'variable', args.defaultStyle); + const entityTypeStyle = this.getScopeStyle(args.tokenColors, 'entity.name.type', 'entity.name.type', args.defaultStyle); // Use these values to fill in our format string return ` @@ -196,40 +201,107 @@ export class CodeCssGenerator implements ICodeCssGenerator { --code-string-color: ${stringStyle.color}; --code-variable-color: ${variableStyle.color}; --code-type-color: ${entityTypeStyle.color}; - --code-font-family: ${fontFamily}; - --code-font-size: ${fontSize}px; + --code-font-family: ${args.fontFamily}; + --code-font-size: ${args.fontSize}px; } - ${defaultStyle ? DefaultCssVars[defaultStyle] : undefined } - - .cm-header, .cm-strong {font-weight: bold;} - .cm-em {font-style: italic;} - .cm-link {text-decoration: underline;} - .cm-strikethrough {text-decoration: line-through;} - - .cm-s-${escapedThemeName} span.cm-keyword {color: ${keywordStyle.color}; font-style: ${keywordStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-number {color: ${numericStyle.color}; font-style: ${numericStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-def {color: ${def}; } - .cm-s-${escapedThemeName} span.cm-variable {color: ${variableStyle.color}; font-style: ${variableStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-punctuation {color: ${punctuationStyle.color}; font-style: ${punctuationStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-property, - .cm-s-${escapedThemeName} span.cm-operator {color: ${operatorStyle.color}; font-style: ${operatorStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-variable-2 {color: ${variableStyle.color}; font-style: ${variableStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-variable-3, .cm-s-${theme} .cm-type {color: ${variableStyle.color}; font-style: ${variableStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-comment {color: ${commentStyle.color}; font-style: ${commentStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-string {color: ${stringStyle.color}; font-style: ${stringStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-string-2 {color: ${stringStyle.color}; font-style: ${stringStyle.fontStyle}; } - .cm-s-${escapedThemeName} span.cm-builtin {color: ${builtinStyle.color}; font-style: ${builtinStyle.fontStyle}; } - .cm-s-${escapedThemeName} div.CodeMirror-cursor ${cursorStyle} - .cm-s-${escapedThemeName} div.CodeMirror-selected {background: var(--vscode-editor-selectionBackground) !important;} + ${args.defaultStyle ? DefaultCssVars[args.defaultStyle] : undefined } `; + } + // Based on this data here: + // https://github.com/Microsoft/vscode/blob/master/src/vs/editor/standalone/common/themes.ts#L13 + private generateMonacoThemeObject(args: IApplyThemeArgs) : monacoEditor.editor.IStandaloneThemeData { + const result: monacoEditor.editor.IStandaloneThemeData = { + base: args.isDark ? 'vs-dark' : 'vs', + inherit: false, + rules: [], + colors: {} + }; + // If we have token colors enumerate them and add them into the rules + if (args.tokenColors && args.tokenColors.length) { + const tokenSet = new Set(); + args.tokenColors.forEach((t: any) => { + const scopes = this.getScopes(t); + const settings = t && t.settings ? t.settings : undefined; + if (scopes && settings) { + scopes.forEach(s => { + const token = s ? s.toString() : ''; + if (!tokenSet.has(token)) { + tokenSet.add(token); + result.rules.push({ + token, + foreground: settings.foreground, + background: settings.background, + fontStyle: settings.fontStyle + }); + + // Special case some items. punctuation.definition.comment doesn't seem to + // be listed anywhere. Add it manually when we find a 'comment' + // tslint:disable-next-line: possible-timing-attack + if (token === 'comment') { + result.rules.push({ + token: 'punctuation.definition.comment', + foreground: settings.foreground, + background: settings.background, + fontStyle: settings.fontStyle + }); + } + + // Same for string + // tslint:disable-next-line: possible-timing-attack + if (token === 'string') { + result.rules.push({ + token: 'punctuation.definition.string', + foreground: settings.foreground, + background: settings.background, + fontStyle: settings.fontStyle + }); + } + } + }); + } + }); + + result.rules = result.rules.sort((a: monacoEditor.editor.ITokenThemeRule, b: monacoEditor.editor.ITokenThemeRule) => { + return a.token.localeCompare(b.token); + }); + } else { + // Otherwise use our default values. + result.base = args.defaultStyle === DarkTheme ? 'vs-dark' : 'vs'; + result.inherit = true; + + if (args.defaultStyle) { + // Special case. We need rules for the comment beginning and the string beginning + result.rules.push({ + token: 'punctuation.definition.comment', + foreground: DefaultColors[`${args.defaultStyle}.comment`] + }); + result.rules.push({ + token: 'punctuation.definition.string', + foreground: DefaultColors[`${args.defaultStyle}.string`] + }); + } + } + // If we have base colors enumerate them and add them to the colors + if (args.baseColors) { + const keys = Object.keys(args.baseColors); + keys.forEach(k => { + const color = args.baseColors && args.baseColors[k] ? args.baseColors[k] : '#000000'; + result.colors[k] = color ? color.toString() : '#000000'; + }); + } // The else case here should end up inheriting. + return result; } private mergeColors = (colors1: JSONArray, colors2: JSONArray): JSONArray => { return [...colors1, ...colors2]; } + private mergeBaseColors = (colors1: JSONObject, colors2: JSONObject) : JSONObject => { + return {...colors1, ...colors2}; + } + private readTokenColors = async (themeFile: string): Promise => { const tokenContent = await fs.readFile(themeFile, 'utf8'); const theme = JSON.parse(stripJsonComments(tokenContent)) as JSONObject; @@ -237,7 +309,7 @@ export class CodeCssGenerator implements ICodeCssGenerator { if (tokenColors && tokenColors.length > 0) { // This theme may include others. If so we need to combine the two together const include = theme ? theme.include : undefined; - if (include && include !== null) { + if (include) { const includePath = path.join(path.dirname(themeFile), include.toString()); const includedColors = await this.readTokenColors(includePath); return this.mergeColors(tokenColors, includedColors); @@ -256,6 +328,23 @@ export class CodeCssGenerator implements ICodeCssGenerator { return []; } + private readBaseColors = async (themeFile: string): Promise => { + const tokenContent = await fs.readFile(themeFile, 'utf8'); + const theme = JSON.parse(stripJsonComments(tokenContent)) as JSONObject; + const colors = theme.colors as JSONObject; + + // This theme may include others. If so we need to combine the two together + const include = theme ? theme.include : undefined; + if (include) { + const includePath = path.join(path.dirname(themeFile), include.toString()); + const includedColors = await this.readBaseColors(includePath); + return this.mergeBaseColors(colors, includedColors); + } + + // Theme is a root, don't need to include others + return colors; + } + private findTokenColors = async (theme: string): Promise => { try { @@ -309,4 +398,54 @@ export class CodeCssGenerator implements ICodeCssGenerator { // Force the colors to the defaults return null; } + + private findBaseColors = async (theme: string): Promise => { + try { + this.logger.logInformation('Attempting search for colors ...'); + const themeRoot = await this.themeFinder.findThemeRootJson(theme); + + // Use the first result if we have one + if (themeRoot) { + this.logger.logInformation(`Loading base colors from ${themeRoot} ...`); + + // This should be the path to the file. Load it as a json object + const contents = await fs.readFile(themeRoot, 'utf8'); + const json = JSON.parse(stripJsonComments(contents)) as JSONObject; + + // There should be a theme colors section + const contributes = json.contributes as JSONObject; + + // If no contributes section, see if we have a tokenColors section. This means + // this is a direct token colors file + if (!contributes) { + return await this.readBaseColors(themeRoot); + } + + // This should have a themes section + const themes = contributes.themes as JSONArray; + + // One of these (it's an array), should have our matching theme entry + const index = themes.findIndex((e: any) => { + return e !== null && (e.id === theme || e.name === theme); + }); + + const found = index >= 0 ? themes[index] as any : null; + if (found !== null) { + // Then the path entry should contain a relative path to the json file with + // the tokens in it + const themeFile = path.join(path.dirname(themeRoot), found.path); + this.logger.logInformation(`Reading base colors from ${themeFile}`); + return await this.readBaseColors(themeFile); + } + } else { + this.logger.logWarning(`Color theme ${theme} not found. Using default colors.`); + } + } catch (err) { + // Swallow any exceptions with searching or parsing + this.logger.logError(err); + } + + // Force the colors to the defaults + return null; + } } diff --git a/src/client/datascience/constants.ts b/src/client/datascience/constants.ts index 9f4343513a69..b0d0dd6122e7 100644 --- a/src/client/datascience/constants.ts +++ b/src/client/datascience/constants.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. - 'use strict'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import { IS_WINDOWS } from '../common/platform/constants'; @@ -129,6 +129,7 @@ export namespace Identifiers { export const GeneratedThemeName = 'ipython-theme'; // This needs to be all lower class and a valid class name. export const HistoryPurpose = 'history'; export const MatplotLibDefaultParams = '_VSCode_defaultMatplotlib_Params'; + export const EditCellId = '3D3AB152-ADC1-4501-B813-4B83B49B0C10'; } export namespace JupyterCommands { @@ -175,6 +176,8 @@ export namespace LiveShareCommands { export namespace CssMessages { export const GetCssRequest = 'get_css_request'; export const GetCssResponse = 'get_css_response'; + export const GetMonacoThemeRequest = 'get_monaco_theme_request'; + export const GetMonacoThemeResponse = 'get_monaco_theme_response'; } export namespace SharedMessages { @@ -186,8 +189,16 @@ export interface IGetCssRequest { isDark: boolean; } +export interface IGetMonacoThemeRequest { + isDark: boolean; +} + export interface IGetCssResponse { css: string; theme: string; knownDark?: boolean; } + +export interface IGetMonacoThemeResponse { + theme: monacoEditor.editor.IStandaloneThemeData; +} diff --git a/src/client/datascience/history/history.ts b/src/client/datascience/history/history.ts index e39d04174b91..f233986db793 100644 --- a/src/client/datascience/history/history.ts +++ b/src/client/datascience/history/history.ts @@ -4,7 +4,7 @@ import '../../common/extensions'; import * as fs from 'fs-extra'; -import { inject, injectable } from 'inversify'; +import { inject, injectable, multiInject } from 'inversify'; import * as path from 'path'; import * as uuid from 'uuid/v4'; import { Event, EventEmitter, Position, Range, Selection, TextEditor, Uri, ViewColumn } from 'vscode'; @@ -20,9 +20,9 @@ import { IWorkspaceService } from '../../common/application/types'; import { CancellationError } from '../../common/cancellation'; -import { EXTENSION_ROOT_DIR } from '../../common/constants'; +import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../../common/constants'; import { ContextKey } from '../../common/contextKey'; -import { traceInfo } from '../../common/logger'; +import { traceInfo, traceWarning } from '../../common/logger'; import { IFileSystem } from '../../common/platform/types'; import { IConfigurationService, IDisposableRegistry, ILogger } from '../../common/types'; import { createDeferred, Deferred } from '../../common/utils/async'; @@ -40,6 +40,7 @@ import { IDataViewerProvider, IHistory, IHistoryInfo, + IHistoryListener, IHistoryProvider, IJupyterExecution, IJupyterVariable, @@ -78,6 +79,7 @@ export class History extends WebViewHost implements IHistory { private executeEvent: EventEmitter = new EventEmitter(); constructor( + @multiInject(IHistoryListener) private readonly listeners: IHistoryListener[], @inject(ILiveShareApi) private liveShare : ILiveShareApi, @inject(IApplicationShell) private applicationShell: IApplicationShell, @inject(IDocumentManager) private documentManager: IDocumentManager, @@ -128,6 +130,9 @@ export class History extends WebViewHost implements IHistory { // Load on a background thread. this.loadPromise = this.load(); + + // For each listener sign up for their post events + this.listeners.forEach(l => l.postMessage((e) => this.postMessageInternal(e.message, e.payload))); } public get ready() : Promise { @@ -161,7 +166,7 @@ export class History extends WebViewHost implements IHistory { return this.submitCode(code, file, line, undefined, editor); } - // tslint:disable-next-line: no-any no-empty cyclomatic-complexity + // tslint:disable-next-line: no-any no-empty cyclomatic-complexity max-func-body-length public onMessage(message: string, payload: any) { switch (message) { case HistoryMessages.GotoCodeCell: @@ -240,10 +245,23 @@ export class History extends WebViewHost implements IHistory { this.dispatchMessage(message, payload, this.requestVariableValue); break; + case HistoryMessages.LoadTmLanguageRequest: + this.dispatchMessage(message, payload, this.requestTmLanguage); + break; + + case HistoryMessages.LoadOnigasmAssemblyRequest: + this.dispatchMessage(message, payload, this.requestOnigasm); + break; + default: break; } + // Let our listeners handle the message too + if (this.listeners) { + this.listeners.forEach(l => l.onMessage(message, payload)); + } + // Pass onto our base class. super.onMessage(message, payload); @@ -266,6 +284,7 @@ export class History extends WebViewHost implements IHistory { super.dispose(); if (!this.disposed) { this.disposed = true; + this.listeners.forEach(l => l.dispose()); if (this.interpreterChangedDisposable) { this.interpreterChangedDisposable.dispose(); } @@ -951,7 +970,7 @@ export class History extends WebViewHost implements IHistory { } } - private requestVariables = async (requestExecutionCount: number): Promise => { + private async requestVariables(requestExecutionCount: number): Promise { // Request our new list of variables const vars: IJupyterVariable[] = await this.jupyterVariables.getVariables(); const variablesResponse: IJupyterVariablesResponse = {executionCount: requestExecutionCount, variables: vars }; @@ -976,7 +995,7 @@ export class History extends WebViewHost implements IHistory { } // tslint:disable-next-line: no-any - private requestVariableValue = async (payload?: any): Promise => { + private async requestVariableValue(payload?: any): Promise { if (payload) { const targetVar = payload as IJupyterVariable; // Request our variable value @@ -995,4 +1014,41 @@ export class History extends WebViewHost implements IHistory { sendTelemetryEvent(Telemetry.VariableExplorerToggled, undefined, { open: openValue }); } } + + private requestTmLanguage() { + // Get the contents of the appropriate tmLanguage file. + traceInfo('Request for tmlanguage file.'); + this.themeFinder.findTmLanguage(PYTHON_LANGUAGE).then(s => { + this.postMessage(HistoryMessages.LoadTmLanguageResponse, s).ignoreErrors(); + }).catch(_e => { + this.postMessage(HistoryMessages.LoadTmLanguageResponse, undefined).ignoreErrors(); + }); + } + + private async requestOnigasm() : Promise { + // Look for the file next or our current file (this is where it's installed in the vsix) + let filePath = path.join(__dirname, 'node_modules', 'onigasm', 'lib', 'onigasm.wasm'); + traceInfo(`Request for onigasm file at ${filePath}`); + if (this.fileSystem) { + if (await this.fileSystem.fileExists(filePath)) { + const contents = await fs.readFile(filePath); + this.postMessage(HistoryMessages.LoadOnigasmAssemblyResponse, contents).ignoreErrors(); + } else { + // During development it's actually in the node_modules folder + filePath = path.join(EXTENSION_ROOT_DIR, 'node_modules', 'onigasm', 'lib', 'onigasm.wasm'); + traceInfo(`Backup request for onigasm file at ${filePath}`); + if (await this.fileSystem.fileExists(filePath)) { + const contents = await fs.readFile(filePath); + this.postMessage(HistoryMessages.LoadOnigasmAssemblyResponse, contents).ignoreErrors(); + } else { + traceWarning('Onigasm file not found. Colorization will not be available.'); + this.postMessage(HistoryMessages.LoadOnigasmAssemblyResponse, undefined).ignoreErrors(); + } + } + } else { + // This happens during testing. Onigasm not needed as we're not testing colorization. + traceWarning('File system not found. Colorization will not be available.'); + this.postMessage(HistoryMessages.LoadOnigasmAssemblyResponse, undefined).ignoreErrors(); + } + } } diff --git a/src/client/datascience/history/historyTypes.ts b/src/client/datascience/history/historyTypes.ts index 6dba830f7428..7a0e7f448090 100644 --- a/src/client/datascience/history/historyTypes.ts +++ b/src/client/datascience/history/historyTypes.ts @@ -1,6 +1,8 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + import { CssMessages, IGetCssRequest, IGetCssResponse, SharedMessages } from '../constants'; import { ICell, IHistoryInfo, IJupyterVariable, IJupyterVariablesResponse } from '../types'; @@ -35,6 +37,19 @@ export namespace HistoryMessages { export const GetVariableValueRequest = 'get_variable_value_request'; export const GetVariableValueResponse = 'get_variable_value_response'; export const VariableExplorerToggle = 'variable_explorer_toggle'; + export const ProvideCompletionItemsRequest = 'provide_completion_items_request'; + export const CancelCompletionItemsRequest = 'cancel_completion_items_request'; + export const ProvideCompletionItemsResponse = 'provide_completion_items_response'; + export const ProvideHoverRequest = 'provide_hover_request'; + export const CancelHoverRequest = 'cancel_hover_request'; + export const ProvideHoverResponse = 'provide_hover_response'; + export const AddCell = 'add_cell'; + export const EditCell = 'edit_cell'; + export const RemoveCell = 'remove_cell'; + export const LoadOnigasmAssemblyRequest = 'load_onigasm_assembly_request'; + export const LoadOnigasmAssemblyResponse = 'load_onigasm_assembly_response'; + export const LoadTmLanguageRequest = 'load_tmlanguage_request'; + export const LoadTmLanguageResponse = 'load_tmlanguage_response'; } // These are the messages that will mirror'd to guest/hosts in @@ -70,6 +85,53 @@ export interface ISubmitNewCell { id: string; } +export interface IProvideCompletionItemsRequest { + position: monacoEditor.Position; + context: monacoEditor.languages.CompletionContext; + requestId: string; + cellId: string; +} + +export interface IProvideHoverRequest { + position: monacoEditor.Position; + requestId: string; + cellId: string; +} + +export interface ICancelIntellisenseRequest { + requestId: string; +} + +export interface IProvideCompletionItemsResponse { + list: monacoEditor.languages.CompletionList; + requestId: string; +} + +export interface IProvideHoverResponse { + hover: monacoEditor.languages.Hover; + requestId: string; +} + +export interface IPosition { + line: number; + ch: number; +} + +export interface IEditCell { + changes: monacoEditor.editor.IModelContentChange[]; + id: string; +} + +export interface IAddCell { + text: string; + file: string; + id: string; +} + +export interface IRemoveCell { + id: string; +} + // Map all messages to specific payloads export class IHistoryMapping { public [HistoryMessages.StartCell]: ICell; @@ -104,4 +166,17 @@ export class IHistoryMapping { public [HistoryMessages.VariableExplorerToggle]: boolean; public [CssMessages.GetCssRequest] : IGetCssRequest; public [CssMessages.GetCssResponse] : IGetCssResponse; + public [HistoryMessages.ProvideCompletionItemsRequest] : IProvideCompletionItemsRequest; + public [HistoryMessages.CancelCompletionItemsRequest] : ICancelIntellisenseRequest; + public [HistoryMessages.ProvideCompletionItemsResponse] : IProvideCompletionItemsResponse; + public [HistoryMessages.ProvideHoverRequest] : IProvideHoverRequest; + public [HistoryMessages.CancelHoverRequest] : ICancelIntellisenseRequest; + public [HistoryMessages.ProvideHoverResponse] : IProvideHoverResponse; + public [HistoryMessages.AddCell] : IAddCell; + public [HistoryMessages.EditCell] : IEditCell; + public [HistoryMessages.RemoveCell] : IRemoveCell; + public [HistoryMessages.LoadOnigasmAssemblyRequest]: never | undefined; + public [HistoryMessages.LoadOnigasmAssemblyResponse]: Buffer; + public [HistoryMessages.LoadTmLanguageRequest]: never | undefined; + public [HistoryMessages.LoadTmLanguageResponse]: string | undefined; } diff --git a/src/client/datascience/history/intellisense/baseIntellisenseProvider.ts b/src/client/datascience/history/intellisense/baseIntellisenseProvider.ts new file mode 100644 index 000000000000..87f8796626ab --- /dev/null +++ b/src/client/datascience/history/intellisense/baseIntellisenseProvider.ts @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import '../../../common/extensions'; + +import { injectable, unmanaged } from 'inversify'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import * as path from 'path'; +import * as uuid from 'uuid/v4'; +import { + CancellationToken, + CancellationTokenSource, + Event, + EventEmitter, + TextDocumentContentChangeEvent, + Uri +} from 'vscode'; + +import { IWorkspaceService } from '../../../common/application/types'; +import { IFileSystem, TemporaryFile } from '../../../common/platform/types'; +import { createDeferred, Deferred } from '../../../common/utils/async'; +import { Identifiers } from '../../constants'; +import { IHistoryListener } from '../../types'; +import { + HistoryMessages, + IAddCell, + ICancelIntellisenseRequest, + IEditCell, + IHistoryMapping, + IProvideCompletionItemsRequest, + IProvideHoverRequest, + IRemoveCell +} from '.././historyTypes'; +import { IntellisenseDocument } from './intellisenseDocument'; + +// tslint:disable:no-any +@injectable() +export abstract class BaseIntellisenseProvider implements IHistoryListener { + + private documentPromise: Deferred | undefined; + private temporaryFile: TemporaryFile | undefined; + private postEmitter: EventEmitter<{message: string; payload: any}> = new EventEmitter<{message: string; payload: any}>(); + private cancellationSources : Map = new Map(); + + constructor( + @unmanaged() private workspaceService: IWorkspaceService, + @unmanaged() private fileSystem: IFileSystem + ) { + } + + public dispose() { + if (this.temporaryFile) { + this.temporaryFile.dispose(); + } + } + + public get postMessage(): Event<{message: string; payload: any}> { + return this.postEmitter.event; + } + + public onMessage(message: string, payload?: any) { + switch (message) { + case HistoryMessages.CancelCompletionItemsRequest: + case HistoryMessages.CancelHoverRequest: + if (this.isActive) { + this.dispatchMessage(message, payload, this.handleCancel); + } + break; + + case HistoryMessages.ProvideCompletionItemsRequest: + if (this.isActive) { + this.dispatchMessage(message, payload, this.handleCompletionItemsRequest); + } + break; + + case HistoryMessages.ProvideHoverRequest: + if (this.isActive) { + this.dispatchMessage(message, payload, this.handleHoverRequest); + } + break; + + case HistoryMessages.EditCell: + this.dispatchMessage(message, payload, this.editCell); + break; + + case HistoryMessages.AddCell: + this.dispatchMessage(message, payload, this.addCell); + break; + + case HistoryMessages.RemoveCell: + this.dispatchMessage(message, payload, this.removeCell); + break; + + case HistoryMessages.DeleteAllCells: + this.dispatchMessage(message, payload, this.removeAllCells); + break; + + case HistoryMessages.RestartKernel: + this.dispatchMessage(message, payload, this.restartKernel); + break; + + default: + break; + } + } + + protected getDocument(resource?: Uri) : Promise { + if (!this.documentPromise) { + this.documentPromise = createDeferred(); + + // Create our dummy document. Compute a file path for it. + if (this.workspaceService.rootPath || resource) { + const dir = resource ? path.dirname(resource.fsPath) : this.workspaceService.rootPath!; + const dummyFilePath = path.join(dir, `History_${uuid().replace(/-/g, '')}.py`); + this.documentPromise.resolve(new IntellisenseDocument(dummyFilePath)); + } else { + this.fileSystem.createTemporaryFile('.py') + .then(t => { + this.temporaryFile = t; + const dummyFilePath = this.temporaryFile.filePath; + this.documentPromise!.resolve(new IntellisenseDocument(dummyFilePath)); + }) + .catch(e => { + this.documentPromise!.reject(e); + }); + } + } + + return this.documentPromise.promise; + } + + protected abstract get isActive(): boolean; + protected abstract provideCompletionItems(position: monacoEditor.Position, context: monacoEditor.languages.CompletionContext, cellId: string, token: CancellationToken) : Promise; + protected abstract provideHover(position: monacoEditor.Position, cellId: string, token: CancellationToken) : Promise; + protected abstract handleChanges(originalFile: string | undefined, document: IntellisenseDocument, changes: TextDocumentContentChangeEvent[]) : Promise; + + private dispatchMessage(_message: T, payload: any, handler: (args : M[T]) => void) { + const args = payload as M[T]; + handler.bind(this)(args); + } + + private postResponse(type: T, payload?: M[T]) : void { + const response = payload as any; + if (response && response.id) { + const cancelSource = this.cancellationSources.get(response.id); + if (cancelSource) { + cancelSource.dispose(); + this.cancellationSources.delete(response.id); + } + } + this.postEmitter.fire({message: type.toString(), payload}); + } + + private handleCancel(request: ICancelIntellisenseRequest) { + const cancelSource = this.cancellationSources.get(request.requestId); + if (cancelSource) { + cancelSource.cancel(); + cancelSource.dispose(); + this.cancellationSources.delete(request.requestId); + } + } + + private handleCompletionItemsRequest(request: IProvideCompletionItemsRequest) { + const cancelSource = new CancellationTokenSource(); + this.cancellationSources.set(request.requestId, cancelSource); + this.provideCompletionItems(request.position, request.context, request.cellId, cancelSource.token).then(list => { + this.postResponse(HistoryMessages.ProvideCompletionItemsResponse, {list, requestId: request.requestId}); + }).catch(_e => { + this.postResponse(HistoryMessages.ProvideCompletionItemsResponse, {list: { suggestions: [], incomplete: true }, requestId: request.requestId}); + }); + } + + private handleHoverRequest(request: IProvideHoverRequest) { + const cancelSource = new CancellationTokenSource(); + this.cancellationSources.set(request.requestId, cancelSource); + this.provideHover(request.position, request.cellId, cancelSource.token).then(hover => { + this.postResponse(HistoryMessages.ProvideHoverResponse, {hover, requestId: request.requestId}); + }).catch(_e => { + this.postResponse(HistoryMessages.ProvideHoverResponse, {hover: { contents: [] }, requestId: request.requestId}); + }); + } + + private async addCell(request: IAddCell): Promise { + // Get the document and then pass onto the sub class + const document = await this.getDocument(request.file === Identifiers.EmptyFileName ? undefined : Uri.file(request.file)); + if (document) { + const changes = document.addCell(request.text, request.id); + return this.handleChanges(request.file, document, changes); + } + } + + private async editCell(request: IEditCell): Promise { + // First get the document + const document = await this.getDocument(); + if (document) { + const changes = document.edit(request.changes, request.id); + return this.handleChanges(undefined, document, changes); + } + } + + private removeCell(_request: IRemoveCell): Promise { + // Skip this request. The logic here being that + // a user can remove a cell from the UI, but it's still loaded into the Jupyter kernel. + return Promise.resolve(); + } + + private removeAllCells(): Promise { + // Skip this request. The logic here being that + // a user can remove a cell from the UI, but it's still loaded into the Jupyter kernel. + return Promise.resolve(); + } + + private async restartKernel(): Promise { + // This is the one that acts like a reset + const document = await this.getDocument(); + if (document) { + const changes = document.removeAllCells(); + return this.handleChanges(undefined, document, changes); + } + } +} diff --git a/src/client/datascience/history/intellisense/conversion.ts b/src/client/datascience/history/intellisense/conversion.ts new file mode 100644 index 000000000000..6d1d352bcf19 --- /dev/null +++ b/src/client/datascience/history/intellisense/conversion.ts @@ -0,0 +1,204 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import '../../../common/extensions'; + +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import * as vscode from 'vscode'; +import * as vscodeLanguageClient from 'vscode-languageclient'; + +// See the comment on convertCompletionItemKind below +// Here's the monaco enum: +// Method = 0, +// Function = 1, +// Constructor = 2, +// Field = 3, +// Variable = 4, +// Class = 5, +// Struct = 6, +// Interface = 7, +// Module = 8, +// Property = 9, +// Event = 10, +// Operator = 11, +// Unit = 12, +// Value = 13, +// Constant = 14, +// Enum = 15, +// EnumMember = 16, +// Keyword = 17, +// Text = 18, +// Color = 19, +// File = 20, +// Reference = 21, +// Customcolor = 22, +// Folder = 23, +// TypeParameter = 24, +// Snippet = 25 +// +// Here's the vscode enum +// const Text: 1; +// const Method: 2; +// const Function: 3; +// const Constructor: 4; +// const Field: 5; +// const Variable: 6; +// const Class: 7; +// const Interface: 8; +// const Module: 9; +// const Property: 10; +// const Unit: 11; +// const Value: 12; +// const Enum: 13; +// const Keyword: 14; +// const Snippet: 15; +// const Color: 16; +// const File: 17; +// const Reference: 18; +// const Folder: 19; +// const EnumMember: 20; +// const Constant: 21; +// const Struct: 22; +// const Event: 23; +// const Operator: 24; +// const TypeParameter: 25; + +// Left side is the vscode value. +const mapCompletionItemKind: Map = new Map([ + [0, 9], // No value for zero in vscode + [1, 18], // Text + [2, 0], // Method + [3, 1], // Function + [4, 2], // Constructor + [5, 3], // Field + [6, 4], // Variable + [7, 5], // Class + [8, 7], // Interface + [9, 8], // Module + [10, 9], // Property + [11, 12], // Unit + [12, 13], // Value + [13, 15], // Enum + [14, 17], // Keyword + [15, 25], // Snippet + [16, 19], // Color + [17, 20], // File + [18, 21], // Reference + [19, 23], // Folder + [20, 16], // EnumMember + [21, 14], // Constant + [22, 6], // Struct + [23, 10], // Event + [24, 11], // Operator + [25, 24] // TypeParameter +]); + +function convertToMonacoRange(range: vscodeLanguageClient.Range | undefined) : monacoEditor.IRange | undefined { + if (range) { + return { + startLineNumber: range.start.line + 1, + startColumn: range.start.character + 1, + endLineNumber: range.end.line + 1, + endColumn: range.end.character + 1 + }; + } +} + +// Something very fishy. If the monacoEditor.languages.CompletionItemKind is included here, we get this error on startup +// Activating extension `ms-python.python` failed: Unexpected token { +// extensionHostProcess.js:457 +// Here is the error stack: f:\vscode-python\node_modules\monaco-editor\esm\vs\editor\editor.api.js:5 +// import { EDITOR_DEFAULTS } from './common/config/editorOptions.js'; +// Instead just use a map +function convertToMonacoCompletionItemKind(kind?: number): number { + const value = kind ? mapCompletionItemKind.get(kind) : 9; // Property is 9 + if (value) { + return value; + } + return 9; // Property +} + +function convertToMonacoCompletionItem(item: vscodeLanguageClient.CompletionItem, requiresKindConversion: boolean) : monacoEditor.languages.CompletionItem { + // They should be pretty much identical? Except for ranges. + // tslint:disable-next-line: no-object-literal-type-assertion + const result = {...item} as monacoEditor.languages.CompletionItem; + if (requiresKindConversion) { + result.kind = convertToMonacoCompletionItemKind(item.kind); + } + + // Make sure we have insert text, otherwise the monaco editor will crash on trying to hit tab or enter on the text + if (!result.insertText && result.label) { + result.insertText = result.label; + } + + return result; +} + +export function convertToMonacoCompletionList( + result: vscodeLanguageClient.CompletionList | vscodeLanguageClient.CompletionItem[] | vscode.CompletionItem[] | vscode.CompletionList | null, + requiresKindConversion: boolean) : monacoEditor.languages.CompletionList { + if (result) { + if (result.hasOwnProperty('items')) { + const list = result as vscodeLanguageClient.CompletionList; + return { + suggestions: list.items.map(l => convertToMonacoCompletionItem(l, requiresKindConversion)), + incomplete: list.isIncomplete + }; + } else { + // Must be one of the two array types since there's no items property. + const array = result as vscodeLanguageClient.CompletionItem[]; + return { + suggestions: array.map(l => convertToMonacoCompletionItem(l, requiresKindConversion)), + incomplete: false + }; + } + } + + return { + suggestions: [], + incomplete: true + }; +} + +function convertToMonacoMarkdown(strings: vscodeLanguageClient.MarkupContent | vscodeLanguageClient.MarkedString | vscodeLanguageClient.MarkedString[] | vscode.MarkedString | vscode.MarkedString[]) : monacoEditor.IMarkdownString[] { + if (strings.hasOwnProperty('kind')) { + const content = strings as vscodeLanguageClient.MarkupContent; + return [ + { + value: content.value + } + ]; + } else if (strings.hasOwnProperty('value')) { +// tslint:disable-next-line: no-any + const content = strings as any; + return [ + { + value: content.value + } + ]; + } else if (typeof strings === 'string') { + return [ + { + value: strings.toString() + } + ]; + } else if (Array.isArray(strings)) { + const array = strings as vscodeLanguageClient.MarkedString[]; + return array.map(a => convertToMonacoMarkdown(a)[0]); + } + + return []; +} + +export function convertToMonacoHover(result: vscodeLanguageClient.Hover | vscode.Hover | null | undefined) : monacoEditor.languages.Hover { + if (result) { + return { + contents: convertToMonacoMarkdown(result.contents), + range: convertToMonacoRange(result.range) + }; + } + + return { + contents: [] + }; +} diff --git a/src/client/datascience/history/intellisense/dotNetIntellisenseProvider.ts b/src/client/datascience/history/intellisense/dotNetIntellisenseProvider.ts new file mode 100644 index 000000000000..45afdbaf39ea --- /dev/null +++ b/src/client/datascience/history/intellisense/dotNetIntellisenseProvider.ts @@ -0,0 +1,121 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import '../../../common/extensions'; + +import { inject, injectable } from 'inversify'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { CancellationToken, TextDocumentContentChangeEvent, Uri } from 'vscode'; +import * as vscodeLanguageClient from 'vscode-languageclient'; + +import { ILanguageServer, ILanguageServerAnalysisOptions } from '../../../activation/types'; +import { IWorkspaceService } from '../../../common/application/types'; +import { IFileSystem } from '../../../common/platform/types'; +import { IConfigurationService } from '../../../common/types'; +import { createDeferred, Deferred } from '../../../common/utils/async'; +import { Identifiers } from '../../constants'; +import { IHistoryListener } from '../../types'; +import { BaseIntellisenseProvider } from './baseIntellisenseProvider'; +import { convertToMonacoCompletionList, convertToMonacoHover } from './conversion'; +import { IntellisenseDocument } from './intellisenseDocument'; + +// tslint:disable:no-any +@injectable() +export class DotNetIntellisenseProvider extends BaseIntellisenseProvider implements IHistoryListener { + + private languageClientPromise : Deferred | undefined; + private sentOpenDocument : boolean = false; + private active: boolean = false; + + constructor( + @inject(ILanguageServer) private languageServer: ILanguageServer, + @inject(ILanguageServerAnalysisOptions) private readonly analysisOptions: ILanguageServerAnalysisOptions, + @inject(IWorkspaceService) workspaceService: IWorkspaceService, + @inject(IConfigurationService) private configService: IConfigurationService, + @inject(IFileSystem) fileSystem: IFileSystem + ) { + super(workspaceService, fileSystem); + + // Make sure we're active. We still listen to messages for adding and editing cells, + // but we don't actually return any data. + this.active = !this.configService.getSettings().jediEnabled; + + // Listen for updates to settings to change this flag. Don't bother disposing the config watcher. It lives + // till the extension dies anyway. + this.configService.getSettings().onDidChange(() => this.active = !this.configService.getSettings().jediEnabled); + } + + protected get isActive() : boolean { + return this.active; + } + + protected async provideCompletionItems(position: monacoEditor.Position, context: monacoEditor.languages.CompletionContext, cellId: string, token: CancellationToken) : Promise { + const languageClient = await this.getLanguageClient(); + const document = await this.getDocument(); + if (languageClient && document) { + const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); + const result = await languageClient.sendRequest( + vscodeLanguageClient.CompletionRequest.type, + languageClient.code2ProtocolConverter.asCompletionParams(document, docPos, context), + token); + return convertToMonacoCompletionList(result, true); + } + + return { + suggestions: [], + incomplete: true + }; + } + protected async provideHover(position: monacoEditor.Position, cellId: string, token: CancellationToken) : Promise { + const languageClient = await this.getLanguageClient(); + const document = await this.getDocument(); + if (languageClient && document) { + const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); + const result = await languageClient.sendRequest( + vscodeLanguageClient.HoverRequest.type, + languageClient.code2ProtocolConverter.asTextDocumentPositionParams(document, docPos), + token); + return convertToMonacoHover(result); + } + + return { + contents: [] + }; + } + + protected async handleChanges(originalFile: string | undefined, document: IntellisenseDocument, changes: TextDocumentContentChangeEvent[]) : Promise { + // Then see if we can talk to our language client + if (this.active && document) { + + // Broadcast an update to the language server + const languageClient = await this.getLanguageClient(originalFile === Identifiers.EmptyFileName || originalFile === undefined ? undefined : Uri.file(originalFile)); + + if (!this.sentOpenDocument) { + this.sentOpenDocument = true; + return languageClient.sendNotification(vscodeLanguageClient.DidOpenTextDocumentNotification.type, { textDocument: document.textDocumentItem }); + } else { + return languageClient.sendNotification(vscodeLanguageClient.DidChangeTextDocumentNotification.type, { textDocument: document.textDocumentId, contentChanges: changes }); + } + } + } + + private getLanguageClient(file?: Uri) : Promise { + if (!this.languageClientPromise) { + this.languageClientPromise = createDeferred(); + this.startup(file) + .then(() => { + this.languageClientPromise!.resolve(this.languageServer.languageClient); + }) + .catch((e: any) => { + this.languageClientPromise!.reject(e); + }); + } + return this.languageClientPromise.promise; + } + + private async startup(resource?: Uri) : Promise { + // Start up the language server. We'll use this to talk to the language server + const options = await this.analysisOptions!.getAnalysisOptions(); + await this.languageServer.start(resource, options); + } +} diff --git a/src/client/datascience/history/intellisense/intellisenseDocument.ts b/src/client/datascience/history/intellisense/intellisenseDocument.ts new file mode 100644 index 000000000000..7113f1f84229 --- /dev/null +++ b/src/client/datascience/history/intellisense/intellisenseDocument.ts @@ -0,0 +1,374 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import '../../../common/extensions'; + +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { EndOfLine, Position, Range, TextDocument, TextDocumentContentChangeEvent, TextLine, Uri } from 'vscode'; +import * as vscodeLanguageClient from 'vscode-languageclient'; + +import { PYTHON_LANGUAGE } from '../../../common/constants'; +import { Identifiers } from '../../constants'; +import { DefaultWordPattern, ensureValidWordDefinition, getWordAtText, regExpLeadsToEndlessLoop } from './wordHelper'; + +class IntellisenseLine implements TextLine { + + private _range: Range; + private _rangeWithLineBreak: Range; + private _firstNonWhitespaceIndex: number | undefined; + private _isEmpty: boolean | undefined; + + constructor(private _contents: string, private _line: number, private _offset: number) { + this._range = new Range(new Position(_line, 0), new Position(_line, _contents.length)); + this._rangeWithLineBreak = new Range(this.range.start, new Position(_line, _contents.length + 1)); + } + + public get offset(): number { + return this._offset; + } + public get lineNumber(): number { + return this._line; + } + public get text(): string { + return this._contents; + } + public get range(): Range { + return this._range; + } + public get rangeIncludingLineBreak(): Range { + return this._rangeWithLineBreak; + } + public get firstNonWhitespaceCharacterIndex(): number { + if (this._firstNonWhitespaceIndex === undefined) { + this._firstNonWhitespaceIndex = this._contents.trimLeft().length - this._contents.length; + } + return this._firstNonWhitespaceIndex; + } + public get isEmptyOrWhitespace(): boolean { + if (this._isEmpty === undefined) { + this._isEmpty = this._contents.length === 0 || this._contents.trim().length === 0; + } + return this._isEmpty; + } +} + +interface ICellRange { + id: string; + start: number; + end: number; +} + +export class IntellisenseDocument implements TextDocument { + + private _uri: Uri; + private _version: number = 0; + private _lines: IntellisenseLine[] = []; + private _contents: string = ''; + private _cellRanges: ICellRange[] = []; + + constructor(fileName: string) { + // The file passed in is the base Uri for where we're basing this + // document. + // + // What about liveshare? + this._uri = Uri.file(fileName); + + // We should start our edit offset at 0. Each cell should end with a '/n' + this._cellRanges.push({ id: Identifiers.EditCellId, start: 0, end: 0 }); + } + + public get uri(): Uri { + return this._uri; + } + public get fileName(): string { + return this._uri.fsPath; + } + + public get isUntitled(): boolean { + return true; + } + public get languageId(): string { + return PYTHON_LANGUAGE; + } + public get version(): number { + return this._version; + } + public get isDirty(): boolean { + return true; + } + public get isClosed(): boolean { + return false; + } + public save(): Thenable { + return Promise.resolve(true); + } + public get eol(): EndOfLine { + return EndOfLine.LF; + } + public get lineCount(): number { + return this._lines.length; + } + public lineAt(position: Position | number): TextLine { + if (typeof position === 'number') { + return this._lines[position as number]; + } else { + return this._lines[position.line]; + } + } + public offsetAt(position: Position): number { + return this.convertToOffset(position); + } + public positionAt(offset: number): Position { + let line = 0; + let ch = 0; + while (line + 1 < this._lines.length && this._lines[line + 1].offset <= offset) { + line += 1; + } + if (line < this._lines.length) { + ch = offset - this._lines[line].offset; + } + return new Position(line, ch); + } + public getText(range?: Range | undefined): string { + if (!range) { + return this._contents; + } else { + const startOffset = this.convertToOffset(range.start); + const endOffset = this.convertToOffset(range.end); + return this._contents.substr(startOffset, endOffset - startOffset); + } + } + public getWordRangeAtPosition(position: Position, regexp?: RegExp | undefined): Range | undefined { + if (!regexp) { + // use default when custom-regexp isn't provided + regexp = DefaultWordPattern; + + } else if (regExpLeadsToEndlessLoop(regexp)) { + // use default when custom-regexp is bad + console.warn(`[getWordRangeAtPosition]: ignoring custom regexp '${regexp.source}' because it matches the empty string.`); + regexp = DefaultWordPattern; + } + + const wordAtText = getWordAtText( + position.character + 1, + ensureValidWordDefinition(regexp), + this._lines[position.line].text, + 0 + ); + + if (wordAtText) { + return new Range(position.line, wordAtText.startColumn - 1, position.line, wordAtText.endColumn - 1); + } + return undefined; + } + public validateRange(range: Range): Range { + return range; + } + public validatePosition(position: Position): Position { + return position; + } + + public get textDocumentItem(): vscodeLanguageClient.TextDocumentItem { + return { + uri: this._uri.toString(), + languageId: this.languageId, + version: this.version, + text: this.getText() + }; + } + + public get textDocumentId(): vscodeLanguageClient.VersionedTextDocumentIdentifier { + return { + uri: this._uri.toString(), + version: this.version + }; + } + public addCell(code: string, id: string): TextDocumentContentChangeEvent[] { + // This should only happen once for each cell. + this._version += 1; + + // Get rid of windows line endings. We're normalizing on linux + const normalized = code.replace(/\r/g, ''); + + // This item should go just before the edit cell + + // Make sure to put a newline between this code and the next code + const newCode = `${normalized}\n`; + + // We should start just before the last cell. + const fromOffset = this._cellRanges[this._cellRanges.length - 1].start; + + // Split our text between the edit text and the cells above + const before = this._contents.substr(0, fromOffset); + const after = this._contents.substr(fromOffset); + const fromPosition = this.positionAt(fromOffset); + + // Save the range for this cell () + this._cellRanges.splice(this._cellRanges.length - 1, 0, { id, start: fromOffset, end: fromOffset + newCode.length }); + + // Update our entire contents and recompute our lines + this._contents = `${before}${newCode}${after}`; + this._lines = this.createLines(); + this._cellRanges[this._cellRanges.length - 1].start += newCode.length; + this._cellRanges[this._cellRanges.length - 1].end += newCode.length; + + return [ + { + range: this.createSerializableRange(fromPosition, fromPosition), + rangeOffset: fromOffset, + rangeLength: 0, // Adds are always zero + text: newCode + } + ]; + } + + public removeCell(id: string): TextDocumentContentChangeEvent[] { + const cellIndex = this._cellRanges.findIndex(c => c.id === id); + if (cellIndex >= 0) { + this._version += 1; + + // Compute the range for this cell. + const from = this.positionAt(this._cellRanges[cellIndex].start); + const to = this.positionAt(this._cellRanges[cellIndex].end); + + // Remove the entire range. + const result = this.removeRange('', from, to, cellIndex); + + // Remove the cell + this._cellRanges.splice(cellIndex, 1); + + return result; + } + + return []; + } + + public removeAllCells(): TextDocumentContentChangeEvent[] { + // Remove everything up to the edit cell + if (this._cellRanges.length > 1) { + this._version += 1; + + // Compute the offset for the edit cell + const toOffset = this._cellRanges[this._cellRanges.length - 1].start; + const from = this.positionAt(0); + const to = this.positionAt(toOffset); + + // Remove the entire range. + const result = this.removeRange('', from, to, 0); + + // Update our cell range + this._cellRanges = [ { + id: Identifiers.EditCellId, + start: 0, + end: this._cellRanges[this._cellRanges.length - 1].end - toOffset + }]; + + return result; + } + + return []; + } + + public edit(editorChanges: monacoEditor.editor.IModelContentChange[], id: string): TextDocumentContentChangeEvent[] { + this._version += 1; + + // Convert the range to local (and remove 1 based) + if (editorChanges && editorChanges.length) { + const normalized = editorChanges[0].text.replace(/\r/g, ''); + + // Figure out which cell we're editing. + const cellIndex = this._cellRanges.findIndex(c => c.id === id); + if (cellIndex >= 0) { + // Line/column are within this cell. Use its offset to compute the real position + const editPos = this.positionAt(this._cellRanges[cellIndex].start); + const from = new Position(editPos.line + editorChanges[0].range.startLineNumber - 1, editorChanges[0].range.startColumn - 1); + const to = new Position(editPos.line + editorChanges[0].range.endLineNumber - 1, editorChanges[0].range.endColumn - 1); + + // Remove this range from the contents and return the change. + return this.removeRange(normalized, from, to, cellIndex); + } + } + + return []; + } + + public convertToDocumentPosition(id: string, line: number, ch: number): Position { + // Monaco is 1 based, and we need to add in our cell offset. + const cellIndex = this._cellRanges.findIndex(c => c.id === id); + if (cellIndex >= 0) { + // Line/column are within this cell. Use its offset to compute the real position + const editLine = this.positionAt(this._cellRanges[cellIndex].start); + const docLine = line - 1 + editLine.line; + const docCh = ch - 1; + return new Position(docLine, docCh); + } + + // We can't find a cell that matches. Just remove the 1 based + return new Position(line - 1, ch - 1); + } + + private removeRange(newText: string, from: Position, to: Position, cellIndex: number) : TextDocumentContentChangeEvent[] { + const fromOffset = this.convertToOffset(from); + const toOffset = this.convertToOffset(to); + + // Recreate our contents, and then recompute all of our lines + const before = this._contents.substr(0, fromOffset); + const after = this._contents.substr(toOffset); + this._contents = `${before}${newText}${after}`; + this._lines = this.createLines(); + + // Update ranges after this. All should move by the diff in length, although the current one + // should stay at the same start point. + const lengthDiff = newText.length - (toOffset - fromOffset); + for (let i = cellIndex; i < this._cellRanges.length; i += 1) { + if (i !== cellIndex) { + this._cellRanges[i].start += lengthDiff; + } + this._cellRanges[i].end += lengthDiff; + } + + return [ + { + range: this.createSerializableRange(from, to), + rangeOffset: fromOffset, + rangeLength: toOffset - fromOffset, + text: newText + } + ]; + } + + private createLines(): IntellisenseLine[] { + const split = this._contents.splitLines({ trim: false, removeEmptyEntries: false }); + let prevLine: IntellisenseLine | undefined; + return split.map((s, i) => { + const nextLine = this.createTextLine(s, i, prevLine); + prevLine = nextLine; + return nextLine; + }); + } + + private createTextLine(line: string, index: number, prevLine: IntellisenseLine | undefined): IntellisenseLine { + return new IntellisenseLine(line, index, prevLine ? prevLine.offset + prevLine.rangeIncludingLineBreak.end.character : 0); + } + + private convertToOffset(pos: Position): number { + if (pos.line < this._lines.length) { + return this._lines[pos.line].offset + pos.character; + } + return this._contents.length; + } + + private createSerializableRange(start: Position, end: Position): Range { + const result = { + start: { + line: start.line, + character: start.character + }, + end: { + line: end.line, + character: end.character + } + }; + return result as Range; + } +} diff --git a/src/client/datascience/history/intellisense/jediIntellisenseProvider.ts b/src/client/datascience/history/intellisense/jediIntellisenseProvider.ts new file mode 100644 index 000000000000..c4121165cac9 --- /dev/null +++ b/src/client/datascience/history/intellisense/jediIntellisenseProvider.ts @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import '../../../common/extensions'; + +import { inject, injectable } from 'inversify'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { CancellationToken, TextDocumentContentChangeEvent } from 'vscode'; + +import { IWorkspaceService } from '../../../common/application/types'; +import { IFileSystem } from '../../../common/platform/types'; +import { IConfigurationService, IDisposableRegistry, IExtensionContext } from '../../../common/types'; +import { IServiceManager } from '../../../ioc/types'; +import { JediFactory } from '../../../languageServices/jediProxyFactory'; +import { PythonCompletionItemProvider } from '../../../providers/completionProvider'; +import { PythonHoverProvider } from '../../../providers/hoverProvider'; +import { IHistoryListener } from '../../types'; +import { BaseIntellisenseProvider } from './baseIntellisenseProvider'; +import { convertToMonacoCompletionList, convertToMonacoHover } from './conversion'; +import { IntellisenseDocument } from './intellisenseDocument'; + +// tslint:disable:no-any +@injectable() +export class JediIntellisenseProvider extends BaseIntellisenseProvider implements IHistoryListener { + + private active: boolean = false; + private pythonHoverProvider : PythonHoverProvider | undefined; + private pythonCompletionItemProvider : PythonCompletionItemProvider | undefined; + private jediFactory: JediFactory; + private readonly context: IExtensionContext; + + constructor( + @inject(IServiceManager) private serviceManager: IServiceManager, + @inject(IDisposableRegistry) private disposables: IDisposableRegistry, + @inject(IWorkspaceService) workspaceService: IWorkspaceService, + @inject(IConfigurationService) private configService: IConfigurationService, + @inject(IFileSystem) fileSystem: IFileSystem + ) { + super(workspaceService, fileSystem); + + this.context = this.serviceManager.get(IExtensionContext); + this.jediFactory = new JediFactory(this.context.asAbsolutePath('.'), this.serviceManager); + this.disposables.push(this.jediFactory); + + // Make sure we're active. We still listen to messages for adding and editing cells, + // but we don't actually return any data. + this.active = this.configService.getSettings().jediEnabled; + + // Listen for updates to settings to change this flag + disposables.push(this.configService.getSettings().onDidChange(() => this.active = this.configService.getSettings().jediEnabled)); + + // Create our jedi wrappers if necessary + if (this.active) { + this.pythonHoverProvider = new PythonHoverProvider(this.jediFactory); + this.pythonCompletionItemProvider = new PythonCompletionItemProvider(this.jediFactory, this.serviceManager); + } + } + + public dispose() { + super.dispose(); + this.jediFactory.dispose(); + } + protected get isActive() : boolean { + return this.active; + } + protected async provideCompletionItems(position: monacoEditor.Position, _context: monacoEditor.languages.CompletionContext, cellId: string, token: CancellationToken) : Promise { + const document = await this.getDocument(); + if (this.pythonCompletionItemProvider && document) { + const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); + const result = await this.pythonCompletionItemProvider.provideCompletionItems(document, docPos, token); + return convertToMonacoCompletionList(result, false); + } + + return { + suggestions: [], + incomplete: true + }; + } + protected async provideHover(position: monacoEditor.Position, cellId: string, token: CancellationToken) : Promise { + const document = await this.getDocument(); + if (this.pythonHoverProvider && document) { + const docPos = document.convertToDocumentPosition(cellId, position.lineNumber, position.column); + const result = await this.pythonHoverProvider.provideHover(document, docPos, token); + return convertToMonacoHover(result); + } + + return { + contents: [] + }; + } + + protected handleChanges(_originalFile: string | undefined, _document: IntellisenseDocument, _changes: TextDocumentContentChangeEvent[]) : Promise { + // We don't need to forward these to jedi. It always uses the entire document + return Promise.resolve(); + } + +} diff --git a/src/client/datascience/history/intellisense/wordHelper.ts b/src/client/datascience/history/intellisense/wordHelper.ts new file mode 100644 index 000000000000..088b43a9ae12 --- /dev/null +++ b/src/client/datascience/history/intellisense/wordHelper.ts @@ -0,0 +1,156 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// Borrowed this from the vscode source. From here: +// src\vs\editor\common\model\wordHelper.ts + +export interface IWordAtPosition { + readonly word: string; + readonly startColumn: number; + readonly endColumn: number; +} + +export const USUAL_WORD_SEPARATORS = '`~!@#$%^&*()-=+[{]}\\|;:\'",.<>/?'; + +/** + * Create a word definition regular expression based on default word separators. + * Optionally provide allowed separators that should be included in words. + * + * The default would look like this: + * /(-?\d*\.\d\w*)|([^\`\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g + */ +function createWordRegExp(allowInWords: string = ''): RegExp { + let source = '(-?\\d*\\.\\d\\w*)|([^'; + for (const sep of USUAL_WORD_SEPARATORS) { + if (allowInWords.indexOf(sep) >= 0) { + continue; + } + source += `\\${sep}`; + } + source += '\\s]+)'; + return new RegExp(source, 'g'); +} + +// catches numbers (including floating numbers) in the first group, and alphanum in the second +export const DEFAULT_WORD_REGEXP = createWordRegExp(); + +export function ensureValidWordDefinition(wordDefinition?: RegExp | null): RegExp { + let result: RegExp = DEFAULT_WORD_REGEXP; + + if (wordDefinition && (wordDefinition instanceof RegExp)) { + if (!wordDefinition.global) { + let flags = 'g'; + if (wordDefinition.ignoreCase) { + flags += 'i'; + } + if (wordDefinition.multiline) { + flags += 'm'; + } + // tslint:disable-next-line: no-any + if ((wordDefinition as any).unicode) { + flags += 'u'; + } + result = new RegExp(wordDefinition.source, flags); + } else { + result = wordDefinition; + } + } + + result.lastIndex = 0; + + return result; +} + +function getWordAtPosFast(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null { + // find whitespace enclosed text around column and match from there + + const pos = column - 1 - textOffset; + const start = text.lastIndexOf(' ', pos - 1) + 1; + + wordDefinition.lastIndex = start; + let match: RegExpMatchArray | null = wordDefinition.exec(text); + while (match) { + const matchIndex = match.index || 0; + if (matchIndex <= pos && wordDefinition.lastIndex >= pos) { + return { + word: match[0], + startColumn: textOffset + 1 + matchIndex, + endColumn: textOffset + 1 + wordDefinition.lastIndex + }; + } + match = wordDefinition.exec(text); + } + + return null; +} + +function getWordAtPosSlow(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null { + // matches all words starting at the beginning + // of the input until it finds a match that encloses + // the desired column. slow but correct + + const pos = column - 1 - textOffset; + wordDefinition.lastIndex = 0; + + let match: RegExpMatchArray | null = wordDefinition.exec(text); + while (match) { + const matchIndex = match.index || 0; + if (matchIndex > pos) { + // |nW -> matched only after the pos + return null; + + } else if (wordDefinition.lastIndex >= pos) { + // W|W -> match encloses pos + return { + word: match[0], + startColumn: textOffset + 1 + matchIndex, + endColumn: textOffset + 1 + wordDefinition.lastIndex + }; + } + match = wordDefinition.exec(text); + } + + return null; +} + +export function getWordAtText(column: number, wordDefinition: RegExp, text: string, textOffset: number): IWordAtPosition | null { + + // if `words` can contain whitespace character we have to use the slow variant + // otherwise we use the fast variant of finding a word + wordDefinition.lastIndex = 0; + const match = wordDefinition.exec(text); + if (!match) { + return null; + } + // todo@joh the `match` could already be the (first) word + const ret = match[0].indexOf(' ') >= 0 + // did match a word which contains a space character -> use slow word find + ? getWordAtPosSlow(column, wordDefinition, text, textOffset) + // sane word definition -> use fast word find + : getWordAtPosFast(column, wordDefinition, text, textOffset); + + // both (getWordAtPosFast and getWordAtPosSlow) leave the wordDefinition-RegExp + // in an undefined state and to not confuse other users of the wordDefinition + // we reset the lastIndex + wordDefinition.lastIndex = 0; + + return ret; +} + +export function regExpLeadsToEndlessLoop(regexp: RegExp): boolean { + // Exit early if it's one of these special cases which are meant to match + // against an empty string + if (regexp.source === '^' || regexp.source === '^$' || regexp.source === '$' || regexp.source === '^\\s*$') { + return false; + } + + // We check against an empty string. If the regular expression doesn't advance + // (e.g. ends in an endless loop) it will match an empty string. + const match = regexp.exec(''); + // tslint:disable-next-line: no-any + return !!(match && regexp.lastIndex === 0); +} + +export const DefaultWordPattern = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g; diff --git a/src/client/datascience/jupyter/jupyterSession.ts b/src/client/datascience/jupyter/jupyterSession.ts index 1aa2851da598..da630605cfc2 100644 --- a/src/client/datascience/jupyter/jupyterSession.ts +++ b/src/client/datascience/jupyter/jupyterSession.ts @@ -84,7 +84,7 @@ export class JupyterSession implements IJupyterSession { this.session.kernel.status !== 'idle' && (Date.now() - startTime < timeout)) { traceInfo(`Waiting for idle: ${this.session.kernel.status}`); - await sleep(10); + await sleep(100); } // If we didn't make it out in ten seconds, indicate an error diff --git a/src/client/datascience/serviceRegistry.ts b/src/client/datascience/serviceRegistry.ts index 3a98946c4472..505d9f8914e5 100644 --- a/src/client/datascience/serviceRegistry.ts +++ b/src/client/datascience/serviceRegistry.ts @@ -13,6 +13,8 @@ import { Decorator } from './editor-integration/decorator'; import { History } from './history/history'; import { HistoryCommandListener } from './history/historycommandlistener'; import { HistoryProvider } from './history/historyProvider'; +import { DotNetIntellisenseProvider } from './history/intellisense/dotNetIntellisenseProvider'; +import { JediIntellisenseProvider } from './history/intellisense/jediIntellisenseProvider'; import { JupyterCommandFactory } from './jupyter/jupyterCommand'; import { JupyterExecutionFactory } from './jupyter/jupyterExecutionFactory'; import { JupyterExporter } from './jupyter/jupyterExporter'; @@ -31,6 +33,7 @@ import { IDataViewer, IDataViewerProvider, IHistory, + IHistoryListener, IHistoryProvider, IJupyterCommandFactory, IJupyterExecution, @@ -63,4 +66,6 @@ export function registerTypes(serviceManager: IServiceManager) { serviceManager.addSingleton(IDataViewerProvider, DataViewerProvider); serviceManager.add(IDataViewer, DataViewer); serviceManager.addSingleton(IExtensionActivationService, Decorator); + serviceManager.add(IHistoryListener, DotNetIntellisenseProvider); + serviceManager.add(IHistoryListener, JediIntellisenseProvider); } diff --git a/src/client/datascience/themeFinder.ts b/src/client/datascience/themeFinder.ts index a1b623ec9dd0..470f8d604400 100644 --- a/src/client/datascience/themeFinder.ts +++ b/src/client/datascience/themeFinder.ts @@ -6,7 +6,9 @@ import * as glob from 'glob'; import { inject, injectable } from 'inversify'; import * as path from 'path'; +import { EXTENSION_ROOT_DIR, PYTHON_LANGUAGE } from '../common/constants'; import { ICurrentProcess, IExtensions, ILogger } from '../common/types'; +import { IThemeFinder } from './types'; // tslint:disable:no-any @@ -16,8 +18,9 @@ interface IThemeData { } @injectable() -export class ThemeFinder { +export class ThemeFinder implements IThemeFinder { private themeCache : { [key: string] : IThemeData | undefined } = {}; + private languageCache: { [key: string] : string | undefined } = {}; constructor( @inject(IExtensions) private extensions: IExtensions, @@ -35,6 +38,18 @@ export class ThemeFinder { } } + public async findTmLanguage(language: string) : Promise { + // See if already found it or not + if (!this.themeCache.hasOwnProperty(language)) { + try { + this.languageCache[language] = await this.findMatchingLanguage(language); + } catch (exc) { + this.logger.logError(exc); + } + } + return this.languageCache[language]; + } + public async isThemeDark(themeName: string) : Promise { // find our data const themeData = await this.findThemeData(themeName); @@ -57,6 +72,57 @@ export class ThemeFinder { return this.themeCache[themeName]; } + private async findMatchingLanguage(language: string) : Promise { + const currentExe = this.currentProcess.execPath; + let currentPath = path.dirname(currentExe); + + // Should be somewhere under currentPath/resources/app/extensions inside of a json file + let extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); + if (!(await fs.pathExists(extensionsPath))) { + // Might be on mac or linux. try a different path + currentPath = path.resolve(currentPath, '../../../..'); + extensionsPath = path.join(currentPath, 'resources', 'app', 'extensions'); + } + + // Search through all of the files in this folder + let results = await this.findMatchingLanguages(language, extensionsPath); + + // If that didn't work, see if it's our MagicPython predefined tmLanguage + if (!results && language === PYTHON_LANGUAGE) { + results = await fs.readFile(path.join(EXTENSION_ROOT_DIR, 'resources', 'MagicPython.tmLanguage.json'), 'utf-8'); + } + + return results; + } + + private async findMatchingLanguages(language: string, rootPath: string) : Promise { + // Environment variable to mimic missing json problem + if (process.env.VSC_PYTHON_MIMIC_REMOTE) { + return undefined; + } + + // Search through all package.json files in the directory and below, looking + // for the themeName in them. + const foundPackages = await new Promise((resolve, reject) => { + glob('**/package.json', { cwd: rootPath }, (err, matches) => { + if (err) { + reject(err); + } + resolve(matches); + }); + }); + if (foundPackages.length > 0) { + // For each one, open it up and look for the theme name. + for (const f of foundPackages) { + const fpath = path.join(rootPath, f); + const data = await this.findMatchingLanguageFromJson(fpath, language); + if (data) { + return data; + } + } + } + } + private async findMatchingTheme(themeName: string) : Promise { // Environment variable to mimic missing json problem if (process.env.VSC_PYTHON_MIMIC_REMOTE) { @@ -114,6 +180,28 @@ export class ThemeFinder { } } + private async findMatchingLanguageFromJson(packageJson: string, language: string) : Promise { + // Read the contents of the json file + const json = await fs.readJSON(packageJson, { encoding: 'utf-8'}); + + // Should have a name entry and a contributes entry + if (json.hasOwnProperty('name') && json.hasOwnProperty('contributes')) { + // See if contributes has a grammars + const contributes = json.contributes; + if (contributes.hasOwnProperty('grammars')) { + const grammars = contributes.grammars as any[]; + // Go through each theme, seeing if the label matches our theme name + for (const t of grammars) { + if (t.hasOwnProperty('language') && t.language === language) { + // Path is relative to the package.json file. + const rootFile = t.hasOwnProperty('path') ? path.join(path.dirname(packageJson), t.path.toString()) : ''; + return fs.readFile(rootFile, 'utf-8'); + } + } + } + } + } + private async findMatchingThemeFromJson(packageJson: string, themeName: string) : Promise { // Read the contents of the json file const json = await fs.readJSON(packageJson, { encoding: 'utf-8'}); diff --git a/src/client/datascience/types.ts b/src/client/datascience/types.ts index a52324ce0da9..1248bdc09ad0 100644 --- a/src/client/datascience/types.ts +++ b/src/client/datascience/types.ts @@ -5,7 +5,16 @@ import { nbformat } from '@jupyterlab/coreutils'; import { Kernel, KernelMessage } from '@jupyterlab/services/lib/kernel'; import { JSONObject } from '@phosphor/coreutils'; import { Observable } from 'rxjs/Observable'; -import { CancellationToken, CodeLens, CodeLensProvider, Disposable, Event, Range, TextDocument, TextEditor } from 'vscode'; +import { + CancellationToken, + CodeLens, + CodeLensProvider, + Disposable, + Event, + Range, + TextDocument, + TextEditor +} from 'vscode'; import { ICommandManager } from '../common/application/types'; import { ExecutionResult, ObservableExecutionResult, SpawnOptions } from '../common/process/types'; @@ -148,6 +157,26 @@ export interface IHistory extends Disposable { exportCells(): void; } +export const IHistoryListener = Symbol('IHistoryListener'); + +/** + * Listens to history messages to provide extra functionality + */ +export interface IHistoryListener extends IDisposable { + /** + * Fires this event when posting a response message + */ + // tslint:disable-next-line: no-any + postMessage: Event<{message: string; payload: any}>; + /** + * Handles messages that the history window receives + * @param message message type + * @param payload message payload + */ + // tslint:disable-next-line: no-any + onMessage(message: string, payload?: any): void; +} + // Wraps the vscode API in order to send messages back and forth from a webview export const IPostOffice = Symbol('IPostOffice'); export interface IPostOffice { @@ -218,11 +247,13 @@ export interface ISysInfo extends nbformat.IBaseCell { export const ICodeCssGenerator = Symbol('ICodeCssGenerator'); export interface ICodeCssGenerator { generateThemeCss(isDark: boolean, theme: string) : Promise; + generateMonacoTheme(isDark: boolean, theme: string) : Promise; } export const IThemeFinder = Symbol('IThemeFinder'); export interface IThemeFinder { findThemeRootJson(themeName: string) : Promise; + findTmLanguage(language: string) : Promise; isThemeDark(themeName: string) : Promise; } diff --git a/src/client/datascience/webViewHost.ts b/src/client/datascience/webViewHost.ts index 7bf2574973c0..783b3ef1a0db 100644 --- a/src/client/datascience/webViewHost.ts +++ b/src/client/datascience/webViewHost.ts @@ -10,7 +10,7 @@ import { IWebPanel, IWebPanelMessageListener, IWebPanelProvider, IWorkspaceServi import { traceInfo } from '../common/logger'; import { IConfigurationService, IDisposable } from '../common/types'; import { createDeferred, Deferred } from '../common/utils/async'; -import { CssMessages, DefaultTheme, IGetCssRequest, SharedMessages } from './constants'; +import { CssMessages, DefaultTheme, IGetCssRequest, IGetMonacoThemeRequest, SharedMessages } from './constants'; import { ICodeCssGenerator, IDataScienceExtraSettings, IThemeFinder } from './types'; @injectable() // For some reason this is necessary to get the class hierarchy to work. @@ -29,7 +29,7 @@ export class WebViewHost implements IDisposable { @unmanaged() private configService: IConfigurationService, @unmanaged() private provider: IWebPanelProvider, @unmanaged() private cssGenerator: ICodeCssGenerator, - @unmanaged() private themeFinder: IThemeFinder, + @unmanaged() protected themeFinder: IThemeFinder, @unmanaged() private workspaceService: IWorkspaceService, // tslint:disable-next-line:no-any @unmanaged() messageListenerCtor: (callback: (message: string, payload: any) => void, viewChanged: (panel: IWebPanel) => void, disposed: () => void) => IWebPanelMessageListener, @@ -104,6 +104,10 @@ export class WebViewHost implements IDisposable { this.handleCssRequest(payload as IGetCssRequest).ignoreErrors(); break; + case CssMessages.GetMonacoThemeRequest: + this.handleMonacoThemeRequest(payload as IGetMonacoThemeRequest).ignoreErrors(); + break; + default: break; } @@ -176,6 +180,18 @@ export class WebViewHost implements IDisposable { return this.postMessageInternal(CssMessages.GetCssResponse, { css, theme: settings.extraSettings.theme, knownDark: isDark }); } + private async handleMonacoThemeRequest(request: IGetMonacoThemeRequest) : Promise { + if (!this.themeIsDarkPromise.resolved) { + this.themeIsDarkPromise.resolve(request.isDark); + } else { + this.themeIsDarkPromise = createDeferred(); + this.themeIsDarkPromise.resolve(request.isDark); + } + const settings = this.generateDataScienceExtraSettings(); + const monacoTheme = await this.cssGenerator.generateMonacoTheme(request.isDark, settings.extraSettings.theme); + return this.postMessageInternal(CssMessages.GetMonacoThemeResponse, { theme: monacoTheme }); + } + // tslint:disable-next-line:no-any private webPanelRendered() { if (!this.webPanelInit.resolved) { diff --git a/src/datascience-ui/data-explorer/mainPanel.tsx b/src/datascience-ui/data-explorer/mainPanel.tsx index add0f4bebacd..9a41b88cfd8a 100644 --- a/src/datascience-ui/data-explorer/mainPanel.tsx +++ b/src/datascience-ui/data-explorer/mainPanel.tsx @@ -141,7 +141,9 @@ export class MainPanel extends React.Component return (
- + {this.container && this.renderGrid()}
diff --git a/src/datascience-ui/history-react/MainPanel.tsx b/src/datascience-ui/history-react/MainPanel.tsx index 49aef9cd3e46..4ca318169759 100644 --- a/src/datascience-ui/history-react/MainPanel.tsx +++ b/src/datascience-ui/history-react/MainPanel.tsx @@ -1,15 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; -import './mainPanel.css'; - import { min } from 'lodash'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as React from 'react'; +import * as uuid from 'uuid/v4'; +import { createDeferred, Deferred } from '../../client/common/utils/async'; +import { noop } from '../../client/common/utils/misc'; import { CellMatcher } from '../../client/datascience/cellMatcher'; import { generateMarkdownFromCodeLines } from '../../client/datascience/common'; +import { Identifiers } from '../../client/datascience/constants'; import { HistoryMessages, IHistoryMapping } from '../../client/datascience/history/historyTypes'; import { CellState, ICell, IHistoryInfo, IJupyterVariable, IJupyterVariablesResponse } from '../../client/datascience/types'; +import { ErrorBoundary } from '../react-common/errorBoundary'; import { IMessageHandler, PostOffice } from '../react-common/postOffice'; import { getSettings, updateSettings } from '../react-common/settingsReactSide'; import { StyleInjector } from '../react-common/styleInjector'; @@ -17,9 +21,13 @@ import { Cell, ICellViewModel } from './cell'; import { ContentPanel, IContentPanelProps } from './contentPanel'; import { HeaderPanel, IHeaderPanelProps } from './headerPanel'; import { InputHistory } from './inputHistory'; +import { IntellisenseProvider } from './intellisenseProvider'; import { createCellVM, createEditableCellVM, extractInputText, generateTestState, IMainPanelState } from './mainPanelState'; +import { initializeTokenizer, registerMonacoLanguage } from './tokenizer'; import { VariableExplorer } from './variableExplorer'; +import './mainPanel.css'; + export interface IMainPanelProps { skipDefault?: boolean; testMode?: boolean; @@ -37,29 +45,53 @@ export class MainPanel extends React.Component private styleInjectorRef: React.RefObject; private currentExecutionCount: number = 0; private postOffice: PostOffice = new PostOffice(); + private intellisenseProvider: IntellisenseProvider; + private onigasmPromise: Deferred | undefined; + private tmlangugePromise: Deferred | undefined; + private monacoIdToCellId: Map = new Map(); // tslint:disable-next-line:max-func-body-length constructor(props: IMainPanelProps, _state: IMainPanelState) { super(props); // Default state should show a busy message - this.state = { cellVMs: [], busy: true, undoStack: [], redoStack : [], submittedText: false, history: new InputHistory(), contentTop: 24 }; + this.state = { + cellVMs: [], + busy: true, + undoStack: [], + redoStack : [], + submittedText: false, + history: new InputHistory(), + contentTop: 24, + editCellVM: getSettings && getSettings().allowInput ? createEditableCellVM(1) : undefined + }; // Add test state if necessary if (!this.props.skipDefault) { this.state = generateTestState(this.inputBlockToggled); } - // Add a single empty cell if it's supported - if (getSettings && getSettings().allowInput) { - this.state.cellVMs.push(createEditableCellVM(1)); - } - // Create the ref to hold our variable explorer this.variableExplorerRef = React.createRef(); // Create the ref to hold our style injector this.styleInjectorRef = React.createRef(); + + // Setup the completion provider for monaco. We only need one + this.intellisenseProvider = new IntellisenseProvider(this.postOffice, this.getCellId); + + // Setup the tokenizer for monaco if running inside of vscode + if (this.props.skipDefault) { + if (this.props.testMode) { + // Running a test, skip the tokenizer. We want the UI to display synchronously + this.state = {tokenizerLoaded: true, ...this.state}; + + // However we still need to register python as a language + registerMonacoLanguage(); + } else { + initializeTokenizer(this.loadOnigasm, this.loadTmlanguage, this.tokenizerLoaded).ignoreErrors(); + } + } } public componentWillMount() { @@ -81,6 +113,9 @@ export class MainPanel extends React.Component // Remove ourselves as a handler for the post office this.postOffice.removeHandler(this); + // Get rid of our completion provider + this.intellisenseProvider.dispose(); + // Get rid of our post office this.postOffice.dispose(); } @@ -94,14 +129,31 @@ export class MainPanel extends React.Component const baseTheme = this.computeBaseTheme(); - const headerProps = this.getHeaderProps(baseTheme); - const contentProps = this.getContentProps(baseTheme); - return (
- - - + +
+
+ {this.renderHeaderPanel(baseTheme)} +
+
+
+
+
+ {this.renderContentPanel(baseTheme)} +
+
+
+
+
+ {this.renderFooterPanel(baseTheme)} +
+
); } @@ -173,6 +225,14 @@ export class MainPanel extends React.Component this.getVariableValueResponse(payload); break; + case HistoryMessages.LoadOnigasmAssemblyResponse: + this.handleOnigasmResponse(payload); + break; + + case HistoryMessages.LoadTmLanguageResponse: + this.handleTmLanguageResponse(payload); + break; + default: break; } @@ -201,6 +261,61 @@ export class MainPanel extends React.Component // this.addCell(cell); // } + private renderHeaderPanel(baseTheme: string) { + const headerProps = this.getHeaderProps(baseTheme); + return ; + } + + private renderContentPanel(baseTheme: string) { + // Skip if the tokenizer isn't finished yet. It needs + // to finish loading so our code editors work. + if (!this.state.tokenizerLoaded && !this.props.testMode) { + return null; + } + + // Otherwise render our cells. + const contentProps = this.getContentProps(baseTheme); + return ; + } + + private renderFooterPanel(baseTheme: string) { + // Skip if the tokenizer isn't finished yet. It needs + // to finish loading so our code editors work. + if (!this.state.tokenizerLoaded || !this.state.editCellVM) { + return null; + } + + const maxOutputSize = getSettings().maxOutputSize; + const errorBackgroundColor = getSettings().errorBackgroundColor; + const actualErrorBackgroundColor = errorBackgroundColor ? errorBackgroundColor : '#FFFFFF'; + const maxTextSize = maxOutputSize && maxOutputSize < 10000 && maxOutputSize > 0 ? maxOutputSize : undefined; + + return ( +
+ + + +
+ ); + } + // Called by the header control when size changes (such as expanding variables) private onHeaderHeightChange = (newHeight: number) => { this.setState({contentTop: newHeight}); @@ -218,6 +333,18 @@ export class MainPanel extends React.Component } } + private monacoThemeChanged = (theme: string) => { + // update our base theme if allowed. Don't do this + // during testing as it will mess up the expected render count. + if (!this.props.testMode) { + this.setState( + { + monacoTheme: theme + } + ); + } + } + private computeBaseTheme(): string { // If we're ignoring, always light if (getSettings && getSettings().ignoreVscodeTheme) { @@ -242,11 +369,12 @@ export class MainPanel extends React.Component testMode: this.props.testMode, codeTheme: this.props.codeTheme, submittedText: this.state.submittedText, - saveEditCellRef: this.saveEditCellRef, gotoCellCode: this.gotoCellCode, deleteCell: this.deleteCell, - submitInput: this.submitInput, - skipNextScroll: this.state.skipNextScroll ? true : false + skipNextScroll: this.state.skipNextScroll ? true : false, + monacoTheme: this.state.monacoTheme, + onCodeCreated: this.readOnlyCodeCreated, + onCodeChange: this.codeChange }; } private getHeaderProps = (baseTheme: string): IHeaderPanelProps => { @@ -391,6 +519,10 @@ export class MainPanel extends React.Component private deleteCell = (index: number) => { this.sendMessage(HistoryMessages.DeleteCell); + const cellVM = this.state.cellVMs[index]; + if (cellVM) { + this.sendMessage(HistoryMessages.RemoveCell, {id: cellVM.cell.id}); + } // Update our state this.setState({ @@ -418,12 +550,9 @@ export class MainPanel extends React.Component } private clearAllSilent = () => { - // Make sure the edit cell doesn't go away - const editCell = this.getEditCell(); - // Update our state this.setState({ - cellVMs: editCell ? [editCell] : [], + cellVMs: [], undoStack : this.pushStack(this.state.undoStack, this.state.cellVMs), skipNextScroll: true, busy: false // No more progress on delete all @@ -501,19 +630,7 @@ export class MainPanel extends React.Component cellVM = this.alterCellVM(cellVM, showInputs, !collapseInputs); if (cellVM) { - let newList : ICellViewModel[] = []; - - // Insert before the edit cell if we have one - const editCell = this.getEditCell(); - if (editCell) { - newList = [...this.state.cellVMs.filter(c => !c.editable), cellVM, editCell]; - - // Update execution count on the last cell - editCell.cell.data.execution_count = this.getInputExecutionCount(newList); - } else { - newList = [...this.state.cellVMs, cellVM]; - } - + const newList = [...this.state.cellVMs, cellVM]; this.setState({ cellVMs: newList, undoStack: this.pushStack(this.state.undoStack, this.state.cellVMs), @@ -528,12 +645,7 @@ export class MainPanel extends React.Component } private getEditCell() : ICellViewModel | undefined { - const editCells = this.state.cellVMs.filter(c => c.editable); - if (editCells && editCells.length === 1) { - return editCells[0]; - } - - return undefined; + return this.state.editCellVM; } private inputBlockToggled = (id: string) => { @@ -720,9 +832,6 @@ export class MainPanel extends React.Component // This should be from our last entry. Switch this entry to read only, and add a new item to our list let editCell = this.getEditCell(); if (editCell) { - // Save a copy of the ones without edits. - const withoutEdits = this.state.cellVMs.filter(c => !c.editable); - // Change this editable cell to not editable. editCell.cell.state = CellState.executing; editCell.cell.data.source = code; @@ -742,6 +851,9 @@ export class MainPanel extends React.Component const collapseInputs = getSettings().collapseCellInputCodeByDefault; editCell = this.alterCellVM(editCell, true, !collapseInputs); + // Generate a new id (as the edit cell always has the same one) + editCell.cell.id = uuid(); + // Indicate this is direct input so that we don't hide it if the user has // hide all inputs turned on. editCell.directInput = true; @@ -749,7 +861,8 @@ export class MainPanel extends React.Component // Stick in a new cell at the bottom that's editable and update our state // so that the last cell becomes busy this.setState({ - cellVMs: [...withoutEdits, editCell, createEditableCellVM(this.getInputExecutionCount(withoutEdits))], + cellVMs: [...this.state.cellVMs, editCell], + editCellVM: createEditableCellVM(this.getInputExecutionCount(this.state.cellVMs)), undoStack : this.pushStack(this.state.undoStack, this.state.cellVMs), redoStack: this.state.redoStack, skipNextScroll: false, @@ -809,4 +922,82 @@ export class MainPanel extends React.Component } } } + + private codeChange = (changes: monacoEditor.editor.IModelContentChange[], id: string, modelId: string) => { + // If the model id doesn't match, skip sending this edit. This happens + // when a cell is reused after deleting another + const expectedCellId = this.monacoIdToCellId.get(modelId); + if (expectedCellId !== id) { + // A cell has been reused. Update our mapping + this.monacoIdToCellId.set(modelId, id); + } else { + // Just a normal edit. Pass this onto the completion provider running in the extension + this.sendMessage(HistoryMessages.EditCell, { changes, id }); + } + } + + private readOnlyCodeCreated = (text: string, file: string, id: string, monacoId: string) => { + // Pass this onto the completion provider running in the extension + this.sendMessage(HistoryMessages.AddCell, { text, file, id }); + + // Save in our map of monaco id to cell id + this.monacoIdToCellId.set(monacoId, id); + } + + private editableCodeCreated = (_text: string, _file: string, id: string, monacoId: string) => { + // Save in our map of monaco id to cell id + this.monacoIdToCellId.set(monacoId, id); + } + + private getCellId = (monacoId: string) : string => { + const result = this.monacoIdToCellId.get(monacoId); + if (result) { + return result; + } + + // Just assume it's the edit cell if not found. + return Identifiers.EditCellId; + } + + // tslint:disable-next-line: no-any + private tokenizerLoaded = (_e?: any) => { + this.setState({ tokenizerLoaded: true }); + } + + private loadOnigasm = () : Promise => { + if (!this.onigasmPromise) { + this.onigasmPromise = createDeferred(); + // Send our load onigasm request + this.sendMessage(HistoryMessages.LoadOnigasmAssemblyRequest); + } + return this.onigasmPromise.promise; + } + + private loadTmlanguage = () : Promise => { + if (!this.tmlangugePromise) { + this.tmlangugePromise = createDeferred(); + // Send our load onigasm request + this.sendMessage(HistoryMessages.LoadTmLanguageRequest); + } + return this.tmlangugePromise.promise; + } + + // tslint:disable-next-line: no-any + private handleOnigasmResponse(payload: any) { + if (payload && this.onigasmPromise) { + const typedArray = new Uint8Array(payload.data); + this.onigasmPromise.resolve(typedArray.buffer); + } else if (this.onigasmPromise) { + this.onigasmPromise.resolve(undefined); + } + } + + // tslint:disable-next-line: no-any + private handleTmLanguageResponse(payload: any) { + if (payload && this.tmlangugePromise) { + this.tmlangugePromise.resolve(payload.toString()); + } else if (this.tmlangugePromise) { + this.tmlangugePromise.resolve(undefined); + } + } } diff --git a/src/datascience-ui/history-react/cell.css b/src/datascience-ui/history-react/cell.css index 0b5f5fc924f9..c412e05800d7 100644 --- a/src/datascience-ui/history-react/cell.css +++ b/src/datascience-ui/history-react/cell.css @@ -2,6 +2,9 @@ margin: 0px; padding: 2px; display: block; +} + +.cell-wrapper-noneditable { border-bottom-color: var(--override-widget-background, var(--vscode-editorGroupHeader-tabsBackground)); border-bottom-style: solid; border-bottom-width: 1px; diff --git a/src/datascience-ui/history-react/cell.tsx b/src/datascience-ui/history-react/cell.tsx index 091a34e36b6a..e5d3e388bcca 100644 --- a/src/datascience-ui/history-react/cell.tsx +++ b/src/datascience-ui/history-react/cell.tsx @@ -1,11 +1,11 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. - 'use strict'; import { nbformat } from '@jupyterlab/coreutils'; import { JSONObject } from '@phosphor/coreutils'; import ansiToHtml from 'ansi-to-html'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as React from 'react'; // tslint:disable-next-line:match-default-export-name import-name import JSONTree from 'react-json-tree'; @@ -39,9 +39,12 @@ interface ICellProps { history: InputHistory | undefined; showWatermark: boolean; errorBackgroundColor: string; + monacoTheme: string | undefined; gotoCode(): void; delete(): void; submitNewCode(code: string): void; + onCodeChange(changes: monacoEditor.editor.IModelContentChange[], cellId: string, modelId: string): void; + onCodeCreated(code: string, file: string, cellId: string, modelId: string): void; } export interface ICellViewModel { @@ -121,11 +124,12 @@ export class Cell extends React.Component { const allowsPlainInput = getSettings().showCellInputCode || this.props.cellVM.directInput || this.props.cellVM.editable; const shouldRender = allowsPlainInput || (results && results.length > 0); const cellOuterClass = this.props.cellVM.editable ? 'cell-outer-editable' : 'cell-outer'; + const cellWrapperClass = this.props.cellVM.editable ? 'cell-wrapper' : 'cell-wrapper cell-wrapper-noneditable'; // Only render if we are allowed to. if (shouldRender) { return ( -
+
); @@ -227,6 +234,14 @@ export class Cell extends React.Component { } } + private onCodeChange = (changes: monacoEditor.editor.IModelContentChange[], modelId: string) => { + this.props.onCodeChange(changes, this.props.cellVM.cell.id, modelId); + } + + private onCodeCreated = (code: string, modelId: string) => { + this.props.onCodeCreated(code, this.props.cellVM.cell.file, this.props.cellVM.cell.id, modelId); + } + private getCursorType = () : string => { if (getSettings && getSettings().extraSettings) { return getSettings().extraSettings.terminalCursor; @@ -444,8 +459,4 @@ export class Cell extends React.Component { const str : string = this.getUnknownMimeTypeFormatString().format(mimetype); return
{str}
; } - - private onChangeLineCount = (_lineCount: number) => { - // Ignored for now. Might use this to update the . next to the code lines - } } diff --git a/src/datascience-ui/history-react/code.css b/src/datascience-ui/history-react/code.css index a01a3b4bffa8..8657534e3dc0 100644 --- a/src/datascience-ui/history-react/code.css +++ b/src/datascience-ui/history-react/code.css @@ -1,32 +1,3 @@ -.CodeMirror { - text-align: left!important; - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - font-family: var(--vscode-editor-font-family); - font-weight: var(--vscode-editor-font-weight); - font-size: var(--vscode-editor-font-size); -} - -.cm-s-default { - background-color: var(--override-background, var(--vscode-editor-background)); - color: var(--override-foreground, var(--vscode-editor-foreground)); - font-family: var(--code-font-family); - font-weight: var(--vscode-editor-font-weight); - font-size: var(--code-font-size); -} - -.CodeMirror-lines { - padding: 0px; -} - -.CodeMirror pre { - padding: 0px; -} - -.code-area .CodeMirror { - height: auto; -} - .code-area { position: relative; @@ -40,9 +11,10 @@ .code-watermark { position: absolute; - top: 0; + top: 3px; left: 30px; z-index: 500; font-style: italic; color: var(--override-watermark-color, var(--vscode-pickerGroup-border)); } + diff --git a/src/datascience-ui/history-react/code.tsx b/src/datascience-ui/history-react/code.tsx index 010ba46fcba8..62aa147db1ac 100644 --- a/src/datascience-ui/history-react/code.tsx +++ b/src/datascience-ui/history-react/code.tsx @@ -2,19 +2,15 @@ // Licensed under the MIT License. 'use strict'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/mode/python/python'; - -import * as CodeMirror from 'codemirror'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as React from 'react'; -import * as RCM from 'react-codemirror'; - -import './code.css'; import { getLocString } from '../react-common/locReactSide'; -import { Cursor } from './cursor'; +import { MonacoEditor } from '../react-common/monacoEditor'; import { InputHistory } from './inputHistory'; +import './code.css'; + export interface ICodeProps { autoFocus: boolean; code : string; @@ -24,9 +20,11 @@ export interface ICodeProps { history: InputHistory | undefined; cursorType: string; showWatermark: boolean; + monacoTheme: string | undefined; + outermostParentClass: string; onSubmit(code: string): void; - onChangeLineCount(lineCount: number) : void; - + onCreated(code: string, modelId: string): void; + onChange(changes: monacoEditor.editor.IModelContentChange[], modelId: string): void; } interface ICodeState { @@ -36,78 +34,69 @@ interface ICodeState { cursorBottom: number; charUnderCursor: string; allowWatermark: boolean; + editor: monacoEditor.editor.IStandaloneCodeEditor | undefined; + model: monacoEditor.editor.ITextModel | null; } export class Code extends React.Component { - - private codeMirror: CodeMirror.Editor | undefined; - private codeMirrorOwner: HTMLDivElement | undefined; - private baseIndentation : number | undefined; + private subscriptions: monacoEditor.IDisposable[] = []; + private lastCleanVersionId: number = 0; constructor(prop: ICodeProps) { super(prop); - this.state = {focused: false, cursorLeft: 0, cursorTop: 0, cursorBottom: 0, charUnderCursor: '', allowWatermark: true}; + this.state = {focused: false, cursorLeft: 0, cursorTop: 0, cursorBottom: 0, charUnderCursor: '', allowWatermark: true, editor: undefined, model: null}; } - public componentDidUpdate(prevProps: Readonly, _prevState: Readonly, _snapshot?: {}) { - // Force our new value. the RCM control doesn't do this correctly - if (this.codeMirror && this.props.readOnly && this.codeMirror.getValue() !== this.props.code) { - this.codeMirror.setValue(this.props.code); - } - // If we are suddenly changing a readonly to not, somebody is reusing a different control. Update - // to be empty - if (this.codeMirror && !this.props.readOnly && prevProps.readOnly) { - this.codeMirror.setOption('readOnly', false); - this.codeMirror.setValue(''); - } + public componentWillUnmount = () => { + this.subscriptions.forEach(d => d.dispose()); } - public componentWillUnmount() { - if (this.codeMirrorOwner) { - const activeElement = document.activeElement as HTMLElement; - if (activeElement && this.codeMirrorOwner.contains(activeElement)) { - activeElement.blur(); - } + public componentDidUpdate = () => { + if (this.props.autoFocus && this.state.editor && !this.props.readOnly) { + this.state.editor.focus(); } } public render() { const readOnly = this.props.readOnly; - const classes = readOnly ? 'code-area' : 'code-area code-area-editable'; const waterMarkClass = this.props.showWatermark && this.state.allowWatermark && !readOnly ? 'code-watermark' : 'hide'; + const classes = readOnly ? 'code-area' : 'code-area code-area-editable'; + const options: monacoEditor.editor.IEditorConstructionOptions = { + minimap: { + enabled: false + }, + glyphMargin: false, + wordWrap: 'on', + scrollBeyondLastLine: false, + scrollbar: { + vertical: 'hidden', + horizontal: 'hidden' + }, + lineNumbers: 'off', + renderLineHighlight: 'none', + highlightActiveIndentGuide: false, + autoIndent: true, + autoClosingBrackets: this.props.testMode ? 'never' : 'languageDefined', + autoClosingQuotes: this.props.testMode ? 'never' : 'languageDefined', + renderIndentGuides: false, + overviewRulerBorder: false, + overviewRulerLanes: 0, + hideCursorInOverviewRuler: true, + folding: false, + readOnly: readOnly, + lineDecorationsWidth: 0 + }; + return ( -
-
@@ -116,190 +105,130 @@ export class Code extends React.Component { public onParentClick(ev: React.MouseEvent) { const readOnly = this.props.testMode || this.props.readOnly; - if (this.codeMirror && !readOnly) { + if (this.state.editor && !readOnly) { ev.stopPropagation(); - this.codeMirror.focus(); + this.state.editor.focus(); } } public giveFocus() { const readOnly = this.props.testMode || this.props.readOnly; - if (this.codeMirror && !readOnly) { - this.codeMirror.focus(); + if (this.state.editor && !readOnly) { + this.state.editor.focus(); } } - private updateRoot = (div: HTMLDivElement) => { - this.codeMirrorOwner = div; - } - private getWatermarkString = () : string => { return getLocString('DataScience.inputWatermark', 'Shift-enter to run'); } - private onCursorActivity = (codeMirror: CodeMirror.Editor) => { - // Update left/top/char for cursor - if (codeMirror) { - const doc = codeMirror.getDoc(); - const selections = doc.listSelections(); - const cursor = doc.getCursor(); - const anchor = selections && selections.length > 0 ? selections[selections.length - 1].anchor : {ch: 10000, line: 10000}; - const wantStart = cursor.line < anchor.line || cursor.line === anchor.line && cursor.ch < anchor.ch; - const coords = codeMirror.cursorCoords(wantStart, 'local'); - const char = this.getCursorChar(); - this.setState({ - cursorLeft: coords.left, - cursorTop: coords.top, - cursorBottom: coords.bottom, - charUnderCursor: char - }); - } - - } + private editorDidMount = (editor: monacoEditor.editor.IStandaloneCodeEditor) => { + // Update our state + const model = editor.getModel(); + this.setState({ editor, model: editor.getModel() }); - private getCursorChar = () : string => { - if (this.codeMirror) { - const doc = this.codeMirror.getDoc(); - const cursorPos = doc.getCursor(); - const line = doc.getLine(cursorPos.line); - if (line.length > cursorPos.ch) { - return line.slice(cursorPos.ch, cursorPos.ch + 1); - } - } + // Listen for model changes + this.subscriptions.push(editor.onDidChangeModelContent(this.modelChanged)); - // We don't need a state update on cursor change because - // we only really need this on focus change - return ''; - } + // List for key up/down events. + this.subscriptions.push(editor.onKeyDown(this.onKeyDown)); + this.subscriptions.push(editor.onKeyUp(this.onKeyUp)); - private onFocusChange = (focused: boolean) => { - this.setState({focused}); + // Indicate we're ready + this.props.onCreated(this.props.code, model!.id); } - private updateCodeMirror = (rcm: ReactCodeMirror.ReactCodeMirror) => { - if (rcm) { - this.codeMirror = rcm.getCodeMirror(); - const coords = this.codeMirror.cursorCoords(false, 'local'); - const char = this.getCursorChar(); - this.setState({ - cursorLeft: coords.left, - cursorTop: coords.top, - cursorBottom: coords.bottom, - charUnderCursor: char - }); + private modelChanged = (e: monacoEditor.editor.IModelContentChangedEvent) => { + if (this.state.model) { + this.props.onChange(e.changes, this.state.model.id); + } + if (!this.props.readOnly) { + this.setState({allowWatermark: false}); } } - private getBaseIndentation(instance: CodeMirror.Editor) : number { - if (!this.baseIndentation) { - const option = instance.getOption('indentUnit'); - if (option) { - this.baseIndentation = parseInt(option.toString(), 10); - } else { - this.baseIndentation = 2; - } + private onKeyDown = (e: monacoEditor.IKeyboardEvent) => { + if (e.shiftKey && e.keyCode === monacoEditor.KeyCode.Enter && this.state.model && this.state.editor) { + // Shift enter was hit + e.stopPropagation(); + e.preventDefault(); + window.setTimeout(this.submitContent, 0); + } else if (e.keyCode === monacoEditor.KeyCode.UpArrow) { + this.arrowUp(e); + } else if (e.keyCode === monacoEditor.KeyCode.DownArrow) { + this.arrowDown(e); } - return this.baseIndentation; } - private expectedIndent(instance: CodeMirror.Editor, line: number) : number { - // Expected should be indent on the previous line and one more if line - // ends with : - const doc = instance.getDoc(); - const baseIndent = this.getBaseIndentation(instance); - const lineStr = doc.getLine(line).trimRight(); - const lastChar = lineStr.length === 0 ? null : lineStr.charAt(lineStr.length - 1); - const frontIndent = lineStr.length - lineStr.trimLeft().length; - return frontIndent + (lastChar === ':' ? baseIndent : 0); + private onKeyUp = (e: monacoEditor.IKeyboardEvent) => { + if (e.shiftKey && e.keyCode === monacoEditor.KeyCode.Enter) { + // Shift enter was hit + e.stopPropagation(); + e.preventDefault(); + } } - private shiftEnter = (instance: CodeMirror.Editor) => { - // Shift enter is always submit (for now) - const doc = instance.getDoc(); - // Double check we don't have an entirely empty document - if (doc.getValue('').trim().length > 0) { - let code = doc.getValue(); - const isClean = doc.isClean(); - // We have to clear the history as this CodeMirror doesn't go away. - doc.clearHistory(); - doc.setValue(''); - - // Submit without the last extra line if we have one. - if (code.endsWith('\n\n')) { - code = code.slice(0, code.length - 1); + private submitContent = () => { + let content = this.getContents(); + if (content) { + // Remove empty lines off the end + let endPos = content.length - 1; + while (endPos >= 0 && content[endPos] === '\n') { + endPos -= 1; } + content = content.slice(0, endPos + 1); // Send to the input history too if necessary if (this.props.history) { - this.props.history.add(code, !isClean); + this.props.history.add(content, this.state.model!.getVersionId() > this.lastCleanVersionId); } - this.props.onSubmit(code); - return; + // Clear our current contents since we submitted + this.state.model!.setValue(''); + + // Send to jupyter + this.props.onSubmit(content); } } - private enter = (instance: CodeMirror.Editor) => { - // See if the cursor is at the end of a single line or if on an indented line. Any indent - // or line ends with : or ;\, then don't submit - const doc = instance.getDoc(); - const cursor = doc.getCursor(); - const lastLine = doc.lastLine(); - if (cursor.line === lastLine) { - // Check for any text - const line = doc.getLine(lastLine); - if (line.length === 0) { - // Do the same thing as shift+enter - this.shiftEnter(instance); - return; - } + private getContents() : string { + if (this.state.model) { + return this.state.model.getValue().replace(/\r/g, ''); } - - // Otherwise add a line and indent the appropriate amount - const cursorLine = doc.getLine(cursor.line); - const afterCursor = cursorLine.slice(cursor.ch).trim(); - const expectedIndents = this.expectedIndent(instance, cursor.line); - const indentString = Array(expectedIndents + 1).join(' '); - doc.replaceRange(`\n${indentString}`, { line: cursor.line, ch: afterCursor.length === 0 ? doc.getLine(cursor.line).length : cursor.ch }); - doc.setCursor({line: cursor.line + 1, ch: indentString.length }); - - // Tell our listener we added a new line - this.props.onChangeLineCount(doc.lineCount()); + return ''; } - private arrowUp = (instance: CodeMirror.Editor) => { - const doc = instance.getDoc(); - const cursor = doc ? doc.getCursor() : undefined; - if (cursor && cursor.line === 0 && this.props.history) { - const currentValue = doc.getValue(); - const newValue = this.props.history.completeUp(currentValue); - if (newValue !== currentValue) { - doc.setValue(newValue); - doc.setCursor(0, doc.getLine(0).length); - doc.markClean(); + private arrowUp(e: monacoEditor.IKeyboardEvent) { + if (this.state.editor && this.state.model) { + const cursor = this.state.editor.getPosition(); + if (cursor && cursor.lineNumber === 1 && this.props.history) { + const currentValue = this.getContents(); + const newValue = this.props.history.completeUp(currentValue); + if (newValue !== currentValue) { + this.state.model.setValue(newValue); + this.lastCleanVersionId = this.state.model.getVersionId(); + this.state.editor.setPosition({lineNumber: 1, column: 1}); + e.stopPropagation(); + } } - return; } - return CodeMirror.Pass; } - private arrowDown = (instance: CodeMirror.Editor) => { - const doc = instance.getDoc(); - const cursor = doc ? doc.getCursor() : undefined; - if (cursor && cursor.line === doc.lastLine() && this.props.history) { - const currentValue = doc.getValue(); - const newValue = this.props.history.completeDown(currentValue); - if (newValue !== currentValue) { - doc.setValue(newValue); - doc.setCursor(doc.lastLine(), doc.getLine(doc.lastLine()).length); - doc.markClean(); + private arrowDown(e: monacoEditor.IKeyboardEvent) { + if (this.state.editor && this.state.model) { + const cursor = this.state.editor.getPosition(); + if (cursor && cursor.lineNumber === this.state.model.getLineCount() && this.props.history) { + const currentValue = this.getContents(); + const newValue = this.props.history.completeDown(currentValue); + if (newValue !== currentValue) { + this.state.model.setValue(newValue); + this.lastCleanVersionId = this.state.model.getVersionId(); + const lastLine = this.state.model.getLineCount(); + this.state.editor.setPosition({lineNumber: lastLine, column: this.state.model.getLineLength(lastLine) + 1}); + e.stopPropagation(); + } } - return; } - return CodeMirror.Pass; } - private onChange = (_newValue: string, _change: CodeMirror.EditorChange) => { - this.setState({allowWatermark: false}); - } } diff --git a/src/datascience-ui/history-react/contentPanel.css b/src/datascience-ui/history-react/contentPanel.css index 4b49d8a463dc..5addb281e286 100644 --- a/src/datascience-ui/history-react/contentPanel.css +++ b/src/datascience-ui/history-react/contentPanel.css @@ -1,7 +1,6 @@ #content-panel-div { height: 100%; overflow: auto; - box-sizing: border-box; } #cell-table { diff --git a/src/datascience-ui/history-react/contentPanel.tsx b/src/datascience-ui/history-react/contentPanel.tsx index a1895e0eee10..c5fd3e2d9e99 100644 --- a/src/datascience-ui/history-react/contentPanel.tsx +++ b/src/datascience-ui/history-react/contentPanel.tsx @@ -3,7 +3,9 @@ 'use strict'; import './contentPanel.css'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as React from 'react'; + import { noop } from '../../test/core'; import { ErrorBoundary } from '../react-common/errorBoundary'; import { getSettings } from '../react-common/settingsReactSide'; @@ -19,10 +21,11 @@ export interface IContentPanelProps { codeTheme: string; submittedText: boolean; skipNextScroll: boolean; - saveEditCellRef(ref: Cell | null): void; + monacoTheme: string | undefined; gotoCellCode(index: number): void; deleteCell(index: number): void; - submitInput(code: string): void; + onCodeChange(changes: monacoEditor.editor.IModelContentChange[], cellId: string, modelId: string): void; + onCodeCreated(code: string, file: string, cellId: string, modelId: string): void; } export class ContentPanel extends React.Component { @@ -40,14 +43,8 @@ export class ContentPanel extends React.Component { } public render() { - const newContentTop = `${this.props.contentTop.toString()}px solid transparent`; - - const newBorderStyle: React.CSSProperties = { - borderTop: newContentTop - }; - return( -
+
{this.renderCells()} @@ -67,19 +64,22 @@ export class ContentPanel extends React.Component { return this.props.cellVMs.map((cellVM: ICellViewModel, index: number) => cellVM.editable ? this.props.saveEditCellRef(r) : noop()} gotoCode={() => this.props.gotoCellCode(index)} - delete={() => this.props.deleteCell(index)}/> + delete={() => this.props.deleteCell(index)} + onCodeChange={this.props.onCodeChange} + onCodeCreated={this.props.onCodeCreated} + monacoTheme={this.props.monacoTheme} + /> ); } diff --git a/src/datascience-ui/history-react/headerPanel.css b/src/datascience-ui/history-react/headerPanel.css index 140973ca0ec3..7d8bf7626afd 100644 --- a/src/datascience-ui/history-react/headerPanel.css +++ b/src/datascience-ui/history-react/headerPanel.css @@ -1,7 +1,3 @@ #header-panel-div { - position: absolute; - top: 0px; - left: 0px; - right: 0px; height: auto; } \ No newline at end of file diff --git a/src/datascience-ui/history-react/intellisenseProvider.ts b/src/datascience-ui/history-react/intellisenseProvider.ts new file mode 100644 index 000000000000..3d6063cd9520 --- /dev/null +++ b/src/datascience-ui/history-react/intellisenseProvider.ts @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import * as uuid from 'uuid/v4'; +import { IDisposable } from '../../client/common/types'; +import { createDeferred, Deferred } from '../../client/common/utils/async'; +import { + HistoryMessages, + IHistoryMapping, + IProvideCompletionItemsResponse, + IProvideHoverResponse +} from '../../client/datascience/history/historyTypes'; +import { IMessageHandler, PostOffice } from '../react-common/postOffice'; + +interface IRequestData { + promise: Deferred; + cancelDisposable: monacoEditor.IDisposable; +} + +export class IntellisenseProvider implements monacoEditor.languages.CompletionItemProvider, monacoEditor.languages.HoverProvider, IDisposable, IMessageHandler { + public triggerCharacters?: string[] | undefined = ['.']; + private completionRequests: Map> = new Map>(); + private hoverRequests: Map> = new Map>(); + private registerDisposables: monacoEditor.IDisposable[] = []; + constructor(private postOffice: PostOffice, private getCellId: (modelId: string) => string) { + // Register a completion provider + this.registerDisposables.push(monacoEditor.languages.registerCompletionItemProvider('python', this)); + this.registerDisposables.push(monacoEditor.languages.registerHoverProvider('python', this)); + this.postOffice.addHandler(this); + } + + public provideCompletionItems( + model: monacoEditor.editor.ITextModel, + position: monacoEditor.Position, + context: monacoEditor.languages.CompletionContext, + token: monacoEditor.CancellationToken): monacoEditor.languages.ProviderResult { + + // Emit a new request + const requestId = uuid(); + const promise = createDeferred(); + + const cancelDisposable = token.onCancellationRequested(() => { + promise.resolve(); + this.sendMessage(HistoryMessages.CancelCompletionItemsRequest, { requestId }); + }); + + this.completionRequests.set(requestId, { promise, cancelDisposable }); + this.sendMessage(HistoryMessages.ProvideCompletionItemsRequest, { position, context, requestId, cellId: this.getCellId(model.id) }); + + return promise.promise; + } + + public provideHover( + model: monacoEditor.editor.ITextModel, + position: monacoEditor.Position, + token: monacoEditor.CancellationToken) : monacoEditor.languages.ProviderResult { + // Emit a new request + const requestId = uuid(); + const promise = createDeferred(); + + const cancelDisposable = token.onCancellationRequested(() => { + promise.resolve(); + this.sendMessage(HistoryMessages.CancelCompletionItemsRequest, { requestId }); + }); + + this.hoverRequests.set(requestId, { promise, cancelDisposable }); + this.sendMessage(HistoryMessages.ProvideHoverRequest, { position, requestId, cellId: this.getCellId(model.id) }); + + return promise.promise; + } + + public dispose() { + this.registerDisposables.forEach(r => r.dispose()); + this.completionRequests.forEach(r => r.promise.resolve()); + this.hoverRequests.forEach(r => r.promise.resolve()); + + this.registerDisposables = []; + this.completionRequests.clear(); + this.hoverRequests.clear(); + + this.postOffice.removeHandler(this); + } + + // tslint:disable-next-line: no-any + public handleMessage(type: string, payload?: any): boolean { + switch (type) { + case HistoryMessages.ProvideCompletionItemsResponse: + this.handleCompletionResponse(payload); + return true; + + case HistoryMessages.ProvideHoverResponse: + this.handleHoverResponse(payload); + return true; + + default: + break; + } + + return false; + } + + // Handle completion response + // tslint:disable-next-line:no-any + private handleCompletionResponse = (payload?: any) => { + if (payload) { + const response = payload as IProvideCompletionItemsResponse; + + // Resolve our waiting promise if we have one + const waiting = this.completionRequests.get(response.requestId); + if (waiting) { + waiting.promise.resolve(response.list); + } + } + } + // Handle hover response + // tslint:disable-next-line:no-any + private handleHoverResponse = (payload?: any) => { + if (payload) { + const response = payload as IProvideHoverResponse; + + // Resolve our waiting promise if we have one + const waiting = this.hoverRequests.get(response.requestId); + if (waiting) { + waiting.promise.resolve(response.hover); + } + } + } + + private sendMessage(type: T, payload?: M[T]) { + this.postOffice.sendMessage(type, payload); + } +} diff --git a/src/datascience-ui/history-react/mainPanel.css b/src/datascience-ui/history-react/mainPanel.css index 2c2641390bbb..77df56cf114c 100644 --- a/src/datascience-ui/history-react/mainPanel.css +++ b/src/datascience-ui/history-react/mainPanel.css @@ -1,6 +1,6 @@ body, html { height: 100%; - overflow: hidden; + margin: 0; } #root { @@ -12,7 +12,9 @@ body, html { background: var(--override-background, var(--vscode-editor-background)); color: var(--override-foreground, var(--vscode-editor-foreground)); height: 100%; + width: 100%; overflow: hidden; + display: table; } .hide { @@ -20,5 +22,35 @@ body, html { } .invisible { - visibility: hidden; + visibility: hidden; +} + +.edit-panel { + min-height:50px; + padding: 10px 0px 10px 0px; + width: 100%; + border-top-color: var(--override-widget-background, var(--vscode-editorGroupHeader-tabsBackground)); + border-top-style: solid; + border-top-width: 1px; } + +.main-panel-header, .main-panel-content, .main-panel-footer { + display: table-row; +} + +.main-panel-inner { + display: table-cell; +} + +.main-panel-content .main-panel-inner { + height: 100%; + position: relative; +} + +.main-panel-scrollable { + position: absolute; + left: 0; right: 0; + top: 0; bottom: 0; + overflow-y: auto; + overflow-x: none; +} \ No newline at end of file diff --git a/src/datascience-ui/history-react/mainPanelState.ts b/src/datascience-ui/history-react/mainPanelState.ts index 8a54d9ad17e8..90c98768837d 100644 --- a/src/datascience-ui/history-react/mainPanelState.ts +++ b/src/datascience-ui/history-react/mainPanelState.ts @@ -3,7 +3,6 @@ 'use strict'; import { nbformat } from '@jupyterlab/coreutils'; import * as path from 'path'; -import * as uuid from 'uuid/v4'; import { IDataScienceSettings } from '../../client/common/types'; import { CellMatcher } from '../../client/datascience/cellMatcher'; @@ -16,6 +15,7 @@ import { InputHistory } from './inputHistory'; export interface IMainPanelState { cellVMs: ICellViewModel[]; + editCellVM?: ICellViewModel; busy: boolean; skipNextScroll? : boolean; undoStack : ICellViewModel[][]; @@ -26,6 +26,8 @@ export interface IMainPanelState { rootStyle?: string; theme?: string; forceDark?: boolean; + monacoTheme?: string; + tokenizerLoaded?: boolean; } // tslint:disable-next-line: no-multiline-string @@ -39,33 +41,13 @@ const darkStyle = ` --code-font-family: Consolas, 'Courier New', monospace; --code-font-size: 14px; } - - .cm-header, .cm-strong {font-weight: bold;} - .cm-em {font-style: italic;} - .cm-link {text-decoration: underline;} - .cm-strikethrough {text-decoration: line-through;} - - .cm-s-ipython-theme span.cm-keyword {color: #C586C0; font-style: normal; } - .cm-s-ipython-theme span.cm-number {color: #b5cea8; font-style: normal; } - .cm-s-ipython-theme span.cm-def {color: var(--vscode-editor-foreground); } - .cm-s-ipython-theme span.cm-variable {color: #9CDCFE; font-style: normal; } - .cm-s-ipython-theme span.cm-punctuation {color: var(--override-foreground, var(--vscode-editor-foreground)); font-style: normal; } - .cm-s-ipython-theme span.cm-property, - .cm-s-ipython-theme span.cm-operator {color: #d4d4d4; font-style: normal; } - .cm-s-ipython-theme span.cm-variable-2 {color: #9CDCFE; font-style: normal; } - .cm-s-ipython-theme span.cm-variable-3, .cm-s-Default Dark+ .cm-type {color: #9CDCFE; font-style: normal; } - .cm-s-ipython-theme span.cm-comment {color: #6A9955; font-style: normal; } - .cm-s-ipython-theme span.cm-string {color: #ce9178; font-style: normal; } - .cm-s-ipython-theme span.cm-string-2 {color: #ce9178; font-style: normal; } - .cm-s-ipython-theme span.cm-builtin {color: #DCDCAA; font-style: normal; } - .cm-s-ipython-theme div.CodeMirror-cursor { border: 1px solid var(--vscode-editor-foreground); background: var(--vscode-editor-foreground); width: 5px; z-index: 100; } - .cm-s-ipython-theme div.CodeMirror-selected {background: var(--vscode-editor-selectionBackground) !important;} `; // This function generates test state when running under a browser instead of inside of export function generateTestState(inputBlockToggled : (id: string) => void, filePath: string = '') : IMainPanelState { return { cellVMs : generateVMs(inputBlockToggled, filePath), + editCellVM: createEditableCellVM(1), busy: true, skipNextScroll : false, undoStack : [], @@ -73,7 +55,8 @@ export function generateTestState(inputBlockToggled : (id: string) => void, file submittedText: false, history: new InputHistory(), contentTop: 24, - rootStyle: darkStyle + rootStyle: darkStyle, + tokenizerLoaded: true }; } @@ -89,7 +72,7 @@ export function createEditableCellVM(executionCount: number) : ICellViewModel { outputs: [], source: '' }, - id: uuid(), + id: Identifiers.EditCellId, file: Identifiers.EmptyFileName, line: 0, state: CellState.editing @@ -170,7 +153,7 @@ function generateCellData() : (nbformat.ICodeCell | nbformat.IMarkdownCell | nbf source: [], metadata: {}, message: 'You have this python data:', - connection: 'https:\\localhost' + connection: 'https:\\localhost\\token?=9343p0843084039483084308430984038403840938409384098304983094803948093848034809384' }, { cell_type: 'code', @@ -183,21 +166,199 @@ function generateCellData() : (nbformat.ICodeCell | nbformat.IMarkdownCell | nbf outputs: [ { data: { - 'text/plain': [ - ' num_preg glucose_conc diastolic_bp thickness insulin bmi diab_pred \\\n', - '0 6 148 72 35 0 33.6 0.627 \n', - '1 1 85 66 29 0 26.6 0.351 \n', - '2 8 183 64 0 0 23.3 0.672 \n', - '3 1 89 66 23 94 28.1 0.167 \n', - '4 0 137 40 35 168 43.1 2.288 \n', - '\n', - ' age skin diabetes \n', - '0 50 1.3790 True \n', - '1 31 1.1426 False \n', - '2 32 0.0000 True \n', - '3 21 0.9062 False \n', - '4 33 1.3790 True super long line that should wrap around but it isnt because we didnt put in the correct css super long line that should wrap around but it isnt because we didnt put in the correct css super long line that should wrap around but it isnt because we didnt put in the correct css' - ] +// tslint:disable-next-line: no-multiline-string + 'text/html': [` +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
0123456789...2990299129922993299429952996299729982999
idx
2007-01-3137.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.060604...37.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.06060437.060604
2007-02-2820.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.603407...20.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.60340720.603407
2007-03-316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.142031...6.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.1420316.142031
2007-04-306.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.931635...6.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.9316356.931635
2007-05-3152.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.642243...52.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.64224352.642243
+

5 rows × 3000 columns

+
` + ] }, execution_count: 4, metadata: {}, diff --git a/src/datascience-ui/history-react/tokenizer.ts b/src/datascience-ui/history-react/tokenizer.ts new file mode 100644 index 000000000000..f35d39b138a3 --- /dev/null +++ b/src/datascience-ui/history-react/tokenizer.ts @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { wireTmGrammars } from 'monaco-editor-textmate'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import { Registry } from 'monaco-textmate'; +import { loadWASM } from 'onigasm'; +import { PYTHON_LANGUAGE } from '../../client/common/constants'; + +export function registerMonacoLanguage() { + // Tell monaco about our language + monacoEditor.languages.register({ + id: PYTHON_LANGUAGE, + extensions: ['.py'] + }); + + // Setup the configuration so that auto indent and other things work. Onigasm is just going to setup the tokenizer + monacoEditor.languages.setLanguageConfiguration( + PYTHON_LANGUAGE, + { + comments: { + lineComment: '#', + blockComment: ['\'\'\'', '\"\"\"'] + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"', notIn: ['string'] }, + { open: '\'', close: '\'', notIn: ['string', 'comment'] } + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: '(', close: ')' }, + { open: '"', close: '"' }, + { open: '\'', close: '\'' } + ], + onEnterRules: [ + { + beforeText: new RegExp('^\\s*(?:def|class|for|if|elif|else|while|try|with|finally|except|async).*?:\\s*$'), + action: { indentAction: monacoEditor.languages.IndentAction.Indent } + } + ], + folding: { + offSide: true, + markers: { + start: new RegExp('^\\s*#region\\b'), + end: new RegExp('^\\s*#endregion\\b') + } + } + } + ); +} + +// tslint:disable: no-any +export async function initializeTokenizer( + getOnigasm: () => Promise, + getTmlanguageJSON: () => Promise, + loadingFinished: (e?: any) => void): Promise { + try { + // Register the language first + registerMonacoLanguage(); + + // Load the web assembly + const blob = await getOnigasm(); + await loadWASM(blob); + + // Setup our registry of different + const registry = new Registry({ + getGrammarDefinition: async (_scopeName) => { + return { + format: 'json', + content: await getTmlanguageJSON() + }; + } + }); + + // map of monaco "language id's" to TextMate scopeNames + const grammars = new Map(); + grammars.set('python', 'source.python'); + + // Wire everything together. + await wireTmGrammars(monacoEditor, registry, grammars); + + // Indicate to the callback that we're done. + loadingFinished(); + } catch (e) { + loadingFinished(e); + } +} diff --git a/src/datascience-ui/react-common/monacoEditor.css b/src/datascience-ui/react-common/monacoEditor.css new file mode 100644 index 000000000000..9e1b8451699a --- /dev/null +++ b/src/datascience-ui/react-common/monacoEditor.css @@ -0,0 +1,108 @@ +.measure-width-div { + width: 100vw; + visibility: hidden; + position: absolute; +} + +.monaco-editor-outer-container .mtk1 { + /* For some reason the monaco editor refuses to update this style no matter the theme. It's always black */ + color: var(--override-foreground, var(--vscode-editor-foreground)); +} + +.monaco-editor .mtk1 { + /* For some reason the monaco editor refuses to update this style no matter the theme. It's always black */ + color: var(--override-foreground, var(--vscode-editor-foreground)); +} + +/* Bunch of styles copied from vscode. Handles the hover window */ + + .monaco-editor-hover { + cursor: default; + position: absolute; + overflow: hidden; + z-index: 50; + -webkit-user-select: text; + -ms-user-select: text; + -khtml-user-select: text; + -moz-user-select: text; + -o-user-select: text; + user-select: text; + box-sizing: initial; + animation: fadein 100ms linear; + line-height: 1.5em; +} + +.monaco-editor-hover.hidden { + display: none; +} + +.monaco-editor-hover .hover-contents { + padding: 4px 8px; +} + +.monaco-editor-hover .markdown-hover > .hover-contents:not(.code-hover-contents) { + max-width: 500px; +} + +.monaco-editor-hover p, +.monaco-editor-hover ul { + margin: 8px 0; +} + +.monaco-editor-hover hr { + margin-top: 4px; + margin-bottom: -6px; + margin-left: -10px; + margin-right: -10px; + height: 1px; +} + +.monaco-editor-hover p:first-child, +.monaco-editor-hover ul:first-child { + margin-top: 0; +} + +.monaco-editor-hover p:last-child, +.monaco-editor-hover ul:last-child { + margin-bottom: 0; +} + +.monaco-editor-hover ul { + padding-left: 20px; +} + +.monaco-editor-hover li > p { + margin-bottom: 0; +} + +.monaco-editor-hover li > ul { + margin-top: 0; +} + +.monaco-editor-hover code { + border-radius: 3px; + padding: 0 0.4em; +} + +.monaco-editor-hover .monaco-tokenized-source { + white-space: pre-wrap; + word-break: break-all; +} + +.monaco-editor-hover .hover-row.status-bar { + font-size: 12px; + line-height: 22px; +} + +.monaco-editor-hover .hover-row.status-bar .actions { + display: flex; +} + +.monaco-editor-hover .hover-row.status-bar .actions .action-container { + margin: 0px 8px; + cursor: pointer; +} + +.monaco-editor-hover .hover-row.status-bar .actions .action-container .action .icon { + padding-right: 4px; +} diff --git a/src/datascience-ui/react-common/monacoEditor.tsx b/src/datascience-ui/react-common/monacoEditor.tsx new file mode 100644 index 000000000000..ac47b4a5c7eb --- /dev/null +++ b/src/datascience-ui/react-common/monacoEditor.tsx @@ -0,0 +1,333 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; + +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import * as React from 'react'; +import { IDisposable } from '../../client/common/types'; + +import './monacoEditor.css'; + +const LINE_HEIGHT = 18; + +export interface IMonacoEditorProps { + language: string; + value: string; + theme?: string; + outermostParentClass: string; + options: monacoEditor.editor.IEditorConstructionOptions; + testMode?: boolean; + editorMounted(editor: monacoEditor.editor.IStandaloneCodeEditor): void; +} + +interface IMonacoEditorState { + editor?: monacoEditor.editor.IStandaloneCodeEditor; + model: monacoEditor.editor.ITextModel | null; +} + +// Need this to prevent wiping of the current value on a componentUpdate. react-monaco-editor has that problem. + +export class MonacoEditor extends React.Component { + private containerRef: React.RefObject; + private measureWidthRef: React.RefObject; + private resizeTimer?: number; + private leaveTimer?: number; + private subscriptions: monacoEditor.IDisposable[] = []; + private widgetParent: HTMLDivElement | undefined; + private outermostParent: HTMLElement | null = null; + private enteredHover: boolean = false; + private lastOffsetLeft: number | undefined; + private lastOffsetTop: number | undefined; + constructor(props: IMonacoEditorProps) { + super(props); + this.state = { editor: undefined, model: null }; + this.containerRef = React.createRef(); + this.measureWidthRef = React.createRef(); + } + + public componentDidMount = () => { + if (window) { + window.addEventListener('resize', this.windowResized); + } + if (this.containerRef.current) { + // Compute our outermost parent + let outerParent = this.containerRef.current.parentElement; + while (outerParent && !outerParent.classList.contains(this.props.outermostParentClass)) { + outerParent = outerParent.parentElement; + } + this.outermostParent = outerParent; + if (this.outermostParent) { + this.outermostParent.addEventListener('mouseleave', this.outermostParentLeave); + } + + // Create the editor + const editor = monacoEditor.editor.create(this.containerRef.current, + { + value: this.props.value, + language: this.props.language, + ...this.props.options + }); + + // Force the editor to behave like a unix editor as + // all of our code is assuming that. + const model = editor.getModel(); + if (model) { + model.setEOL(monacoEditor.editor.EndOfLineSequence.LF); + } + + // Save the editor and the model in our state. + this.setState({ editor, model }); + if (this.props.theme) { + monacoEditor.editor.setTheme(this.props.theme); + } + + // do the initial set of the height (wait a bit) + this.windowResized(); + + // on each edit recompute height (wait a bit) + this.subscriptions.push(editor.onDidChangeModelDecorations(() => { + this.windowResized(); + })); + + // Setup our context menu to show up outside. Autocomplete doesn't have this problem so it just works + this.subscriptions.push(editor.onContextMenu((e) => { + if (this.state.editor) { + const domNode = this.state.editor.getDomNode(); + const contextMenuElement = domNode ? domNode.querySelector('.monaco-menu-container') as HTMLElement : null; + if (contextMenuElement) { + const posY = (e.event.posy + contextMenuElement.clientHeight) > window.outerHeight + ? e.event.posy - contextMenuElement.clientHeight + : e.event.posy; + const posX = (e.event.posx + contextMenuElement.clientWidth) > window.outerWidth + ? e.event.posx - contextMenuElement.clientWidth + : e.event.posx; + contextMenuElement.style.position = 'fixed'; + contextMenuElement.style.top = `${Math.max(0, Math.floor(posY))}px`; + contextMenuElement.style.left = `${Math.max(0, Math.floor(posX))}px`; + } + } + })); + + // Make sure our suggest and hover windows show up on top of other stuff + this.updateWidgetParent(editor); + + // Tell our parent the editor is ready to use + this.props.editorMounted(editor); + } + } + + public componentWillUnmount = () => { + if (this.resizeTimer) { + window.clearTimeout(this.resizeTimer); + } + + if (window) { + window.removeEventListener('resize', this.windowResized); + } + + if (this.outermostParent) { + this.outermostParent.removeEventListener('mouseleave', this.outermostParentLeave); + this.outermostParent = null; + } + if (this.widgetParent) { + this.widgetParent.remove(); + } + + this.subscriptions.forEach(d => d.dispose()); + if (this.state.editor) { + this.state.editor.dispose(); + } + } + + public componentDidUpdate(prevProps: IMonacoEditorProps) { + if (this.state.editor) { + if (prevProps.language !== this.props.language && this.state.model) { + monacoEditor.editor.setModelLanguage(this.state.model, this.props.language); + } + if (prevProps.theme !== this.props.theme && this.props.theme) { + monacoEditor.editor.setTheme(this.props.theme); + } + if (prevProps.options !== this.props.options) { + this.state.editor.updateOptions(this.props.options); + } + if (prevProps.value !== this.props.value && this.state.model) { + this.state.model.setValue(this.props.value); + } + } + this.updateEditorSize(); + } + + public render() { + return ( +
+
+
+
+ ); + } + + private windowResized = () => { + if (this.resizeTimer) { + clearTimeout(this.resizeTimer); + } + this.resizeTimer = window.setTimeout(this.updateEditorSize, 0); + } + + private startUpdateWidgetPosition = () => { + this.updateWidgetPosition(); + } + + private updateWidgetPosition(width?: number) { + if (this.state.editor && this.widgetParent) { + // Position should be at the top of the editor. + const editorDomNode = this.state.editor.getDomNode(); + if (editorDomNode) { + const rect = editorDomNode.getBoundingClientRect(); + if (rect && + (rect.left !== this.lastOffsetLeft || rect.top !== this.lastOffsetTop)) { + this.lastOffsetLeft = rect.left; + this.lastOffsetTop = rect.top; + + this.widgetParent.setAttribute( + 'style', + `position: absolute; left: ${rect.left}px; top: ${rect.top}px; width:${width ? width : rect.width}px`); + } + } + } + } + + private updateEditorSize = () => { + if (this.measureWidthRef.current && + this.measureWidthRef.current.clientWidth && + this.containerRef.current && + this.containerRef.current.parentElement && + this.state.editor && + this.state.model) { + const editorDomNode = this.state.editor.getDomNode(); + if (!editorDomNode) { return; } + const container = editorDomNode.getElementsByClassName('view-lines')[0] as HTMLElement; + const lineHeight = container.firstChild + ? (container.firstChild as HTMLElement).offsetHeight + : LINE_HEIGHT; + const currLineCount = this.state.model.getLineCount(); + const height = (currLineCount * lineHeight) + 3; // Fudge factor + const width = this.measureWidthRef.current.clientWidth - this.containerRef.current.parentElement.offsetLeft - 15; // Leave room for the scroll bar in regular cell table + + // For some reason this is flashing. Need to debug the editor code to see if + // it draws more than once. Or if we can have React turn off DOM updates + this.state.editor.layout({ width: width, height: height }); + + // Also need to update our widget positions + this.updateWidgetPosition(width); + } + } + + private onHoverLeave = () => { + // If the hover is active, make sure to hide it. + if (this.state.editor && this.widgetParent) { + this.enteredHover = false; + // tslint:disable-next-line: no-any + const hover = this.state.editor.getContribution('editor.contrib.hover') as any; + if (hover._hideWidgets) { + hover._hideWidgets(); + } + } + } + + private onHoverEnter = () => { + if (this.state.editor && this.widgetParent) { + // If we enter the hover, indicate it so we don't leave + this.enteredHover = true; + } + } + + private outermostParentLeave = () => { + // Have to bounce this because the leave for the cell is the + // enter for the hover + if (this.leaveTimer) { + clearTimeout(this.leaveTimer); + } + this.leaveTimer = window.setTimeout(this.outermostParentLeaveBounced, 0); + } + + private outermostParentLeaveBounced = () => { + if (this.state.editor && !this.enteredHover) { + // If we haven't already entered hover, then act like it shuts down + this.onHoverLeave(); + } + } + + private updateWidgetParent(editor: monacoEditor.editor.IStandaloneCodeEditor) { + // Reparent the hover widgets. They cannot be inside anything that has overflow hidden or scrolling or they won't show + // up overtop of anything. Warning, this is a big hack. If the class name changes or the logic + // for figuring out the position of hover widgets changes, this won't work anymore. + // appendChild on a DOM node moves it, but doesn't clone it. + // https://developer.mozilla.org/en-US/docs/Web/API/Node/appendChild + const editorNode = editor.getDomNode(); + if (editorNode) { + try { + const elements = editorNode.getElementsByClassName('overflowingContentWidgets'); + if (elements && elements.length) { + const contentWidgets = elements[0] as HTMLDivElement; + if (contentWidgets) { + // Go up to the document. + const document = contentWidgets.getRootNode() as HTMLDocument; + + // His first child with the id 'root' should be where we want to parent our overflow widgets + if (document) { + const root = document.getElementById('root'); + if (root) { + // We need to create a dummy 'monaco-editor' div so that the content widgets get the same styles. + this.widgetParent = document.createElement('div', {}); + this.widgetParent.setAttribute('class', `${editorNode.className} monaco-editor-pretend-parent`); + + // We also need to make sure its position follows the editor around on the screen. + const rect = editorNode.getBoundingClientRect(); + if (rect) { + this.lastOffsetLeft = rect.left; + this.lastOffsetTop = rect.top; + this.widgetParent.setAttribute( + 'style', + `position: absolute; left: ${rect.left}px; top: ${rect.top}px`); + } + + root.appendChild(this.widgetParent); + this.widgetParent.appendChild(contentWidgets); + + // Listen for changes so we can update the position dynamically + editorNode.addEventListener('mouseenter', this.startUpdateWidgetPosition); + + // We also need to trick the editor into thinking mousing over the hover does not + // mean the mouse has left the editor. + // tslint:disable-next-line: no-any + const hover = editor.getContribution('editor.contrib.hover') as any; + if (hover._toUnhook && hover._toUnhook.length === 8 && hover.contentWidget) { + // This should mean our 5th element is the event handler for mouse leave. Remove it. + const array = hover._toUnhook as IDisposable[]; + array[5].dispose(); + array.splice(5, 1); + + // Instead listen to mouse leave for our hover widget + const hoverWidget = this.widgetParent.getElementsByClassName('monaco-editor-hover')[0] as HTMLElement; + if (hoverWidget) { + hoverWidget.addEventListener('mouseenter', this.onHoverEnter); + hoverWidget.addEventListener('mouseleave', this.onHoverLeave); + } + } + } + } + } + } + } catch (e) { + // If something fails, then the hover will just work inside the main frame + if (!this.props.testMode) { + window.console.warn(`Error moving editor widgets: ${e}`); + } + + // Make sure we don't try moving it around. + this.widgetParent = undefined; + } + } + } +} diff --git a/src/datascience-ui/react-common/styleInjector.tsx b/src/datascience-ui/react-common/styleInjector.tsx index 313fa02ba4cc..68a5a31ee423 100644 --- a/src/datascience-ui/react-common/styleInjector.tsx +++ b/src/datascience-ui/react-common/styleInjector.tsx @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. 'use strict'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; import * as React from 'react'; -import { CssMessages, IGetCssResponse, SharedMessages } from '../../client/datascience/constants'; +import { CssMessages, IGetCssResponse, IGetMonacoThemeResponse, SharedMessages } from '../../client/datascience/constants'; import { IDataScienceExtraSettings } from '../../client/datascience/types'; import { IMessageHandler, PostOffice } from './postOffice'; import { getSettings } from './settingsReactSide'; @@ -13,6 +14,7 @@ export interface IStyleInjectorProps { expectingDark: boolean; postOffice: PostOffice; darkChanged?(newDark: boolean): void; + monacoThemeChanged?(theme: string): void; } interface IStyleInjectorState { @@ -43,6 +45,7 @@ export class StyleInjector extends React.Component { switch (msg) { case CssMessages.GetCssResponse: - this.handleResponse(payload); + this.handleCssResponse(payload); + break; + + case CssMessages.GetMonacoThemeResponse: + this.handleMonacoThemeResponse(payload); break; case SharedMessages.UpdateSettings: @@ -76,7 +83,7 @@ export class StyleInjector extends React.Component { .returns((_s, d) => { return d; }); + const theme = await cssGenerator.generateMonacoTheme(isDark, themeName); + assert.ok(theme, `Cannot find monaco theme for ${themeName}`); const colors = await cssGenerator.generateThemeCss(isDark, themeName); - assert.ok(colors, `Cannot find theme colors for ${themeName}`); + assert.ok(colors, 'Cannot find theme colors for Kimbie Dark'); // Make sure we have a string value that is not set to a variable // (that would be the default and all themes have a string color) - const matches = /span\.cm-string\s\{color\:\s(.*?);/gm.exec(colors); - assert.ok(matches, 'No matches found for string color'); - assert.equal(matches!.length, 2, 'Wrong number of matches for for string color'); - assert.ok(matches![1].includes('#'), 'String color not found'); + assert.ok(theme.rules, 'No rules found in monaco theme'); + // tslint:disable-next-line: no-any + const commentPunctuation = (theme.rules as any[]).findIndex(r => r.token === 'punctuation.definition.comment'); + assert.ok(commentPunctuation >= 0, 'No punctuation.comment found'); } else { assert.notOk(json, `Found ${themeName} when not expected`); } @@ -147,7 +149,7 @@ suite('Theme colors', () => { // Make sure we have a string value that is not set to a variable // (that would be the default and all themes have a string color) - const matches = /span\.cm-string\s\{color\:\s(.*?);/gm.exec(colors); + const matches = /--code-string-color\:\s(.*?);/gm.exec(colors); assert.ok(matches, 'No matches found for string color'); assert.equal(matches!.length, 2, 'Wrong number of matches for for string color'); assert.ok(matches![1].includes('#'), 'String color not found'); diff --git a/src/test/datascience/dataScienceIocContainer.ts b/src/test/datascience/dataScienceIocContainer.ts index e537949c6d45..b6677269e2cd 100644 --- a/src/test/datascience/dataScienceIocContainer.ts +++ b/src/test/datascience/dataScienceIocContainer.ts @@ -21,6 +21,7 @@ import { } from 'vscode'; import * as vsls from 'vsls/vscode'; +import { ILanguageServer, ILanguageServerAnalysisOptions } from '../../client/activation/types'; import { TerminalManager } from '../../client/common/application/terminalManager'; import { IApplicationShell, @@ -76,7 +77,7 @@ import { IPersistentStateFactory, IsWindows } from '../../client/common/types'; -import { Deferred } from '../../client/common/utils/async'; +import { Deferred, sleep } from '../../client/common/utils/async'; import { noop } from '../../client/common/utils/misc'; import { Architecture } from '../../client/common/utils/platform'; import { EnvironmentVariablesService } from '../../client/common/variables/environment'; @@ -89,6 +90,7 @@ import { CodeWatcher } from '../../client/datascience/editor-integration/codewat import { History } from '../../client/datascience/history/history'; import { HistoryCommandListener } from '../../client/datascience/history/historycommandlistener'; import { HistoryProvider } from '../../client/datascience/history/historyProvider'; +import { DotNetIntellisenseProvider } from '../../client/datascience/history/intellisense/dotNetIntellisenseProvider'; import { JupyterCommandFactory } from '../../client/datascience/jupyter/jupyterCommand'; import { JupyterExecutionFactory } from '../../client/datascience/jupyter/jupyterExecutionFactory'; import { JupyterExporter } from '../../client/datascience/jupyter/jupyterExporter'; @@ -106,6 +108,7 @@ import { IDataViewer, IDataViewerProvider, IHistory, + IHistoryListener, IHistoryProvider, IJupyterCommandFactory, IJupyterExecution, @@ -192,6 +195,8 @@ import { MockCommandManager } from './mockCommandManager'; import { MockDocumentManager } from './mockDocumentManager'; import { MockExtensions } from './mockExtensions'; import { MockJupyterManager, SupportedCommands } from './mockJupyterManager'; +import { MockLanguageServer } from './mockLanguageServer'; +import { MockLanguageServerAnalysisOptions } from './mockLanguageServerAnalysisOptions'; import { MockLiveShareApi } from './mockLiveShare'; import { blurWindow, createMessageEvent } from './reactHelpers'; @@ -247,6 +252,30 @@ export class DataScienceIocContainer extends UnitTestIocContainer { this.wrapper.unmount(); this.wrapper = undefined; } + + // Bounce this so that our editor has time to shutdown + await sleep(10); + + // Clear out the monaco global services. Some of these services are preventing shutdown. + // tslint:disable: no-require-imports + const services = require('monaco-editor/esm/vs/editor/standalone/browser/standaloneServices') as any; + if (services.StaticServices) { + const keys = Object.keys(services.StaticServices); + keys.forEach(k => { + const service = services.StaticServices[k] as any; + if (service && service._value && service._value.dispose) { + if (typeof service._value.dispose === 'function') { + service._value.dispose(); + } + } + }); + } + + // This file doesn't have an export so we can't force a dispose. Instead it has a 5 second timeout + const config = require('monaco-editor/esm/vs/editor/browser/config/configuration') as any; + if (config.getCSSBasedConfiguration) { + config.getCSSBasedConfiguration().dispose(); + } } //tslint:disable:max-func-body-length @@ -288,6 +317,9 @@ export class DataScienceIocContainer extends UnitTestIocContainer { ITerminalActivationCommandProvider, PipEnvActivationCommandProvider, TerminalActivationProviders.pipenv); this.serviceManager.addSingleton(ITerminalManager, TerminalManager); this.serviceManager.addSingleton(IPipEnvServiceHelper, PipEnvServiceHelper); + this.serviceManager.addSingleton(ILanguageServer, MockLanguageServer); + this.serviceManager.addSingleton(ILanguageServerAnalysisOptions, MockLanguageServerAnalysisOptions); + this.serviceManager.add(IHistoryListener, DotNetIntellisenseProvider); // Setup our command list this.commandManager.registerCommand('setContext', (name: string, value: boolean) => { @@ -563,6 +595,10 @@ export class DataScienceIocContainer extends UnitTestIocContainer { this.extraListeners.push(callback); } + public changeJediEnabled(enabled: boolean) { + this.pythonSettings.jediEnabled = enabled; + } + private findPythonPath(): string { try { const output = child_process.execFileSync('python', ['-c', 'import sys;print(sys.executable)'], { encoding: 'utf8' }); diff --git a/src/test/datascience/history.functional.test.tsx b/src/test/datascience/history.functional.test.tsx index 26ee667eb093..d03726a1ecaa 100644 --- a/src/test/datascience/history.functional.test.tsx +++ b/src/test/datascience/history.functional.test.tsx @@ -16,6 +16,7 @@ import { HistoryMessages } from '../../client/datascience/history/historyTypes'; import { IHistory, IHistoryProvider } from '../../client/datascience/types'; import { CellButton } from '../../datascience-ui/history-react/cellButton'; import { MainPanel } from '../../datascience-ui/history-react/MainPanel'; +//import { asyncDump } from '../common/asyncDump'; import { sleep } from '../core'; import { DataScienceIocContainer } from './dataScienceIocContainer'; import { @@ -64,8 +65,9 @@ suite('DataScience History output tests', () => { await ioc.dispose(); }); + // Uncomment this to debug hangs on exit // suiteTeardown(() => { - // asyncDump(); + // asyncDump(); // }); async function getOrCreateHistory(): Promise { diff --git a/src/test/datascience/historyTestHelpers.tsx b/src/test/datascience/historyTestHelpers.tsx index 5ca6b611306a..0a98cc409f1d 100644 --- a/src/test/datascience/historyTestHelpers.tsx +++ b/src/test/datascience/historyTestHelpers.tsx @@ -16,7 +16,7 @@ import { CellButton } from '../../datascience-ui/history-react/cellButton'; import { MainPanel } from '../../datascience-ui/history-react/MainPanel'; import { updateSettings } from '../../datascience-ui/react-common/settingsReactSide'; import { DataScienceIocContainer } from './dataScienceIocContainer'; -import { blurWindow, createInputEvent, createKeyboardEvent, waitForUpdate } from './reactHelpers'; +import { createInputEvent, createKeyboardEvent, waitForUpdate } from './reactHelpers'; //tslint:disable:trailing-comma no-any no-multiline-string export enum CellInputState { @@ -39,15 +39,7 @@ export function runMountedTest(name: string, testFunc: (wrapper: ReactWrapper); - try { - await testFunc(wrapper); - } finally { - // Blur window focus so we don't have editors polling - blurWindow(); - - // Make sure to unmount the wrapper or it will interfere with other tests - wrapper.unmount(); - } + await testFunc(wrapper); } else { // tslint:disable-next-line:no-console console.log(`${name} skipped, no Jupyter installed.`); @@ -224,8 +216,15 @@ function simulateKey(domNode: HTMLTextAreaElement, key: string, shiftDown?: bool event = createKeyboardEvent('keyup', { key, code: key, shiftKey: shiftDown }); domNode.dispatchEvent(event); - // Dispatch an input event so we update the textarea + // Update our value. This will reset selection to zero. domNode.value = domNode.value + key; + + // Tell the dom node its selection start has changed. Monaco + // reads this to determine where the character went. + domNode.selectionEnd = domNode.value.length; + domNode.selectionStart = domNode.value.length; + + // Dispatch an input event so we update the textarea domNode.dispatchEvent(createInputEvent()); } } @@ -248,21 +247,24 @@ function enterKey(_wrapper: ReactWrapper, React.Component>, te simulateKey(textArea, key); } -export async function enterInput(wrapper: ReactWrapper, React.Component>, code: string): Promise, React.Component>> { +export function getEditor(wrapper: ReactWrapper, React.Component>) : ReactWrapper, React.Component> { + // Find the last cell. It should have a monacoEditor object + const cells = wrapper.find('Cell'); + const lastCell = cells.last(); + return lastCell.find('MonacoEditor'); +} - // First we have to type the code into the input box +export function typeCode(wrapper: ReactWrapper, React.Component>, code: string) : HTMLTextAreaElement | null { - // Find the last cell. It should have a CodeMirror object. We need to search - // through its DOM to find the actual codemirror textarea to send input to + // Find the last cell. It should have a monacoEditor object. We need to search + // through its DOM to find the actual textarea to send input to // (we can't actually find it with the enzyme wrappers because they only search - // React accessible nodes and the codemirror html is not react) - const cells = wrapper.find('Cell'); - const lastCell = cells.last(); - const rcm = lastCell.find('div.ReactCodeMirror'); - const rcmDom = rcm.getDOMNode(); - assert.ok(rcmDom, 'rcm DOM object not found'); - const textArea = rcmDom!.querySelector('.CodeMirror')!.querySelector('textarea'); - assert.ok(textArea!, 'Cannot find the textarea inside the code mirror'); + // React accessible nodes and the monaco html is not react) + const editorControl = getEditor(wrapper); + const ecDom = editorControl.getDOMNode(); + assert.ok(ecDom, 'ec DOM object not found'); + const textArea = ecDom!.querySelector('.overflow-guard')!.querySelector('textarea'); + assert.ok(textArea!, 'Cannot find the textarea inside the monaco editor'); textArea!.focus(); // Now simulate entering all of the keys @@ -270,6 +272,14 @@ export async function enterInput(wrapper: ReactWrapper, React. enterKey(wrapper, textArea!, code.charAt(i)); } + return textArea; +} + +export async function enterInput(wrapper: ReactWrapper, React.Component>, code: string): Promise, React.Component>> { + + // First we have to type the code into the input box + const textArea = typeCode(wrapper, code); + // Now simulate a shift enter. This should cause a new cell to be added await submitInput(wrapper, textArea!); diff --git a/src/test/datascience/intellisense.functional.test.tsx b/src/test/datascience/intellisense.functional.test.tsx new file mode 100644 index 000000000000..dfce5d5ec8fb --- /dev/null +++ b/src/test/datascience/intellisense.functional.test.tsx @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import * as assert from 'assert'; +import { ReactWrapper } from 'enzyme'; +import { IDisposable } from 'monaco-editor'; +import { Disposable } from 'vscode'; + +import { createDeferred } from '../../client/common/utils/async'; +import { HistoryMessageListener } from '../../client/datascience/history/historyMessageListener'; +import { HistoryMessages } from '../../client/datascience/history/historyTypes'; +import { IHistory, IHistoryProvider } from '../../client/datascience/types'; +import { MonacoEditor } from '../../datascience-ui/react-common/monacoEditor'; +import { noop } from '../core'; +import { DataScienceIocContainer } from './dataScienceIocContainer'; +import { getEditor, runMountedTest, typeCode } from './historyTestHelpers'; + +// tslint:disable:max-func-body-length trailing-comma no-any no-multiline-string +suite('DataScience Intellisense tests', () => { + const disposables: Disposable[] = []; + let ioc: DataScienceIocContainer; + + setup(() => { + ioc = new DataScienceIocContainer(); + // For this test, jedi is turned off so we use our mock language server + ioc.changeJediEnabled(false); + ioc.registerDataScienceTypes(); + }); + + teardown(async () => { + for (const disposable of disposables) { + if (!disposable) { + continue; + } + // tslint:disable-next-line:no-any + const promise = disposable.dispose() as Promise; + if (promise) { + await promise; + } + } + await ioc.dispose(); + }); + + // suiteTeardown(() => { + // asyncDump(); + // }); + + async function getOrCreateHistory(): Promise { + const historyProvider = ioc.get(IHistoryProvider); + const result = await historyProvider.getOrCreateActive(); + + // During testing the MainPanel sends the init message before our history is created. + // Pretend like it's happening now + const listener = ((result as any).messageListener) as HistoryMessageListener; + listener.onMessage(HistoryMessages.Started, {}); + + return result; + } + + function verifyIntellisenseVisible(wrapper: ReactWrapper, React.Component>, expectedSpan: string) { + assert.ok(wrapper); + const editor = getEditor(wrapper); + assert.ok(editor); + const domNode = editor.getDOMNode(); + assert.ok(domNode); + const node = domNode!.querySelector('.monaco-list-row .label-name .highlight') as HTMLElement; + assert.ok(node); + assert.equal(node!.innerHTML, expectedSpan, 'Intellisense row not matching'); + } + + function waitForSuggestion(wrapper: ReactWrapper, React.Component>) : { disposable: IDisposable; promise: Promise} { + const editorEnzyme = getEditor(wrapper); + const reactEditor = editorEnzyme.instance() as MonacoEditor; + const editor = reactEditor.state.editor; + if (editor) { + // The suggest controller has a suggest model on it. It has an event + // that fires when the suggest controller is opened. + const suggest = editor.getContribution('editor.contrib.suggestController') as any; + if (suggest && suggest._model) { + const promise = createDeferred(); + const disposable = suggest._model.onDidSuggest(() => { + promise.resolve(); + }); + return { + disposable, + promise: promise.promise + }; + } + } + + return { + disposable: { + dispose: noop + }, + promise: Promise.resolve() + }; + } + + runMountedTest('Simple autocomplete', async (wrapper) => { + // Create a history so that it listens to the results. + const history = await getOrCreateHistory(); + await history.show(); + + // Then enter some code. Don't submit, we're just testing that autocomplete appears + const suggestion = waitForSuggestion(wrapper); + typeCode(wrapper, 'print'); + await suggestion.promise; + suggestion.disposable.dispose(); + verifyIntellisenseVisible(wrapper, 'print'); + }, () => { return ioc; }); +}); diff --git a/src/test/datascience/intellisense.unit.test.ts b/src/test/datascience/intellisense.unit.test.ts new file mode 100644 index 000000000000..cb569f7ccacb --- /dev/null +++ b/src/test/datascience/intellisense.unit.test.ts @@ -0,0 +1,274 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { expect } from 'chai'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; +import * as TypeMoq from 'typemoq'; + +import { ILanguageServer, ILanguageServerAnalysisOptions } from '../../client/activation/types'; +import { IWorkspaceService } from '../../client/common/application/types'; +import { PythonSettings } from '../../client/common/configSettings'; +import { IFileSystem } from '../../client/common/platform/types'; +import { IConfigurationService } from '../../client/common/types'; +import { Identifiers } from '../../client/datascience/constants'; +import { HistoryMessages, IHistoryMapping } from '../../client/datascience/history/historyTypes'; +import { DotNetIntellisenseProvider } from '../../client/datascience/history/intellisense/dotNetIntellisenseProvider'; +import { IHistoryListener } from '../../client/datascience/types'; +import { MockAutoSelectionService } from '../mocks/autoSelector'; +import { MockLanguageClient } from './mockLanguageClient'; + +// tslint:disable:no-any unified-signatures + +// tslint:disable-next-line: max-func-body-length +suite('DataScience Intellisense Unit Tests', () => { + let intellisenseProvider: IHistoryListener; + let languageServer: TypeMoq.IMock; + let analysisOptions: TypeMoq.IMock; + let workspaceService: TypeMoq.IMock; + let configService: TypeMoq.IMock; + let fileSystem: TypeMoq.IMock; + const pythonSettings = new class extends PythonSettings { + public fireChangeEvent() { + this.changed.fire(); + } + }(undefined, new MockAutoSelectionService()); + + const languageClient = new MockLanguageClient( + 'mockLanguageClient', { module: 'dummy' }, {}); + + setup(() => { + languageServer = TypeMoq.Mock.ofType(); + analysisOptions = TypeMoq.Mock.ofType(); + workspaceService = TypeMoq.Mock.ofType(); + configService = TypeMoq.Mock.ofType(); + fileSystem = TypeMoq.Mock.ofType(); + + pythonSettings.jediEnabled = false; + languageServer.setup(l => l.start(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve()); + analysisOptions.setup(a => a.getAnalysisOptions()).returns(() => Promise.resolve({})); + languageServer.setup(l => l.languageClient).returns(() => languageClient); + configService.setup(c => c.getSettings(TypeMoq.It.isAny())).returns(() => pythonSettings); + workspaceService.setup(w => w.rootPath).returns(() => '/foo/bar'); + + intellisenseProvider = new DotNetIntellisenseProvider(languageServer.object, analysisOptions.object, workspaceService.object, configService.object, fileSystem.object); + }); + + function sendMessage(type: T, payload?: M[T]) : Promise { + const result = languageClient.waitForNotification(); + intellisenseProvider.onMessage(type.toString(), payload); + return result; + } + + function addCell(code: string, id: string) : Promise { + return sendMessage(HistoryMessages.AddCell, { text: code, file: 'foo.py', id }); + } + + function updateCell(newCode: string, oldCode: string, id: string) : Promise { + const oldSplit = oldCode.split('\n'); + const change: monacoEditor.editor.IModelContentChange = { + range: { + startLineNumber: 1, + startColumn: 1, + endLineNumber: oldSplit.length, + endColumn: oldSplit[oldSplit.length - 1].length + 1 + }, + rangeOffset: 0, + rangeLength: oldCode.length, + text: newCode + }; + return sendMessage(HistoryMessages.EditCell, { changes: [change], id}); + } + + function addCode(code: string, line: number, pos: number, offset: number) : Promise { + if (!line || !pos) { + throw new Error('Invalid line or position data'); + } + const change: monacoEditor.editor.IModelContentChange = { + range: { + startLineNumber: line, + startColumn: pos, + endLineNumber: line, + endColumn: pos + }, + rangeOffset: offset, + rangeLength: 0, + text: code + }; + return sendMessage(HistoryMessages.EditCell, { changes: [change], id: Identifiers.EditCellId}); + } + + function removeCode(line: number, startPos: number, endPos: number, length: number) : Promise { + if (!line || !startPos || !endPos) { + throw new Error('Invalid line or position data'); + } + const change: monacoEditor.editor.IModelContentChange = { + range: { + startLineNumber: line, + startColumn: startPos, + endLineNumber: line, + endColumn: endPos + }, + rangeOffset: startPos, + rangeLength: length, + text: '' + }; + return sendMessage(HistoryMessages.EditCell, { changes: [change], id: Identifiers.EditCellId}); + } + + function removeCell(id: string) : Promise { + sendMessage(HistoryMessages.RemoveCell, { id }).ignoreErrors(); + return Promise.resolve(); + } + + function removeAllCells() : Promise { + sendMessage(HistoryMessages.DeleteAllCells).ignoreErrors(); + return Promise.resolve(); + } + + test('Add a single cell', async () => { + await addCell('import sys\n\n', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n\n\n', 'Document not set'); + }); + + test('Add two cells', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCell('import sys', '2'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport sys\n', 'Document not set after double'); + }); + + test('Add a cell and edit', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('i', 1, 1, 0); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\ni', 'Document not set after edit'); + await addCode('m', 1, 2, 1); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nim', 'Document not set after edit'); + await addCode('\n', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nim\n', 'Document not set after edit'); + }); + + test('Add a cell and remove', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('i', 1, 1, 0); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\ni', 'Document not set after edit'); + await removeCode(1, 1, 2, 1); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set after edit'); + await addCode('\n', 1, 1, 0); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n\n', 'Document not set after edit'); + }); + + test('Remove a section in the middle', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('import os', 1, 1, 0); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport os', 'Document not set after edit'); + await removeCode(1, 4, 7, 4); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimp os', 'Document not set after edit'); + }); + + test('Remove a bunch in a row', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('p', 1, 1, 0); + await addCode('r', 1, 2, 1); + await addCode('i', 1, 3, 2); + await addCode('n', 1, 4, 3); + await addCode('t', 1, 5, 4); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nprint', 'Document not set after edit'); + await removeCode(1, 5, 6, 1); + await removeCode(1, 4, 5, 1); + await removeCode(1, 3, 4, 1); + await removeCode(1, 2, 3, 1); + await removeCode(1, 1, 2, 1); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set after edit'); + }); + test('Remove from a line', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('s', 1, 1, 0); + await addCode('y', 1, 2, 1); + await addCode('s', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Document not set after edit'); + await addCode('\n', 1, 4, 3); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys\n', 'Document not set after edit'); + await addCode('s', 2, 1, 3); + await addCode('y', 2, 2, 4); + await addCode('s', 2, 3, 5); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys\nsys', 'Document not set after edit'); + await removeCode(1, 3, 4, 1); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsy\nsys', 'Document not set after edit'); + }); + + test('Add cell after adding code', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('s', 1, 1, 0); + await addCode('y', 1, 2, 1); + await addCode('s', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Document not set after edit'); + await addCell('import sys', '2'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport sys\nsys', 'Adding a second cell broken'); + }); + + test('Collapse expand cell', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await updateCell('import sys\nsys.version_info', 'import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys.version_info\n', 'Readding a cell broken'); + await updateCell('import sys', 'import sys\nsys.version_info', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Collapsing a cell broken'); + await updateCell('import sys', 'import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Updating a cell broken'); + }); + + test('Collapse expand cell after adding code', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('s', 1, 1, 0); + await addCode('y', 1, 2, 1); + await addCode('s', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Document not set after edit'); + await updateCell('import sys\nsys.version_info', 'import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys.version_info\nsys', 'Readding a cell broken'); + await updateCell('import sys', 'import sys\nsys.version_info', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Collapsing a cell broken'); + await updateCell('import sys', 'import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Updating a cell broken'); + }); + + test('Add a cell and remove it', async () => { + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\n', 'Document not set'); + await addCode('s', 1, 1, 0); + await addCode('y', 1, 2, 1); + await addCode('s', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Document not set after edit'); + await removeCell('1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Removing a cell broken'); + await addCell('import sys', '2'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport sys\nsys', 'Adding a cell broken'); + await addCell('import bar', '3'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport sys\nimport bar\nsys', 'Adding a cell broken'); + await removeCell('1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport sys\nimport bar\nsys', 'Removing a cell broken'); + }); + + test('Add a bunch of cells and remove them', async () => { + await addCode('s', 1, 1, 0); + await addCode('y', 1, 2, 1); + await addCode('s', 1, 3, 2); + expect(languageClient.getDocumentContents()).to.be.eq('sys', 'Document not set after edit'); + await addCell('import sys', '1'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nsys', 'Document not set'); + await addCell('import foo', '2'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport foo\nsys', 'Document not set'); + await addCell('import bar', '3'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport foo\nimport bar\nsys', 'Document not set'); + await removeAllCells(); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport foo\nimport bar\nsys', 'Removing all cells broken'); + await addCell('import baz', '3'); + expect(languageClient.getDocumentContents()).to.be.eq('import sys\nimport foo\nimport bar\nimport baz\nsys', 'Document not set'); + }); +}); diff --git a/src/test/datascience/mockLanguageClient.ts b/src/test/datascience/mockLanguageClient.ts new file mode 100644 index 000000000000..beb1d0b72270 --- /dev/null +++ b/src/test/datascience/mockLanguageClient.ts @@ -0,0 +1,221 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { + CancellationToken, + DiagnosticCollection, + Disposable, + Event, + OutputChannel, + TextDocumentContentChangeEvent +} from 'vscode'; +import { + Code2ProtocolConverter, + CompletionItem, + DynamicFeature, + ErrorHandler, + GenericNotificationHandler, + GenericRequestHandler, + InitializeResult, + LanguageClient, + LanguageClientOptions, + MessageTransports, + NotificationHandler, + NotificationHandler0, + NotificationType, + NotificationType0, + Protocol2CodeConverter, + RequestHandler, + RequestHandler0, + RequestType, + RequestType0, + RPCMessageType, + StateChangeEvent, + StaticFeature, + TextDocumentItem, + Trace, + VersionedTextDocumentIdentifier +} from 'vscode-languageclient'; + +import { createDeferred, Deferred } from '../../client/common/utils/async'; +import { noop } from '../core'; +import { MockProtocolConverter } from './mockProtocolConverter'; + +// tslint:disable:no-any unified-signatures +export class MockLanguageClient extends LanguageClient { + private notificationPromise : Deferred | undefined; + private contents : string = ''; + private versionId: number | null = 0; + private converter: MockProtocolConverter = new MockProtocolConverter(); + + public waitForNotification() : Promise { + this.notificationPromise = createDeferred(); + return this.notificationPromise.promise; + } + + // Returns the current contents of the document being built by the completion provider calls + public getDocumentContents() : string { + return this.contents; + } + + public getVersionId() : number | null { + return this.versionId; + } + + public stop(): Thenable { + throw new Error('Method not implemented.'); + } + public registerProposedFeatures(): void { + throw new Error('Method not implemented.'); + } + public get initializeResult(): InitializeResult | undefined { + throw new Error('Method not implemented.'); + } + public sendRequest(type: RequestType0, token?: CancellationToken | undefined): Thenable; + public sendRequest(type: RequestType, params: P, token?: CancellationToken | undefined): Thenable; + public sendRequest(method: string, token?: CancellationToken | undefined): Thenable; + public sendRequest(method: string, param: any, token?: CancellationToken | undefined): Thenable; + public sendRequest(_method: any, _param?: any, _token?: any) : Thenable { + switch (_method.method) { + case 'textDocument/completion': + // Just return one for each line of our contents + return Promise.resolve(this.getDocumentCompletions()); + break; + + default: + break; + } + return Promise.resolve(); + } + public onRequest(type: RequestType0, handler: RequestHandler0): void; + public onRequest(type: RequestType, handler: RequestHandler): void; + public onRequest(method: string, handler: GenericRequestHandler): void; + public onRequest(_method: any, _handler: any) { + throw new Error('Method not implemented.'); + } + public sendNotification(type: NotificationType0): void; + public sendNotification(type: NotificationType, params?: P | undefined): void; + public sendNotification(method: string): void; + public sendNotification(method: string, params: any): void; + public sendNotification(method: any, params?: any) { + switch (method.method) { + case 'textDocument/didOpen': + const item = params.textDocument as TextDocumentItem; + if (item) { + this.contents = item.text; + this.versionId = item.version; + } + break; + + case 'textDocument/didChange': + const id = params.textDocument as VersionedTextDocumentIdentifier; + const changes = params.contentChanges as TextDocumentContentChangeEvent[]; + if (id && changes) { + this.applyChanges(changes); + this.versionId = id.version; + } + break; + + default: + if (this.notificationPromise) { + this.notificationPromise.reject(new Error(`Unknown notification ${method.method}`)); + } + break; + } + if (this.notificationPromise && !this.notificationPromise.resolved) { + this.notificationPromise.resolve(); + } + } + public onNotification(type: NotificationType0, handler: NotificationHandler0): void; + public onNotification(type: NotificationType, handler: NotificationHandler

): void; + public onNotification(method: string, handler: GenericNotificationHandler): void; + public onNotification(_method: any, _handler: any) { + throw new Error('Method not implemented.'); + } + public get clientOptions(): LanguageClientOptions { + throw new Error('Method not implemented.'); + } + public get protocol2CodeConverter(): Protocol2CodeConverter { + throw new Error('Method not implemented.'); + } + public get code2ProtocolConverter(): Code2ProtocolConverter { + return this.converter; + } + public get onTelemetry(): Event { + throw new Error('Method not implemented.'); + } + public get onDidChangeState(): Event { + throw new Error('Method not implemented.'); + } + public get outputChannel(): OutputChannel { + throw new Error('Method not implemented.'); + } + public get diagnostics(): DiagnosticCollection | undefined { + throw new Error('Method not implemented.'); + } + public createDefaultErrorHandler(): ErrorHandler { + throw new Error('Method not implemented.'); + } + public get trace(): Trace { + throw new Error('Method not implemented.'); + } + public info(_message: string, _data?: any): void { + throw new Error('Method not implemented.'); + } + public warn(_message: string, _data?: any): void { + throw new Error('Method not implemented.'); + } + public error(_message: string, _data?: any): void { + throw new Error('Method not implemented.'); + } + public needsStart(): boolean { + throw new Error('Method not implemented.'); + } + public needsStop(): boolean { + throw new Error('Method not implemented.'); + } + public onReady(): Promise { + throw new Error('Method not implemented.'); + } + public start(): Disposable { + throw new Error('Method not implemented.'); + } + public registerFeatures(_features: (StaticFeature | DynamicFeature)[]): void { + throw new Error('Method not implemented.'); + } + public registerFeature(_feature: StaticFeature | DynamicFeature): void { + throw new Error('Method not implemented.'); + } + public logFailedRequest(_type: RPCMessageType, _error: any): void { + throw new Error('Method not implemented.'); + } + + protected handleConnectionClosed(): void { + throw new Error('Method not implemented.'); + } + protected createMessageTransports(_encoding: string): Thenable { + throw new Error('Method not implemented.'); + } + protected registerBuiltinFeatures(): void { + noop(); + } + + private applyChanges(changes: TextDocumentContentChangeEvent[]) { + changes.forEach(c => { + const before = this.contents.substr(0, c.rangeOffset); + const after = this.contents.substr(c.rangeOffset + c.rangeLength); + this.contents = `${before}${c.text}${after}`; + }); + } + + private getDocumentCompletions() : CompletionItem[] { + const lines = this.contents.splitLines(); + return lines.map(l => { + return { + label: l, + insertText: l, + sortText: l + }; + }); + } +} diff --git a/src/test/datascience/mockLanguageServer.ts b/src/test/datascience/mockLanguageServer.ts new file mode 100644 index 000000000000..f9daf07ed2ff --- /dev/null +++ b/src/test/datascience/mockLanguageServer.ts @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { injectable } from 'inversify'; +import { Uri } from 'vscode'; +import { LanguageClient, LanguageClientOptions } from 'vscode-languageclient'; + +import { ILanguageServer } from '../../client/activation/types'; +import { MockLanguageClient } from './mockLanguageClient'; + +// tslint:disable:no-any unified-signatures +@injectable() +export class MockLanguageServer implements ILanguageServer { + private mockLanguageClient: MockLanguageClient | undefined; + + public get languageClient(): LanguageClient | undefined { + if (!this.mockLanguageClient) { + this.mockLanguageClient = new MockLanguageClient('mockLanguageClient', { module: 'dummy' }, {}); + } + return this.mockLanguageClient; + } + + public start(_resource: Uri | undefined, _options: LanguageClientOptions): Promise { + if (!this.mockLanguageClient) { + this.mockLanguageClient = new MockLanguageClient('mockLanguageClient', { module: 'dummy' }, {}); + } + return Promise.resolve(); + } + public loadExtension(_args?: {} | undefined): void { + throw new Error('Method not implemented.'); + } + public dispose(): void | undefined { + this.mockLanguageClient = undefined; + } + +} diff --git a/src/test/datascience/mockLanguageServerAnalysisOptions.ts b/src/test/datascience/mockLanguageServerAnalysisOptions.ts new file mode 100644 index 000000000000..f31c494f58df --- /dev/null +++ b/src/test/datascience/mockLanguageServerAnalysisOptions.ts @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import { injectable } from 'inversify'; +import { Event, EventEmitter } from 'vscode'; +import { LanguageClientOptions } from 'vscode-languageclient'; + +import { ILanguageServerAnalysisOptions } from '../../client/activation/types'; +import { Resource } from '../../client/common/types'; +import { noop } from '../core'; + +// tslint:disable:no-any unified-signatures +@injectable() +export class MockLanguageServerAnalysisOptions implements ILanguageServerAnalysisOptions { + private onDidChangeEmitter: EventEmitter = new EventEmitter(); + + public get onDidChange(): Event { + return this.onDidChangeEmitter.event; + } + + public initialize(_resource: Resource): Promise { + return Promise.resolve(); + } + public getAnalysisOptions(): Promise { + return Promise.resolve({ + }); + } + public dispose(): void | undefined { + noop(); + } +} diff --git a/src/test/datascience/mockProtocolConverter.ts b/src/test/datascience/mockProtocolConverter.ts new file mode 100644 index 000000000000..b3a41f56b474 --- /dev/null +++ b/src/test/datascience/mockProtocolConverter.ts @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +'use strict'; +import * as code from 'vscode'; +import { Code2ProtocolConverter } from 'vscode-languageclient'; +import * as proto from 'vscode-languageserver-protocol'; + +// tslint:disable:no-any unified-signatures +export class MockProtocolConverter implements Code2ProtocolConverter { + public asUri(_uri: code.Uri): string { + throw new Error('Method not implemented.'); + } + public asTextDocumentIdentifier(_textDocument: code.TextDocument): proto.TextDocumentIdentifier { + throw new Error('Method not implemented.'); + } + public asOpenTextDocumentParams(_textDocument: code.TextDocument): proto.DidOpenTextDocumentParams { + throw new Error('Method not implemented.'); + } + public asChangeTextDocumentParams(textDocument: code.TextDocument): proto.DidChangeTextDocumentParams; + public asChangeTextDocumentParams(event: code.TextDocumentChangeEvent): proto.DidChangeTextDocumentParams; + public asChangeTextDocumentParams(_event: any): proto.DidChangeTextDocumentParams { + throw new Error('Method not implemented.'); + } + public asCloseTextDocumentParams(_textDocument: code.TextDocument): proto.DidCloseTextDocumentParams { + throw new Error('Method not implemented.'); + } + public asSaveTextDocumentParams(_textDocument: code.TextDocument, _includeContent?: boolean | undefined): proto.DidSaveTextDocumentParams { + throw new Error('Method not implemented.'); + } + public asWillSaveTextDocumentParams(_event: code.TextDocumentWillSaveEvent): proto.WillSaveTextDocumentParams { + throw new Error('Method not implemented.'); + } + public asTextDocumentPositionParams(_textDocument: code.TextDocument, _position: code.Position): proto.TextDocumentPositionParams { + return { + textDocument: { + uri: _textDocument.uri.fsPath + }, + position: { + line: _position.line, + character: _position.character + } + }; + } + public asCompletionParams(_textDocument: code.TextDocument, _position: code.Position, _context: code.CompletionContext): proto.CompletionParams { + const triggerKind = _context.triggerKind as number; + return { + textDocument: { + uri: _textDocument.uri.fsPath + }, + position: { + line: _position.line, + character: _position.character + }, + context: { + triggerCharacter: _context.triggerCharacter, + triggerKind: triggerKind as proto.CompletionTriggerKind + } + }; + } + public asWorkerPosition(_position: code.Position): proto.Position { + throw new Error('Method not implemented.'); + } + public asPosition(value: code.Position): proto.Position; + public asPosition(value: undefined): undefined; + public asPosition(value: null): null; + public asPosition(value: code.Position | null | undefined): proto.Position | null | undefined; + public asPosition(_value: any): any { + throw new Error('Method not implemented.'); + } + public asRange(value: code.Range): proto.Range; + public asRange(value: undefined): undefined; + public asRange(value: null): null; + public asRange(value: code.Range | null | undefined): proto.Range | null | undefined; + public asRange(_value: any): any { + throw new Error('Method not implemented.'); + } + public asDiagnosticSeverity(_value: code.DiagnosticSeverity): number { + throw new Error('Method not implemented.'); + } + public asDiagnostic(_item: code.Diagnostic): proto.Diagnostic { + throw new Error('Method not implemented.'); + } + public asDiagnostics(_items: code.Diagnostic[]): proto.Diagnostic[] { + throw new Error('Method not implemented.'); + } + public asCompletionItem(_item: code.CompletionItem): proto.CompletionItem { + throw new Error('Method not implemented.'); + } + public asTextEdit(_edit: code.TextEdit): proto.TextEdit { + throw new Error('Method not implemented.'); + } + public asReferenceParams(_textDocument: code.TextDocument, _position: code.Position, _options: { includeDeclaration: boolean }): proto.ReferenceParams { + throw new Error('Method not implemented.'); + } + public asCodeActionContext(_context: code.CodeActionContext): proto.CodeActionContext { + throw new Error('Method not implemented.'); + } + public asCommand(_item: code.Command): proto.Command { + throw new Error('Method not implemented.'); + } + public asCodeLens(_item: code.CodeLens): proto.CodeLens { + throw new Error('Method not implemented.'); + } + public asFormattingOptions(_item: code.FormattingOptions): proto.FormattingOptions { + throw new Error('Method not implemented.'); + } + public asDocumentSymbolParams(_textDocument: code.TextDocument): proto.DocumentSymbolParams { + throw new Error('Method not implemented.'); + } + public asCodeLensParams(_textDocument: code.TextDocument): proto.CodeLensParams { + throw new Error('Method not implemented.'); + } + public asDocumentLink(_item: code.DocumentLink): proto.DocumentLink { + throw new Error('Method not implemented.'); + } + public asDocumentLinkParams(_textDocument: code.TextDocument): proto.DocumentLinkParams { + throw new Error('Method not implemented.'); + } +} diff --git a/src/test/datascience/reactHelpers.ts b/src/test/datascience/reactHelpers.ts index a17316082ff0..80c0d54b7b1d 100644 --- a/src/test/datascience/reactHelpers.ts +++ b/src/test/datascience/reactHelpers.ts @@ -8,13 +8,199 @@ import * as React from 'react'; import { noop } from '../../client/common/utils/misc'; -// tslint:disable:no-string-literal no-any object-literal-key-quotes max-func-body-length +// tslint:disable:no-string-literal no-any object-literal-key-quotes max-func-body-length member-ordering +// tslint:disable: no-require-imports +class MockCanvas implements CanvasRenderingContext2D { + public canvas!: HTMLCanvasElement; + public restore(): void { + throw new Error('Method not implemented.'); + } + public save(): void { + throw new Error('Method not implemented.'); + } + public getTransform(): DOMMatrix { + throw new Error('Method not implemented.'); + } + public resetTransform(): void { + throw new Error('Method not implemented.'); + } + public rotate(_angle: number): void { + throw new Error('Method not implemented.'); + } + public scale(_x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public setTransform(a: number, b: number, c: number, d: number, e: number, f: number): void; + public setTransform(transform?: DOMMatrix2DInit | undefined): void; + public setTransform(_a?: any, _b?: any, _c?: any, _d?: any, _e?: any, _f?: any) { + throw new Error('Method not implemented.'); + } + public transform(_a: number, _b: number, _c: number, _d: number, _e: number, _f: number): void { + throw new Error('Method not implemented.'); + } + public translate(_x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public globalAlpha!: number; + public globalCompositeOperation!: string; + public imageSmoothingEnabled!: boolean; + public imageSmoothingQuality!: ImageSmoothingQuality; + public fillStyle!: string | CanvasGradient | CanvasPattern; + public strokeStyle!: string | CanvasGradient | CanvasPattern; + public createLinearGradient(_x0: number, _y0: number, _x1: number, _y1: number): CanvasGradient { + throw new Error('Method not implemented.'); + } + public createPattern(_image: CanvasImageSource, _repetition: string): CanvasPattern | null { + throw new Error('Method not implemented.'); + } + public createRadialGradient(_x0: number, _y0: number, _r0: number, _x1: number, _y1: number, _r1: number): CanvasGradient { + throw new Error('Method not implemented.'); + } + public shadowBlur!: number; + public shadowColor!: string; + public shadowOffsetX!: number; + public shadowOffsetY!: number; + public filter!: string; + public clearRect(_x: number, _y: number, _w: number, _h: number): void { + throw new Error('Method not implemented.'); + } + public fillRect(_x: number, _y: number, _w: number, _h: number): void { + throw new Error('Method not implemented.'); + } + public strokeRect(_x: number, _y: number, _w: number, _h: number): void { + throw new Error('Method not implemented.'); + } + public beginPath(): void { + throw new Error('Method not implemented.'); + } + public clip(fillRule?: 'nonzero' | 'evenodd' | undefined): void; + public clip(path: Path2D, fillRule?: 'nonzero' | 'evenodd' | undefined): void; + public clip(_path?: any, _fillRule?: any) { + throw new Error('Method not implemented.'); + } + public fill(fillRule?: 'nonzero' | 'evenodd' | undefined): void; + public fill(path: Path2D, fillRule?: 'nonzero' | 'evenodd' | undefined): void; + public fill(_path?: any, _fillRule?: any) { + throw new Error('Method not implemented.'); + } + public isPointInPath(x: number, y: number, fillRule?: 'nonzero' | 'evenodd' | undefined): boolean; + public isPointInPath(path: Path2D, x: number, y: number, fillRule?: 'nonzero' | 'evenodd' | undefined): boolean; + public isPointInPath(_path: any, _x: any, _y?: any, _fillRule?: any): boolean { + throw new Error('Method not implemented.'); + } + public isPointInStroke(x: number, y: number): boolean; + public isPointInStroke(path: Path2D, x: number, y: number): boolean; + public isPointInStroke(_path: any, _x: any, _y?: any): boolean { + throw new Error('Method not implemented.'); + } + public stroke(): void; + // tslint:disable-next-line: unified-signatures + public stroke(path: Path2D): void; + public stroke(_path?: any) { + throw new Error('Method not implemented.'); + } + public drawFocusIfNeeded(element: Element): void; + public drawFocusIfNeeded(path: Path2D, element: Element): void; + public drawFocusIfNeeded(_path: any, _element?: any) { + throw new Error('Method not implemented.'); + } + public scrollPathIntoView(): void; + // tslint:disable-next-line: unified-signatures + public scrollPathIntoView(path: Path2D): void; + public scrollPathIntoView(_path?: any) { + throw new Error('Method not implemented.'); + } + public fillText(_text: string, _x: number, _y: number, _maxWidth?: number | undefined): void { + throw new Error('Method not implemented.'); + } + public measureText(_text: string): TextMetrics { + throw new Error('Method not implemented.'); + } + public strokeText(_text: string, _x: number, _y: number, _maxWidth?: number | undefined): void { + throw new Error('Method not implemented.'); + } + public drawImage(image: CanvasImageSource, dx: number, dy: number): void; + public drawImage(image: CanvasImageSource, dx: number, dy: number, dw: number, dh: number): void; + public drawImage(image: CanvasImageSource, sx: number, sy: number, sw: number, sh: number, dx: number, dy: number, dw: number, dh: number): void; + public drawImage(_image: any, _sx: any, _sy: any, _sw?: any, _sh?: any, _dx?: any, _dy?: any, _dw?: any, _dh?: any) { + throw new Error('Method not implemented.'); + } + public createImageData(sw: number, sh: number): ImageData; + public createImageData(imagedata: ImageData): ImageData; + public createImageData(_sw: any, _sh?: any): ImageData { + throw new Error('Method not implemented.'); + } + public getImageData(_sx: number, _sy: number, _sw: number, _sh: number): ImageData { + throw new Error('Method not implemented.'); + } + public putImageData(imagedata: ImageData, dx: number, dy: number): void; + public putImageData(imagedata: ImageData, dx: number, dy: number, dirtyX: number, dirtyY: number, dirtyWidth: number, dirtyHeight: number): void; + public putImageData(_imagedata: any, _dx: any, _dy: any, _dirtyX?: any, _dirtyY?: any, _dirtyWidth?: any, _dirtyHeight?: any) { + throw new Error('Method not implemented.'); + } + public lineCap!: CanvasLineCap; + public lineDashOffset!: number; + public lineJoin!: CanvasLineJoin; + public lineWidth!: number; + public miterLimit!: number; + public getLineDash(): number[] { + throw new Error('Method not implemented.'); + } + public setLineDash(_segments: number[]): void { + throw new Error('Method not implemented.'); + } + public direction!: CanvasDirection; + public font!: string; + public textAlign!: CanvasTextAlign; + public textBaseline!: CanvasTextBaseline; + public arc(_x: number, _y: number, _radius: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean | undefined): void { + throw new Error('Method not implemented.'); + } + public arcTo(_x1: number, _y1: number, _x2: number, _y2: number, _radius: number): void { + throw new Error('Method not implemented.'); + } + public bezierCurveTo(_cp1x: number, _cp1y: number, _cp2x: number, _cp2y: number, _x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public closePath(): void { + throw new Error('Method not implemented.'); + } + public ellipse(_x: number, _y: number, _radiusX: number, _radiusY: number, _rotation: number, _startAngle: number, _endAngle: number, _anticlockwise?: boolean | undefined): void { + throw new Error('Method not implemented.'); + } + public lineTo(_x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public moveTo(_x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public quadraticCurveTo(_cpx: number, _cpy: number, _x: number, _y: number): void { + throw new Error('Method not implemented.'); + } + public rect(_x: number, _y: number, _w: number, _h: number): void { + throw new Error('Method not implemented.'); + } +} + +const mockCanvas = new MockCanvas(); export function setUpDomEnvironment() { // tslint:disable-next-line:no-http-string const dom = new JSDOM('

', { pretendToBeVisual: true, url: 'http://localhost'}); const { window } = dom; + // tslint:disable: no-function-expression no-empty + window.HTMLCanvasElement.prototype.getContext = (contextId: string, _contextAttributes?: {}): any => { + if (contextId === '2d') { + return mockCanvas; + } + return null; + }; + + window.HTMLCanvasElement.prototype.toDataURL = function () { + return ''; + }; + // tslist:disable-next-line:no-string-literal no-any (global as any)['Element'] = window.Element; // tslint:disable-next-line:no-string-literal no-any @@ -32,6 +218,9 @@ export function setUpDomEnvironment() { (global as any)['self'] = window; copyProps(window, global); + // Special case. Monaco needs queryCommandSupported + (global as any)['document'].queryCommandSupported = () => (false); + // Special case. Transform needs createRange (global as any)['document'].createRange = () => ({ createContextualFragment: (str: string) => JSDOM.fragment(str), @@ -113,6 +302,32 @@ export function setUpDomEnvironment() { }; configure({ adapter: new Adapter() }); + + // Special case for the node_modules\monaco-editor\esm\vs\editor\browser\config\configuration.js. It doesn't + // export the function we need to dispose of the timer it's set. So force it to. + const configurationRegex = /.*(\\|\/)node_modules(\\|\/)monaco-editor(\\|\/)esm(\\|\/)vs(\\|\/)editor(\\|\/)browser(\\|\/)config(\\|\/)configuration\.js/g; + const _oldLoader = require.extensions['.js']; + // tslint:disable-next-line:no-function-expression + require.extensions['.js'] = function (mod: any, filename) { + if (configurationRegex.test(filename)) { + let content = require('fs').readFileSync(filename, 'utf8'); + content += 'export function getCSSBasedConfiguration() { return CSSBasedConfiguration.INSTANCE; };\n'; + mod._compile(content, filename); + } else { + _oldLoader(mod, filename); + } + }; +} + +export function setupTranspile() { + // Some special work for getting the monaco editor to work. + // We need to babel transpile some modules. Monaco-editor is not in commonJS format so imports + // can't be loaded. + require('@babel/register')({ plugins: ['@babel/transform-modules-commonjs'], only: [ /monaco-editor/ ] }); + + // Special case for editor api. Webpack bundles editor.all.js as well. Tests don't. + require('monaco-editor/esm/vs/editor/editor.api'); + require('monaco-editor/esm/vs/editor/editor.all'); } function copyProps(src: any, target: any) { diff --git a/src/test/mocks/vsc/index.ts b/src/test/mocks/vsc/index.ts index b910031590e8..d9586c4a4d35 100644 --- a/src/test/mocks/vsc/index.ts +++ b/src/test/mocks/vsc/index.ts @@ -41,7 +41,7 @@ export namespace vscMock { decodeURIComponent(match[7] || this._empty), decodeURIComponent(match[9] || this._empty), decodeURIComponent(match[5] || this._empty)); - } + } public with(_change: { scheme?: string; authority?: string; path?: string; query?: string; fragment?: string }): vscode.Uri { throw new Error('Not implemented'); } @@ -172,4 +172,39 @@ export namespace vscMock { Operator = 24, TypeParameter = 25 } + + export class CodeActionKind { + public static readonly Empty: CodeActionKind = new CodeActionKind('empty'); + public static readonly QuickFix: CodeActionKind = new CodeActionKind('quick.fix'); + + public static readonly Refactor: CodeActionKind = new CodeActionKind('refactor'); + + public static readonly RefactorExtract: CodeActionKind = new CodeActionKind('refactor.extract'); + + public static readonly RefactorInline: CodeActionKind = new CodeActionKind('refactor.inline'); + + public static readonly RefactorRewrite: CodeActionKind = new CodeActionKind('refactor.rewrite'); + public static readonly Source: CodeActionKind = new CodeActionKind('source'); + public static readonly SourceOrganizeImports: CodeActionKind = new CodeActionKind('source.organize.imports'); + public static readonly SourceFixAll: CodeActionKind = new CodeActionKind('source.fix.all'); + + private constructor(private _value: string) { + } + + public append(parts: string): CodeActionKind { + return new CodeActionKind(`${this._value}.${parts}`); + } + public intersects(other: CodeActionKind): boolean { + return this._value.includes(other._value) || other._value.includes(this._value); + } + + public contains(other: CodeActionKind): boolean { + return this._value.startsWith(other._value); + } + + public get value(): string { + return this._value; + } + } + } diff --git a/src/test/unittests.ts b/src/test/unittests.ts index 9536fcdfcde0..1d0b38db8245 100644 --- a/src/test/unittests.ts +++ b/src/test/unittests.ts @@ -11,7 +11,7 @@ if ((Reflect as any).metadata === undefined) { process.env.VSC_PYTHON_CI_TEST = '1'; process.env.VSC_PYTHON_UNIT_TEST = '1'; -import { setUpDomEnvironment } from './datascience/reactHelpers'; +import { setUpDomEnvironment, setupTranspile } from './datascience/reactHelpers'; import { initialize } from './vscode-mock'; // Custom module loader so we skip .css files that break non webpack wrapped compiles @@ -37,4 +37,7 @@ const Module = require('module'); // nteract/transforms-full expects to run in the browser so we have to fake // parts of the browser here. setUpDomEnvironment(); + +// Also have to setup babel to get the monaco editor to work. +setupTranspile(); initialize(); diff --git a/src/test/vscode-mock.ts b/src/test/vscode-mock.ts index 551d78163950..e23089359cdb 100644 --- a/src/test/vscode-mock.ts +++ b/src/test/vscode-mock.ts @@ -77,6 +77,7 @@ mockedVSCode.ViewColumn = vscodeMocks.vscMockExtHostedTypes.ViewColumn; mockedVSCode.TextEditorRevealType = vscodeMocks.vscMockExtHostedTypes.TextEditorRevealType; mockedVSCode.TreeItem = vscodeMocks.vscMockExtHostedTypes.TreeItem; mockedVSCode.TreeItemCollapsibleState = vscodeMocks.vscMockExtHostedTypes.TreeItemCollapsibleState; +mockedVSCode.CodeActionKind = vscodeMocks.vscMock.CodeActionKind; // This API is used in src/client/telemetry/telemetry.ts const extensions = TypeMoq.Mock.ofType(); diff --git a/webpack.datascience-ui.config.js b/webpack.datascience-ui.config.js index a675ac945748..a400cbd7a726 100644 --- a/webpack.datascience-ui.config.js +++ b/webpack.datascience-ui.config.js @@ -3,7 +3,9 @@ const webpack = require('webpack'); const HtmlWebpackPlugin = require('html-webpack-plugin'); const FixDefaultImportPlugin = require('webpack-fix-default-import-plugin'); const path = require('path'); -const CopyWebpackPlugin = require('copy-webpack-plugin') +const CopyWebpackPlugin = require('copy-webpack-plugin'); +const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); +const TerserPlugin = require('terser-webpack-plugin') const configFileName = 'tsconfig.datascience-ui.json'; @@ -21,6 +23,9 @@ module.exports = [ // We need to use one where source is embedded, due to webviews (they restrict resources to specific schemes, // this seems to prevent chrome from downloading the source maps) devtool: 'eval-source-map', + optimization: { + minimizer: [new TerserPlugin()] + }, node: { fs: 'empty' }, @@ -33,6 +38,9 @@ module.exports = [ { from: './**/*.css', to: '.' }, { from: './**/*theme*.json', to: '.' } ], { context: 'src' }), + new MonacoWebpackPlugin({ + languages: [] // force to empty so onigasm will be used + }) ], resolve: { // Add '.ts' and '.tsx' as resolvable extensions.