diff --git a/.eslintrc b/.eslintrc index 808ac90b2db..2445c0c7288 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,7 +6,9 @@ "rules": { "no-console": 0, "no-cond-assign": 0, - "no-unused-vars": 1 + "no-unused-vars": 1, + "no-extra-semi": "warn", + "semi": "warn" }, "extends": "eslint:recommended" } \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 133cdf519df..df5aedd1c04 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -17,25 +17,6 @@ "sourceMaps": true, "outDir": "${workspaceRoot}/out" }, - { - "name": "Stacks Tests", - "type": "node", - "request": "launch", - "program": "${workspaceRoot}/node_modules/mocha/bin/_mocha", - "stopOnEntry": false, - "args": [ - "--timeout", - "999999", - "--colors", - "-g", - "Stacks" - ], - "cwd": "${workspaceRoot}", - "runtimeArgs": [], - "env": {}, - "sourceMaps": true, - "outDir": "${workspaceRoot}/out" - }, { "name": "Attach to Extension Host", "type": "node", @@ -91,7 +72,15 @@ "name": "Launch VSCode", "type": "chrome", "request": "launch", - "runtimeExecutable": "${workspaceRoot}/scripts/code.bat", + "windows": { + "runtimeExecutable": "${workspaceRoot}/scripts/code.bat" + }, + "osx": { + "runtimeExecutable": "${workspaceRoot}/scripts/code.sh" + }, + "linux": { + "runtimeExecutable": "${workspaceRoot}/scripts/code.sh" + }, "sourceMaps": true }, { diff --git a/.vscode/settings.json b/.vscode/settings.json index 691000844b3..3b946931d76 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -17,5 +17,11 @@ "tslint.enable": true, "tslint.rulesDirectory": "build/lib/tslint", "lcov.path": ["./.build/coverage/lcov.info", "./.build/coverage-single/lcov.info"], - "lcov.watcherExec.windows": "${workspaceRoot}\\scripts\\test.bat --coverage --run ${file.replace(/^src/,'out').replace(/\\.ts$/,'.js')}" + "lcov.watch": [{ + "pattern": "**/*.test.js", + "command": "${workspaceRoot}\\scripts\\test.sh --coverage --run ${file}", + "windows": { + "command": "${workspaceRoot}\\scripts\\test.bat --coverage --run ${file}" + } + }] } \ No newline at end of file diff --git a/OSSREADME.json b/OSSREADME.json index 343ae205326..5fe88517ee4 100644 --- a/OSSREADME.json +++ b/OSSREADME.json @@ -6,6 +6,36 @@ "license": "MIT", "isProd": true }, +{ + "isLicense": true, + "name": "async-each", + "repositoryURL": "https://github.com/paulmillr/async-each", + "license": "MIT", + "licenseDetail": [ + "The MIT License (MIT)", + "", + "Copyright (c) 2016 Paul Miller [(paulmillr.com)](http://paulmillr.com)", + "", + "Permission is hereby granted, free of charge, to any person obtaining a copy", + "of this software and associated documentation files (the “Software”), to deal", + "in the Software without restriction, including without limitation the rights", + "to use, copy, modify, merge, publish, distribute, sublicense, and/or sell", + "copies of the Software, and to permit persons to whom the Software is", + "furnished to do so, subject to the following conditions:", + "", + "The above copyright notice and this permission notice shall be included in", + "all copies or substantial portions of the Software.", + "", + "THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR", + "IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,", + "FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE", + "AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER", + "LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,", + "OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN", + "THE SOFTWARE." + ], + "isProd": true +}, { "name": "chromium", "version": "49.0.2623.75", diff --git a/README.md b/README.md index 1862261e1fb..119cf632d13 100644 --- a/README.md +++ b/README.md @@ -43,4 +43,7 @@ Many of the core components and extensions to Code live in their own repositorie For a complete list, please see the [Related Projects](https://github.com/Microsoft/vscode/wiki/Related-Projects) page on our wiki. ## License -[MIT](LICENSE.txt) + +Copyright (c) Microsoft Corporation. All rights reserved. + +Licensed under the [MIT](LICENSE.txt) License. diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt index ed5ffe9901d..d33bcabbfd7 100644 --- a/ThirdPartyNotices.txt +++ b/ThirdPartyNotices.txt @@ -23,40 +23,40 @@ This project incorporates components from the projects listed below. The origina 16. language-go version 0.39.0 (https://github.com/atom/language-go) 17. language-php version 0.29.0 (https://github.com/atom/language-php) 18. language-rust version 0.4.4 (https://github.com/zargony/atom-language-rust) -19. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) -20. mmcgrana/textmate-clojure (https://github.com/mmcgrana/textmate-clojure) -21. octicons-code version 3.1.0 (https://octicons.github.com) -22. octicons-font version 3.1.0 (https://octicons.github.com) -23. string_scorer version 0.1.20 (https://github.com/joshaven/string_score) -24. sublimehq/Packages (https://github.com/sublimehq/Packages) -25. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell) -26. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) -27. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) -28. textmate/coffee-script.tmbundle (https://github.com/textmate/coffee-script.tmbundle) -29. textmate/css.tmbundle (https://github.com/textmate/css.tmbundle) -30. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) -31. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) -32. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) -33. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) -34. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) -35. textmate/java.tmbundle (https://github.com/textmate/java.tmbundle) -36. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) -37. textmate/less.tmbundle (https://github.com/textmate/less.tmbundle) -38. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) -39. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle) -40. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) -41. textmate/objective-c.tmbundle (https://github.com/textmate/objective-c.tmbundle) -42. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) -43. textmate/php.tmbundle (https://github.com/textmate/php.tmbundle) -44. textmate/python.tmbundle (https://github.com/textmate/python.tmbundle) -45. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle) -46. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) -47. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle) -48. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle) -49. textmate/xml.tmbundle (https://github.com/textmate/xml.tmbundle) -50. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) -51. typescript version 1.5 (https://github.com/Microsoft/TypeScript) -52. typescript version 1.8.2 (https://github.com/Microsoft/TypeScript) +19. MagicStack/MagicPython (https://github.com/MagicStack/MagicPython) +20. Microsoft/TypeScript-TmLanguage version 0.0.1 (https://github.com/Microsoft/TypeScript-TmLanguage) +21. mmcgrana/textmate-clojure (https://github.com/mmcgrana/textmate-clojure) +22. octicons-code version 3.1.0 (https://octicons.github.com) +23. octicons-font version 3.1.0 (https://octicons.github.com) +24. seti-ui version 0.1.0 (https://github.com/jesseweed/seti-ui) +25. string_scorer version 0.1.20 (https://github.com/joshaven/string_score) +26. sublimehq/Packages (https://github.com/sublimehq/Packages) +27. SublimeText/PowerShell (https://github.com/SublimeText/PowerShell) +28. textmate/asp.vb.net.tmbundle (https://github.com/textmate/asp.vb.net.tmbundle) +29. textmate/c.tmbundle (https://github.com/textmate/c.tmbundle) +30. textmate/coffee-script.tmbundle (https://github.com/textmate/coffee-script.tmbundle) +31. textmate/css.tmbundle (https://github.com/textmate/css.tmbundle) +32. textmate/diff.tmbundle (https://github.com/textmate/diff.tmbundle) +33. textmate/git.tmbundle (https://github.com/textmate/git.tmbundle) +34. textmate/groovy.tmbundle (https://github.com/textmate/groovy.tmbundle) +35. textmate/html.tmbundle (https://github.com/textmate/html.tmbundle) +36. textmate/ini.tmbundle (https://github.com/textmate/ini.tmbundle) +37. textmate/java.tmbundle (https://github.com/textmate/java.tmbundle) +38. textmate/javascript.tmbundle (https://github.com/textmate/javascript.tmbundle) +39. textmate/less.tmbundle (https://github.com/textmate/less.tmbundle) +40. textmate/lua.tmbundle (https://github.com/textmate/lua.tmbundle) +41. textmate/make.tmbundle (https://github.com/textmate/make.tmbundle) +42. textmate/markdown.tmbundle (https://github.com/textmate/markdown.tmbundle) +43. textmate/objective-c.tmbundle (https://github.com/textmate/objective-c.tmbundle) +44. textmate/perl.tmbundle (https://github.com/textmate/perl.tmbundle) +45. textmate/php.tmbundle (https://github.com/textmate/php.tmbundle) +46. textmate/r.tmbundle (https://github.com/textmate/r.tmbundle) +47. textmate/ruby.tmbundle (https://github.com/textmate/ruby.tmbundle) +48. textmate/shellscript.tmbundle (https://github.com/textmate/shellscript.tmbundle) +49. textmate/sql.tmbundle (https://github.com/textmate/sql.tmbundle) +50. textmate/xml.tmbundle (https://github.com/textmate/xml.tmbundle) +51. textmate/yaml.tmbundle (https://github.com/textmate/yaml.tmbundle) +52. typescript version 1.5 (https://github.com/Microsoft/TypeScript) 53. TypeScript-TmLanguage version 0.1.8 (https://github.com/Microsoft/TypeScript-TmLanguage) 54. unity-shader-files version 0.1.0 (https://github.com/nashella/unity-shader-files) 55. vscode-swift version 0.0.1 (https://github.com/owensd/vscode-swift) @@ -784,6 +784,32 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ========================================= END OF language-rust NOTICES AND INFORMATION +%% MagicStack/MagicPython NOTICES AND INFORMATION BEGIN HERE +========================================= +The MIT License + +Copyright (c) 2015 MagicStack Inc. http://magic.io + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +========================================= +END OF MagicStack/MagicPython NOTICES AND INFORMATION + %% Microsoft/TypeScript-TmLanguage NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) Microsoft Corporation. All rights reserved. @@ -1110,6 +1136,31 @@ OTHER DEALINGS IN THE FONT SOFTWARE. ========================================= END OF octicons-font NOTICES AND INFORMATION +%% seti-ui NOTICES AND INFORMATION BEGIN HERE +========================================= +Copyright (c) 2014 Jesse Weed + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +========================================= +END OF seti-ui NOTICES AND INFORMATION + %% string_scorer NOTICES AND INFORMATION BEGIN HERE ========================================= This software is released under the MIT license: @@ -1514,24 +1565,6 @@ to the base-name name of the original file, and an extension of txt, html, or si ========================================= END OF textmate/php.tmbundle NOTICES AND INFORMATION -%% textmate/python.tmbundle NOTICES AND INFORMATION BEGIN HERE -========================================= -Copyright (c) textmate-python.tmbundle project authors - -If not otherwise specified (see below), files in this repository fall under the following license: - -Permission to copy, use, modify, sell and distribute this -software is granted. This software is provided "as is" without -express or implied warranty, and with no claim as to its -suitability for any purpose. - -An exception is made for files in readable text which contain their own license information, -or files where an accompanying file exists (in the same directory) with a "-license" suffix added -to the base-name name of the original file, and an extension of txt, html, or similar. For example -"tidy" is accompanied by "tidy-license.txt". -========================================= -END OF textmate/python.tmbundle NOTICES AND INFORMATION - %% textmate/r.tmbundle NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) textmate-r.tmbundle project authors @@ -1697,68 +1730,6 @@ END OF TERMS AND CONDITIONS ========================================= END OF typescript NOTICES AND INFORMATION -%% typescript NOTICES AND INFORMATION BEGIN HERE -========================================= -Copyright (c) Microsoft Corporation. All rights reserved. - -Apache License - -Version 2.0, January 2004 - -http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - -"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. - -"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. - -"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. - -"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. - -"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. - -"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. - -"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). - -"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. - -"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." - -"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: - -You must give any other recipients of the Work or Derivative Works a copy of this License; and - -You must cause any modified files to carry prominent notices stating that You changed the files; and - -You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and - -If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS -========================================= -END OF typescript NOTICES AND INFORMATION - %% TypeScript-TmLanguage NOTICES AND INFORMATION BEGIN HERE ========================================= Copyright (c) Microsoft Corporation. All rights reserved. diff --git a/appveyor.yml b/appveyor.yml index 86d73d0671f..67d80cae24f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -3,7 +3,8 @@ environment: VSCODE_BUILD_VERBOSE: true install: - - ps: Install-Product node 5.10.1 x64 + - ps: Install-Product node 6.5.0 x64 + - npm install -g npm - npm install -g gulp mocha build_script: @@ -12,5 +13,7 @@ build_script: - gulp compile test_script: + - node --version + - npm --version - .\scripts\test.bat - .\scripts\test-integration.bat diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 286cc7ad973..fb56d7c575f 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -32,6 +32,7 @@ var editorResources = [ '!out-build/vs/base/browser/ui/splitview/**/*', '!out-build/vs/base/browser/ui/toolbar/**/*', '!out-build/vs/base/browser/ui/octiconLabel/**/*', + '!out-build/vs/editor/contrib/defineKeybinding/**/*', 'out-build/vs/base/worker/workerMainCompatibility.html', 'out-build/vs/base/worker/workerMain.{js,js.map}', '!out-build/vs/workbench/**', @@ -59,7 +60,8 @@ function editorLoaderConfig() { // never ship octicons in editor result.paths['vs/base/browser/ui/octiconLabel/octiconLabel'] = 'out-build/vs/base/browser/ui/octiconLabel/octiconLabel.mock'; - result['vs/css'] = { inlineResources: true }; + // force css inlining to use base64 -- see https://github.com/Microsoft/monaco-editor/issues/148 + result['vs/css'] = { inlineResources: 'base64' }; return result; } @@ -84,7 +86,6 @@ gulp.task('editor-distro', ['clean-editor-distro', 'minify-editor', 'optimize-ed // other assets es.merge( gulp.src('build/monaco/LICENSE'), - gulp.src('build/monaco/CHANGELOG.md'), gulp.src('build/monaco/ThirdPartyNotices.txt'), gulp.src('src/vs/monaco.d.ts') ).pipe(gulp.dest('out-monaco-editor-core')), diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 97523c34cbe..fc896f9a11f 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -11,7 +11,7 @@ const path = require('path'); const es = require('event-stream'); const azure = require('gulp-azure-storage'); const electron = require('gulp-atom-electron'); -const symdest = require('gulp-symdest'); +const vfs = require('vinyl-fs'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); @@ -130,7 +130,7 @@ gulp.task('electron', ['clean-electron'], () => { .pipe(json({ name })) .pipe(electron(opts)) .pipe(filter(['**', '!**/app/package.json'])) - .pipe(symdest('.build/electron')); + .pipe(vfs.dest('.build/electron')); }); const languages = ['chs', 'cht', 'jpn', 'kor', 'deu', 'fra', 'esn', 'rus', 'ita']; @@ -186,7 +186,7 @@ function packageTask(platform, arch, opts) { const productJsonStream = gulp.src(['product.json'], { base: '.' }) .pipe(json({ commit, date })); - const license = gulp.src(['Credits_*', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.' }); + const license = gulp.src(['LICENSES.chromium.html', 'LICENSE.txt', 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.' }); // TODO the API should be copied to `out` during compile, not here const api = gulp.src('src/vs/vscode.d.ts').pipe(rename('out/vs/vscode.d.ts')); @@ -243,7 +243,7 @@ function packageTask(platform, arch, opts) { .pipe(rename('bin/' + product.applicationName))); } - return result.pipe(symdest(destination)); + return result.pipe(vfs.dest(destination)); }; } diff --git a/build/gulpfile.vscode.linux.js b/build/gulpfile.vscode.linux.js index 23d1a3d6f72..628a9dff844 100644 --- a/build/gulpfile.vscode.linux.js +++ b/build/gulpfile.vscode.linux.js @@ -10,13 +10,13 @@ const replace = require('gulp-replace'); const rename = require('gulp-rename'); const shell = require('gulp-shell'); const es = require('event-stream'); -const symdest = require('gulp-symdest'); +const vfs = require('vinyl-fs'); const util = require('./lib/util'); const packageJson = require('../package.json'); const product = require('../product.json'); function getDebPackageArch(arch) { - return { x64: 'amd64', ia32: 'i386' }[arch]; + return { x64: 'amd64', ia32: 'i386', arm: 'armhf' }[arch]; } const linuxPackageRevision = Math.floor(new Date().getTime() / 1000); @@ -55,29 +55,29 @@ function prepareDebPackage(arch) { const prerm = gulp.src('resources/linux/debian/prerm.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('DEBIAN/prerm')) + .pipe(rename('DEBIAN/prerm')); const postrm = gulp.src('resources/linux/debian/postrm.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) - .pipe(rename('DEBIAN/postrm')) + .pipe(rename('DEBIAN/postrm')); const postinst = gulp.src('resources/linux/debian/postinst.template', { base: '.' }) .pipe(replace('@@NAME@@', product.applicationName)) .pipe(replace('@@ARCHITECTURE@@', debArch)) .pipe(replace('@@QUALITY@@', product.quality || '@@QUALITY@@')) .pipe(replace('@@UPDATEURL@@', product.updateUrl || '@@UPDATEURL@@')) - .pipe(rename('DEBIAN/postinst')) + .pipe(rename('DEBIAN/postinst')); const all = es.merge(control, postinst, postrm, prerm, desktop, icon, code); - return all.pipe(symdest(destination)); + return all.pipe(vfs.dest(destination)); }; } function buildDebPackage(arch) { const debArch = getDebPackageArch(arch); return shell.task([ - 'chmod 755 ' + product.applicationName + '-' + debArch + '/DEBIAN/postinst ' + product.applicationName + '-' + debArch + '/DEBIAN/prerm', + 'chmod 755 ' + product.applicationName + '-' + debArch + '/DEBIAN/postinst ' + product.applicationName + '-' + debArch + '/DEBIAN/prerm ' + product.applicationName + '-' + debArch + '/DEBIAN/postrm', 'mkdir -p deb', 'fakeroot dpkg-deb -b ' + product.applicationName + '-' + debArch + ' deb', 'dpkg-scanpackages deb /dev/null > Packages' @@ -89,7 +89,7 @@ function getRpmBuildPath(rpmArch) { } function getRpmPackageArch(arch) { - return { x64: 'x86_64', ia32: 'i386' }[arch]; + return { x64: 'x86_64', ia32: 'i386', arm: 'armhf' }[arch]; } function prepareRpmPackage(arch) { @@ -124,8 +124,8 @@ function prepareRpmPackage(arch) { const all = es.merge(code, desktop, icon, spec, specIcon); - return all.pipe(symdest(getRpmBuildPath(rpmArch))); - } + return all.pipe(vfs.dest(getRpmBuildPath(rpmArch))); + }; } function buildRpmPackage(arch) { @@ -133,6 +133,7 @@ function buildRpmPackage(arch) { const rpmBuildPath = getRpmBuildPath(rpmArch); const rpmOut = rpmBuildPath + '/RPMS/' + rpmArch; const destination = '.build/linux/rpm/' + rpmArch; + return shell.task([ 'mkdir -p ' + destination, 'HOME="$(pwd)/' + destination + '" fakeroot rpmbuild -bb ' + rpmBuildPath + '/SPECS/' + product.applicationName + '.spec --target=' + rpmArch, @@ -143,15 +144,21 @@ function buildRpmPackage(arch) { gulp.task('clean-vscode-linux-ia32-deb', util.rimraf('.build/linux/deb/i386')); gulp.task('clean-vscode-linux-x64-deb', util.rimraf('.build/linux/deb/amd64')); +gulp.task('clean-vscode-linux-arm-deb', util.rimraf('.build/linux/deb/armhf')); gulp.task('clean-vscode-linux-ia32-rpm', util.rimraf('.build/linux/rpm/i386')); gulp.task('clean-vscode-linux-x64-rpm', util.rimraf('.build/linux/rpm/x86_64')); +gulp.task('clean-vscode-linux-arm-rpm', util.rimraf('.build/linux/rpm/armhf')); gulp.task('vscode-linux-ia32-prepare-deb', ['clean-vscode-linux-ia32-deb', 'vscode-linux-ia32-min'], prepareDebPackage('ia32')); gulp.task('vscode-linux-x64-prepare-deb', ['clean-vscode-linux-x64-deb', 'vscode-linux-x64-min'], prepareDebPackage('x64')); +gulp.task('vscode-linux-arm-prepare-deb', ['clean-vscode-linux-arm-deb', 'vscode-linux-arm-min'], prepareDebPackage('armhf')); gulp.task('vscode-linux-ia32-build-deb', ['vscode-linux-ia32-prepare-deb'], buildDebPackage('ia32')); gulp.task('vscode-linux-x64-build-deb', ['vscode-linux-x64-prepare-deb'], buildDebPackage('x64')); +gulp.task('vscode-linux-arm-build-deb', ['vscode-linux-arm-prepare-deb'], buildDebPackage('armhf')); gulp.task('vscode-linux-ia32-prepare-rpm', ['clean-vscode-linux-ia32-rpm', 'vscode-linux-ia32-min'], prepareRpmPackage('ia32')); gulp.task('vscode-linux-x64-prepare-rpm', ['clean-vscode-linux-x64-rpm', 'vscode-linux-x64-min'], prepareRpmPackage('x64')); +gulp.task('vscode-linux-arm-prepare-rpm', ['clean-vscode-linux-arm-rpm', 'vscode-linux-arm-min'], prepareRpmPackage('armhf')); gulp.task('vscode-linux-ia32-build-rpm', ['vscode-linux-ia32-prepare-rpm'], buildRpmPackage('ia32')); gulp.task('vscode-linux-x64-build-rpm', ['vscode-linux-x64-prepare-rpm'], buildRpmPackage('x64')); +gulp.task('vscode-linux-arm-build-rpm', ['vscode-linux-arm-prepare-rpm'], buildRpmPackage('armhf')); diff --git a/build/lib/bundle.js b/build/lib/bundle.js index c01a56844c7..cbc7e92e55d 100644 --- a/build/lib/bundle.js +++ b/build/lib/bundle.js @@ -161,7 +161,7 @@ function extractStrings(destFiles) { destFile.sources.forEach(function (source) { source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, function (_, moduleMatch, depsMatch) { var defineCall = parseDefineCall(moduleMatch, depsMatch); - return "define(__m[" + replacementMap[defineCall.module] + "], __M([" + defineCall.deps.map(function (dep) { return replacementMap[dep]; }).join(',') + "])"; + return "define(__m[" + replacementMap[defineCall.module] + "/*" + defineCall.module + "*/], __M([" + defineCall.deps.map(function (dep) { return replacementMap[dep] + '/*' + dep + '*/'; }).join(',') + "])"; }); }); destFile.sources.unshift({ diff --git a/build/lib/bundle.ts b/build/lib/bundle.ts index 0b68d98271d..bf500e5c92b 100644 --- a/build/lib/bundle.ts +++ b/build/lib/bundle.ts @@ -268,7 +268,7 @@ function extractStrings(destFiles:IConcatFile[]):IConcatFile[] { destFile.sources.forEach((source) => { source.contents = source.contents.replace(/define\(("[^"]+"),\s*\[(((, )?("|')[^"']+("|'))+)\]/, (_, moduleMatch, depsMatch) => { let defineCall = parseDefineCall(moduleMatch, depsMatch); - return `define(__m[${replacementMap[defineCall.module]}], __M([${defineCall.deps.map(dep => replacementMap[dep]).join(',')}])`; + return `define(__m[${replacementMap[defineCall.module]}/*${defineCall.module}*/], __M([${defineCall.deps.map(dep => replacementMap[dep] + '/*' + dep + '*/').join(',')}])`; }); }); diff --git a/build/monaco/CHANGELOG.md b/build/monaco/CHANGELOG.md deleted file mode 100644 index 6cccaa0d532..00000000000 --- a/build/monaco/CHANGELOG.md +++ /dev/null @@ -1,20 +0,0 @@ -# Monaco Editor Change log - -## [0.5.1] - -- Fixed mouse handling in IE - -## [0.5.0] - -### Breaking changes -- `monaco.editor.createWebWorker` now loads the AMD module and calls `create` and passes in as first argument a context of type `monaco.worker.IWorkerContext` and as second argument the `initData`. This breaking change was needed to allow handling the case of misconfigured web workers (running on a file protocol or the cross-domain case) -- the `CodeActionProvider.provideCodeActions` now gets passed in a `CodeActionContext` that contains the markers at the relevant range. -- the `hoverMessage` of a decoration is now a `MarkedString | MarkedString[]` -- the `contents` of a `Hover` returned by a `HoverProvider` is now a `MarkedString | MarkedString[]` -- removed deprecated `IEditor.onDidChangeModelRawContent`, `IModel.onDidChangeRawContent` - -### Notable fixes -- Broken configurations (loading from `file://` or misconfigured cross-domain loading) now load the web worker code in the UI thread. This caused a breaking change in the behaviour of `monaco.editor.createWebWorker` -- The right-pointing mouse pointer is oversized in high DPI - [issue](https://github.com/Microsoft/monaco-editor/issues/5) -- The editor functions now correctly when hosted inside a `position:fixed` element. -- Cross origin configuration is now picked up (as advertised in documentation from MonacoEnvironment) diff --git a/build/monaco/ThirdPartyNotices.txt b/build/monaco/ThirdPartyNotices.txt index 29b175e7597..ced73100132 100644 --- a/build/monaco/ThirdPartyNotices.txt +++ b/build/monaco/ThirdPartyNotices.txt @@ -8,6 +8,28 @@ herein, whether by implication, estoppel or otherwise. +%% winjs version 4.4.0 (https://github.com/winjs/winjs) +========================================= +WinJS + +Copyright (c) Microsoft Corporation + +All rights reserved. + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the ""Software""), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +========================================= +END OF winjs NOTICES AND INFORMATION + + + + %% HTML 5.1 W3C Working Draft version 08 October 2015 (http://www.w3.org/TR/2015/WD-html51-20151008/) ========================================= Copyright © 2015 W3C® (MIT, ERCIM, Keio, Beihang). This software or document includes material copied diff --git a/build/monaco/monaco.d.ts.recipe b/build/monaco/monaco.d.ts.recipe index ff27705017d..af39c661697 100644 --- a/build/monaco/monaco.d.ts.recipe +++ b/build/monaco/monaco.d.ts.recipe @@ -53,6 +53,7 @@ declare module monaco { declare module monaco.editor { #includeAll(vs/editor/browser/standalone/standaloneEditor;modes.=>languages.): +#include(vs/editor/common/services/webWorker): MonacoWebWorker, IWebWorkerOptions #include(vs/editor/browser/standalone/standaloneCodeEditor): IEditorConstructionOptions, IDiffEditorConstructionOptions, IStandaloneCodeEditor, IStandaloneDiffEditor export interface ICommandHandler { (...args:any[]): void; diff --git a/build/monaco/package.json b/build/monaco/package.json index 9575f609362..0394ef9c29b 100644 --- a/build/monaco/package.json +++ b/build/monaco/package.json @@ -1,7 +1,7 @@ { "name": "monaco-editor-core", "private": true, - "version": "0.5.3", + "version": "0.6.1", "description": "A browser based code editor", "author": "Microsoft Corporation", "license": "MIT", diff --git a/build/win32/code.iss b/build/win32/code.iss index 2d5d4281991..e6e906038b5 100644 --- a/build/win32/code.iss +++ b/build/win32/code.iss @@ -47,6 +47,7 @@ Name: "traditionalChinese"; MessagesFile: "{#RepoDir}\build\win32\i18n\Default.z Type: filesandordirs; Name: {app}\resources\app\plugins Type: filesandordirs; Name: {app}\resources\app\extensions Type: filesandordirs; Name: {app}\resources\app\node_modules +Type: files; Name: {app}\resources\app\Credits_45.0.2454.85.html [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}" diff --git a/extensions/css/client/src/colorDecorators.ts b/extensions/css/client/src/colorDecorators.ts index 39165bc42dc..90a0c28607d 100644 --- a/extensions/css/client/src/colorDecorators.ts +++ b/extensions/css/client/src/colorDecorators.ts @@ -28,7 +28,7 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The let colorsDecorationType = window.createTextEditorDecorationType(decorationType); disposables.push(colorsDecorationType); - let pendingUpdateRequests : { [key:string]:number; } = {}; + let pendingUpdateRequests : { [key:string]: NodeJS.Timer; } = {}; // we care about all visible editors window.visibleTextEditors.forEach(editor => { diff --git a/extensions/css/npm-shrinkwrap.json b/extensions/css/npm-shrinkwrap.json index e7216492872..0b55dcb027a 100644 --- a/extensions/css/npm-shrinkwrap.json +++ b/extensions/css/npm-shrinkwrap.json @@ -3,19 +3,19 @@ "version": "0.1.0", "dependencies": { "vscode-jsonrpc": { - "version": "2.2.0", - "from": "vscode-jsonrpc@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.2.0.tgz" + "version": "2.3.2-next.5", + "from": "vscode-jsonrpc@>=2.3.2-next.2 <3.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.3.2-next.5.tgz" }, "vscode-languageclient": { - "version": "2.4.2-next.3", + "version": "2.4.2-next.22", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-2.4.2-next.3.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-2.4.2-next.22.tgz" }, "vscode-languageserver-types": { - "version": "1.0.1", - "from": "vscode-languageserver-types@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.1.tgz" + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" }, "vscode-nls": { "version": "1.0.7", diff --git a/extensions/css/package.json b/extensions/css/package.json index 0c0e65189bb..a58f5f3387c 100644 --- a/extensions/css/package.json +++ b/extensions/css/package.json @@ -646,7 +646,7 @@ } }, "dependencies": { - "vscode-languageclient": "^2.4.2-next.3", + "vscode-languageclient": "^2.4.2-next.22", "vscode-nls": "^1.0.7" } } diff --git a/extensions/css/server/npm-shrinkwrap.json b/extensions/css/server/npm-shrinkwrap.json index 2c2f3c23ee8..5a3feef73f3 100644 --- a/extensions/css/server/npm-shrinkwrap.json +++ b/extensions/css/server/npm-shrinkwrap.json @@ -3,23 +3,31 @@ "version": "1.0.0", "dependencies": { "vscode-css-languageservice": { - "version": "1.0.7-next.2", - "from": "vscode-css-languageservice@1.0.7-next.2" + "version": "1.0.9-next.1", + "from": "vscode-css-languageservice@next", + "resolved": "https://registry.npmjs.org/vscode-css-languageservice/-/vscode-css-languageservice-1.0.9-next.1.tgz" }, "vscode-jsonrpc": { - "version": "2.2.0", - "from": "vscode-jsonrpc@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.2.0.tgz" + "version": "2.3.2-next.5", + "from": "vscode-jsonrpc@>=2.3.2-next.2 <3.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.3.2-next.5.tgz" }, "vscode-languageserver": { - "version": "2.4.0-next.4", + "version": "2.4.0-next.12", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-2.4.0-next.4.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-2.4.0-next.12.tgz", + "dependencies": { + "vscode-languageserver-types": { + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" + } + } }, "vscode-languageserver-types": { - "version": "1.0.1", - "from": "vscode-languageserver-types@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.1.tgz" + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" }, "vscode-nls": { "version": "1.0.7", diff --git a/extensions/css/server/package.json b/extensions/css/server/package.json index 087a4bd4dc3..a5a90bb7138 100644 --- a/extensions/css/server/package.json +++ b/extensions/css/server/package.json @@ -8,8 +8,8 @@ "node": "*" }, "dependencies": { - "vscode-css-languageservice": "^1.0.7-next.2", - "vscode-languageserver": "^2.4.0-next.4" + "vscode-css-languageservice": "^1.0.9-next.1", + "vscode-languageserver": "^2.4.0-next.12" }, "scripts": { "compile": "gulp compile-extension:css-server", diff --git a/extensions/css/server/src/cssServerMain.ts b/extensions/css/server/src/cssServerMain.ts index e90bfe41aaa..751be2baefd 100644 --- a/extensions/css/server/src/cssServerMain.ts +++ b/extensions/css/server/src/cssServerMain.ts @@ -44,7 +44,7 @@ connection.onShutdown(() => { }); // After the server has started the client sends an initilize request. The server receives -// in the passed params the rootPath of the workspace plus the client capabilites. +// in the passed params the rootPath of the workspace plus the client capabilities. connection.onInitialize((params: InitializeParams): InitializeResult => { return { capabilities: { @@ -90,7 +90,7 @@ function updateConfiguration(settings: Settings) { documents.all().forEach(triggerValidation); } -let pendingValidationRequests : {[uri:string]:number} = {}; +let pendingValidationRequests : { [uri:string]: NodeJS.Timer } = {}; const validationDelayMs = 200; // The content of a text document has changed. This event is emitted diff --git a/extensions/declares.d.ts b/extensions/declares.d.ts index 7f9ee0a8862..2f1f06c0bf2 100644 --- a/extensions/declares.d.ts +++ b/extensions/declares.d.ts @@ -4,40 +4,16 @@ *--------------------------------------------------------------------------------------------*/ /// /// -/// - -declare var define: { - (moduleName: string, dependencies: string[], callback: (...args: any[]) => any): any; - (moduleName: string, dependencies: string[], definition: any): any; - (moduleName: string, callback: (...args: any[]) => any): any; - (moduleName: string, definition: any): any; - (dependencies: string[], callback: (...args: any[]) => any): any; - (dependencies: string[], definition: any): any; -}; - -declare var require: { - toUrl(path: string): string; - (moduleName: string): any; - (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; - config(data: any): any; - onError: Function; - __$__nodeRequire(moduleName: string): T; -}; // Declaring the following because the code gets compiled with es5, which lack definitions for console and timers. declare var console: { - info(message?: any, ...optionalParams: any[]): void; - profile(reportName?: string): void; - assert(test?: boolean, message?: string, ...optionalParams: any[]): void; - clear(): void; - dir(value?: any, ...optionalParams: any[]): void; - warn(message?: any, ...optionalParams: any[]): void; - error(message?: any, ...optionalParams: any[]): void; - log(message?: any, ...optionalParams: any[]): void; - profileEnd(): void; -}; - -declare function clearTimeout(handle: number): void; -declare function setTimeout(handler: any, timeout?: any, ...args: any[]): number; -declare function clearInterval(handle: number): void; -declare function setInterval(handler: any, timeout?: any, ...args: any[]): number; + assert(value: any, message?: string, ...optionalParams: any[]): void; + dir(obj: any, options?: {showHidden?: boolean, depth?: number, colors?: boolean}): void; + error(message?: any, ...optionalParams: any[]): void; + info(message?: any, ...optionalParams: any[]): void; + log(message?: any, ...optionalParams: any[]): void; + time(label: string): void; + timeEnd(label: string): void; + trace(message?: any, ...optionalParams: any[]): void; + warn(message?: any, ...optionalParams: any[]): void; +}; \ No newline at end of file diff --git a/extensions/javascript/src/features/bowerJSONContribution.ts b/extensions/javascript/src/features/bowerJSONContribution.ts index ae8e664ccdd..c2034433ce8 100644 --- a/extensions/javascript/src/features/bowerJSONContribution.ts +++ b/extensions/javascript/src/features/bowerJSONContribution.ts @@ -8,6 +8,7 @@ import {MarkedString, CompletionItemKind, CompletionItem, DocumentSelector} from import {IJSONContribution, ISuggestionsCollector} from './jsonContributions'; import {XHRRequest} from 'request-light'; import {Location} from 'jsonc-parser'; +import {textToMarkedString} from './markedTextUtil'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -173,7 +174,7 @@ export class BowerJSONContribution implements IJSONContribution { htmlContent.push(localize('json.bower.package.hover', '{0}', pack)); return this.getInfo(pack).then(documentation => { if (documentation) { - htmlContent.push({ language: 'string', value: documentation}); + htmlContent.push(textToMarkedString(documentation)); } return htmlContent; }); diff --git a/src/vs/workbench/common/constants.ts b/extensions/javascript/src/features/markedTextUtil.ts similarity index 53% rename from src/vs/workbench/common/constants.ts rename to extensions/javascript/src/features/markedTextUtil.ts index b20bae1c145..97f3198b80c 100644 --- a/src/vs/workbench/common/constants.ts +++ b/extensions/javascript/src/features/markedTextUtil.ts @@ -2,15 +2,10 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - 'use strict'; -// Container Identifiers -export const Identifiers = { - WORKBENCH_CONTAINER: 'workbench.main.container', - ACTIVITYBAR_PART: 'workbench.parts.activitybar', - SIDEBAR_PART: 'workbench.parts.sidebar', - PANEL_PART: 'workbench.parts.panel', - EDITOR_PART: 'workbench.parts.editor', - STATUSBAR_PART: 'workbench.parts.statusbar' -}; \ No newline at end of file +import {MarkedString} from 'vscode'; + +export function textToMarkedString(text: string) : MarkedString { + return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash +} \ No newline at end of file diff --git a/extensions/javascript/src/features/packageJSONContribution.ts b/extensions/javascript/src/features/packageJSONContribution.ts index cd1bc8e4d15..e7107bef811 100644 --- a/extensions/javascript/src/features/packageJSONContribution.ts +++ b/extensions/javascript/src/features/packageJSONContribution.ts @@ -8,6 +8,7 @@ import {MarkedString, CompletionItemKind, CompletionItem, DocumentSelector} from import {IJSONContribution, ISuggestionsCollector} from './jsonContributions'; import {XHRRequest} from 'request-light'; import {Location} from 'jsonc-parser'; +import {textToMarkedString} from './markedTextUtil'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); @@ -121,29 +122,29 @@ export class PackageJSONContribution implements IJSONContribution { if ((location.matches(['dependencies', '*']) || location.matches(['devDependencies', '*']) || location.matches(['optionalDependencies', '*']) || location.matches(['peerDependencies', '*']))) { let currentKey = location.path[location.path.length - 1]; if (typeof currentKey === 'string') { - let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(currentKey) + '/latest'; + let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(currentKey).replace('%40', '@'); return this.xhr({ url : queryUrl }).then((success) => { try { let obj = JSON.parse(success.responseText); - if (obj && obj.version) { - let version = obj.version; - let name = JSON.stringify(version); + let latest = obj && obj['dist-tags'] && obj['dist-tags']['latest']; + if (latest) { + let name = JSON.stringify(latest); let proposal = new CompletionItem(name); proposal.kind = CompletionItemKind.Property; proposal.insertText = name; proposal.documentation = localize('json.npm.latestversion', 'The currently latest version of the package'); result.add(proposal); - name = JSON.stringify('^' + version); + name = JSON.stringify('^' + latest); proposal = new CompletionItem(name); proposal.kind = CompletionItemKind.Property; proposal.insertText = name; proposal.documentation = localize('json.npm.majorversion', 'Matches the most recent major version (1.x.x)'); result.add(proposal); - name = JSON.stringify('~' + version); + name = JSON.stringify('~' + latest); proposal = new CompletionItem(name); proposal.kind = CompletionItemKind.Property; proposal.insertText = name; @@ -179,8 +180,8 @@ export class PackageJSONContribution implements IJSONContribution { } private getInfo(pack: string): Thenable { - let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack) + '/latest'; + let queryUrl = 'http://registry.npmjs.org/' + encodeURIComponent(pack).replace('%40', '@'); return this.xhr({ url : queryUrl }).then((success) => { @@ -191,8 +192,9 @@ export class PackageJSONContribution implements IJSONContribution { if (obj.description) { result.push(obj.description); } - if (obj.version) { - result.push(localize('json.npm.version.hover', 'Latest version: {0}', obj.version)); + let latest = obj && obj['dist-tags'] && obj['dist-tags']['latest']; + if (latest) { + result.push(localize('json.npm.version.hover', 'Latest version: {0}', latest)); } return result; } @@ -213,7 +215,7 @@ export class PackageJSONContribution implements IJSONContribution { htmlContent.push(localize('json.npm.package.hover', '{0}', pack)); return this.getInfo(pack).then(infos => { infos.forEach(info => { - htmlContent.push({language: 'string', value: info}); + htmlContent.push(textToMarkedString(info)); }); return htmlContent; }); diff --git a/extensions/json/npm-shrinkwrap.json b/extensions/json/npm-shrinkwrap.json index b4c97acd780..c6f216d2d28 100644 --- a/extensions/json/npm-shrinkwrap.json +++ b/extensions/json/npm-shrinkwrap.json @@ -13,19 +13,19 @@ "resolved": "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz" }, "vscode-jsonrpc": { - "version": "2.2.0", - "from": "vscode-jsonrpc@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.2.0.tgz" + "version": "2.3.2-next.5", + "from": "vscode-jsonrpc@>=2.3.2-next.2 <3.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.3.2-next.5.tgz" }, "vscode-languageclient": { - "version": "2.4.2-next.3", + "version": "2.4.2-next.22", "from": "vscode-languageclient@next", - "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-2.4.2-next.3.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageclient/-/vscode-languageclient-2.4.2-next.22.tgz" }, "vscode-languageserver-types": { - "version": "1.0.1", - "from": "vscode-languageserver-types@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.1.tgz" + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" }, "vscode-nls": { "version": "1.0.7", diff --git a/extensions/json/package.json b/extensions/json/package.json index 973728f5472..b5603841c6b 100644 --- a/extensions/json/package.json +++ b/extensions/json/package.json @@ -98,7 +98,7 @@ }, "dependencies": { "vscode-extension-telemetry": "^0.0.5", - "vscode-languageclient": "^2.4.2-next.3", + "vscode-languageclient": "^2.4.2-next.22", "vscode-nls": "^1.0.7" } } diff --git a/extensions/json/server/npm-shrinkwrap.json b/extensions/json/server/npm-shrinkwrap.json index 47d61112c09..6ce6dfe75fc 100644 --- a/extensions/json/server/npm-shrinkwrap.json +++ b/extensions/json/server/npm-shrinkwrap.json @@ -43,29 +43,41 @@ "resolved": "https://registry.npmjs.org/request-light/-/request-light-0.1.0.tgz" }, "vscode-json-languageservice": { - "version": "1.1.4", - "from": "vscode-json-languageservice@1.1.4", - "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-1.1.4.tgz" + "version": "1.1.8-next.1", + "from": "vscode-json-languageservice@next", + "resolved": "https://registry.npmjs.org/vscode-json-languageservice/-/vscode-json-languageservice-1.1.8-next.1.tgz", + "dependencies": { + "vscode-languageserver-types": { + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" + } + } }, "vscode-jsonrpc": { - "version": "2.2.0", - "from": "vscode-jsonrpc@>=2.2.0 <3.0.0", - "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.2.0.tgz" + "version": "2.3.2-next.5", + "from": "vscode-jsonrpc@>=2.3.2-next.2 <3.0.0", + "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-2.3.2-next.5.tgz" }, "vscode-languageserver": { - "version": "2.4.0-next.4", + "version": "2.4.0-next.12", "from": "vscode-languageserver@next", - "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-2.4.0-next.4.tgz" + "resolved": "https://registry.npmjs.org/vscode-languageserver/-/vscode-languageserver-2.4.0-next.12.tgz" }, "vscode-languageserver-types": { - "version": "1.0.1", - "from": "vscode-languageserver-types@1.0.1", - "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.1.tgz" + "version": "1.0.3", + "from": "vscode-languageserver-types@>=1.0.3 <2.0.0", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-1.0.3.tgz" }, "vscode-nls": { "version": "1.0.7", "from": "vscode-nls@>=1.0.4 <2.0.0", "resolved": "https://registry.npmjs.org/vscode-nls/-/vscode-nls-1.0.7.tgz" + }, + "vscode-uri": { + "version": "0.0.6", + "from": "vscode-uri@>=0.0.6 <0.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-0.0.6.tgz" } } } diff --git a/extensions/json/server/package.json b/extensions/json/server/package.json index 23be3423884..8fa3aa27e34 100644 --- a/extensions/json/server/package.json +++ b/extensions/json/server/package.json @@ -9,8 +9,8 @@ }, "dependencies": { "request-light": "^0.1.0", - "vscode-json-languageservice": "^1.1.4", - "vscode-languageserver": "^2.4.0-next.4", + "vscode-json-languageservice": "^1.1.8-next.1", + "vscode-languageserver": "^2.4.0-next.12", "vscode-nls": "^1.0.4" }, "scripts": { diff --git a/extensions/json/server/src/jsonServerMain.ts b/extensions/json/server/src/jsonServerMain.ts index 393a606230f..3340ba905f4 100644 --- a/extensions/json/server/src/jsonServerMain.ts +++ b/extensions/json/server/src/jsonServerMain.ts @@ -52,7 +52,7 @@ documents.listen(connection); const filesAssociationContribution = new FileAssociationContribution(); // After the server has started the client sends an initilize request. The server receives -// in the passed params the rootPath of the workspace plus the client capabilites. +// in the passed params the rootPath of the workspace plus the client capabilities. let workspaceRoot: URI; connection.onInitialize((params: InitializeParams): InitializeResult => { workspaceRoot = URI.parse(params.rootPath); @@ -204,7 +204,7 @@ documents.onDidClose(event => { connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] }); }); -let pendingValidationRequests : {[uri:string]:number} = {}; +let pendingValidationRequests : { [uri: string]: NodeJS.Timer; } = {}; const validationDelayMs = 200; function cleanPendingValidation(textDocument: TextDocument): void { diff --git a/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts b/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts index 381a8d1aec8..6a458db385c 100644 --- a/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts +++ b/extensions/json/server/src/jsoncontributions/projectJSONContribution.ts @@ -219,10 +219,10 @@ export class ProjectJSONContribution implements JSONWorkerContribution { this.addCached(res.id, res.version, res.description); if (res.id === pack) { if (res.description) { - htmlContent.push({ language: 'string', value: res.description }); + htmlContent.push(MarkedString.fromPlainText(res.description)); } if (res.version) { - htmlContent.push({ language: 'string', value: localize('json.nugget.version.hover', 'Latest version: {0}', res.version)}); + htmlContent.push(MarkedString.fromPlainText(localize('json.nugget.version.hover', 'Latest version: {0}', res.version))); } break; } diff --git a/extensions/lib.core.d.ts b/extensions/lib.core.d.ts index e898494d299..fb3fe52c13d 100644 --- a/extensions/lib.core.d.ts +++ b/extensions/lib.core.d.ts @@ -1,20 +1,19 @@ /*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. +Copyright (c) Microsoft Corporation. All rights reserved. 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 - +License at http://www.apache.org/licenses/LICENSE-2.0 + THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ /// - ///////////////////////////// /// ECMAScript APIs ///////////////////////////// @@ -23,7 +22,7 @@ declare var NaN: number; declare var Infinity: number; /** - * Evaluates JavaScript code and executes it. + * Evaluates JavaScript code and executes it. * @param x A String value that contains valid JavaScript code. */ declare function eval(x: string): any; @@ -31,25 +30,25 @@ declare function eval(x: string): any; /** * Converts A string to an integer. * @param s A string to convert into a number. - * @param radix A value between 2 and 36 that specifies the base of the number in numString. + * @param radix A value between 2 and 36 that specifies the base of the number in numString. * If this argument is not supplied, strings with a prefix of '0x' are considered hexadecimal. * All other strings are considered decimal. */ declare function parseInt(s: string, radix?: number): number; /** - * Converts a string to a floating-point number. - * @param string A string that contains a floating-point number. + * Converts a string to a floating-point number. + * @param string A string that contains a floating-point number. */ declare function parseFloat(string: string): number; /** - * Returns a Boolean value that indicates whether a value is the reserved value NaN (not a number). + * Returns a Boolean value that indicates whether a value is the reserved value NaN (not a number). * @param number A numeric value. */ declare function isNaN(number: number): boolean; -/** +/** * Determines whether a supplied number is finite. * @param number Any numeric value. */ @@ -67,7 +66,7 @@ declare function decodeURI(encodedURI: string): string; */ declare function decodeURIComponent(encodedURIComponent: string): string; -/** +/** * Encodes a text string as a valid Uniform Resource Identifier (URI) * @param uri A value representing an encoded URI. */ @@ -106,18 +105,18 @@ interface Object { valueOf(): Object; /** - * Determines whether an object has a property with the specified name. + * Determines whether an object has a property with the specified name. * @param v A property name. */ hasOwnProperty(v: string): boolean; /** - * Determines whether an object exists in another object's prototype chain. + * Determines whether an object exists in another object's prototype chain. * @param v Another object whose prototype chain is to be checked. */ isPrototypeOf(v: Object): boolean; - /** + /** * Determines whether a specified property is enumerable. * @param v A property name. */ @@ -132,36 +131,36 @@ interface ObjectConstructor { /** A reference to the prototype for a class of objects. */ prototype: Object; - /** - * Returns the prototype of an object. + /** + * Returns the prototype of an object. * @param o The object that references the prototype. */ getPrototypeOf(o: any): any; /** - * Gets the own property descriptor of the specified object. - * An own property descriptor is one that is defined directly on the object and is not inherited from the object's prototype. + * Gets the own property descriptor of the specified object. + * An own property descriptor is one that is defined directly on the object and is not inherited from the object's prototype. * @param o Object that contains the property. * @param p Name of the property. */ getOwnPropertyDescriptor(o: any, p: string): PropertyDescriptor; - /** - * Returns the names of the own properties of an object. The own properties of an object are those that are defined directly + /** + * Returns the names of the own properties of an object. The own properties of an object are those that are defined directly * on that object, and are not inherited from the object's prototype. The properties of an object include both fields (objects) and functions. * @param o Object that contains the own properties. */ getOwnPropertyNames(o: any): string[]; - /** + /** * Creates an object that has the specified prototype, and that optionally contains specified properties. * @param o Object to use as a prototype. May be null - * @param properties JavaScript object that contains one or more property descriptors. + * @param properties JavaScript object that contains one or more property descriptors. */ create(o: any, properties?: PropertyDescriptorMap): any; /** - * Adds a property to an object, or modifies attributes of an existing property. + * Adds a property to an object, or modifies attributes of an existing property. * @param o Object on which to add or modify the property. This can be a native JavaScript object (that is, a user-defined object or a built in object) or a DOM object. * @param p The property name. * @param attributes Descriptor for the property. It can be for a data property or an accessor property. @@ -169,7 +168,7 @@ interface ObjectConstructor { defineProperty(o: any, p: string, attributes: PropertyDescriptor): any; /** - * Adds one or more properties to an object, and/or modifies attributes of existing properties. + * Adds one or more properties to an object, and/or modifies attributes of existing properties. * @param o Object on which to add or modify the properties. This can be a native JavaScript object or a DOM object. * @param properties JavaScript object that contains one or more descriptor objects. Each descriptor object describes a data property or an accessor property. */ @@ -177,7 +176,7 @@ interface ObjectConstructor { /** * Prevents the modification of attributes of existing properties, and prevents the addition of new properties. - * @param o Object on which to lock the attributes. + * @param o Object on which to lock the attributes. */ seal(o: T): T; @@ -189,25 +188,25 @@ interface ObjectConstructor { /** * Prevents the addition of new properties to an object. - * @param o Object to make non-extensible. + * @param o Object to make non-extensible. */ preventExtensions(o: T): T; /** * Returns true if existing property attributes cannot be modified in an object and new properties cannot be added to the object. - * @param o Object to test. + * @param o Object to test. */ isSealed(o: any): boolean; /** * Returns true if existing property attributes and values cannot be modified in an object, and new properties cannot be added to the object. - * @param o Object to test. + * @param o Object to test. */ isFrozen(o: any): boolean; /** * Returns a value that indicates whether new properties can be added to an object. - * @param o Object to test. + * @param o Object to test. */ isExtensible(o: any): boolean; @@ -242,7 +241,7 @@ interface Function { call(thisArg: any, ...argArray: any[]): any; /** - * For a given function, creates a bound function that has the same body as the original function. + * For a given function, creates a bound function that has the same body as the original function. * The this object of the bound function is associated with the specified object, and has the specified initial parameters. * @param thisArg An object to which the this keyword can refer inside the new function. * @param argArray A list of arguments to be passed to the new function. @@ -285,7 +284,7 @@ interface String { */ charAt(pos: number): string; - /** + /** * Returns the Unicode value of the character at the specified location. * @param index The zero-based index of the desired character. If there is no character at the specified index, NaN is returned. */ @@ -293,12 +292,12 @@ interface String { /** * Returns a string that contains the concatenation of two or more strings. - * @param strings The strings to append to the end of the string. + * @param strings The strings to append to the end of the string. */ concat(...strings: string[]): string; /** - * Returns the position of the first occurrence of a substring. + * Returns the position of the first occurrence of a substring. * @param searchString The substring to search for in the string * @param position The index at which to begin searching the String object. If omitted, search starts at the beginning of the string. */ @@ -317,82 +316,82 @@ interface String { */ localeCompare(that: string): number; - /** + /** * Matches a string with a regular expression, and returns an array containing the results of that search. * @param regexp A variable name or string literal containing the regular expression pattern and flags. */ match(regexp: string): RegExpMatchArray; - /** + /** * Matches a string with a regular expression, and returns an array containing the results of that search. - * @param regexp A regular expression object that contains the regular expression pattern and applicable flags. + * @param regexp A regular expression object that contains the regular expression pattern and applicable flags. */ match(regexp: RegExp): RegExpMatchArray; /** * Replaces text in a string, using a regular expression or search string. - * @param searchValue A String object or string literal that represents the regular expression - * @param replaceValue A String object or string literal containing the text to replace for every successful match of rgExp in stringObj. + * @param searchValue A string that represents the regular expression. + * @param replaceValue A string containing the text to replace for every successful match of searchValue in this string. */ replace(searchValue: string, replaceValue: string): string; /** * Replaces text in a string, using a regular expression or search string. - * @param searchValue A String object or string literal that represents the regular expression - * @param replaceValue A function that returns the replacement text. + * @param searchValue A string that represents the regular expression. + * @param replacer A function that returns the replacement text. */ - replace(searchValue: string, replaceValue: (substring: string, ...args: any[]) => string): string; + replace(searchValue: string, replacer: (substring: string, ...args: any[]) => string): string; /** * Replaces text in a string, using a regular expression or search string. - * @param searchValue A Regular Expression object containing the regular expression pattern and applicable flags - * @param replaceValue A String object or string literal containing the text to replace for every successful match of rgExp in stringObj. + * @param searchValue A Regular Expression object containing the regular expression pattern and applicable flags. + * @param replaceValue A string containing the text to replace for every successful match of searchValue in this string. */ replace(searchValue: RegExp, replaceValue: string): string; /** * Replaces text in a string, using a regular expression or search string. * @param searchValue A Regular Expression object containing the regular expression pattern and applicable flags - * @param replaceValue A function that returns the replacement text. + * @param replacer A function that returns the replacement text. */ - replace(searchValue: RegExp, replaceValue: (substring: string, ...args: any[]) => string): string; + replace(searchValue: RegExp, replacer: (substring: string, ...args: any[]) => string): string; /** * Finds the first substring match in a regular expression search. - * @param regexp The regular expression pattern and applicable flags. + * @param regexp The regular expression pattern and applicable flags. */ search(regexp: string): number; /** * Finds the first substring match in a regular expression search. - * @param regexp The regular expression pattern and applicable flags. + * @param regexp The regular expression pattern and applicable flags. */ search(regexp: RegExp): number; /** * Returns a section of a string. - * @param start The index to the beginning of the specified portion of stringObj. - * @param end The index to the end of the specified portion of stringObj. The substring includes the characters up to, but not including, the character indicated by end. + * @param start The index to the beginning of the specified portion of stringObj. + * @param end The index to the end of the specified portion of stringObj. The substring includes the characters up to, but not including, the character indicated by end. * If this value is not specified, the substring continues to the end of stringObj. */ slice(start?: number, end?: number): string; /** * Split a string into substrings using the specified separator and return them as an array. - * @param separator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned. + * @param separator A string that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned. * @param limit A value used to limit the number of elements returned in the array. */ split(separator: string, limit?: number): string[]; /** * Split a string into substrings using the specified separator and return them as an array. - * @param separator A Regular Express that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned. + * @param separator A Regular Express that identifies character or characters to use in separating the string. If omitted, a single-element array containing the entire string is returned. * @param limit A value used to limit the number of elements returned in the array. */ split(separator: RegExp, limit?: number): string[]; /** - * Returns the substring at the specified location within a String object. + * Returns the substring at the specified location within a String object. * @param start The zero-based index number indicating the beginning of the substring. * @param end Zero-based index number indicating the end of the substring. The substring includes the characters up to, but not including, the character indicated by end. * If end is omitted, the characters from start through the end of the original string are returned. @@ -438,8 +437,8 @@ interface StringConstructor { fromCharCode(...codes: number[]): string; } -/** - * Allows manipulation and formatting of text strings and determination and location of substrings within strings. +/** + * Allows manipulation and formatting of text strings and determination and location of substrings within strings. */ declare var String: StringConstructor; @@ -463,7 +462,7 @@ interface Number { */ toString(radix?: number): string; - /** + /** * Returns a string representing a number in fixed-point notation. * @param fractionDigits Number of digits after the decimal point. Must be in the range 0 - 20, inclusive. */ @@ -496,21 +495,21 @@ interface NumberConstructor { /** The closest number to zero that can be represented in JavaScript. Equal to approximately 5.00E-324. */ MIN_VALUE: number; - /** + /** * A value that is not a number. * In equality comparisons, NaN does not equal any value, including itself. To test whether a value is equivalent to NaN, use the isNaN function. */ NaN: number; - /** + /** * A value that is less than the largest negative number that can be represented in JavaScript. - * JavaScript displays NEGATIVE_INFINITY values as -infinity. + * JavaScript displays NEGATIVE_INFINITY values as -infinity. */ NEGATIVE_INFINITY: number; /** - * A value greater than the largest number that can be represented in JavaScript. - * JavaScript displays POSITIVE_INFINITY values as infinity. + * A value greater than the largest number that can be represented in JavaScript. + * JavaScript displays POSITIVE_INFINITY values as infinity. */ POSITIVE_INFINITY: number; } @@ -540,23 +539,23 @@ interface Math { /** The square root of 2. */ SQRT2: number; /** - * Returns the absolute value of a number (the value without regard to whether it is positive or negative). + * Returns the absolute value of a number (the value without regard to whether it is positive or negative). * For example, the absolute value of -5 is the same as the absolute value of 5. * @param x A numeric expression for which the absolute value is needed. */ abs(x: number): number; /** - * Returns the arc cosine (or inverse cosine) of a number. + * Returns the arc cosine (or inverse cosine) of a number. * @param x A numeric expression. */ acos(x: number): number; - /** - * Returns the arcsine of a number. + /** + * Returns the arcsine of a number. * @param x A numeric expression. */ asin(x: number): number; /** - * Returns the arctangent of a number. + * Returns the arctangent of a number. * @param x A numeric expression for which the arctangent is needed. */ atan(x: number): number; @@ -567,49 +566,49 @@ interface Math { */ atan2(y: number, x: number): number; /** - * Returns the smallest number greater than or equal to its numeric argument. + * Returns the smallest number greater than or equal to its numeric argument. * @param x A numeric expression. */ ceil(x: number): number; /** - * Returns the cosine of a number. + * Returns the cosine of a number. * @param x A numeric expression that contains an angle measured in radians. */ cos(x: number): number; /** - * Returns e (the base of natural logarithms) raised to a power. + * Returns e (the base of natural logarithms) raised to a power. * @param x A numeric expression representing the power of e. */ exp(x: number): number; /** - * Returns the greatest number less than or equal to its numeric argument. + * Returns the greatest number less than or equal to its numeric argument. * @param x A numeric expression. */ floor(x: number): number; /** - * Returns the natural logarithm (base e) of a number. + * Returns the natural logarithm (base e) of a number. * @param x A numeric expression. */ log(x: number): number; /** - * Returns the larger of a set of supplied numeric expressions. + * Returns the larger of a set of supplied numeric expressions. * @param values Numeric expressions to be evaluated. */ max(...values: number[]): number; /** - * Returns the smaller of a set of supplied numeric expressions. + * Returns the smaller of a set of supplied numeric expressions. * @param values Numeric expressions to be evaluated. */ min(...values: number[]): number; /** - * Returns the value of a base expression taken to a specified power. + * Returns the value of a base expression taken to a specified power. * @param x The base value of the expression. * @param y The exponent value of the expression. */ pow(x: number, y: number): number; /** Returns a pseudorandom number between 0 and 1. */ random(): number; - /** + /** * Returns a supplied numeric expression rounded to the nearest number. * @param x The value to be rounded to the nearest number. */ @@ -685,24 +684,24 @@ interface Date { getUTCMilliseconds(): number; /** Gets the difference in minutes between the time on the local computer and Universal Coordinated Time (UTC). */ getTimezoneOffset(): number; - /** + /** * Sets the date and time value in the Date object. - * @param time A numeric value representing the number of elapsed milliseconds since midnight, January 1, 1970 GMT. + * @param time A numeric value representing the number of elapsed milliseconds since midnight, January 1, 1970 GMT. */ setTime(time: number): number; /** - * Sets the milliseconds value in the Date object using local time. + * Sets the milliseconds value in the Date object using local time. * @param ms A numeric value equal to the millisecond value. */ setMilliseconds(ms: number): number; - /** + /** * Sets the milliseconds value in the Date object using Universal Coordinated Time (UTC). - * @param ms A numeric value equal to the millisecond value. + * @param ms A numeric value equal to the millisecond value. */ setUTCMilliseconds(ms: number): number; /** - * Sets the seconds value in the Date object using local time. + * Sets the seconds value in the Date object using local time. * @param sec A numeric value equal to the seconds value. * @param ms A numeric value equal to the milliseconds value. */ @@ -714,16 +713,16 @@ interface Date { */ setUTCSeconds(sec: number, ms?: number): number; /** - * Sets the minutes value in the Date object using local time. - * @param min A numeric value equal to the minutes value. - * @param sec A numeric value equal to the seconds value. + * Sets the minutes value in the Date object using local time. + * @param min A numeric value equal to the minutes value. + * @param sec A numeric value equal to the seconds value. * @param ms A numeric value equal to the milliseconds value. */ setMinutes(min: number, sec?: number, ms?: number): number; /** * Sets the minutes value in the Date object using Universal Coordinated Time (UTC). - * @param min A numeric value equal to the minutes value. - * @param sec A numeric value equal to the seconds value. + * @param min A numeric value equal to the minutes value. + * @param sec A numeric value equal to the seconds value. * @param ms A numeric value equal to the milliseconds value. */ setUTCMinutes(min: number, sec?: number, ms?: number): number; @@ -731,7 +730,7 @@ interface Date { * Sets the hour value in the Date object using local time. * @param hours A numeric value equal to the hours value. * @param min A numeric value equal to the minutes value. - * @param sec A numeric value equal to the seconds value. + * @param sec A numeric value equal to the seconds value. * @param ms A numeric value equal to the milliseconds value. */ setHours(hours: number, min?: number, sec?: number, ms?: number): number; @@ -739,23 +738,23 @@ interface Date { * Sets the hours value in the Date object using Universal Coordinated Time (UTC). * @param hours A numeric value equal to the hours value. * @param min A numeric value equal to the minutes value. - * @param sec A numeric value equal to the seconds value. + * @param sec A numeric value equal to the seconds value. * @param ms A numeric value equal to the milliseconds value. */ setUTCHours(hours: number, min?: number, sec?: number, ms?: number): number; /** - * Sets the numeric day-of-the-month value of the Date object using local time. + * Sets the numeric day-of-the-month value of the Date object using local time. * @param date A numeric value equal to the day of the month. */ setDate(date: number): number; - /** + /** * Sets the numeric day of the month in the Date object using Universal Coordinated Time (UTC). - * @param date A numeric value equal to the day of the month. + * @param date A numeric value equal to the day of the month. */ setUTCDate(date: number): number; - /** - * Sets the month value in the Date object using local time. - * @param month A numeric value equal to the month. The value for January is 0, and other month values follow consecutively. + /** + * Sets the month value in the Date object using local time. + * @param month A numeric value equal to the month. The value for January is 0, and other month values follow consecutively. * @param date A numeric value representing the day of the month. If this value is not supplied, the value from a call to the getDate method is used. */ setMonth(month: number, date?: number): number; @@ -800,7 +799,7 @@ interface DateConstructor { */ parse(s: string): number; /** - * Returns the number of milliseconds between midnight, January 1, 1970 Universal Coordinated Time (UTC) (or GMT) and the specified date. + * Returns the number of milliseconds between midnight, January 1, 1970 Universal Coordinated Time (UTC) (or GMT) and the specified date. * @param year The full year designation is required for cross-century date accuracy. If year is between 0 and 99 is used, then year is assumed to be 1900 + year. * @param month The month as an number between 0 and 11 (January to December). * @param date The date as an number between 1 and 31. @@ -826,13 +825,13 @@ interface RegExpExecArray extends Array { } interface RegExp { - /** + /** * Executes a search on a string using a regular expression pattern, and returns an array containing the results of that search. * @param string The String object or string literal on which to perform the search. */ exec(string: string): RegExpExecArray; - /** + /** * Returns a Boolean value that indicates whether or not a pattern exists in a searched string. * @param string String on which to perform the search. */ @@ -959,8 +958,8 @@ interface JSON { /** * Converts a JavaScript Object Notation (JSON) string into an object. * @param text A valid JSON string. - * @param reviver A function that transforms the results. This function is called for each member of the object. - * If a member contains nested objects, the nested objects are transformed before the parent object is. + * @param reviver A function that transforms the results. This function is called for each member of the object. + * If a member contains nested objects, the nested objects are transformed before the parent object is. */ parse(text: string, reviver?: (key: any, value: any) => any): any; /** @@ -986,14 +985,14 @@ interface JSON { * @param replacer A function that transforms the results. * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */ - stringify(value: any, replacer: (key: string, value: any) => any, space: any): string; + stringify(value: any, replacer: (key: string, value: any) => any, space: string | number): string; /** * Converts a JavaScript value to a JavaScript Object Notation (JSON) string. * @param value A JavaScript value, usually an object or array, to be converted. * @param replacer Array that transforms the results. * @param space Adds indentation, white space, and line break characters to the return-value JSON text to make it easier to read. */ - stringify(value: any, replacer: any[], space: any): string; + stringify(value: any, replacer: any[], space: string | number): string; } /** * An intrinsic object that provides functions to convert JavaScript values to and from the JavaScript Object Notation (JSON) format. @@ -1040,14 +1039,14 @@ interface Array { */ join(separator?: string): string; /** - * Reverses the elements in an Array. + * Reverses the elements in an Array. */ reverse(): T[]; /** * Removes the first element from an array and returns it. */ shift(): T; - /** + /** * Returns a section of an array. * @param start The beginning of the specified portion of the array. * @param end The end of the specified portion of the array. @@ -1110,21 +1109,21 @@ interface Array { /** * Performs the specified action for each element in an array. - * @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the callbackfn function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; /** * Calls a defined callback function on each element of an array, and returns an array that contains the results. - * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array. + * @param callbackfn A function that accepts up to three arguments. The map method calls the callbackfn function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; /** - * Returns the elements of an array that meet the condition specified in a callback function. - * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls the callbackfn function one time for each element in the array. * @param thisArg An object to which the this keyword can refer in the callbackfn function. If thisArg is omitted, undefined is used as the this value. */ filter(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[]; @@ -1142,15 +1141,15 @@ interface Array { */ reduce(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - /** + /** * Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. - * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array. * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value. */ reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue?: T): T; - /** + /** * Calls the specified callback function for all the elements in an array, in descending order. The return value of the callback function is the accumulated result, and is provided as an argument in the next call to the callback function. - * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls the callbackfn function one time for each element in the array. * @param initialValue If initialValue is specified, it is used as the initial value to start the accumulation. The first call to the callbackfn function provides this value as an argument instead of an array value. */ reduceRight(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; @@ -1165,7 +1164,7 @@ interface ArrayConstructor { (arrayLength?: number): any[]; (arrayLength: number): T[]; (...items: T[]): T[]; - isArray(arg: any): boolean; + isArray(arg: any): arg is Array; prototype: Array; } @@ -1184,3 +1183,2657 @@ declare type ClassDecorator = (target: TFunction) => declare type PropertyDecorator = (target: Object, propertyKey: string | symbol) => void; declare type MethodDecorator = (target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor) => TypedPropertyDescriptor | void; declare type ParameterDecorator = (target: Object, propertyKey: string | symbol, parameterIndex: number) => void; + +declare type PromiseConstructorLike = new (executor: (resolve: (value?: T | PromiseLike) => void, reject: (reason?: any) => void) => void) => PromiseLike; + +interface PromiseLike { + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => TResult | PromiseLike): PromiseLike; + then(onfulfilled?: (value: T) => TResult | PromiseLike, onrejected?: (reason: any) => void): PromiseLike; +} + +interface ArrayLike { + length: number; + [n: number]: T; +} + + +/** + * Represents a raw buffer of binary data, which is used to store data for the + * different typed arrays. ArrayBuffers cannot be read from or written to directly, + * but can be passed to a typed array or DataView Object to interpret the raw + * buffer as needed. + */ +interface ArrayBuffer { + /** + * Read-only. The length of the ArrayBuffer (in bytes). + */ + byteLength: number; + + /** + * Returns a section of an ArrayBuffer. + */ + slice(begin:number, end?:number): ArrayBuffer; +} + +interface ArrayBufferConstructor { + prototype: ArrayBuffer; + new (byteLength: number): ArrayBuffer; + isView(arg: any): arg is ArrayBufferView; +} +declare var ArrayBuffer: ArrayBufferConstructor; + +interface ArrayBufferView { + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; +} + +interface DataView { + buffer: ArrayBuffer; + byteLength: number; + byteOffset: number; + /** + * Gets the Float32 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getFloat32(byteOffset: number, littleEndian?: boolean): number; + + /** + * Gets the Float64 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getFloat64(byteOffset: number, littleEndian?: boolean): number; + + /** + * Gets the Int8 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getInt8(byteOffset: number): number; + + /** + * Gets the Int16 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getInt16(byteOffset: number, littleEndian?: boolean): number; + /** + * Gets the Int32 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getInt32(byteOffset: number, littleEndian?: boolean): number; + + /** + * Gets the Uint8 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getUint8(byteOffset: number): number; + + /** + * Gets the Uint16 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getUint16(byteOffset: number, littleEndian?: boolean): number; + + /** + * Gets the Uint32 value at the specified byte offset from the start of the view. There is + * no alignment constraint; multi-byte values may be fetched from any offset. + * @param byteOffset The place in the buffer at which the value should be retrieved. + */ + getUint32(byteOffset: number, littleEndian?: boolean): number; + + /** + * Stores an Float32 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setFloat32(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * Stores an Float64 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setFloat64(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * Stores an Int8 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + */ + setInt8(byteOffset: number, value: number): void; + + /** + * Stores an Int16 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setInt16(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * Stores an Int32 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setInt32(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * Stores an Uint8 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + */ + setUint8(byteOffset: number, value: number): void; + + /** + * Stores an Uint16 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setUint16(byteOffset: number, value: number, littleEndian?: boolean): void; + + /** + * Stores an Uint32 value at the specified byte offset from the start of the view. + * @param byteOffset The place in the buffer at which the value should be set. + * @param value The value to set. + * @param littleEndian If false or undefined, a big-endian value should be written, + * otherwise a little-endian value should be written. + */ + setUint32(byteOffset: number, value: number, littleEndian?: boolean): void; +} + +interface DataViewConstructor { + new (buffer: ArrayBuffer, byteOffset?: number, byteLength?: number): DataView; +} +declare var DataView: DataViewConstructor; + +/** + * A typed array of 8-bit integer values. The contents are initialized to 0. If the requested + * number of bytes could not be allocated an exception is raised. + */ +interface Int8Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Int8Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Int8Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Int8Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Int8Array) => boolean, thisArg?: any): Int8Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Int8Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Int8Array) => number, thisArg?: any): Int8Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int8Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int8Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int8Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Int8Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Int8Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Int8Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Int8Array; + + /** + * Gets a new Int8Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Int8Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} +interface Int8ArrayConstructor { + prototype: Int8Array; + new (length: number): Int8Array; + new (array: ArrayLike): Int8Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int8Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Int8Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Int8Array; + +} +declare var Int8Array: Int8ArrayConstructor; + +/** + * A typed array of 8-bit unsigned integer values. The contents are initialized to 0. If the + * requested number of bytes could not be allocated an exception is raised. + */ +interface Uint8Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Uint8Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Uint8Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Uint8Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Uint8Array) => boolean, thisArg?: any): Uint8Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Uint8Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Uint8Array) => number, thisArg?: any): Uint8Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Uint8Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Uint8Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Uint8Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Uint8Array; + + /** + * Gets a new Uint8Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Uint8Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Uint8ArrayConstructor { + prototype: Uint8Array; + new (length: number): Uint8Array; + new (array: ArrayLike): Uint8Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Uint8Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Uint8Array; + +} +declare var Uint8Array: Uint8ArrayConstructor; + +/** + * A typed array of 8-bit unsigned integer (clamped) values. The contents are initialized to 0. + * If the requested number of bytes could not be allocated an exception is raised. + */ +interface Uint8ClampedArray { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Uint8ClampedArray; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Uint8ClampedArray) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Uint8ClampedArray; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Uint8ClampedArray) => boolean, thisArg?: any): Uint8ClampedArray; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Uint8ClampedArray) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Uint8ClampedArray) => number, thisArg?: any): Uint8ClampedArray; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint8ClampedArray) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Uint8ClampedArray; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: Uint8ClampedArray, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Uint8ClampedArray; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Uint8ClampedArray) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Uint8ClampedArray; + + /** + * Gets a new Uint8ClampedArray view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Uint8ClampedArray; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Uint8ClampedArrayConstructor { + prototype: Uint8ClampedArray; + new (length: number): Uint8ClampedArray; + new (array: ArrayLike): Uint8ClampedArray; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint8ClampedArray; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Uint8ClampedArray; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Uint8ClampedArray; +} +declare var Uint8ClampedArray: Uint8ClampedArrayConstructor; + +/** + * A typed array of 16-bit signed integer values. The contents are initialized to 0. If the + * requested number of bytes could not be allocated an exception is raised. + */ +interface Int16Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Int16Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Int16Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Int16Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Int16Array) => boolean, thisArg?: any): Int16Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Int16Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Int16Array) => number, thisArg?: any): Int16Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int16Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int16Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int16Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Int16Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Int16Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Int16Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Int16Array; + + /** + * Gets a new Int16Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Int16Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Int16ArrayConstructor { + prototype: Int16Array; + new (length: number): Int16Array; + new (array: ArrayLike): Int16Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int16Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Int16Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Int16Array; + +} +declare var Int16Array: Int16ArrayConstructor; + +/** + * A typed array of 16-bit unsigned integer values. The contents are initialized to 0. If the + * requested number of bytes could not be allocated an exception is raised. + */ +interface Uint16Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Uint16Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Uint16Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Uint16Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Uint16Array) => boolean, thisArg?: any): Uint16Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Uint16Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Uint16Array) => number, thisArg?: any): Uint16Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint16Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint16Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint16Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Uint16Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Uint16Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Uint16Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Uint16Array; + + /** + * Gets a new Uint16Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Uint16Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Uint16ArrayConstructor { + prototype: Uint16Array; + new (length: number): Uint16Array; + new (array: ArrayLike): Uint16Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint16Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Uint16Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Uint16Array; + +} +declare var Uint16Array: Uint16ArrayConstructor; +/** + * A typed array of 32-bit signed integer values. The contents are initialized to 0. If the + * requested number of bytes could not be allocated an exception is raised. + */ +interface Int32Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Int32Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Int32Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Int32Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Int32Array) => boolean, thisArg?: any): Int32Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Int32Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Int32Array) => number, thisArg?: any): Int32Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int32Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Int32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Int32Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Int32Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Int32Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Int32Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Int32Array; + + /** + * Gets a new Int32Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Int32Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Int32ArrayConstructor { + prototype: Int32Array; + new (length: number): Int32Array; + new (array: ArrayLike): Int32Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Int32Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Int32Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Int32Array; +} +declare var Int32Array: Int32ArrayConstructor; + +/** + * A typed array of 32-bit unsigned integer values. The contents are initialized to 0. If the + * requested number of bytes could not be allocated an exception is raised. + */ +interface Uint32Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Uint32Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Uint32Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Uint32Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Uint32Array) => boolean, thisArg?: any): Uint32Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Uint32Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Uint32Array) => number, thisArg?: any): Uint32Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint32Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Uint32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Uint32Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Uint32Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Uint32Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Uint32Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Uint32Array; + + /** + * Gets a new Uint32Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Uint32Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Uint32ArrayConstructor { + prototype: Uint32Array; + new (length: number): Uint32Array; + new (array: ArrayLike): Uint32Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Uint32Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Uint32Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Uint32Array; +} +declare var Uint32Array: Uint32ArrayConstructor; + +/** + * A typed array of 32-bit float values. The contents are initialized to 0. If the requested number + * of bytes could not be allocated an exception is raised. + */ +interface Float32Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Float32Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Float32Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Float32Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Float32Array) => boolean, thisArg?: any): Float32Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Float32Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Float32Array) => number, thisArg?: any): Float32Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Float32Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float32Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Float32Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Float32Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Float32Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Float32Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Float32Array; + + /** + * Gets a new Float32Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Float32Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Float32ArrayConstructor { + prototype: Float32Array; + new (length: number): Float32Array; + new (array: ArrayLike): Float32Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float32Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Float32Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Float32Array; + +} +declare var Float32Array: Float32ArrayConstructor; + +/** + * A typed array of 64-bit float values. The contents are initialized to 0. If the requested + * number of bytes could not be allocated an exception is raised. + */ +interface Float64Array { + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * The ArrayBuffer instance referenced by the array. + */ + buffer: ArrayBuffer; + + /** + * The length in bytes of the array. + */ + byteLength: number; + + /** + * The offset in bytes of the array. + */ + byteOffset: number; + + /** + * Returns the this object after copying a section of the array identified by start and end + * to the same array starting at position target + * @param target If target is negative, it is treated as length+target where length is the + * length of the array. + * @param start If start is negative, it is treated as length+start. If end is negative, it + * is treated as length+end. + * @param end If not specified, length of the this object is used as its default value. + */ + copyWithin(target: number, start: number, end?: number): Float64Array; + + /** + * Determines whether all the members of an array satisfy the specified test. + * @param callbackfn A function that accepts up to three arguments. The every method calls + * the callbackfn function for each element in array1 until the callbackfn returns false, + * or until the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + every(callbackfn: (value: number, index: number, array: Float64Array) => boolean, thisArg?: any): boolean; + + /** + * Returns the this object after filling the section identified by start and end with value + * @param value value to fill array section with + * @param start index to start filling the array at. If start is negative, it is treated as + * length+start where length is the length of the array. + * @param end index to stop filling the array at. If end is negative, it is treated as + * length+end. + */ + fill(value: number, start?: number, end?: number): Float64Array; + + /** + * Returns the elements of an array that meet the condition specified in a callback function. + * @param callbackfn A function that accepts up to three arguments. The filter method calls + * the callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + filter(callbackfn: (value: number, index: number, array: Float64Array) => boolean, thisArg?: any): Float64Array; + + /** + * Returns the value of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + find(predicate: (value: number, index: number, obj: Array) => boolean, thisArg?: any): number; + + /** + * Returns the index of the first element in the array where predicate is true, and undefined + * otherwise. + * @param predicate find calls predicate once for each element of the array, in ascending + * order, until it finds one where predicate returns true. If such an element is found, find + * immediately returns that element value. Otherwise, find returns undefined. + * @param thisArg If provided, it will be used as the this value for each invocation of + * predicate. If it is not provided, undefined is used instead. + */ + findIndex(predicate: (value: number) => boolean, thisArg?: any): number; + + /** + * Performs the specified action for each element in an array. + * @param callbackfn A function that accepts up to three arguments. forEach calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + forEach(callbackfn: (value: number, index: number, array: Float64Array) => void, thisArg?: any): void; + + /** + * Returns the index of the first occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + indexOf(searchElement: number, fromIndex?: number): number; + + /** + * Adds all the elements of an array separated by the specified separator string. + * @param separator A string used to separate one element of an array from the next in the + * resulting String. If omitted, the array elements are separated with a comma. + */ + join(separator?: string): string; + + /** + * Returns the index of the last occurrence of a value in an array. + * @param searchElement The value to locate in the array. + * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the + * search starts at index 0. + */ + lastIndexOf(searchElement: number, fromIndex?: number): number; + + /** + * The length of the array. + */ + length: number; + + /** + * Calls a defined callback function on each element of an array, and returns an array that + * contains the results. + * @param callbackfn A function that accepts up to three arguments. The map method calls the + * callbackfn function one time for each element in the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + map(callbackfn: (value: number, index: number, array: Float64Array) => number, thisArg?: any): Float64Array; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array. The return value of + * the callback function is the accumulated result, and is provided as an argument in the next + * call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduce method calls the + * callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduce(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Float64Array) => U, initialValue: U): U; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an + * argument instead of an array value. + */ + reduceRight(callbackfn: (previousValue: number, currentValue: number, currentIndex: number, array: Float64Array) => number, initialValue?: number): number; + + /** + * Calls the specified callback function for all the elements in an array, in descending order. + * The return value of the callback function is the accumulated result, and is provided as an + * argument in the next call to the callback function. + * @param callbackfn A function that accepts up to four arguments. The reduceRight method calls + * the callbackfn function one time for each element in the array. + * @param initialValue If initialValue is specified, it is used as the initial value to start + * the accumulation. The first call to the callbackfn function provides this value as an argument + * instead of an array value. + */ + reduceRight(callbackfn: (previousValue: U, currentValue: number, currentIndex: number, array: Float64Array) => U, initialValue: U): U; + + /** + * Reverses the elements in an Array. + */ + reverse(): Float64Array; + + /** + * Sets a value or an array of values. + * @param index The index of the location to set. + * @param value The value to set. + */ + set(index: number, value: number): void; + + /** + * Sets a value or an array of values. + * @param array A typed or untyped array of values to set. + * @param offset The index in the current array at which the values are to be written. + */ + set(array: ArrayLike, offset?: number): void; + + /** + * Returns a section of an array. + * @param start The beginning of the specified portion of the array. + * @param end The end of the specified portion of the array. + */ + slice(start?: number, end?: number): Float64Array; + + /** + * Determines whether the specified callback function returns true for any element of an array. + * @param callbackfn A function that accepts up to three arguments. The some method calls the + * callbackfn function for each element in array1 until the callbackfn returns true, or until + * the end of the array. + * @param thisArg An object to which the this keyword can refer in the callbackfn function. + * If thisArg is omitted, undefined is used as the this value. + */ + some(callbackfn: (value: number, index: number, array: Float64Array) => boolean, thisArg?: any): boolean; + + /** + * Sorts an array. + * @param compareFn The name of the function used to determine the order of the elements. If + * omitted, the elements are sorted in ascending, ASCII character order. + */ + sort(compareFn?: (a: number, b: number) => number): Float64Array; + + /** + * Gets a new Float64Array view of the ArrayBuffer store for this array, referencing the elements + * at begin, inclusive, up to end, exclusive. + * @param begin The index of the beginning of the array. + * @param end The index of the end of the array. + */ + subarray(begin: number, end?: number): Float64Array; + + /** + * Converts a number to a string by using the current locale. + */ + toLocaleString(): string; + + /** + * Returns a string representation of an array. + */ + toString(): string; + + [index: number]: number; +} + +interface Float64ArrayConstructor { + prototype: Float64Array; + new (length: number): Float64Array; + new (array: ArrayLike): Float64Array; + new (buffer: ArrayBuffer, byteOffset?: number, length?: number): Float64Array; + + /** + * The size in bytes of each element in the array. + */ + BYTES_PER_ELEMENT: number; + + /** + * Returns a new array from a set of elements. + * @param items A set of elements to include in the new array object. + */ + of(...items: number[]): Float64Array; + + /** + * Creates an array from an array-like or iterable object. + * @param arrayLike An array-like or iterable object to convert to an array. + * @param mapfn A mapping function to call on every element of the array. + * @param thisArg Value of 'this' used to invoke the mapfn. + */ + from(arrayLike: ArrayLike, mapfn?: (v: number, k: number) => number, thisArg?: any): Float64Array; +} +declare var Float64Array: Float64ArrayConstructor; diff --git a/extensions/node-debug/node-debug.azure.json b/extensions/node-debug/node-debug.azure.json index b71b93ca684..b5e964fb7c1 100644 --- a/extensions/node-debug/node-debug.azure.json +++ b/extensions/node-debug/node-debug.azure.json @@ -1,6 +1,6 @@ { "account": "monacobuild", "container": "debuggers", - "zip": "6366d8f/node-debug.zip", + "zip": "864e0bd/node-debug.zip", "output": "" } diff --git a/extensions/node.d.ts b/extensions/node.d.ts index 89f52915a65..df0802add52 100644 --- a/extensions/node.d.ts +++ b/extensions/node.d.ts @@ -1,14 +1,29 @@ -// Type definitions for Node.js v0.11.13 +// Type definitions for Node.js v4.x // Project: http://nodejs.org/ -// Definitions by: Microsoft TypeScript , DefinitelyTyped -// Definitions: https://github.com/borisyankov/DefinitelyTyped +// Definitions by: Microsoft TypeScript , DefinitelyTyped +// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped + +/// /************************************************ * * -* Node.js v0.11.13 API * +* Node.js v4.x API * * * ************************************************/ +interface Error { + stack?: string; +} + + +// compat for TypeScript 1.8 +// if you use with --target es3 or --target es5 and use below definitions, +// use the lib.es6.d.ts that is bundled with TypeScript 1.8. +interface MapConstructor {} +interface WeakMapConstructor {} +interface SetConstructor {} +interface WeakSetConstructor {} + /************************************************ * * * GLOBAL * @@ -20,55 +35,44 @@ declare var global: any; declare var __filename: string; declare var __dirname: string; -// declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; -// declare function clearTimeout(timeoutId: NodeJS.Timer): void; -// declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; -// declare function clearInterval(intervalId: NodeJS.Timer): void; -// declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; -// declare function clearImmediate(immediateId: any): void; +declare function setTimeout(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearTimeout(timeoutId: NodeJS.Timer): void; +declare function setInterval(callback: (...args: any[]) => void, ms: number, ...args: any[]): NodeJS.Timer; +declare function clearInterval(intervalId: NodeJS.Timer): void; +declare function setImmediate(callback: (...args: any[]) => void, ...args: any[]): any; +declare function clearImmediate(immediateId: any): void; -//declare var require: { -// (id: string): any; -// resolve(id:string): string; -// cache: any; -// extensions: any; -// main: any; -//}; +interface NodeRequireFunction { + (id: string): any; +} -// declare var define: { -// (moduleName: string, dependencies: string[], callback: (...args: any[]) => any): any; -// (moduleName: string, dependencies: string[], definition: any): any; -// (moduleName: string, callback: (...args: any[]) => any): any; -// (moduleName: string, definition: any): any; -// (dependencies: string[], callback: (...args: any[]) => any): any; -// (dependencies: string[], definition: any): any; -// }; +interface NodeRequire extends NodeRequireFunction { + resolve(id:string): string; + cache: any; + extensions: any; + main: any; +} -// declare var require: { -// toUrl(path: string): string; -// (moduleName: string): any; -// (dependencies: string[], callback: (...args: any[]) => any, errorback?: (err: any) => void): any; -// config(data: any): any; -// onError: Function; -// __$__nodeRequire(moduleName: string): T; -// }; +declare var require: NodeRequire; -//declare var module: { -// exports: any; -// require(id: string): any; -// id: string; -// filename: string; -// loaded: boolean; -// parent: any; -// children: any[]; -//}; +interface NodeModule { + exports: any; + require: NodeRequireFunction; + id: string; + filename: string; + loaded: boolean; + parent: any; + children: any[]; +} + +declare var module: NodeModule; // Same as module.exports declare var exports: any; declare var SlowBuffer: { new (str: string, encoding?: string): Buffer; new (size: number): Buffer; - // new (size: Uint8Array): Buffer; + new (size: Uint8Array): Buffer; new (array: any[]): Buffer; prototype: Buffer; isBuffer(obj: any): boolean; @@ -78,16 +82,84 @@ declare var SlowBuffer: { // Buffer class +type BufferEncoding = "ascii" | "utf8" | "utf16le" | "ucs2" | "binary" | "hex"; interface Buffer extends NodeBuffer {} + +/** + * Raw data is stored in instances of the Buffer class. + * A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized. + * Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + */ declare var Buffer: { + /** + * Allocates a new buffer containing the given {str}. + * + * @param str String to store in buffer. + * @param encoding encoding to use, optional. Default is 'utf8' + */ new (str: string, encoding?: string): Buffer; + /** + * Allocates a new buffer of {size} octets. + * + * @param size count of octets to allocate. + */ new (size: number): Buffer; - // new (size: Uint8Array): Buffer; + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ + new (array: Uint8Array): Buffer; + /** + * Allocates a new buffer containing the given {array} of octets. + * + * @param array The octets to store. + */ new (array: any[]): Buffer; + /** + * Copies the passed {buffer} data onto a new {Buffer} instance. + * + * @param buffer The buffer to copy. + */ + new (buffer: Buffer): Buffer; prototype: Buffer; - isBuffer(obj: any): boolean; + /** + * Returns true if {obj} is a Buffer + * + * @param obj object to test. + */ + isBuffer(obj: any): obj is Buffer; + /** + * Returns true if {encoding} is a valid encoding argument. + * Valid string encodings in Node 0.12: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'binary'(deprecated)|'hex' + * + * @param encoding string to test. + */ + isEncoding(encoding: string): boolean; + /** + * Gives the actual byte length of a string. encoding defaults to 'utf8'. + * This is not the same as String.prototype.length since that returns the number of characters in a string. + * + * @param string string to test. + * @param encoding encoding used to evaluate (defaults to 'utf8') + */ byteLength(string: string, encoding?: string): number; + /** + * Returns a buffer which is the result of concatenating all the buffers in the list together. + * + * If the list has no items, or if the totalLength is 0, then it returns a zero-length buffer. + * If the list has exactly one item, then the first item of the list is returned. + * If the list has more than one item, then a new Buffer is created. + * + * @param list An array of Buffer objects to concatenate + * @param totalLength Total length of the buffers when concatenated. + * If totalLength is not provided, it is read from the buffers in the list. However, this adds an additional loop to the function, so it is faster to provide the length explicitly. + */ concat(list: Buffer[], totalLength?: number): Buffer; + /** + * The same as buf1.compare(buf2). + */ + compare(buf1: Buffer, buf2: Buffer): number; }; /************************************************ @@ -95,28 +167,31 @@ declare var Buffer: { * GLOBAL INTERFACES * * * ************************************************/ -declare module NodeJS { +declare namespace NodeJS { export interface ErrnoException extends Error { - errno?: any; + errno?: number; code?: string; path?: string; syscall?: string; + stack?: string; } export interface EventEmitter { - addListener(event: string, listener: Function): EventEmitter; - on(event: string, listener: Function): EventEmitter; - once(event: string, listener: Function): EventEmitter; - removeListener(event: string, listener: Function): EventEmitter; - removeAllListeners(event?: string): EventEmitter; - setMaxListeners(n: number): void; + addListener(event: string, listener: Function): this; + on(event: string, listener: Function): this; + once(event: string, listener: Function): this; + removeListener(event: string, listener: Function): this; + removeAllListeners(event?: string): this; + setMaxListeners(n: number): this; + getMaxListeners(): number; listeners(event: string): Function[]; emit(event: string, ...args: any[]): boolean; + listenerCount(type: string): number; } export interface ReadableStream extends EventEmitter { readable: boolean; - read(size?: number): any; + read(size?: number): string|Buffer; setEncoding(encoding: string): void; pause(): void; resume(): void; @@ -129,8 +204,7 @@ declare module NodeJS { export interface WritableStream extends EventEmitter { writable: boolean; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; + write(buffer: Buffer|string, cb?: Function): boolean; write(str: string, encoding?: string, cb?: Function): boolean; end(): void; end(buffer: Buffer, cb?: Function): void; @@ -140,11 +214,35 @@ declare module NodeJS { export interface ReadWriteStream extends ReadableStream, WritableStream {} + export interface Events extends EventEmitter { } + + export interface Domain extends Events { + run(fn: Function): void; + add(emitter: Events): void; + remove(emitter: Events): void; + bind(cb: (err: Error, data: any) => any): any; + intercept(cb: (data: any) => any): any; + dispose(): void; + + addListener(event: string, listener: Function): this; + on(event: string, listener: Function): this; + once(event: string, listener: Function): this; + removeListener(event: string, listener: Function): this; + removeAllListeners(event?: string): this; + } + + export interface MemoryUsage { + rss: number; + heapTotal: number; + heapUsed: number; + } + export interface Process extends EventEmitter { stdout: WritableStream; stderr: WritableStream; stdin: ReadableStream; argv: string[]; + execArgv: string[]; execPath: string; abort(): void; chdir(directory: string): void; @@ -193,19 +291,88 @@ declare module NodeJS { visibility: string; }; }; - kill(pid: number, signal?: string): void; + kill(pid:number, signal?: string|number): void; pid: number; title: string; arch: string; platform: string; - memoryUsage(): { rss: number; heapTotal: number; heapUsed: number; }; + memoryUsage(): MemoryUsage; nextTick(callback: Function): void; umask(mask?: number): number; uptime(): number; hrtime(time?:number[]): number[]; + domain: Domain; // Worker send?(message: any, sendHandle?: any): void; + disconnect(): void; + connected: boolean; + } + + export interface Global { + Array: typeof Array; + ArrayBuffer: typeof ArrayBuffer; + Boolean: typeof Boolean; + Buffer: typeof Buffer; + DataView: typeof DataView; + Date: typeof Date; + Error: typeof Error; + EvalError: typeof EvalError; + Float32Array: typeof Float32Array; + Float64Array: typeof Float64Array; + Function: typeof Function; + GLOBAL: Global; + Infinity: typeof Infinity; + Int16Array: typeof Int16Array; + Int32Array: typeof Int32Array; + Int8Array: typeof Int8Array; + // Intl: typeof Intl; + JSON: typeof JSON; + Map: MapConstructor; + Math: typeof Math; + NaN: typeof NaN; + Number: typeof Number; + Object: typeof Object; + Promise: Function; + RangeError: typeof RangeError; + ReferenceError: typeof ReferenceError; + RegExp: typeof RegExp; + Set: SetConstructor; + String: typeof String; + Symbol: Function; + SyntaxError: typeof SyntaxError; + TypeError: typeof TypeError; + URIError: typeof URIError; + Uint16Array: typeof Uint16Array; + Uint32Array: typeof Uint32Array; + Uint8Array: typeof Uint8Array; + Uint8ClampedArray: Function; + WeakMap: WeakMapConstructor; + WeakSet: WeakSetConstructor; + clearImmediate: (immediateId: any) => void; + clearInterval: (intervalId: NodeJS.Timer) => void; + clearTimeout: (timeoutId: NodeJS.Timer) => void; + console: typeof console; + decodeURI: typeof decodeURI; + decodeURIComponent: typeof decodeURIComponent; + encodeURI: typeof encodeURI; + encodeURIComponent: typeof encodeURIComponent; + escape: (str: string) => string; + eval: typeof eval; + global: Global; + isFinite: typeof isFinite; + isNaN: typeof isNaN; + parseFloat: typeof parseFloat; + parseInt: typeof parseInt; + process: Process; + root: Global; + setImmediate: (callback: (...args: any[]) => void, ...args: any[]) => any; + setInterval: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; + setTimeout: (callback: (...args: any[]) => void, ms: number, ...args: any[]) => NodeJS.Timer; + undefined: typeof undefined; + unescape: (str: string) => string; + gc: () => void; + v8debug?: any; } export interface Timer { @@ -223,9 +390,19 @@ interface NodeBuffer { toString(encoding?: string, start?: number, end?: number): string; toJSON(): any; length: number; + equals(otherBuffer: Buffer): boolean; + compare(otherBuffer: Buffer): number; copy(targetBuffer: Buffer, targetStart?: number, sourceStart?: number, sourceEnd?: number): number; slice(start?: number, end?: number): Buffer; - readUInt8(offset: number, noAsset?: boolean): number; + writeUIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeUIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntLE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + writeIntBE(value: number, offset: number, byteLength: number, noAssert?: boolean): number; + readUIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readUIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntLE(offset: number, byteLength: number, noAssert?: boolean): number; + readIntBE(offset: number, byteLength: number, noAssert?: boolean): number; + readUInt8(offset: number, noAssert?: boolean): number; readUInt16LE(offset: number, noAssert?: boolean): number; readUInt16BE(offset: number, noAssert?: boolean): number; readUInt32LE(offset: number, noAssert?: boolean): number; @@ -239,21 +416,22 @@ interface NodeBuffer { readFloatBE(offset: number, noAssert?: boolean): number; readDoubleLE(offset: number, noAssert?: boolean): number; readDoubleBE(offset: number, noAssert?: boolean): number; - writeUInt8(value: number, offset: number, noAssert?: boolean): void; - writeUInt16LE(value: number, offset: number, noAssert?: boolean): void; - writeUInt16BE(value: number, offset: number, noAssert?: boolean): void; - writeUInt32LE(value: number, offset: number, noAssert?: boolean): void; - writeUInt32BE(value: number, offset: number, noAssert?: boolean): void; - writeInt8(value: number, offset: number, noAssert?: boolean): void; - writeInt16LE(value: number, offset: number, noAssert?: boolean): void; - writeInt16BE(value: number, offset: number, noAssert?: boolean): void; - writeInt32LE(value: number, offset: number, noAssert?: boolean): void; - writeInt32BE(value: number, offset: number, noAssert?: boolean): void; - writeFloatLE(value: number, offset: number, noAssert?: boolean): void; - writeFloatBE(value: number, offset: number, noAssert?: boolean): void; - writeDoubleLE(value: number, offset: number, noAssert?: boolean): void; - writeDoubleBE(value: number, offset: number, noAssert?: boolean): void; - fill(value: any, offset?: number, end?: number): void; + writeUInt8(value: number, offset: number, noAssert?: boolean): number; + writeUInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeUInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeInt8(value: number, offset: number, noAssert?: boolean): number; + writeInt16LE(value: number, offset: number, noAssert?: boolean): number; + writeInt16BE(value: number, offset: number, noAssert?: boolean): number; + writeInt32LE(value: number, offset: number, noAssert?: boolean): number; + writeInt32BE(value: number, offset: number, noAssert?: boolean): number; + writeFloatLE(value: number, offset: number, noAssert?: boolean): number; + writeFloatBE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleLE(value: number, offset: number, noAssert?: boolean): number; + writeDoubleBE(value: number, offset: number, noAssert?: boolean): number; + fill(value: any, offset?: number, end?: number): Buffer; + indexOf(value: string | number | Buffer, byteOffset?: number): number; } /************************************************ @@ -263,52 +441,76 @@ interface NodeBuffer { ************************************************/ declare module "buffer" { export var INSPECT_MAX_BYTES: number; + var BuffType: typeof Buffer; + var SlowBuffType: typeof SlowBuffer; + export { BuffType as Buffer, SlowBuffType as SlowBuffer }; } declare module "querystring" { - export function stringify(obj: any, sep?: string, eq?: string): string; - export function parse(str: string, sep?: string, eq?: string, options?: { maxKeys?: number; }): any; - export function escape(): any; - export function unescape(): any; + export interface StringifyOptions { + encodeURIComponent?: Function; + } + + export interface ParseOptions { + maxKeys?: number; + decodeURIComponent?: Function; + } + + export function stringify(obj: T, sep?: string, eq?: string, options?: StringifyOptions): string; + export function parse(str: string, sep?: string, eq?: string, options?: ParseOptions): any; + export function parse(str: string, sep?: string, eq?: string, options?: ParseOptions): T; + export function escape(str: string): string; + export function unescape(str: string): string; } declare module "events" { export class EventEmitter implements NodeJS.EventEmitter { - static listenerCount(emitter: EventEmitter, event: string): number; + static EventEmitter: EventEmitter; + static listenerCount(emitter: EventEmitter, event: string): number; // deprecated + static defaultMaxListeners: number; - addListener(event: string, listener: Function): EventEmitter; - on(event: string, listener: Function): EventEmitter; - once(event: string, listener: Function): EventEmitter; - removeListener(event: string, listener: Function): EventEmitter; - removeAllListeners(event?: string): EventEmitter; - setMaxListeners(n: number): void; + addListener(event: string, listener: Function): this; + on(event: string, listener: Function): this; + once(event: string, listener: Function): this; + removeListener(event: string, listener: Function): this; + removeAllListeners(event?: string): this; + setMaxListeners(n: number): this; + getMaxListeners(): number; listeners(event: string): Function[]; emit(event: string, ...args: any[]): boolean; - } + listenerCount(type: string): number; + } } declare module "http" { - import events = require("events"); - import net = require("net"); - import stream = require("stream"); + import * as events from "events"; + import * as net from "net"; + import * as stream from "stream"; - export interface Server extends events.EventEmitter { - listen(port: number, hostname?: string, backlog?: number, callback?: Function): Server; - listen(path: string, callback?: Function): Server; - listen(handle: any, listeningListener?: Function): Server; - close(cb?: any): Server; - address(): { port: number; family: string; address: string; }; - maxHeadersCount: number; + export interface RequestOptions { + protocol?: string; + host?: string; + hostname?: string; + family?: number; + port?: number; + localAddress?: string; + socketPath?: string; + method?: string; + path?: string; + headers?: { [key: string]: any }; + auth?: string; + agent?: Agent|boolean; } - export interface ServerRequest extends events.EventEmitter, stream.Readable { - method: string; - url: string; - headers: any; - trailers: string; - httpVersion: string; - setEncoding(encoding?: string): void; - pause(): void; - resume(): void; + + export interface Server extends events.EventEmitter, net.Server { + setTimeout(msecs: number, callback: Function): void; + maxHeadersCount: number; + timeout: number; + } + /** + * @deprecated Use IncomingMessage + */ + export interface ServerRequest extends IncomingMessage { connection: net.Socket; } export interface ServerResponse extends events.EventEmitter, stream.Writable { @@ -323,7 +525,9 @@ declare module "http" { writeHead(statusCode: number, reasonPhrase?: string, headers?: any): void; writeHead(statusCode: number, headers?: any): void; statusCode: number; - setHeader(name: string, value: string): void; + statusMessage: string; + headersSent: boolean; + setHeader(name: string, value: string | string[]): void; sendDate: boolean; getHeader(name: string): string; removeHeader(name: string): void; @@ -351,6 +555,11 @@ declare module "http" { setNoDelay(noDelay?: boolean): void; setSocketKeepAlive(enable?: boolean, initialDelay?: number): void; + setHeader(name: string, value: string | string[]): void; + getHeader(name: string): string; + removeHeader(name: string): void; + addTrailers(headers: any): void; + // Extended base methods end(): void; end(buffer: Buffer, cb?: Function): void; @@ -358,31 +567,88 @@ declare module "http" { end(str: string, encoding?: string, cb?: Function): void; end(data?: any, encoding?: string): void; } - export interface ClientResponse extends events.EventEmitter, stream.Readable { - statusCode: number; + export interface IncomingMessage extends events.EventEmitter, stream.Readable { httpVersion: string; headers: any; + rawHeaders: string[]; trailers: any; - setEncoding(encoding?: string): void; - pause(): void; - resume(): void; + rawTrailers: any; + setTimeout(msecs: number, callback: Function): NodeJS.Timer; + /** + * Only valid for request obtained from http.Server. + */ + method?: string; + /** + * Only valid for request obtained from http.Server. + */ + url?: string; + /** + * Only valid for response obtained from http.ClientRequest. + */ + statusCode?: number; + /** + * Only valid for response obtained from http.ClientRequest. + */ + statusMessage?: string; + socket: net.Socket; } - export interface Agent { maxSockets: number; sockets: any; requests: any; } + /** + * @deprecated Use IncomingMessage + */ + export interface ClientResponse extends IncomingMessage { } + + export interface AgentOptions { + /** + * Keep sockets around in a pool to be used by other requests in the future. Default = false + */ + keepAlive?: boolean; + /** + * When using HTTP KeepAlive, how often to send TCP KeepAlive packets over sockets being kept alive. Default = 1000. + * Only relevant if keepAlive is set to true. + */ + keepAliveMsecs?: number; + /** + * Maximum number of sockets to allow per host. Default for Node 0.10 is 5, default for Node 0.12 is Infinity + */ + maxSockets?: number; + /** + * Maximum number of sockets to leave open in a free state. Only relevant if keepAlive is set to true. Default = 256. + */ + maxFreeSockets?: number; + } + + export class Agent { + maxSockets: number; + sockets: any; + requests: any; + + constructor(opts?: AgentOptions); + + /** + * Destroy any sockets that are currently in use by the agent. + * It is usually not necessary to do this. However, if you are using an agent with KeepAlive enabled, + * then it is best to explicitly shut down the agent when you know that it will no longer be used. Otherwise, + * sockets may hang open for quite a long time before the server terminates them. + */ + destroy(): void; + } + + export var METHODS: string[]; export var STATUS_CODES: { [errorCode: number]: string; [errorCode: string]: string; }; - export function createServer(requestListener?: (request: ServerRequest, response: ServerResponse) =>void ): Server; + export function createServer(requestListener?: (request: IncomingMessage, response: ServerResponse) =>void ): Server; export function createClient(port?: number, host?: string): any; - export function request(options: any, callback?: Function): ClientRequest; - export function get(options: any, callback?: Function): ClientRequest; + export function request(options: RequestOptions, callback?: (res: IncomingMessage) => void): ClientRequest; + export function get(options: any, callback?: (res: IncomingMessage) => void): ClientRequest; export var globalAgent: Agent; } declare module "cluster" { - import child = require("child_process"); - import events = require("events"); + import * as child from "child_process"; + import * as events from "events"; export interface ClusterSettings { exec?: string; @@ -390,6 +656,12 @@ declare module "cluster" { silent?: boolean; } + export interface Address { + address: string; + port: number; + addressType: string; + } + export class Worker extends events.EventEmitter { id: string; process: child.ChildProcess; @@ -411,6 +683,13 @@ declare module "cluster" { // Event emitter export function addListener(event: string, listener: Function): void; + export function on(event: "disconnect", listener: (worker: Worker) => void): void; + export function on(event: "exit", listener: (worker: Worker, code: number, signal: string) => void): void; + export function on(event: "fork", listener: (worker: Worker) => void): void; + export function on(event: "listening", listener: (worker: Worker, address: any) => void): void; + export function on(event: "message", listener: (worker: Worker, message: any) => void): void; + export function on(event: "online", listener: (worker: Worker) => void): void; + export function on(event: "setup", listener: (settings: any) => void): void; export function on(event: string, listener: Function): any; export function once(event: string, listener: Function): void; export function removeListener(event: string, listener: Function): void; @@ -421,7 +700,7 @@ declare module "cluster" { } declare module "zlib" { - import stream = require("stream"); + import * as stream from "stream"; export interface ZlibOptions { chunkSize?: number; windowBits?: number; level?: number; memLevel?: number; strategy?: number; dictionary?: any; } export interface Gzip extends stream.Transform { } @@ -441,12 +720,19 @@ declare module "zlib" { export function createUnzip(options?: ZlibOptions): Unzip; export function deflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function deflateSync(buf: Buffer, options?: ZlibOptions): any; export function deflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function deflateRawSync(buf: Buffer, options?: ZlibOptions): any; export function gzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gzipSync(buf: Buffer, options?: ZlibOptions): any; export function gunzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function gunzipSync(buf: Buffer, options?: ZlibOptions): any; export function inflate(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflateSync(buf: Buffer, options?: ZlibOptions): any; export function inflateRaw(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function inflateRawSync(buf: Buffer, options?: ZlibOptions): any; export function unzip(buf: Buffer, callback: (error: Error, result: any) =>void ): void; + export function unzipSync(buf: Buffer, options?: ZlibOptions): any; // Constants export var Z_NO_FLUSH: number; @@ -483,7 +769,29 @@ declare module "zlib" { } declare module "os" { + export interface CpuInfo { + model: string; + speed: number; + times: { + user: number; + nice: number; + sys: number; + idle: number; + irq: number; + }; + } + + export interface NetworkInterfaceInfo { + address: string; + netmask: string; + family: string; + mac: string; + internal: boolean; + } + export function tmpdir(): string; + export function homedir(): string; + export function endianness(): string; export function hostname(): string; export function type(): string; export function platform(): string; @@ -493,15 +801,15 @@ declare module "os" { export function loadavg(): number[]; export function totalmem(): number; export function freemem(): number; - export function cpus(): { model: string; speed: number; times: { user: number; nice: number; sys: number; idle: number; irq: number; }; }[]; - export function networkInterfaces(): any; + export function cpus(): CpuInfo[]; + export function networkInterfaces(): {[index: string]: NetworkInterfaceInfo[]}; export var EOL: string; } declare module "https" { - import tls = require("tls"); - import events = require("events"); - import http = require("http"); + import * as tls from "tls"; + import * as events from "events"; + import * as http from "http"; export interface ServerOptions { pfx?: any; @@ -518,15 +826,7 @@ declare module "https" { SNICallback?: (servername: string) => any; } - export interface RequestOptions { - host?: string; - hostname?: string; - port?: number; - path?: string; - method?: string; - headers?: any; - auth?: string; - agent?: any; + export interface RequestOptions extends http.RequestOptions{ pfx?: any; key?: any; passphrase?: string; @@ -534,6 +834,7 @@ declare module "https" { ca?: any; ciphers?: string; rejectUnauthorized?: boolean; + secureProtocol?: string; } export interface Agent { @@ -546,8 +847,8 @@ declare module "https" { }; export interface Server extends tls.Server { } export function createServer(options: ServerOptions, requestListener?: Function): Server; - export function request(options: RequestOptions, callback?: (res: events.EventEmitter) =>void ): http.ClientRequest; - export function get(options: RequestOptions, callback?: (res: events.EventEmitter) =>void ): http.ClientRequest; + export function request(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; + export function get(options: RequestOptions, callback?: (res: http.IncomingMessage) =>void ): http.ClientRequest; export var globalAgent: Agent; } @@ -558,15 +859,15 @@ declare module "punycode" { export function toASCII(domain: string): string; export var ucs2: ucs2; interface ucs2 { - decode(string: string): string; + decode(string: string): number[]; encode(codePoints: number[]): string; } export var version: any; } declare module "repl" { - import stream = require("stream"); - import events = require("events"); + import * as stream from "stream"; + import * as events from "events"; export interface ReplOptions { prompt?: string; @@ -583,25 +884,52 @@ declare module "repl" { } declare module "readline" { - import events = require("events"); - import stream = require("stream"); + import * as events from "events"; + import * as stream from "stream"; + + export interface Key { + sequence?: string; + name?: string; + ctrl?: boolean; + meta?: boolean; + shift?: boolean; + } export interface ReadLine extends events.EventEmitter { - setPrompt(prompt: string, length: number): void; + setPrompt(prompt: string): void; prompt(preserveCursor?: boolean): void; - question(query: string, callback: Function): void; - pause(): void; - resume(): void; + question(query: string, callback: (answer: string) => void): void; + pause(): ReadLine; + resume(): ReadLine; close(): void; - write(data: any, key?: any): void; + write(data: string|Buffer, key?: Key): void; } + + export interface Completer { + (line: string): CompleterResult; + (line: string, callback: (err: any, result: CompleterResult) => void): any; + } + + export interface CompleterResult { + completions: string[]; + line: string; + } + export interface ReadLineOptions { input: NodeJS.ReadableStream; - output: NodeJS.WritableStream; - completer?: Function; + output?: NodeJS.WritableStream; + completer?: Completer; terminal?: boolean; + historySize?: number; } + + export function createInterface(input: NodeJS.ReadableStream, output?: NodeJS.WritableStream, completer?: Completer, terminal?: boolean): ReadLine; export function createInterface(options: ReadLineOptions): ReadLine; + + export function cursorTo(stream: NodeJS.WritableStream, x: number, y: number): void; + export function moveCursor(stream: NodeJS.WritableStream, dx: number|string, dy: number|string): void; + export function clearLine(stream: NodeJS.WritableStream, dir: number): void; + export function clearScreenDown(stream: NodeJS.WritableStream): void; } declare module "vm" { @@ -618,71 +946,179 @@ declare module "vm" { } declare module "child_process" { - import events = require("events"); - import stream = require("stream"); + import * as events from "events"; + import * as stream from "stream"; export interface ChildProcess extends events.EventEmitter { stdin: stream.Writable; stdout: stream.Readable; stderr: stream.Readable; + stdio: [stream.Writable, stream.Readable, stream.Readable]; pid: number; + connected: boolean; kill(signal?: string): void; - send(message: any, sendHandle: any): void; + send(message: any, sendHandle?: any): void; disconnect(): void; + unref(): void; } - export function spawn(command: string, args?: string[], options?: { + export interface SpawnOptions { cwd?: string; - stdio?: any; - custom?: any; env?: any; + stdio?: any; detached?: boolean; - }): ChildProcess; - export function exec(command: string, options: { + uid?: number; + gid?: number; + shell?: boolean | string; + } + export function spawn(command: string, args?: string[], options?: SpawnOptions): ChildProcess; + + export interface ExecOptions { cwd?: string; - stdio?: any; - customFds?: any; env?: any; - encoding?: string; + shell?: string; timeout?: number; maxBuffer?: number; killSignal?: string; - }, callback: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + uid?: number; + gid?: number; + } + export interface ExecOptionsWithStringEncoding extends ExecOptions { + encoding: BufferEncoding; + } + export interface ExecOptionsWithBufferEncoding extends ExecOptions { + encoding: string; // specify `null`. + } export function exec(command: string, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; - export function execFile(file: string, args: string[], options: { + export function exec(command: string, options: ExecOptions, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + + export interface ExecFileOptions { cwd?: string; - stdio?: any; - customFds?: any; env?: any; - encoding?: string; timeout?: number; - maxBuffer?: string; + maxBuffer?: number; killSignal?: string; - }, callback: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; - export function fork(modulePath: string, args?: string[], options?: { + uid?: number; + gid?: number; + } + export interface ExecFileOptionsWithStringEncoding extends ExecFileOptions { + encoding: BufferEncoding; + } + export interface ExecFileOptionsWithBufferEncoding extends ExecFileOptions { + encoding: string; // specify `null`. + } + export function execFile(file: string, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + export function execFile(file: string, options?: ExecFileOptionsWithStringEncoding, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + // usage. child_process.execFile("file.sh", {encoding: null as string}, (err, stdout, stderr) => {}); + export function execFile(file: string, options?: ExecFileOptionsWithBufferEncoding, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, options?: ExecFileOptions, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], options?: ExecFileOptionsWithStringEncoding, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + // usage. child_process.execFile("file.sh", ["foo"], {encoding: null as string}, (err, stdout, stderr) => {}); + export function execFile(file: string, args?: string[], options?: ExecFileOptionsWithBufferEncoding, callback?: (error: Error, stdout: Buffer, stderr: Buffer) =>void ): ChildProcess; + export function execFile(file: string, args?: string[], options?: ExecFileOptions, callback?: (error: Error, stdout: string, stderr: string) =>void ): ChildProcess; + + export interface ForkOptions { cwd?: string; env?: any; + execPath?: string; + execArgv?: string[]; + silent?: boolean; + uid?: number; + gid?: number; + } + export function fork(modulePath: string, args?: string[], options?: ForkOptions): ChildProcess; + + export interface SpawnSyncOptions { + cwd?: string; + input?: string | Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + killSignal?: string; + maxBuffer?: number; encoding?: string; - }): ChildProcess; + shell?: boolean | string; + } + export interface SpawnSyncOptionsWithStringEncoding extends SpawnSyncOptions { + encoding: BufferEncoding; + } + export interface SpawnSyncOptionsWithBufferEncoding extends SpawnSyncOptions { + encoding: string; // specify `null`. + } + export interface SpawnSyncReturns { + pid: number; + output: string[]; + stdout: T; + stderr: T; + status: number; + signal: string; + error: Error; + } + export function spawnSync(command: string): SpawnSyncReturns; + export function spawnSync(command: string, options?: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; + export function spawnSync(command: string, options?: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns; + export function spawnSync(command: string, options?: SpawnSyncOptions): SpawnSyncReturns; + export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns; + export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns; + export function spawnSync(command: string, args?: string[], options?: SpawnSyncOptions): SpawnSyncReturns; + + export interface ExecSyncOptions { + cwd?: string; + input?: string | Buffer; + stdio?: any; + env?: any; + shell?: string; + uid?: number; + gid?: number; + timeout?: number; + killSignal?: string; + maxBuffer?: number; + encoding?: string; + } + export interface ExecSyncOptionsWithStringEncoding extends ExecSyncOptions { + encoding: BufferEncoding; + } + export interface ExecSyncOptionsWithBufferEncoding extends ExecSyncOptions { + encoding: string; // specify `null`. + } + export function execSync(command: string): Buffer; + export function execSync(command: string, options?: ExecSyncOptionsWithStringEncoding): string; + export function execSync(command: string, options?: ExecSyncOptionsWithBufferEncoding): Buffer; + export function execSync(command: string, options?: ExecSyncOptions): Buffer; + + export interface ExecFileSyncOptions { + cwd?: string; + input?: string | Buffer; + stdio?: any; + env?: any; + uid?: number; + gid?: number; + timeout?: number; + killSignal?: string; + maxBuffer?: number; + encoding?: string; + } + export interface ExecFileSyncOptionsWithStringEncoding extends ExecFileSyncOptions { + encoding: BufferEncoding; + } + export interface ExecFileSyncOptionsWithBufferEncoding extends ExecFileSyncOptions { + encoding: string; // specify `null`. + } + export function execFileSync(command: string): Buffer; + export function execFileSync(command: string, options?: ExecFileSyncOptionsWithStringEncoding): string; + export function execFileSync(command: string, options?: ExecFileSyncOptionsWithBufferEncoding): Buffer; + export function execFileSync(command: string, options?: ExecFileSyncOptions): Buffer; + export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptionsWithStringEncoding): string; + export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptionsWithBufferEncoding): Buffer; + export function execFileSync(command: string, args?: string[], options?: ExecFileSyncOptions): Buffer; } declare module "url" { export interface Url { - href: string; - protocol: string; - auth: string; - hostname: string; - port: string; - host: string; - pathname: string; - search: string; - query: any; // string | Object - slashes: boolean; - hash?: string; - path?: string; - } - - export interface UrlOptions { + href?: string; protocol?: string; auth?: string; hostname?: string; @@ -690,13 +1126,14 @@ declare module "url" { host?: string; pathname?: string; search?: string; - query?: any; + query?: any; // string | Object + slashes?: boolean; hash?: string; path?: string; } export function parse(urlStr: string, parseQueryString?: boolean , slashesDenoteHost?: boolean ): Url; - export function format(url: UrlOptions): string; + export function format(url: Url): string; export function resolve(from: string, to: string): string; } @@ -716,7 +1153,7 @@ declare module "dns" { } declare module "net" { - import stream = require("stream"); + import * as stream from "stream"; export interface Socket extends stream.Duplex { // Extended base methods @@ -738,8 +1175,14 @@ declare module "net" { setNoDelay(noDelay?: boolean): void; setKeepAlive(enable?: boolean, initialDelay?: number): void; address(): { port: number; family: string; address: string; }; + unref(): void; + ref(): void; + remoteAddress: string; + remoteFamily: string; remotePort: number; + localAddress: string; + localPort: number; bytesRead: number; bytesWritten: number; @@ -755,21 +1198,38 @@ declare module "net" { new (options?: { fd?: string; type?: string; allowHalfOpen?: boolean; }): Socket; }; + export interface ListenOptions { + port?: number; + host?: string; + backlog?: number; + path?: string; + exclusive?: boolean; + } + export interface Server extends Socket { - listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; + listen(port: number, hostname?: string, backlog?: number, listeningListener?: Function): Server; + listen(port: number, hostname?: string, listeningListener?: Function): Server; + listen(port: number, backlog?: number, listeningListener?: Function): Server; + listen(port: number, listeningListener?: Function): Server; + listen(path: string, backlog?: number, listeningListener?: Function): Server; listen(path: string, listeningListener?: Function): Server; + listen(handle: any, backlog?: number, listeningListener?: Function): Server; listen(handle: any, listeningListener?: Function): Server; + listen(options: ListenOptions, listeningListener?: Function): Server; close(callback?: Function): Server; address(): { port: number; family: string; address: string; }; + getConnections(cb: (error: Error, count: number) => void): void; + ref(): Server; + unref(): Server; maxConnections: number; connections: number; } export function createServer(connectionListener?: (socket: Socket) =>void ): Server; export function createServer(options?: { allowHalfOpen?: boolean; }, connectionListener?: (socket: Socket) =>void ): Server; - export function connect(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function connect(options: { port: number, host?: string, localAddress? : string, localPort? : string, family? : number, allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; export function connect(port: number, host?: string, connectionListener?: Function): Socket; export function connect(path: string, connectionListener?: Function): Socket; - export function createConnection(options: { allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; + export function createConnection(options: { port: number, host?: string, localAddress? : string, localPort? : string, family? : number, allowHalfOpen?: boolean; }, connectionListener?: Function): Socket; export function createConnection(port: number, host?: string, connectionListener?: Function): Socket; export function createConnection(path: string, connectionListener?: Function): Socket; export function isIP(input: string): number; @@ -778,7 +1238,7 @@ declare module "net" { } declare module "dgram" { - import events = require("events"); + import * as events from "events"; interface RemoteInfo { address: string; @@ -808,8 +1268,8 @@ declare module "dgram" { } declare module "fs" { - import stream = require("stream"); - import events = require("events"); + import * as stream from "stream"; + import * as events from "events"; interface Stats { isFile(): boolean; @@ -831,18 +1291,34 @@ declare module "fs" { blocks: number; atime: Date; mtime: Date; - birthtime: Date; ctime: Date; + birthtime: Date; } interface FSWatcher extends events.EventEmitter { close(): void; } - export interface ReadStream extends stream.Readable {} - export interface WriteStream extends stream.Writable {} + export interface ReadStream extends stream.Readable { + close(): void; + } + export interface WriteStream extends stream.Writable { + close(): void; + bytesWritten: number; + } + /** + * Asynchronous rename. + * @param oldPath + * @param newPath + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function rename(oldPath: string, newPath: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /** + * Synchronous rename + * @param oldPath + * @param newPath + */ export function renameSync(oldPath: string, newPath: string): void; export function truncate(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; export function truncate(path: string, len: number, callback?: (err?: NodeJS.ErrnoException) => void): void; @@ -882,15 +1358,71 @@ declare module "fs" { export function readlinkSync(path: string): string; export function realpath(path: string, callback?: (err: NodeJS.ErrnoException, resolvedPath: string) => any): void; export function realpath(path: string, cache: {[path: string]: string}, callback: (err: NodeJS.ErrnoException, resolvedPath: string) =>any): void; - export function realpathSync(path: string, cache?: {[path: string]: string}): string; + export function realpathSync(path: string, cache?: { [path: string]: string }): string; + /* + * Asynchronous unlink - deletes the file specified in {path} + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function unlink(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous unlink - deletes the file specified in {path} + * + * @param path + */ export function unlinkSync(path: string): void; + /* + * Asynchronous rmdir - removes the directory specified in {path} + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function rmdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous rmdir - removes the directory specified in {path} + * + * @param path + */ export function rmdirSync(path: string): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function mkdir(path: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function mkdir(path: string, mode: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Asynchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function mkdir(path: string, mode: string, callback?: (err?: NodeJS.ErrnoException) => void): void; + /* + * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function mkdirSync(path: string, mode?: number): void; + /* + * Synchronous mkdir - creates the directory specified in {path}. Parameter {mode} defaults to 0777. + * + * @param path + * @param mode + * @param callback No arguments other than a possible exception are given to the completion callback. + */ export function mkdirSync(path: string, mode?: string): void; export function readdir(path: string, callback?: (err: NodeJS.ErrnoException, files: string[]) => void): void; export function readdirSync(path: string): string[]; @@ -911,20 +1443,72 @@ declare module "fs" { export function futimesSync(fd: number, atime: Date, mtime: Date): void; export function fsync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; export function fsyncSync(fd: number): void; + export function fdatasync(fd: number, callback?: (err?: NodeJS.ErrnoException) => void): void; + export function fdatasyncSync(fd: number): void; export function write(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; - export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + export function write(fd: number, buffer: Buffer, offset: number, length: number, callback?: (err: NodeJS.ErrnoException, written: number, buffer: Buffer) => void): void; + export function write(fd: number, data: any, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function write(fd: number, data: any, offset: number, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function write(fd: number, data: any, offset: number, encoding: string, callback?: (err: NodeJS.ErrnoException, written: number, str: string) => void): void; + export function writeSync(fd: number, buffer: Buffer, offset: number, length: number, position?: number): number; + export function writeSync(fd: number, data: any, position?: number, enconding?: string): number; export function read(fd: number, buffer: Buffer, offset: number, length: number, position: number, callback?: (err: NodeJS.ErrnoException, bytesRead: number, buffer: Buffer) => void): void; export function readSync(fd: number, buffer: Buffer, offset: number, length: number, position: number): number; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param encoding + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ export function readFile(filename: string, encoding: string, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ export function readFile(filename: string, options: { encoding: string; flag?: string; }, callback: (err: NodeJS.ErrnoException, data: string) => void): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFile returns a string; otherwise it returns a Buffer. + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ export function readFile(filename: string, options: { flag?: string; }, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; - export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void ): void; + /* + * Asynchronous readFile - Asynchronously reads the entire contents of a file. + * + * @param fileName + * @param callback - The callback is passed two arguments (err, data), where data is the contents of the file. + */ + export function readFile(filename: string, callback: (err: NodeJS.ErrnoException, data: Buffer) => void): void; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param encoding + */ export function readFileSync(filename: string, encoding: string): string; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. + */ export function readFileSync(filename: string, options: { encoding: string; flag?: string; }): string; + /* + * Synchronous readFile - Synchronously reads the entire contents of a file. + * + * @param fileName + * @param options An object with optional {encoding} and {flag} properties. If {encoding} is specified, readFileSync returns a string; otherwise it returns a Buffer. + */ export function readFileSync(filename: string, options?: { flag?: string; }): Buffer; - export function writeFile(filename: string, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; - export function writeFile(filename: string, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; + export function writeFile(filename: string|number, data: any, options: { encoding?: string; mode?: string; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: number; flag?: string; }): void; export function writeFileSync(filename: string, data: any, options?: { encoding?: string; mode?: string; flag?: string; }): void; export function appendFile(filename: string, data: any, options: { encoding?: string; mode?: number; flag?: string; }, callback?: (err: NodeJS.ErrnoException) => void): void; @@ -939,36 +1523,178 @@ declare module "fs" { export function watch(filename: string, options: { persistent?: boolean; }, listener?: (event: string, filename: string) => any): FSWatcher; export function exists(path: string, callback?: (exists: boolean) => void): void; export function existsSync(path: string): boolean; + /** Constant for fs.access(). File is visible to the calling process. */ + export var F_OK: number; + /** Constant for fs.access(). File can be read by the calling process. */ + export var R_OK: number; + /** Constant for fs.access(). File can be written by the calling process. */ + export var W_OK: number; + /** Constant for fs.access(). File can be executed by the calling process. */ + export var X_OK: number; + /** Tests a user's permissions for the file specified by path. */ + export function access(path: string, callback: (err: NodeJS.ErrnoException) => void): void; + export function access(path: string, mode: number, callback: (err: NodeJS.ErrnoException) => void): void; + /** Synchronous version of fs.access. This throws if any accessibility checks fail, and does nothing otherwise. */ + export function accessSync(path: string, mode ?: number): void; export function createReadStream(path: string, options?: { flags?: string; encoding?: string; - fd?: string; + fd?: number; mode?: number; - bufferSize?: number; - }): ReadStream; - export function createReadStream(path: string, options?: { - flags?: string; - encoding?: string; - fd?: string; - mode?: string; - bufferSize?: number; + autoClose?: boolean; }): ReadStream; export function createWriteStream(path: string, options?: { flags?: string; encoding?: string; - string?: string; + fd?: number; + mode?: number; }): WriteStream; } declare module "path" { + + /** + * A parsed path object generated by path.parse() or consumed by path.format(). + */ + export interface ParsedPath { + /** + * The root of the path such as '/' or 'c:\' + */ + root: string; + /** + * The full directory path such as '/home/user/dir' or 'c:\path\dir' + */ + dir: string; + /** + * The file name including extension (if any) such as 'index.html' + */ + base: string; + /** + * The file extension (if any) such as '.html' + */ + ext: string; + /** + * The file name without extension (if any) such as 'index' + */ + name: string; + } + + /** + * Normalize a string path, reducing '..' and '.' parts. + * When multiple slashes are found, they're replaced by a single one; when the path contains a trailing slash, it is preserved. On Windows backslashes are used. + * + * @param p string path to normalize. + */ export function normalize(p: string): string; + /** + * Join all arguments together and normalize the resulting path. + * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. + * + * @param paths string paths to join. + */ export function join(...paths: any[]): string; + /** + * Join all arguments together and normalize the resulting path. + * Arguments must be strings. In v0.8, non-string arguments were silently ignored. In v0.10 and up, an exception is thrown. + * + * @param paths string paths to join. + */ + export function join(...paths: string[]): string; + /** + * The right-most parameter is considered {to}. Other parameters are considered an array of {from}. + * + * Starting from leftmost {from} paramter, resolves {to} to an absolute path. + * + * If {to} isn't already absolute, {from} arguments are prepended in right to left order, until an absolute path is found. If after using all {from} paths still no absolute path is found, the current working directory is used as well. The resulting path is normalized, and trailing slashes are removed unless the path gets resolved to the root directory. + * + * @param pathSegments string paths to join. Non-string arguments are ignored. + */ export function resolve(...pathSegments: any[]): string; + /** + * Determines whether {path} is an absolute path. An absolute path will always resolve to the same location, regardless of the working directory. + * + * @param path path to test. + */ + export function isAbsolute(path: string): boolean; + /** + * Solve the relative path from {from} to {to}. + * At times we have two absolute paths, and we need to derive the relative path from one to the other. This is actually the reverse transform of path.resolve. + * + * @param from + * @param to + */ export function relative(from: string, to: string): string; + /** + * Return the directory name of a path. Similar to the Unix dirname command. + * + * @param p the path to evaluate. + */ export function dirname(p: string): string; + /** + * Return the last portion of a path. Similar to the Unix basename command. + * Often used to extract the file name from a fully qualified path. + * + * @param p the path to evaluate. + * @param ext optionally, an extension to remove from the result. + */ export function basename(p: string, ext?: string): string; + /** + * Return the extension of the path, from the last '.' to end of string in the last portion of the path. + * If there is no '.' in the last portion of the path or the first character of it is '.', then it returns an empty string + * + * @param p the path to evaluate. + */ export function extname(p: string): string; + /** + * The platform-specific file separator. '\\' or '/'. + */ export var sep: string; + /** + * The platform-specific file delimiter. ';' or ':'. + */ + export var delimiter: string; + /** + * Returns an object from a path string - the opposite of format(). + * + * @param pathString path to evaluate. + */ + export function parse(pathString: string): ParsedPath; + /** + * Returns a path string from an object - the opposite of parse(). + * + * @param pathString path to evaluate. + */ + export function format(pathObject: ParsedPath): string; + + export module posix { + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function isAbsolute(p: string): boolean; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + export var delimiter: string; + export function parse(p: string): ParsedPath; + export function format(pP: ParsedPath): string; + } + + export module win32 { + export function normalize(p: string): string; + export function join(...paths: any[]): string; + export function resolve(...pathSegments: any[]): string; + export function isAbsolute(p: string): boolean; + export function relative(from: string, to: string): string; + export function dirname(p: string): string; + export function basename(p: string, ext?: string): string; + export function extname(p: string): string; + export var sep: string; + export var delimiter: string; + export function parse(p: string): ParsedPath; + export function format(pP: ParsedPath): string; + } } declare module "string_decoder" { @@ -982,14 +1708,16 @@ declare module "string_decoder" { } declare module "tls" { - import crypto = require("crypto"); - import net = require("net"); - import stream = require("stream"); + import * as crypto from "crypto"; + import * as net from "net"; + import * as stream from "stream"; var CLIENT_RENEG_LIMIT: number; var CLIENT_RENEG_WINDOW: number; export interface TlsOptions { + host?: string; + port?: number; pfx?: any; //string or buffer key?: any; //string or buffer passphrase?: string; @@ -1019,12 +1747,6 @@ declare module "tls" { } export interface Server extends net.Server { - // Extended base methods - listen(port: number, host?: string, backlog?: number, listeningListener?: Function): Server; - listen(path: string, listeningListener?: Function): Server; - listen(handle: any, listeningListener?: Function): Server; - - listen(port: number, host?: string, callback?: Function): Server; close(): Server; address(): { port: number; family: string; address: string; }; addContext(hostName: string, credentials: { @@ -1058,11 +1780,27 @@ declare module "tls" { cleartext: any; } + export interface SecureContextOptions { + pfx?: any; //string | buffer + key?: any; //string | buffer + passphrase?: string; + cert?: any; // string | buffer + ca?: any; // string | buffer + crl?: any; // string | string[] + ciphers?: string; + honorCipherOrder?: boolean; + } + + export interface SecureContext { + context: any; + } + export function createServer(options: TlsOptions, secureConnectionListener?: (cleartextStream: ClearTextStream) =>void ): Server; export function connect(options: TlsOptions, secureConnectionListener?: () =>void ): ClearTextStream; export function connect(port: number, host?: string, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; export function connect(port: number, options?: ConnectionOptions, secureConnectListener?: () =>void ): ClearTextStream; export function createSecurePair(credentials?: crypto.Credentials, isServer?: boolean, requestCert?: boolean, rejectUnauthorized?: boolean): SecurePair; + export function createSecureContext(details: SecureContextOptions): SecureContext; } declare module "crypto" { @@ -1080,13 +1818,13 @@ declare module "crypto" { export function createHash(algorithm: string): Hash; export function createHmac(algorithm: string, key: string): Hmac; export function createHmac(algorithm: string, key: Buffer): Hmac; - interface Hash { + export interface Hash { update(data: any, input_encoding?: string): Hash; digest(encoding: 'buffer'): Buffer; digest(encoding: string): any; digest(): Buffer; } - interface Hmac { + export interface Hmac extends NodeJS.ReadWriteStream { update(data: any, input_encoding?: string): Hmac; digest(encoding: 'buffer'): Buffer; digest(encoding: string): any; @@ -1094,31 +1832,41 @@ declare module "crypto" { } export function createCipher(algorithm: string, password: any): Cipher; export function createCipheriv(algorithm: string, key: any, iv: any): Cipher; - interface Cipher { - update(data: any, input_encoding?: string, output_encoding?: string): string; - final(output_encoding?: string): string; + export interface Cipher extends NodeJS.ReadWriteStream { + update(data: Buffer): Buffer; + update(data: string, input_encoding: "utf8"|"ascii"|"binary"): Buffer; + update(data: Buffer, input_encoding: any, output_encoding: "binary"|"base64"|"hex"): string; + update(data: string, input_encoding: "utf8"|"ascii"|"binary", output_encoding: "binary"|"base64"|"hex"): string; + final(): Buffer; + final(output_encoding: string): string; setAutoPadding(auto_padding: boolean): void; + getAuthTag(): Buffer; } export function createDecipher(algorithm: string, password: any): Decipher; export function createDecipheriv(algorithm: string, key: any, iv: any): Decipher; - interface Decipher { - update(data: any, input_encoding?: string, output_encoding?: string): void; - final(output_encoding?: string): string; + export interface Decipher extends NodeJS.ReadWriteStream { + update(data: Buffer): Buffer; + update(data: string, input_encoding: "binary"|"base64"|"hex"): Buffer; + update(data: Buffer, input_encoding: any, output_encoding: "utf8"|"ascii"|"binary"): string; + update(data: string, input_encoding: "binary"|"base64"|"hex", output_encoding: "utf8"|"ascii"|"binary"): string; + final(): Buffer; + final(output_encoding: string): string; setAutoPadding(auto_padding: boolean): void; + setAuthTag(tag: Buffer): void; } export function createSign(algorithm: string): Signer; - interface Signer { + export interface Signer extends NodeJS.WritableStream { update(data: any): void; sign(private_key: string, output_format: string): string; } export function createVerify(algorith: string): Verify; - interface Verify { + export interface Verify extends NodeJS.WritableStream { update(data: any): void; verify(object: string, signature: string, signature_format?: string): boolean; } export function createDiffieHellman(prime_length: number): DiffieHellman; export function createDiffieHellman(prime: number, encoding?: string): DiffieHellman; - interface DiffieHellman { + export interface DiffieHellman { generateKeys(encoding?: string): string; computeSecret(other_public_key: string, input_encoding?: string, output_encoding?: string): string; getPrime(encoding?: string): string; @@ -1129,18 +1877,31 @@ declare module "crypto" { setPrivateKey(public_key: string, encoding?: string): void; } export function getDiffieHellman(group_name: string): DiffieHellman; - export function pbkdf2(password: string, salt: string, iterations: number, keylen: number, callback: (err: Error, derivedKey: Buffer) => any): void; - export function pbkdf2Sync(password: string, salt: string, iterations: number, keylen: number) : Buffer; + export function pbkdf2(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, callback: (err: Error, derivedKey: Buffer) => any): void; + export function pbkdf2(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, digest: string, callback: (err: Error, derivedKey: Buffer) => any): void; + export function pbkdf2Sync(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number) : Buffer; + export function pbkdf2Sync(password: string|Buffer, salt: string|Buffer, iterations: number, keylen: number, digest: string) : Buffer; export function randomBytes(size: number): Buffer; export function randomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; export function pseudoRandomBytes(size: number): Buffer; export function pseudoRandomBytes(size: number, callback: (err: Error, buf: Buffer) =>void ): void; + export interface RsaPublicKey { + key: string; + padding?: any; + } + export interface RsaPrivateKey { + key: string; + passphrase?: string, + padding?: any; + } + export function publicEncrypt(public_key: string|RsaPublicKey, buffer: Buffer): Buffer + export function privateDecrypt(private_key: string|RsaPrivateKey, buffer: Buffer): Buffer } declare module "stream" { - import events = require("events"); + import * as events from "events"; - export interface Stream extends events.EventEmitter { + export class Stream extends events.EventEmitter { pipe(destination: T, options?: { end?: boolean; }): T; } @@ -1158,10 +1919,10 @@ declare module "stream" { setEncoding(encoding: string): void; pause(): void; resume(): void; + destroy(): void; pipe(destination: T, options?: { end?: boolean; }): T; unpipe(destination?: T): void; - unshift(chunk: string): void; - unshift(chunk: Buffer): void; + unshift(chunk: any): void; wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; push(chunk: any, encoding?: string): boolean; } @@ -1169,20 +1930,18 @@ declare module "stream" { export interface WritableOptions { highWaterMark?: number; decodeStrings?: boolean; + objectMode?: boolean; } export class Writable extends events.EventEmitter implements NodeJS.WritableStream { writable: boolean; constructor(opts?: WritableOptions); - _write(data: Buffer, encoding: string, callback: Function): void; - _write(data: string, encoding: string, callback: Function): void; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; + _write(chunk: any, encoding: string, callback: Function): void; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; } export interface DuplexOptions extends ReadableOptions, WritableOptions { @@ -1193,15 +1952,12 @@ declare module "stream" { export class Duplex extends Readable implements NodeJS.ReadWriteStream { writable: boolean; constructor(opts?: DuplexOptions); - _write(data: Buffer, encoding: string, callback: Function): void; - _write(data: string, encoding: string, callback: Function): void; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; + _write(chunk: any, encoding: string, callback: Function): void; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; } export interface TransformOptions extends ReadableOptions, WritableOptions {} @@ -1211,8 +1967,7 @@ declare module "stream" { readable: boolean; writable: boolean; constructor(opts?: TransformOptions); - _transform(chunk: Buffer, encoding: string, callback: Function): void; - _transform(chunk: string, encoding: string, callback: Function): void; + _transform(chunk: any, encoding: string, callback: Function): void; _flush(callback: Function): void; read(size?: number): any; setEncoding(encoding: string): void; @@ -1220,17 +1975,14 @@ declare module "stream" { resume(): void; pipe(destination: T, options?: { end?: boolean; }): T; unpipe(destination?: T): void; - unshift(chunk: string): void; - unshift(chunk: Buffer): void; + unshift(chunk: any): void; wrap(oldStream: NodeJS.ReadableStream): NodeJS.ReadableStream; push(chunk: any, encoding?: string): boolean; - write(buffer: Buffer, cb?: Function): boolean; - write(str: string, cb?: Function): boolean; - write(str: string, encoding?: string, cb?: Function): boolean; + write(chunk: any, cb?: Function): boolean; + write(chunk: any, encoding?: string, cb?: Function): boolean; end(): void; - end(buffer: Buffer, cb?: Function): void; - end(str: string, cb?: Function): void; - end(str: string, encoding?: string, cb?: Function): void; + end(chunk: any, cb?: Function): void; + end(chunk: any, encoding?: string, cb?: Function): void; } export class PassThrough extends Transform {} @@ -1245,38 +1997,24 @@ declare module "util" { } export function format(format: any, ...param: any[]): string; - export function deprecate(fn: Function, msg: string): Function; - export function debuglog(set: string): Function; + export function debug(string: string): void; + export function error(...param: any[]): void; + export function puts(...param: any[]): void; + export function print(...param: any[]): void; + export function log(string: string): void; export function inspect(object: any, showHidden?: boolean, depth?: number, color?: boolean): string; export function inspect(object: any, options: InspectOptions): string; export function isArray(object: any): boolean; - export function isBoolean(arg: any): boolean; - export function isNull(arg: any): boolean; - export function isNullOrUndefined(arg: any): boolean; - export function isNumber(arg: any): boolean; - export function isString(arg: any): boolean; - export function isSymbol(arg: any): boolean; - export function isUndefined(arg: any): boolean; - export function isRegExp(arg: any): boolean; - export function isObject(arg: any): boolean; - export function isDate(arg: any): boolean; - export function isError(arg: any): boolean; - export function isFunction(arg: any): boolean; - export function isPrimitive(arg: any): boolean; - export function isBuffer(arg: any): boolean; - export function log(...arg: any[]): void + export function isRegExp(object: any): boolean; + export function isDate(object: any): boolean; + export function isError(object: any): boolean; export function inherits(constructor: any, superConstructor: any): void; - export function p(...arg: any[]): void; - export function exec(...arg: any[]): void; - export function print(...arg: any[]): void; - export function puts(...arg: any[]): void; - export function debug(string: string): void; - export function error(...arg: any[]): void; + export function debuglog(key:string): (msg:string,...param: any[])=>void; } declare module "assert" { function internal (value: any, message?: string): void; - module internal { + namespace internal { export class AssertionError implements Error { name: string; message: string; @@ -1297,6 +2035,8 @@ declare module "assert" { export function notDeepEqual(acutal: any, expected: any, message?: string): void; export function strictEqual(actual: any, expected: any, message?: string): void; export function notStrictEqual(actual: any, expected: any, message?: string): void; + export function deepStrictEqual(actual: any, expected: any, message?: string): void; + export function notDeepStrictEqual(actual: any, expected: any, message?: string): void; export var throws: { (block: Function, message?: string): void; (block: Function, error: Function, message?: string): void; @@ -1318,36 +2058,256 @@ declare module "assert" { } declare module "tty" { - import net = require("net"); + import * as net from "net"; export function isatty(fd: number): boolean; export interface ReadStream extends net.Socket { isRaw: boolean; setRawMode(mode: boolean): void; + isTTY: boolean; } export interface WriteStream extends net.Socket { columns: number; rows: number; + isTTY: boolean; } } declare module "domain" { - import events = require("events"); + import * as events from "events"; - export class Domain extends events.EventEmitter { + export class Domain extends events.EventEmitter implements NodeJS.Domain { run(fn: Function): void; add(emitter: events.EventEmitter): void; remove(emitter: events.EventEmitter): void; bind(cb: (err: Error, data: any) => any): any; intercept(cb: (data: any) => any): any; dispose(): void; - - addListener(event: string, listener: Function): Domain; - on(event: string, listener: Function): Domain; - once(event: string, listener: Function): Domain; - removeListener(event: string, listener: Function): Domain; - removeAllListeners(event?: string): Domain; } export function create(): Domain; } + +declare module "constants" { + export var E2BIG: number; + export var EACCES: number; + export var EADDRINUSE: number; + export var EADDRNOTAVAIL: number; + export var EAFNOSUPPORT: number; + export var EAGAIN: number; + export var EALREADY: number; + export var EBADF: number; + export var EBADMSG: number; + export var EBUSY: number; + export var ECANCELED: number; + export var ECHILD: number; + export var ECONNABORTED: number; + export var ECONNREFUSED: number; + export var ECONNRESET: number; + export var EDEADLK: number; + export var EDESTADDRREQ: number; + export var EDOM: number; + export var EEXIST: number; + export var EFAULT: number; + export var EFBIG: number; + export var EHOSTUNREACH: number; + export var EIDRM: number; + export var EILSEQ: number; + export var EINPROGRESS: number; + export var EINTR: number; + export var EINVAL: number; + export var EIO: number; + export var EISCONN: number; + export var EISDIR: number; + export var ELOOP: number; + export var EMFILE: number; + export var EMLINK: number; + export var EMSGSIZE: number; + export var ENAMETOOLONG: number; + export var ENETDOWN: number; + export var ENETRESET: number; + export var ENETUNREACH: number; + export var ENFILE: number; + export var ENOBUFS: number; + export var ENODATA: number; + export var ENODEV: number; + export var ENOENT: number; + export var ENOEXEC: number; + export var ENOLCK: number; + export var ENOLINK: number; + export var ENOMEM: number; + export var ENOMSG: number; + export var ENOPROTOOPT: number; + export var ENOSPC: number; + export var ENOSR: number; + export var ENOSTR: number; + export var ENOSYS: number; + export var ENOTCONN: number; + export var ENOTDIR: number; + export var ENOTEMPTY: number; + export var ENOTSOCK: number; + export var ENOTSUP: number; + export var ENOTTY: number; + export var ENXIO: number; + export var EOPNOTSUPP: number; + export var EOVERFLOW: number; + export var EPERM: number; + export var EPIPE: number; + export var EPROTO: number; + export var EPROTONOSUPPORT: number; + export var EPROTOTYPE: number; + export var ERANGE: number; + export var EROFS: number; + export var ESPIPE: number; + export var ESRCH: number; + export var ETIME: number; + export var ETIMEDOUT: number; + export var ETXTBSY: number; + export var EWOULDBLOCK: number; + export var EXDEV: number; + export var WSAEINTR: number; + export var WSAEBADF: number; + export var WSAEACCES: number; + export var WSAEFAULT: number; + export var WSAEINVAL: number; + export var WSAEMFILE: number; + export var WSAEWOULDBLOCK: number; + export var WSAEINPROGRESS: number; + export var WSAEALREADY: number; + export var WSAENOTSOCK: number; + export var WSAEDESTADDRREQ: number; + export var WSAEMSGSIZE: number; + export var WSAEPROTOTYPE: number; + export var WSAENOPROTOOPT: number; + export var WSAEPROTONOSUPPORT: number; + export var WSAESOCKTNOSUPPORT: number; + export var WSAEOPNOTSUPP: number; + export var WSAEPFNOSUPPORT: number; + export var WSAEAFNOSUPPORT: number; + export var WSAEADDRINUSE: number; + export var WSAEADDRNOTAVAIL: number; + export var WSAENETDOWN: number; + export var WSAENETUNREACH: number; + export var WSAENETRESET: number; + export var WSAECONNABORTED: number; + export var WSAECONNRESET: number; + export var WSAENOBUFS: number; + export var WSAEISCONN: number; + export var WSAENOTCONN: number; + export var WSAESHUTDOWN: number; + export var WSAETOOMANYREFS: number; + export var WSAETIMEDOUT: number; + export var WSAECONNREFUSED: number; + export var WSAELOOP: number; + export var WSAENAMETOOLONG: number; + export var WSAEHOSTDOWN: number; + export var WSAEHOSTUNREACH: number; + export var WSAENOTEMPTY: number; + export var WSAEPROCLIM: number; + export var WSAEUSERS: number; + export var WSAEDQUOT: number; + export var WSAESTALE: number; + export var WSAEREMOTE: number; + export var WSASYSNOTREADY: number; + export var WSAVERNOTSUPPORTED: number; + export var WSANOTINITIALISED: number; + export var WSAEDISCON: number; + export var WSAENOMORE: number; + export var WSAECANCELLED: number; + export var WSAEINVALIDPROCTABLE: number; + export var WSAEINVALIDPROVIDER: number; + export var WSAEPROVIDERFAILEDINIT: number; + export var WSASYSCALLFAILURE: number; + export var WSASERVICE_NOT_FOUND: number; + export var WSATYPE_NOT_FOUND: number; + export var WSA_E_NO_MORE: number; + export var WSA_E_CANCELLED: number; + export var WSAEREFUSED: number; + export var SIGHUP: number; + export var SIGINT: number; + export var SIGILL: number; + export var SIGABRT: number; + export var SIGFPE: number; + export var SIGKILL: number; + export var SIGSEGV: number; + export var SIGTERM: number; + export var SIGBREAK: number; + export var SIGWINCH: number; + export var SSL_OP_ALL: number; + export var SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: number; + export var SSL_OP_CIPHER_SERVER_PREFERENCE: number; + export var SSL_OP_CISCO_ANYCONNECT: number; + export var SSL_OP_COOKIE_EXCHANGE: number; + export var SSL_OP_CRYPTOPRO_TLSEXT_BUG: number; + export var SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: number; + export var SSL_OP_EPHEMERAL_RSA: number; + export var SSL_OP_LEGACY_SERVER_CONNECT: number; + export var SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER: number; + export var SSL_OP_MICROSOFT_SESS_ID_BUG: number; + export var SSL_OP_MSIE_SSLV2_RSA_PADDING: number; + export var SSL_OP_NETSCAPE_CA_DN_BUG: number; + export var SSL_OP_NETSCAPE_CHALLENGE_BUG: number; + export var SSL_OP_NETSCAPE_DEMO_CIPHER_CHANGE_BUG: number; + export var SSL_OP_NETSCAPE_REUSE_CIPHER_CHANGE_BUG: number; + export var SSL_OP_NO_COMPRESSION: number; + export var SSL_OP_NO_QUERY_MTU: number; + export var SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: number; + export var SSL_OP_NO_SSLv2: number; + export var SSL_OP_NO_SSLv3: number; + export var SSL_OP_NO_TICKET: number; + export var SSL_OP_NO_TLSv1: number; + export var SSL_OP_NO_TLSv1_1: number; + export var SSL_OP_NO_TLSv1_2: number; + export var SSL_OP_PKCS1_CHECK_1: number; + export var SSL_OP_PKCS1_CHECK_2: number; + export var SSL_OP_SINGLE_DH_USE: number; + export var SSL_OP_SINGLE_ECDH_USE: number; + export var SSL_OP_SSLEAY_080_CLIENT_DH_BUG: number; + export var SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG: number; + export var SSL_OP_TLS_BLOCK_PADDING_BUG: number; + export var SSL_OP_TLS_D5_BUG: number; + export var SSL_OP_TLS_ROLLBACK_BUG: number; + export var ENGINE_METHOD_DSA: number; + export var ENGINE_METHOD_DH: number; + export var ENGINE_METHOD_RAND: number; + export var ENGINE_METHOD_ECDH: number; + export var ENGINE_METHOD_ECDSA: number; + export var ENGINE_METHOD_CIPHERS: number; + export var ENGINE_METHOD_DIGESTS: number; + export var ENGINE_METHOD_STORE: number; + export var ENGINE_METHOD_PKEY_METHS: number; + export var ENGINE_METHOD_PKEY_ASN1_METHS: number; + export var ENGINE_METHOD_ALL: number; + export var ENGINE_METHOD_NONE: number; + export var DH_CHECK_P_NOT_SAFE_PRIME: number; + export var DH_CHECK_P_NOT_PRIME: number; + export var DH_UNABLE_TO_CHECK_GENERATOR: number; + export var DH_NOT_SUITABLE_GENERATOR: number; + export var NPN_ENABLED: number; + export var RSA_PKCS1_PADDING: number; + export var RSA_SSLV23_PADDING: number; + export var RSA_NO_PADDING: number; + export var RSA_PKCS1_OAEP_PADDING: number; + export var RSA_X931_PADDING: number; + export var RSA_PKCS1_PSS_PADDING: number; + export var POINT_CONVERSION_COMPRESSED: number; + export var POINT_CONVERSION_UNCOMPRESSED: number; + export var POINT_CONVERSION_HYBRID: number; + export var O_RDONLY: number; + export var O_WRONLY: number; + export var O_RDWR: number; + export var S_IFMT: number; + export var S_IFREG: number; + export var S_IFDIR: number; + export var S_IFCHR: number; + export var S_IFLNK: number; + export var O_CREAT: number; + export var O_EXCL: number; + export var O_TRUNC: number; + export var O_APPEND: number; + export var F_OK: number; + export var R_OK: number; + export var W_OK: number; + export var X_OK: number; + export var UV_UDP_REUSEADDR: number; +} \ No newline at end of file diff --git a/extensions/php/src/features/hoverProvider.ts b/extensions/php/src/features/hoverProvider.ts index 9f19de9b7e1..abfe6f65c4b 100644 --- a/extensions/php/src/features/hoverProvider.ts +++ b/extensions/php/src/features/hoverProvider.ts @@ -7,6 +7,7 @@ import {HoverProvider, Hover, MarkedString, TextDocument, CancellationToken, Position} from 'vscode'; import phpGlobals = require('./phpGlobals'); +import { textToMarkedString } from './utils/markedTextUtil'; export default class PHPHoverProvider implements HoverProvider { @@ -21,7 +22,7 @@ export default class PHPHoverProvider implements HoverProvider { var entry = phpGlobals.globalfunctions[name] || phpGlobals.compiletimeconstants[name] || phpGlobals.globalvariables[name] || phpGlobals.keywords[name]; if (entry && entry.description) { let signature = name + (entry.signature || ''); - let contents: MarkedString[] = [entry.description, { language: 'php', value: signature }]; + let contents: MarkedString[] = [ textToMarkedString(entry.description), { language: 'php', value: signature }]; return new Hover(contents, wordRange); } } diff --git a/extensions/php/src/features/utils/markedTextUtil.ts b/extensions/php/src/features/utils/markedTextUtil.ts new file mode 100644 index 00000000000..97f3198b80c --- /dev/null +++ b/extensions/php/src/features/utils/markedTextUtil.ts @@ -0,0 +1,11 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {MarkedString} from 'vscode'; + +export function textToMarkedString(text: string) : MarkedString { + return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash +} \ No newline at end of file diff --git a/extensions/ruby/package.json b/extensions/ruby/package.json index 6745d14cdf8..2782d12525e 100644 --- a/extensions/ruby/package.json +++ b/extensions/ruby/package.json @@ -9,6 +9,7 @@ "extensions": [ ".rb", ".rbx", ".rjs", ".gemspec", ".rake", ".ru" ], "filenames": [ "rakefile", "gemfile", "guardfile" ], "aliases": [ "Ruby", "rb" ], + "firstLine": "^#!/.*\\bruby\\b", "configuration": "./language-configuration.json" }], "grammars": [{ diff --git a/extensions/scss/syntaxes/scss.json b/extensions/scss/syntaxes/scss.json index 3b7aafc7ad2..6821cee6913 100644 --- a/extensions/scss/syntaxes/scss.json +++ b/extensions/scss/syntaxes/scss.json @@ -155,7 +155,7 @@ "name": "meta.at-rule.else.scss", "patterns": [ { - "include": "#logical_operators" + "include": "#conditional_operators" }, { "include": "#variable" @@ -295,7 +295,7 @@ "name": "meta.at-rule.if.scss", "patterns": [ { - "include": "#logical_operators" + "include": "#conditional_operators" }, { "include": "#variable" @@ -388,53 +388,105 @@ ] }, "at_rule_keyframes": { + "begin": "(?<=^|\\s)(@)(?:-(?:webkit|moz)-)?keyframes\\b", + "beginCaptures": { + "0": { + "name": "keyword.control.at-rule.keyframes.scss" + }, + "1": { + "name": "punctuation.definition.keyword.scss" + } + }, + "end": "(?<=})", + "name": "meta.at-rule.keyframes.scss", "patterns": [ { - "begin": "^\\s*((@)(-[\\w-]*-)?keyframes\\b)\\s*([\\w-]*)", + "match": "(?<=@keyframes)\\s+((?:[_A-Za-z][-\\w]|-[_A-Za-z])[-\\w]*)", "captures": { "1": { - "name": "keyword.control.at-rule.keyframes.scss" - }, - "2": { - "name": "punctuation.definition.keyword.scss" - }, - "3": { - "name": "punctuation.definition.keyword.scss" - }, - "4": { "name": "entity.name.function.scss" } + } + }, + { + "begin": "(?<=@keyframes)\\s+(\")", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.scss" + } }, - "comment": "Keyframes with Attributes", - "end": "(?<=})(?:\\s*$)?", - "name": "meta.at-rule.keyframes.scss", + "end": "\"", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.scss" + } + }, + "name": "string.quoted.double.scss", + "contentName": "entity.name.function.scss", "patterns": [ { - "begin": "{", - "end": "}", - "beginCaptures": { - "0": { - "name": "punctuation.section.keyframes.begin.scss" - } - }, - "endCaptures": { - "0": { - "name": "punctuation.section.keyframes.end.scss" - } - }, - "name": "meta.keyframes.scss", - "patterns": [ - { - "match": "(\\b(\\d+%|from\\b|to\\b))", - "name": "entity.other.attribute-name.scss" - }, - { - "include": "#interpolation" - }, - { - "include": "#property_list" - } - ] + "match": "\\\\(\\h{1,6}|.)", + "name": "constant.character.escape.scss" + }, + { + "include": "#interpolation" + } + ] + }, + { + "begin": "(?<=@keyframes)\\s+(')", + "beginCaptures": { + "1": { + "name": "punctuation.definition.string.begin.scss" + } + }, + "end": "'", + "endCaptures": { + "0": { + "name": "punctuation.definition.string.end.scss" + } + }, + "name": "string.quoted.single.scss", + "contentName": "entity.name.function.scss", + "patterns": [ + { + "match": "\\\\(\\h{1,6}|.)", + "name": "constant.character.escape.scss" + }, + { + "include": "#interpolation" + } + ] + }, + { + "begin": "{", + "beginCaptures": { + "0": { + "name": "punctuation.section.keyframes.begin.scss" + } + }, + "end": "}", + "endCaptures": { + "0": { + "name": "punctuation.section.keyframes.end.scss" + } + }, + "patterns": [ + { + "match": "\\b(?:(?:100|[1-9]\\d|\\d)%|from|to)(?=\\s*{)", + "name": "entity.other.attribute-name.scss" + }, + { + "include": "#flow_control" + }, + { + "include": "#interpolation" + }, + { + "include": "#property_list" + }, + { + "include": "#rules" } ] } @@ -469,7 +521,7 @@ "include": "#variable" }, { - "include": "#logical_operators" + "include": "#conditional_operators" }, { "include": "#media_types" @@ -665,29 +717,23 @@ ] }, "at_rule_supports": { - "begin": "((@)supports)\\b", + "begin": "(?<=^|\\s)(@)supports\\b", "captures": { - "1": { + "0": { "name": "keyword.control.at-rule.supports.scss" }, - "2": { + "1": { "name": "punctuation.definition.keyword.scss" } }, - "end": "(?={|$)", + "end": "(?={)|$", "name": "meta.at-rule.supports.scss", "patterns": [ { "include": "#logical_operators" }, { - "include": "#constant_property_value" - }, - { - "include": "#property_names" - }, - { - "include": "#property_values" + "include": "#properties" }, { "match": "\\(", @@ -696,10 +742,6 @@ { "match": "\\)", "name": "punctuation.definition.condition.end.bracket.round.scss" - }, - { - "match": ":", - "name": "punctuation.separator.key-value.scss" } ] }, @@ -741,7 +783,7 @@ "name": "meta.at-rule.while.scss", "patterns": [ { - "include": "#logical_operators" + "include": "#conditional_operators" }, { "include": "#variable" @@ -1034,9 +1076,23 @@ } ] }, + "conditional_operators": { + "patterns": [ + { + "include": "#comparison_operators" + }, + { + "include": "#logical_operators" + } + ] + }, + "comparison_operators": { + "match": "==|!=|<=|>=|<|>", + "name": "keyword.operator.comparison.scss" + }, "logical_operators": { - "match": "\\b(==|!=|<=|>=|<|>|not|or|and)\\b", - "name": "keyword.control.operator" + "match": "\\b(not\\b|or\\b|and\\b)", + "name": "keyword.operator.logical.scss" }, "map": { "begin": "\\(", @@ -1190,7 +1246,7 @@ } }, "comment": "Kuroir: fixed nested elements for sass.", - "end": "\\s*(;|(?=}))", + "end": "\\s*(;|(?=}|\\)))", "endCaptures": { "1": { "name": "punctuation.terminator.rule.scss" @@ -1386,7 +1442,7 @@ ] }, "selector_entities": { - "match": "\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|circle|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|em|embed|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|g|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|image|input|ins|kbd|keygen|label|legend|li|line(?!-)|link|main|map|mark|menu|menuitem|meta|meter|nav|noframes|noscript|object(?!-)|ol|optgroup|option|output|p|param|path|picture|polygon|polyline|pre|progress|q|rb|rect|rp|rt|rtc|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|table(?!-)|tbody|td|template|text(?!-)|textarea|textpath|tfoot|th|thead|time|title|tr|track|tspan|tt|u|ul|var|video|wbr)\\b", + "match": "\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|bdi|bdo|big|blockquote|body|br|button|canvas|caption|circle|cite|code|col|colgroup|data|datalist|dd|del|details|dfn|dialog|div|dl|dt|ellipse|em|embed|eventsource|fieldset|figure|figcaption|footer|form|frame|frameset|g|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|image|input|ins|kbd|keygen|label|legend|li|line(?!-)|link|main|map|mark|menu|menuitem|meta|meter|nav|noframes|noscript|object(?!-)|ol|optgroup|option|output|p|param|path|picture|polygon|polyline|pre|progress|q|rb|rect|rp|rt|rtc|ruby|s|samp|script|section|select|small|source|span|strike|strong|style|sub|summary|sup|svg|table(?!-)|tbody|td|template|text(?!-)|textarea|textpath|tfoot|th|thead|time|title|tr|track|tspan|tt|u|ul|var|video|wbr)\\b", "name": "entity.name.tag.scss" }, "selector_custom": { @@ -1612,5 +1668,5 @@ "name": "variable.scss" } }, - "version": "https://github.com/atom/language-sass/commit/38b8d07b0e5edc8ac1e3ac1e370a208e838fc198" + "version": "https://github.com/atom/language-sass/commit/b0417d1412a9169562f637133099fe2bb841a735" } \ No newline at end of file diff --git a/extensions/scss/test/colorize-results/test-cssvariables_scss.json b/extensions/scss/test/colorize-results/test-cssvariables_scss.json index ec12e65c9fc..e83ae1ea6b2 100644 --- a/extensions/scss/test/colorize-results/test-cssvariables_scss.json +++ b/extensions/scss/test/colorize-results/test-cssvariables_scss.json @@ -264,19 +264,8 @@ } }, { - "c": ")", - "t": "meta.property-list.property-value.scss", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": ";", - "t": "meta.property-list.punctuation.rule.scss.terminator", + "c": ");", + "t": "meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", diff --git a/extensions/scss/test/colorize-results/test_scss.json b/extensions/scss/test/colorize-results/test_scss.json index feea42709a0..0ec4476459a 100644 --- a/extensions/scss/test/colorize-results/test_scss.json +++ b/extensions/scss/test/colorize-results/test_scss.json @@ -4181,7 +4181,7 @@ }, { "c": ") ", - "t": "meta.property-list.property-value.scss", + "t": "meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -4192,40 +4192,18 @@ }, { "c": "*", - "t": "css.keyword.meta.operator.property-list.property-value.scss", + "t": "entity.meta.name.property-list.scss.tag.wildcard", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.name.tag rgb(86, 156, 214)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.name.tag rgb(128, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.name.tag rgb(86, 156, 214)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.entity.name.tag rgb(128, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.entity.name.tag rgb(86, 156, 214)" } }, { - "c": " ", - "t": "meta.property-list.property-value.scss", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "3", - "t": "constant.meta.numeric.property-list.property-value.scss", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.constant.numeric rgb(181, 206, 168)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.constant.numeric rgb(9, 136, 90)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.constant.numeric rgb(181, 206, 168)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.constant.numeric rgb(9, 136, 90)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.constant.numeric rgb(181, 206, 168)" - } - }, - { - "c": ";", - "t": "meta.property-list.punctuation.rule.scss.terminator", + "c": " 3;", + "t": "meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -6755,10 +6733,10 @@ }, { "c": "and", - "t": "at-rule.control.keyword.media.meta.operator.property-list.scss", + "t": "at-rule.keyword.logical.media.meta.operator.property-list.scss", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.control rgb(175, 0, 219)", + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" @@ -9218,7 +9196,29 @@ } }, { - "c": " == ", + "c": " ", + "t": "at-rule.if.meta.property-list.scss", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": "==", + "t": "at-rule.comparison.if.keyword.meta.operator.property-list.scss", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " ", "t": "at-rule.if.meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -9438,7 +9438,29 @@ } }, { - "c": " < ", + "c": " ", + "t": "at-rule.if.meta.property-list.scss", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": "<", + "t": "at-rule.comparison.if.keyword.meta.operator.property-list.scss", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " ", "t": "at-rule.if.meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -9977,7 +9999,29 @@ } }, { - "c": " == ocean ", + "c": " ", + "t": "at-rule.if.meta.property-list.scss", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": "==", + "t": "at-rule.comparison.if.keyword.meta.operator.property-list.scss", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " ocean ", "t": "at-rule.if.meta.property-list.scss", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -11209,7 +11253,29 @@ } }, { - "c": " > ", + "c": " ", + "t": "at-rule.each.meta.scss.while", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": ">", + "t": "at-rule.comparison.each.keyword.meta.operator.scss.while", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " ", "t": "at-rule.each.meta.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -11990,7 +12056,29 @@ } }, { - "c": " == ", + "c": " ", + "t": "at-rule.each.if.meta.property-list.scss.while", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": "==", + "t": "at-rule.comparison.each.if.keyword.meta.operator.property-list.scss.while", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " ", "t": "at-rule.each.if.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -12046,10 +12134,10 @@ }, { "c": "and", - "t": "at-rule.control.each.if.keyword.meta.operator.property-list.scss.while", + "t": "at-rule.each.if.keyword.logical.meta.operator.property-list.scss.while", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.control rgb(175, 0, 219)", + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" @@ -12078,7 +12166,29 @@ } }, { - "c": " == (", + "c": " ", + "t": "at-rule.each.if.meta.property-list.scss.while", + "r": { + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" + } + }, + { + "c": "==", + "t": "at-rule.comparison.each.if.keyword.meta.operator.property-list.scss.while", + "r": { + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.operator rgb(212, 212, 212)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.operator rgb(0, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.operator rgb(212, 212, 212)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.operator rgb(0, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.operator rgb(212, 212, 212)" + } + }, + { + "c": " (", "t": "at-rule.each.if.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -18623,7 +18733,7 @@ } }, { - "c": "@-webkit-", + "c": "@", "t": "at-rule.control.definition.each.keyframes.keyword.meta.property-list.punctuation.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", @@ -18634,7 +18744,7 @@ } }, { - "c": "keyframes", + "c": "-webkit-keyframes", "t": "at-rule.control.each.keyframes.keyword.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", @@ -18645,29 +18755,7 @@ } }, { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "NAME-YOUR-ANIMATION", - "t": "at-rule.each.entity.function.keyframes.meta.name.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.name.function rgb(220, 220, 170)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.name.function rgb(121, 94, 38)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.name.function rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": " ", + "c": " NAME-YOUR-ANIMATION ", "t": "at-rule.each.keyframes.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -18964,7 +19052,7 @@ } }, { - "c": "@-moz-", + "c": "@", "t": "at-rule.control.definition.each.keyframes.keyword.meta.property-list.punctuation.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", @@ -18975,7 +19063,7 @@ } }, { - "c": "keyframes", + "c": "-moz-keyframes", "t": "at-rule.control.each.keyframes.keyword.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", @@ -18986,29 +19074,7 @@ } }, { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "NAME-YOUR-ANIMATION", - "t": "at-rule.each.entity.function.keyframes.meta.name.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.name.function rgb(220, 220, 170)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.name.function rgb(121, 94, 38)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.name.function rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": " ", + "c": " NAME-YOUR-ANIMATION ", "t": "at-rule.each.keyframes.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", @@ -19305,30 +19371,30 @@ } }, { - "c": "@-o-", - "t": "at-rule.control.definition.each.keyframes.keyword.meta.property-list.punctuation.scss.while", + "c": "@", + "t": "at-rule.each.meta.property-list.scss.while", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.control rgb(175, 0, 219)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.control rgb(86, 156, 214)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.control rgb(0, 0, 255)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.control rgb(86, 156, 214)" + "dark_plus": ".vs-dark .token rgb(212, 212, 212)", + "light_plus": ".vs .token rgb(0, 0, 0)", + "dark_vs": ".vs-dark .token rgb(212, 212, 212)", + "light_vs": ".vs .token rgb(0, 0, 0)", + "hc_black": ".hc-black .token rgb(255, 255, 255)" } }, { - "c": "keyframes", - "t": "at-rule.control.each.keyframes.keyword.meta.property-list.scss.while", + "c": "-o-keyframes", + "t": "at-rule.each.illegal.invalid.meta.property-list.property-name.scss.while", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.keyword.control rgb(197, 134, 192)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.keyword.control rgb(175, 0, 219)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.keyword.control rgb(86, 156, 214)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.keyword.control rgb(0, 0, 255)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.keyword.control rgb(86, 156, 214)" + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.invalid rgb(244, 71, 71)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.invalid rgb(205, 49, 49)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.invalid rgb(244, 71, 71)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.invalid rgb(205, 49, 49)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.invalid rgb(244, 71, 71)" } }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19339,18 +19405,18 @@ }, { "c": "NAME-YOUR-ANIMATION", - "t": "at-rule.each.entity.function.keyframes.meta.name.property-list.scss.while", + "t": "at-rule.custom.each.entity.meta.name.property-list.scss.tag.while", "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.name.function rgb(220, 220, 170)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.name.function rgb(121, 94, 38)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.name.function rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" + "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.name.tag rgb(86, 156, 214)", + "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.name.tag rgb(128, 0, 0)", + "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.name.tag rgb(86, 156, 214)", + "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.entity.name.tag rgb(128, 0, 0)", + "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.entity.name.tag rgb(86, 156, 214)" } }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19361,7 +19427,7 @@ }, { "c": "{", - "t": "at-rule.begin.each.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.begin.bracket.curly.each.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19371,30 +19437,8 @@ } }, { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "0%", - "t": "at-rule.attribute-name.each.entity.keyframes.meta.other.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.other.attribute-name rgb(156, 220, 254)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.other.attribute-name.scss rgb(128, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.other.attribute-name rgb(156, 220, 254)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.entity.other.attribute-name.scss rgb(128, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.entity.other.attribute-name rgb(156, 220, 254)" - } - }, - { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "c": " 0% ", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19405,7 +19449,7 @@ }, { "c": "{", - "t": "at-rule.begin.bracket.curly.each.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.begin.bracket.curly.each.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19416,7 +19460,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19427,7 +19471,7 @@ }, { "c": "opacity", - "t": "at-rule.each.keyframes.meta.property-list.property-name.scss.support.type.while", + "t": "at-rule.each.meta.property-list.property-name.scss.support.type.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.support.type.property-name rgb(156, 220, 254)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.support.type.property-name.scss rgb(255, 0, 0)", @@ -19438,7 +19482,7 @@ }, { "c": ":", - "t": "at-rule.each.key-value.keyframes.meta.property-list.punctuation.scss.separator.while", + "t": "at-rule.each.key-value.meta.property-list.punctuation.scss.separator.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19449,7 +19493,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19460,7 +19504,7 @@ }, { "c": "0", - "t": "at-rule.constant.each.keyframes.meta.numeric.property-list.property-value.scss.while", + "t": "at-rule.constant.each.meta.numeric.property-list.property-value.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.constant.numeric rgb(181, 206, 168)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.constant.numeric rgb(9, 136, 90)", @@ -19471,7 +19515,7 @@ }, { "c": ";", - "t": "at-rule.each.keyframes.meta.property-list.punctuation.rule.scss.terminator.while", + "t": "at-rule.each.meta.property-list.punctuation.rule.scss.terminator.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19482,7 +19526,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19493,7 +19537,7 @@ }, { "c": "}", - "t": "at-rule.bracket.curly.each.end.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.bracket.curly.each.end.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19503,30 +19547,8 @@ } }, { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark .token rgb(212, 212, 212)", - "light_plus": ".vs .token rgb(0, 0, 0)", - "dark_vs": ".vs-dark .token rgb(212, 212, 212)", - "light_vs": ".vs .token rgb(0, 0, 0)", - "hc_black": ".hc-black .token rgb(255, 255, 255)" - } - }, - { - "c": "100%", - "t": "at-rule.attribute-name.each.entity.keyframes.meta.other.property-list.scss.while", - "r": { - "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.entity.other.attribute-name rgb(156, 220, 254)", - "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.entity.other.attribute-name.scss rgb(128, 0, 0)", - "dark_vs": ".vs-dark.vscode-theme-defaults-themes-dark_vs-json .token.entity.other.attribute-name rgb(156, 220, 254)", - "light_vs": ".vs.vscode-theme-defaults-themes-light_vs-json .token.entity.other.attribute-name.scss rgb(128, 0, 0)", - "hc_black": ".hc-black.vscode-theme-defaults-themes-hc_black-json .token.entity.other.attribute-name rgb(156, 220, 254)" - } - }, - { - "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "c": " 100% ", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19537,7 +19559,7 @@ }, { "c": "{", - "t": "at-rule.begin.bracket.curly.each.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.begin.bracket.curly.each.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19548,7 +19570,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19559,7 +19581,7 @@ }, { "c": "opacity", - "t": "at-rule.each.keyframes.meta.property-list.property-name.scss.support.type.while", + "t": "at-rule.each.meta.property-list.property-name.scss.support.type.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.support.type.property-name rgb(156, 220, 254)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.support.type.property-name.scss rgb(255, 0, 0)", @@ -19570,7 +19592,7 @@ }, { "c": ":", - "t": "at-rule.each.key-value.keyframes.meta.property-list.punctuation.scss.separator.while", + "t": "at-rule.each.key-value.meta.property-list.punctuation.scss.separator.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19581,7 +19603,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19592,7 +19614,7 @@ }, { "c": "1", - "t": "at-rule.constant.each.keyframes.meta.numeric.property-list.property-value.scss.while", + "t": "at-rule.constant.each.meta.numeric.property-list.property-value.scss.while", "r": { "dark_plus": ".vs-dark.vscode-theme-defaults-themes-dark_plus-json .token.constant.numeric rgb(181, 206, 168)", "light_plus": ".vs.vscode-theme-defaults-themes-light_plus-json .token.constant.numeric rgb(9, 136, 90)", @@ -19603,7 +19625,7 @@ }, { "c": ";", - "t": "at-rule.each.keyframes.meta.property-list.punctuation.rule.scss.terminator.while", + "t": "at-rule.each.meta.property-list.punctuation.rule.scss.terminator.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19614,7 +19636,7 @@ }, { "c": " ", - "t": "at-rule.each.keyframes.meta.property-list.scss.while", + "t": "at-rule.each.meta.property-list.scss.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19625,7 +19647,7 @@ }, { "c": "}", - "t": "at-rule.bracket.curly.each.end.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.bracket.curly.each.end.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", @@ -19636,7 +19658,7 @@ }, { "c": "}", - "t": "at-rule.each.end.keyframes.meta.property-list.punctuation.scss.section.while", + "t": "at-rule.bracket.curly.each.end.meta.property-list.punctuation.scss.section.while", "r": { "dark_plus": ".vs-dark .token rgb(212, 212, 212)", "light_plus": ".vs .token rgb(0, 0, 0)", diff --git a/extensions/shellscript/package.json b/extensions/shellscript/package.json index 46f456c1087..94b8ad1db0a 100644 --- a/extensions/shellscript/package.json +++ b/extensions/shellscript/package.json @@ -12,7 +12,7 @@ "aliases": ["Shell Script (Bash)", "shellscript", "bash", "sh", "zsh"], "extensions": [".sh", ".bash", ".bashrc", ".bash_aliases", ".bash_profile", ".bash_login", ".ebuild", ".install", ".profile", ".bash_logout", ".zsh", ".zshrc", ".zprofile", ".zlogin", ".zlogout", ".zshenv"], "filenames": ["PKGBUILD"], - "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh)|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", + "firstLine": "^#!.*\\b(bash|zsh|sh|tcsh).*|^#\\s*-\\*-[^*]*mode:\\s*shell-script[^*]*-\\*-", "configuration": "./language-configuration.json", "mimetypes": ["text/x-shellscript"] }], diff --git a/extensions/theme-defaults/fileicons/images/Document_16x.svg b/extensions/theme-defaults/fileicons/images/Document_16x.svg old mode 100755 new mode 100644 index 6fe50edf379..46a9f38cc88 --- a/extensions/theme-defaults/fileicons/images/Document_16x.svg +++ b/extensions/theme-defaults/fileicons/images/Document_16x.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/extensions/theme-defaults/fileicons/images/FolderOpen_16x.svg b/extensions/theme-defaults/fileicons/images/FolderOpen_16x.svg old mode 100755 new mode 100644 index 09903fb5c8c..1a3933d6351 --- a/extensions/theme-defaults/fileicons/images/FolderOpen_16x.svg +++ b/extensions/theme-defaults/fileicons/images/FolderOpen_16x.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/extensions/theme-defaults/fileicons/images/Folder_16x.svg b/extensions/theme-defaults/fileicons/images/Folder_16x.svg old mode 100755 new mode 100644 index 431af214571..3d64ae71db4 --- a/extensions/theme-defaults/fileicons/images/Folder_16x.svg +++ b/extensions/theme-defaults/fileicons/images/Folder_16x.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/extensions/theme-defaults/fileicons/vs_standard.json b/extensions/theme-defaults/fileicons/vs_minimal_icons.json similarity index 100% rename from extensions/theme-defaults/fileicons/vs_standard.json rename to extensions/theme-defaults/fileicons/vs_minimal_icons.json diff --git a/extensions/theme-defaults/package.json b/extensions/theme-defaults/package.json index a89c117d6b8..f7e58ccea08 100644 --- a/extensions/theme-defaults/package.json +++ b/extensions/theme-defaults/package.json @@ -36,9 +36,9 @@ ], "iconThemes": [ { - "id": "vs-standard", - "label": "Standard", - "path": "./fileicons/vs_standard.json" + "id": "vs-minimal", + "label": "Minimal (Visual Studio Code)", + "path": "./fileicons/vs_minimal_icons.json" } ] } diff --git a/extensions/theme-seti/OSSREADME.json b/extensions/theme-seti/OSSREADME.json new file mode 100644 index 00000000000..7601d594b32 --- /dev/null +++ b/extensions/theme-seti/OSSREADME.json @@ -0,0 +1,8 @@ +// ATTENTION - THIS DIRECTORY CONTAINS THIRD PARTY OPEN SOURCE MATERIALS: + +[{ + "name": "seti-ui", + "version": "0.1.0", + "repositoryURL": "https://github.com/jesseweed/seti-ui", + "description": "The file ./icons/seti.woff has been copied from https://github.com/jesseweed/seti-ui/blob/master/styles/_fonts/seti/seti.woff" +}] diff --git a/extensions/theme-seti/build/update-icon-theme.js b/extensions/theme-seti/build/update-icon-theme.js new file mode 100644 index 00000000000..c8ff7733533 --- /dev/null +++ b/extensions/theme-seti/build/update-icon-theme.js @@ -0,0 +1,169 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +var path = require('path'); +var fs = require('fs'); +var https = require('https'); +var url = require('url'); + +function getCommitSha(repoId, repoPath) { + var commitInfo = 'https://api.github.com/repos/' + repoId + '/commits?path=' + repoPath; + return download(commitInfo).then(function (content) { + try { + let lastCommit = JSON.parse(content)[0]; + return Promise.resolve({ + commitSha: lastCommit.sha, + commitDate: lastCommit.commit.author.date + }); + } catch (e) { + return Promise.resolve(null); + } + }, function () { + console.err('Failed loading ' + commitInfo); + return Promise.resolve(null); + }); +} + +function download(urlString) { + return new Promise((c, e) => { + var _url = url.parse(urlString); + var options = { host: _url.host, port: _url.port, path: _url.path, headers: { 'User-Agent': 'NodeJS' }}; + var content = ''; + var request = https.get(options, function (response) { + response.on('data', function (data) { + content += data.toString(); + }).on('end', function () { + c(content); + }); + }).on('error', function (err) { + e(err.message); + }); + }); +} + +function invertColor(color) { + var res = '#' + for (var i = 1; i < 7; i+=2) { + var newVal = 255 - parseInt('0x' + color.substr(i, 2), 16); + res += newVal.toString(16); + } + return res; +} + + +exports.update = function () { + var fontMappings = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/_fonts/seti.less'; + console.log('Reading from ' + fontMappings); + var def2Content = {}; + var ext2Def = {}; + var fileName2Def = {}; + var def2ColorId = {}; + var colorId2Value = {}; + + function writeFileIconContent(info) { + var iconDefinitions = {}; + + for (var def in def2Content) { + var entry = { fontCharacter: def2Content[def] }; + var colorId = def2ColorId[def]; + if (colorId) { + var colorValue = colorId2Value[colorId]; + if (colorValue) { + entry.fontColor = colorValue; + + var entryInverse = { fontCharacter: entry.fontCharacter, fontColor: invertColor(colorValue) }; + iconDefinitions[def + '_light'] = entryInverse; + } + } + iconDefinitions[def] = entry; + } + + function getInvertSet(input) { + var result = {}; + for (var assoc in input) { + let invertDef = input[assoc] + '_light';; + if (iconDefinitions[invertDef]) { + result[assoc] = invertDef; + } + } + return result; + } + + var res = { + fonts: [{ + id: "seti", + src: [{ "path": "./seti.woff", "format": "woff" }], + weight: "normal", + style: "normal", + size: "150%" + }], + iconDefinitions: iconDefinitions, + // folder: "_folder", + file: "_default", + fileExtensions: ext2Def, + fileNames: fileName2Def, + light: { + file: "_default_light", + fileExtensions: getInvertSet(ext2Def), + fileNames: getInvertSet(fileName2Def) + }, + version: 'https://github.com/jesseweed/seti-ui/commit/' + info.commitSha, + }; + fs.writeFileSync('./icons/seti-icon-theme.json', JSON.stringify(res, null, '\t')); + + } + + + var match; + + return download(fontMappings).then(function (content) { + var regex = /@([\w-]+):\s*'(\\E[0-9A-F]+)';/g; + while ((match = regex.exec(content)) !== null) { + def2Content['_' + match[1]] = match[2]; + } + + var mappings = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/icons/mapping.less'; + return download(mappings).then(function (content) { + var regex2 = /\.icon-(?:set|partial)\('([\w-\.]+)',\s*'([\w-]+)',\s*(@[\w-]+)\)/g; + while ((match = regex2.exec(content)) !== null) { + let pattern = match[1]; + let def = '_' + match[2]; + let colorId = match[3]; + if (pattern[0] === '.') { + ext2Def[pattern.substr(1)] = def; + } else { + fileName2Def[pattern] = def; + } + def2ColorId[def] = colorId; + } + var colors = 'https://raw.githubusercontent.com/jesseweed/seti-ui/master/styles/ui-variables.less'; + return download(colors).then(function (content) { + var regex3 = /(@[\w-]+):\s*(#[0-9a-z]+)/g; + while ((match = regex3.exec(content)) !== null) { + colorId2Value[match[1]] = match[2]; + } + return getCommitSha('jesseweed/seti-ui', 'styles/_fonts/seti.less').then(function (info) { + try { + writeFileIconContent(info); + if (info) { + console.log('Updated to jesseweed/seti-ui@' + info.commitSha.substr(0, 7) + ' (' + info.commitDate.substr(0, 10) + ')'); + } + } catch (e) { + console.error(e); + } + }); + }); + }); + }, console.error); +} + +if (path.basename(process.argv[1]) === 'update-icon-theme.js') { + exports.update(); +} + + + diff --git a/extensions/theme-seti/icons/seti.woff b/extensions/theme-seti/icons/seti.woff new file mode 100644 index 00000000000..d6446457fcb Binary files /dev/null and b/extensions/theme-seti/icons/seti.woff differ diff --git a/extensions/theme-seti/icons/vs-seti-icon-theme.json b/extensions/theme-seti/icons/vs-seti-icon-theme.json new file mode 100644 index 00000000000..fbf3e22199c --- /dev/null +++ b/extensions/theme-seti/icons/vs-seti-icon-theme.json @@ -0,0 +1,1036 @@ +{ + "fonts": [ + { + "id": "seti", + "src": [ + { + "path": "./seti.woff", + "format": "woff" + } + ], + "weight": "normal", + "style": "normal", + "size": "150%" + } + ], + "iconDefinitions": { + "_apple": { + "fontCharacter": "\\E001" + }, + "_audio_light": { + "fontCharacter": "\\E002", + "fontColor": "#5f8b3b" + }, + "_audio": { + "fontCharacter": "\\E002", + "fontColor": "#a074c4" + }, + "_bower_light": { + "fontCharacter": "\\E003", + "fontColor": "#1c86cc" + }, + "_bower": { + "fontCharacter": "\\E003", + "fontColor": "#e37933" + }, + "_c-sharp_light": { + "fontCharacter": "\\E004", + "fontColor": "#ae6545" + }, + "_c-sharp": { + "fontCharacter": "\\E004", + "fontColor": "#519aba" + }, + "_c_light": { + "fontCharacter": "\\E005", + "fontColor": "#3434be" + }, + "_c": { + "fontCharacter": "\\E005", + "fontColor": "#cbcb41" + }, + "_cake_php_light": { + "fontCharacter": "\\E006", + "fontColor": "#33c1bb" + }, + "_cake_php": { + "fontCharacter": "\\E006", + "fontColor": "#cc3e44" + }, + "_checkbox-unchecked": { + "fontCharacter": "\\E007" + }, + "_checkbox": { + "fontCharacter": "\\E008" + }, + "_cjsx": { + "fontCharacter": "\\E009" + }, + "_clock_light": { + "fontCharacter": "\\E00A", + "fontColor": "#927f79" + }, + "_clock": { + "fontCharacter": "\\E00A", + "fontColor": "#6d8086" + }, + "_coffee_light": { + "fontCharacter": "\\E00B", + "fontColor": "#3434be" + }, + "_coffee": { + "fontCharacter": "\\E00B", + "fontColor": "#cbcb41" + }, + "_coffee_erb": { + "fontCharacter": "\\E00C" + }, + "_coldfusion_light": { + "fontCharacter": "\\E00D", + "fontColor": "#ae6545" + }, + "_coldfusion": { + "fontCharacter": "\\E00D", + "fontColor": "#519aba" + }, + "_config_light": { + "fontCharacter": "\\E00E", + "fontColor": "#927f79" + }, + "_config": { + "fontCharacter": "\\E00E", + "fontColor": "#6d8086" + }, + "_cpp_light": { + "fontCharacter": "\\E00F", + "fontColor": "#ae6545" + }, + "_cpp": { + "fontCharacter": "\\E00F", + "fontColor": "#519aba" + }, + "_css_light": { + "fontCharacter": "\\E010", + "fontColor": "#ae6545" + }, + "_css": { + "fontCharacter": "\\E010", + "fontColor": "#519aba" + }, + "_csv_light": { + "fontCharacter": "\\E011", + "fontColor": "#723eb6" + }, + "_csv": { + "fontCharacter": "\\E011", + "fontColor": "#8dc149" + }, + "_default_light": { + "fontCharacter": "\\E012", + "fontColor": "#2b2829" + }, + "_default": { + "fontCharacter": "\\E012", + "fontColor": "#d4d7d6" + }, + "_deprecation-cop": { + "fontCharacter": "\\E013" + }, + "_docker_light": { + "fontCharacter": "\\E014", + "fontColor": "#ae6545" + }, + "_docker": { + "fontCharacter": "\\E014", + "fontColor": "#519aba" + }, + "_editorconfig_light": { + "fontCharacter": "\\E015", + "fontColor": "#ae6545" + }, + "_editorconfig": { + "fontCharacter": "\\E015", + "fontColor": "#519aba" + }, + "_ejs_light": { + "fontCharacter": "\\E016", + "fontColor": "#3434be" + }, + "_ejs": { + "fontCharacter": "\\E016", + "fontColor": "#cbcb41" + }, + "_elm_light": { + "fontCharacter": "\\E017", + "fontColor": "#ae6545" + }, + "_elm": { + "fontCharacter": "\\E017", + "fontColor": "#519aba" + }, + "_error": { + "fontCharacter": "\\E018" + }, + "_favicon_light": { + "fontCharacter": "\\E019", + "fontColor": "#3434be" + }, + "_favicon": { + "fontCharacter": "\\E019", + "fontColor": "#cbcb41" + }, + "_folder": { + "fontCharacter": "\\E01A" + }, + "_font_light": { + "fontCharacter": "\\E01B", + "fontColor": "#33c1bb" + }, + "_font": { + "fontCharacter": "\\E01B", + "fontColor": "#cc3e44" + }, + "_git_folder": { + "fontCharacter": "\\E01C" + }, + "_git_ignore": { + "fontCharacter": "\\E01D" + }, + "_github_light": { + "fontCharacter": "\\E01E", + "fontColor": "#2b2829" + }, + "_github": { + "fontCharacter": "\\E01E", + "fontColor": "#d4d7d6" + }, + "_go_light": { + "fontCharacter": "\\E01F", + "fontColor": "#ae6545" + }, + "_go": { + "fontCharacter": "\\E01F", + "fontColor": "#519aba" + }, + "_go2_light": { + "fontCharacter": "\\E020", + "fontColor": "#ae6545" + }, + "_go2": { + "fontCharacter": "\\E020", + "fontColor": "#519aba" + }, + "_gradle_light": { + "fontCharacter": "\\E021", + "fontColor": "#723eb6" + }, + "_gradle": { + "fontCharacter": "\\E021", + "fontColor": "#8dc149" + }, + "_grails_light": { + "fontCharacter": "\\E022", + "fontColor": "#723eb6" + }, + "_grails": { + "fontCharacter": "\\E022", + "fontColor": "#8dc149" + }, + "_grunt_light": { + "fontCharacter": "\\E023", + "fontColor": "#1c86cc" + }, + "_grunt": { + "fontCharacter": "\\E023", + "fontColor": "#e37933" + }, + "_gulp_light": { + "fontCharacter": "\\E024", + "fontColor": "#33c1bb" + }, + "_gulp": { + "fontCharacter": "\\E024", + "fontColor": "#cc3e44" + }, + "_hacklang_light": { + "fontCharacter": "\\E025", + "fontColor": "#1c86cc" + }, + "_hacklang": { + "fontCharacter": "\\E025", + "fontColor": "#e37933" + }, + "_haml_light": { + "fontCharacter": "\\E026", + "fontColor": "#33c1bb" + }, + "_haml": { + "fontCharacter": "\\E026", + "fontColor": "#cc3e44" + }, + "_haskell_light": { + "fontCharacter": "\\E027", + "fontColor": "#5f8b3b" + }, + "_haskell": { + "fontCharacter": "\\E027", + "fontColor": "#a074c4" + }, + "_heroku_light": { + "fontCharacter": "\\E028", + "fontColor": "#5f8b3b" + }, + "_heroku": { + "fontCharacter": "\\E028", + "fontColor": "#a074c4" + }, + "_html_light": { + "fontCharacter": "\\E029", + "fontColor": "#1c86cc" + }, + "_html": { + "fontCharacter": "\\E029", + "fontColor": "#e37933" + }, + "_html_erb": { + "fontCharacter": "\\E02A" + }, + "_ignored_light": { + "fontCharacter": "\\E02B", + "fontColor": "#beaca4" + }, + "_ignored": { + "fontCharacter": "\\E02B", + "fontColor": "#41535b" + }, + "_illustrator_light": { + "fontCharacter": "\\E02C", + "fontColor": "#3434be" + }, + "_illustrator": { + "fontCharacter": "\\E02C", + "fontColor": "#cbcb41" + }, + "_image_light": { + "fontCharacter": "\\E02D", + "fontColor": "#5f8b3b" + }, + "_image": { + "fontCharacter": "\\E02D", + "fontColor": "#a074c4" + }, + "_info": { + "fontCharacter": "\\E02E" + }, + "_ionic_light": { + "fontCharacter": "\\E02F", + "fontColor": "#ae6545" + }, + "_ionic": { + "fontCharacter": "\\E02F", + "fontColor": "#519aba" + }, + "_jade_light": { + "fontCharacter": "\\E030", + "fontColor": "#33c1bb" + }, + "_jade": { + "fontCharacter": "\\E030", + "fontColor": "#cc3e44" + }, + "_java_light": { + "fontCharacter": "\\E031", + "fontColor": "#33c1bb" + }, + "_java": { + "fontCharacter": "\\E031", + "fontColor": "#cc3e44" + }, + "_javascript_light": { + "fontCharacter": "\\E032", + "fontColor": "#ae6545" + }, + "_javascript": { + "fontCharacter": "\\E032", + "fontColor": "#519aba" + }, + "_js_erb": { + "fontCharacter": "\\E033" + }, + "_json_light": { + "fontCharacter": "\\E034", + "fontColor": "#3434be" + }, + "_json": { + "fontCharacter": "\\E034", + "fontColor": "#cbcb41" + }, + "_julia_light": { + "fontCharacter": "\\E035", + "fontColor": "#5f8b3b" + }, + "_julia": { + "fontCharacter": "\\E035", + "fontColor": "#a074c4" + }, + "_karma_light": { + "fontCharacter": "\\E036", + "fontColor": "#723eb6" + }, + "_karma": { + "fontCharacter": "\\E036", + "fontColor": "#8dc149" + }, + "_less_light": { + "fontCharacter": "\\E037", + "fontColor": "#ae6545" + }, + "_less": { + "fontCharacter": "\\E037", + "fontColor": "#519aba" + }, + "_license_light": { + "fontCharacter": "\\E038", + "fontColor": "#3434be" + }, + "_license": { + "fontCharacter": "\\E038", + "fontColor": "#cbcb41" + }, + "_liquid_light": { + "fontCharacter": "\\E039", + "fontColor": "#723eb6" + }, + "_liquid": { + "fontCharacter": "\\E039", + "fontColor": "#8dc149" + }, + "_livescript_light": { + "fontCharacter": "\\E03A", + "fontColor": "#ae6545" + }, + "_livescript": { + "fontCharacter": "\\E03A", + "fontColor": "#519aba" + }, + "_lua_light": { + "fontCharacter": "\\E03B", + "fontColor": "#ae6545" + }, + "_lua": { + "fontCharacter": "\\E03B", + "fontColor": "#519aba" + }, + "_markdown_light": { + "fontCharacter": "\\E03C", + "fontColor": "#ae6545" + }, + "_markdown": { + "fontCharacter": "\\E03C", + "fontColor": "#519aba" + }, + "_mustache_light": { + "fontCharacter": "\\E03D", + "fontColor": "#1c86cc" + }, + "_mustache": { + "fontCharacter": "\\E03D", + "fontColor": "#e37933" + }, + "_new-file": { + "fontCharacter": "\\E03E" + }, + "_npm_light": { + "fontCharacter": "\\E03F", + "fontColor": "#33c1bb" + }, + "_npm": { + "fontCharacter": "\\E03F", + "fontColor": "#cc3e44" + }, + "_npm_ignored_light": { + "fontCharacter": "\\E040", + "fontColor": "#beaca4" + }, + "_npm_ignored": { + "fontCharacter": "\\E040", + "fontColor": "#41535b" + }, + "_ocaml_light": { + "fontCharacter": "\\E041", + "fontColor": "#1c86cc" + }, + "_ocaml": { + "fontCharacter": "\\E041", + "fontColor": "#e37933" + }, + "_pdf_light": { + "fontCharacter": "\\E042", + "fontColor": "#33c1bb" + }, + "_pdf": { + "fontCharacter": "\\E042", + "fontColor": "#cc3e44" + }, + "_perl_light": { + "fontCharacter": "\\E043", + "fontColor": "#ae6545" + }, + "_perl": { + "fontCharacter": "\\E043", + "fontColor": "#519aba" + }, + "_photoshop_light": { + "fontCharacter": "\\E044", + "fontColor": "#ae6545" + }, + "_photoshop": { + "fontCharacter": "\\E044", + "fontColor": "#519aba" + }, + "_php_light": { + "fontCharacter": "\\E045", + "fontColor": "#5f8b3b" + }, + "_php": { + "fontCharacter": "\\E045", + "fontColor": "#a074c4" + }, + "_project": { + "fontCharacter": "\\E046" + }, + "_pug_light": { + "fontCharacter": "\\E047", + "fontColor": "#ae6545" + }, + "_pug": { + "fontCharacter": "\\E047", + "fontColor": "#519aba" + }, + "_puppet_light": { + "fontCharacter": "\\E048", + "fontColor": "#5f8b3b" + }, + "_puppet": { + "fontCharacter": "\\E048", + "fontColor": "#a074c4" + }, + "_python_light": { + "fontCharacter": "\\E049", + "fontColor": "#ae6545" + }, + "_python": { + "fontCharacter": "\\E049", + "fontColor": "#519aba" + }, + "_rails": { + "fontCharacter": "\\E04A" + }, + "_react_light": { + "fontCharacter": "\\E04B", + "fontColor": "#ae6545" + }, + "_react": { + "fontCharacter": "\\E04B", + "fontColor": "#519aba" + }, + "_ruby_light": { + "fontCharacter": "\\E04C", + "fontColor": "#33c1bb" + }, + "_ruby": { + "fontCharacter": "\\E04C", + "fontColor": "#cc3e44" + }, + "_rust_light": { + "fontCharacter": "\\E04D", + "fontColor": "#927f79" + }, + "_rust": { + "fontCharacter": "\\E04D", + "fontColor": "#6d8086" + }, + "_sass_light": { + "fontCharacter": "\\E04E", + "fontColor": "#aac7a" + }, + "_sass": { + "fontCharacter": "\\E04E", + "fontColor": "#f55385" + }, + "_sbt_light": { + "fontCharacter": "\\E04F", + "fontColor": "#ae6545" + }, + "_sbt": { + "fontCharacter": "\\E04F", + "fontColor": "#519aba" + }, + "_scala_light": { + "fontCharacter": "\\E050", + "fontColor": "#33c1bb" + }, + "_scala": { + "fontCharacter": "\\E050", + "fontColor": "#cc3e44" + }, + "_search": { + "fontCharacter": "\\E051" + }, + "_settings": { + "fontCharacter": "\\E052" + }, + "_shell_light": { + "fontCharacter": "\\E053", + "fontColor": "#b2a5a1" + }, + "_shell": { + "fontCharacter": "\\E053", + "fontColor": "#4d5a5e" + }, + "_slim_light": { + "fontCharacter": "\\E054", + "fontColor": "#1c86cc" + }, + "_slim": { + "fontCharacter": "\\E054", + "fontColor": "#e37933" + }, + "_smarty_light": { + "fontCharacter": "\\E055", + "fontColor": "#3434be" + }, + "_smarty": { + "fontCharacter": "\\E055", + "fontColor": "#cbcb41" + }, + "_stylus_light": { + "fontCharacter": "\\E056", + "fontColor": "#723eb6" + }, + "_stylus": { + "fontCharacter": "\\E056", + "fontColor": "#8dc149" + }, + "_svg_light": { + "fontCharacter": "\\E057", + "fontColor": "#5f8b3b" + }, + "_svg": { + "fontCharacter": "\\E057", + "fontColor": "#a074c4" + }, + "_swift_light": { + "fontCharacter": "\\E058", + "fontColor": "#1c86cc" + }, + "_swift": { + "fontCharacter": "\\E058", + "fontColor": "#e37933" + }, + "_terraform_light": { + "fontCharacter": "\\E059", + "fontColor": "#5f8b3b" + }, + "_terraform": { + "fontCharacter": "\\E059", + "fontColor": "#a074c4" + }, + "_tex_light": { + "fontCharacter": "\\E05A", + "fontColor": "#2b2829" + }, + "_tex": { + "fontCharacter": "\\E05A", + "fontColor": "#d4d7d6" + }, + "_time-cop": { + "fontCharacter": "\\E05B" + }, + "_todo": { + "fontCharacter": "\\E05C" + }, + "_twig_light": { + "fontCharacter": "\\E05D", + "fontColor": "#723eb6" + }, + "_twig": { + "fontCharacter": "\\E05D", + "fontColor": "#8dc149" + }, + "_typescript_light": { + "fontCharacter": "\\E05E", + "fontColor": "#ae6545" + }, + "_typescript": { + "fontCharacter": "\\E05E", + "fontColor": "#519aba" + }, + "_vala_light": { + "fontCharacter": "\\E05F", + "fontColor": "#927f79" + }, + "_vala": { + "fontCharacter": "\\E05F", + "fontColor": "#6d8086" + }, + "_video_light": { + "fontCharacter": "\\E060", + "fontColor": "#aac7a" + }, + "_video": { + "fontCharacter": "\\E060", + "fontColor": "#f55385" + }, + "_xml_light": { + "fontCharacter": "\\E061", + "fontColor": "#1c86cc" + }, + "_xml": { + "fontCharacter": "\\E061", + "fontColor": "#e37933" + }, + "_yml_light": { + "fontCharacter": "\\E062", + "fontColor": "#5f8b3b" + }, + "_yml": { + "fontCharacter": "\\E062", + "fontColor": "#a074c4" + } + }, + "file": "_default", + "fileExtensions": { + "cpp": "_cpp", + "c": "_c", + "cs": "_c-sharp", + "cc": "_cpp", + "cfc": "_coldfusion", + "cfm": "_coldfusion", + "coffee": "_coffee", + "config": "_config", + "cson": "_json", + "css": "_css", + "css.map": "_css", + "sss": "_css", + "csv": "_csv", + "ctp": "_cake_php", + "ejs": "_ejs", + "elm": "_elm", + "ico": "_favicon", + "gitignore": "_github", + "gitconfig": "_github", + "gitkeep": "_github", + "gitattributes": "_github", + "go": "_go2", + "slide": "_go", + "article": "_go", + "gradle": "_gradle", + "groovy": "_grails", + "gsp": "_grails", + "hh": "_hacklang", + "haml": "_haml", + "handlebars": "_mustache", + "hbs": "_mustache", + "hjs": "_mustache", + "hs": "_haskell", + "lhs": "_haskell", + "html": "_html", + "jade": "_jade", + "java": "_java", + "class": "_java", + "classpath": "_java", + "js": "_javascript", + "js.map": "_javascript", + "es": "_javascript", + "es5": "_javascript", + "es6": "_javascript", + "es7": "_javascript", + "json": "_json", + "jl": "_julia", + "less": "_less", + "liquid": "_liquid", + "ls": "_livescript", + "lua": "_lua", + "markdown": "_markdown", + "md": "_markdown", + "mustache": "_mustache", + "stache": "_mustache", + "npm-debug.log": "_npm", + "npmignore": "_npm", + "h": "_c", + "m": "_c", + "ml": "_ocaml", + "mli": "_ocaml", + "cmx": "_ocaml", + "cmxa": "_ocaml", + "pl": "_perl", + "php": "_php", + "php.inc": "_php", + "pug": "_pug", + "pp": "_puppet", + "py": "_python", + "jsx": "_react", + "cjsx": "_react", + "tsx": "_react", + "rb": "_ruby", + "erb": "_ruby", + "erb.html": "_ruby", + "html.erb": "_ruby", + "rs": "_rust", + "sass": "_sass", + "scss": "_sass", + "slim": "_slim", + "smarty.tpl": "_smarty", + "sbt": "_sbt", + "scala": "_scala", + "styl": "_stylus", + "swift": "_swift", + "tf": "_terraform", + "tf.json": "_terraform", + "tex": "_tex", + "sty": "_tex", + "cls": "_tex", + "dtx": "_tex", + "ins": "_tex", + "txt": "_default", + "twig": "_twig", + "ts": "_typescript", + "vala": "_vala", + "vapi": "_vala", + "xml": "_xml", + "yml": "_yml", + "yaml": "_yml", + "ai": "_illustrator", + "psd": "_photoshop", + "pdf": "_pdf", + "eot": "_font", + "ttf": "_font", + "woff": "_font", + "woff2": "_font", + "png": "_image", + "gif": "_image", + "jpg": "_image", + "jpeg": "_image", + "svg": "_svg", + "svgx": "_image", + "cmd": "_shell", + "sh": "_shell", + "zsh": "_shell", + "fish": "_shell", + "zshrc": "_shell", + "mov": "_video", + "ogv": "_video", + "webm": "_video", + "avi": "_video", + "mpg": "_video", + "mp4": "_video", + "mp3": "_audio", + "ogg": "_audio", + "wav": "_audio", + "bowerrc": "_bower", + "jshintrc": "_javascript", + "jscsrc": "_javascript", + "direnv": "_config", + "env": "_config", + "static": "_config", + "editorconfig": "_editorconfig", + "slugignore": "_config", + "tmp": "_clock", + "DS_Store": "_ignored" + }, + "fileNames": { + "karma.conf.js": "_karma", + "karma.conf.coffee": "_karma", + "bower.json": "_bower", + "Bower.json": "_bower", + "dockerfile": "_docker", + "Dockerfile": "_docker", + "DOCKERFILE": "_docker", + "Gruntfile.js": "_grunt", + "gruntfile.babel.js": "_grunt", + "Gruntfile.babel.js": "_grunt", + "gruntfile.js": "_grunt", + "Gruntfile.coffee": "_grunt", + "gruntfile.coffee": "_grunt", + "GULPFILE": "_gulp", + "Gulpfile": "_gulp", + "gulpfile": "_gulp", + "ionic.config.json": "_ionic", + "Ionic.config.json": "_ionic", + "ionic.project": "_ionic", + "Ionic.project": "_ionic", + "LICENSE": "_license", + "Procfile": "_heroku", + "TODO": "_todo", + "npm-debug.log": "_npm_ignored" + }, + "light": { + "file": "_default_light", + "fileExtensions": { + "cpp": "_cpp_light", + "c": "_c_light", + "cs": "_c-sharp_light", + "cc": "_cpp_light", + "cfc": "_coldfusion_light", + "cfm": "_coldfusion_light", + "coffee": "_coffee_light", + "config": "_config_light", + "cson": "_json_light", + "css": "_css_light", + "css.map": "_css_light", + "sss": "_css_light", + "csv": "_csv_light", + "ctp": "_cake_php_light", + "ejs": "_ejs_light", + "elm": "_elm_light", + "ico": "_favicon_light", + "gitignore": "_github_light", + "gitconfig": "_github_light", + "gitkeep": "_github_light", + "gitattributes": "_github_light", + "go": "_go2_light", + "slide": "_go_light", + "article": "_go_light", + "gradle": "_gradle_light", + "groovy": "_grails_light", + "gsp": "_grails_light", + "hh": "_hacklang_light", + "haml": "_haml_light", + "handlebars": "_mustache_light", + "hbs": "_mustache_light", + "hjs": "_mustache_light", + "hs": "_haskell_light", + "lhs": "_haskell_light", + "html": "_html_light", + "jade": "_jade_light", + "java": "_java_light", + "class": "_java_light", + "classpath": "_java_light", + "js": "_javascript_light", + "js.map": "_javascript_light", + "es": "_javascript_light", + "es5": "_javascript_light", + "es6": "_javascript_light", + "es7": "_javascript_light", + "json": "_json_light", + "jl": "_julia_light", + "less": "_less_light", + "liquid": "_liquid_light", + "ls": "_livescript_light", + "lua": "_lua_light", + "markdown": "_markdown_light", + "md": "_markdown_light", + "mustache": "_mustache_light", + "stache": "_mustache_light", + "npm-debug.log": "_npm_light", + "npmignore": "_npm_light", + "h": "_c_light", + "m": "_c_light", + "ml": "_ocaml_light", + "mli": "_ocaml_light", + "cmx": "_ocaml_light", + "cmxa": "_ocaml_light", + "pl": "_perl_light", + "php": "_php_light", + "php.inc": "_php_light", + "pug": "_pug_light", + "pp": "_puppet_light", + "py": "_python_light", + "jsx": "_react_light", + "cjsx": "_react_light", + "tsx": "_react_light", + "rb": "_ruby_light", + "erb": "_ruby_light", + "erb.html": "_ruby_light", + "html.erb": "_ruby_light", + "rs": "_rust_light", + "sass": "_sass_light", + "scss": "_sass_light", + "slim": "_slim_light", + "smarty.tpl": "_smarty_light", + "sbt": "_sbt_light", + "scala": "_scala_light", + "styl": "_stylus_light", + "swift": "_swift_light", + "tf": "_terraform_light", + "tf.json": "_terraform_light", + "tex": "_tex_light", + "sty": "_tex_light", + "cls": "_tex_light", + "dtx": "_tex_light", + "ins": "_tex_light", + "txt": "_default_light", + "twig": "_twig_light", + "ts": "_typescript_light", + "vala": "_vala_light", + "vapi": "_vala_light", + "xml": "_xml_light", + "yml": "_yml_light", + "yaml": "_yml_light", + "ai": "_illustrator_light", + "psd": "_photoshop_light", + "pdf": "_pdf_light", + "eot": "_font_light", + "ttf": "_font_light", + "woff": "_font_light", + "woff2": "_font_light", + "png": "_image_light", + "gif": "_image_light", + "jpg": "_image_light", + "jpeg": "_image_light", + "svg": "_svg_light", + "svgx": "_image_light", + "cmd": "_shell_light", + "sh": "_shell_light", + "zsh": "_shell_light", + "fish": "_shell_light", + "zshrc": "_shell_light", + "mov": "_video_light", + "ogv": "_video_light", + "webm": "_video_light", + "avi": "_video_light", + "mpg": "_video_light", + "mp4": "_video_light", + "mp3": "_audio_light", + "ogg": "_audio_light", + "wav": "_audio_light", + "bowerrc": "_bower_light", + "jshintrc": "_javascript_light", + "jscsrc": "_javascript_light", + "direnv": "_config_light", + "env": "_config_light", + "static": "_config_light", + "editorconfig": "_editorconfig_light", + "slugignore": "_config_light", + "tmp": "_clock_light", + "DS_Store": "_ignored_light" + }, + "fileNames": { + "karma.conf.js": "_karma_light", + "karma.conf.coffee": "_karma_light", + "bower.json": "_bower_light", + "Bower.json": "_bower_light", + "dockerfile": "_docker_light", + "Dockerfile": "_docker_light", + "DOCKERFILE": "_docker_light", + "Gruntfile.js": "_grunt_light", + "gruntfile.babel.js": "_grunt_light", + "Gruntfile.babel.js": "_grunt_light", + "gruntfile.js": "_grunt_light", + "Gruntfile.coffee": "_grunt_light", + "gruntfile.coffee": "_grunt_light", + "GULPFILE": "_gulp_light", + "Gulpfile": "_gulp_light", + "gulpfile": "_gulp_light", + "ionic.config.json": "_ionic_light", + "Ionic.config.json": "_ionic_light", + "ionic.project": "_ionic_light", + "Ionic.project": "_ionic_light", + "LICENSE": "_license_light", + "Procfile": "_heroku_light", + "npm-debug.log": "_npm_ignored_light" + } + }, + "version": "https://github.com/jesseweed/seti-ui/commit/00ab2334531a5f91f9eb5cc49c584b2610682ba6" +} \ No newline at end of file diff --git a/extensions/theme-seti/package.json b/extensions/theme-seti/package.json new file mode 100644 index 00000000000..60bac73aa45 --- /dev/null +++ b/extensions/theme-seti/package.json @@ -0,0 +1,20 @@ +{ + "name": "vscode-theme-seti", + "private": true, + "version": "0.1.0", + "description": "A file icon theme made out of the Seti UI file idons", + "publisher": "vscode", + "scripts": { + "update": "node ./build/update-icon-theme.js" + }, + "engines": { "vscode": "*" }, + "contributes": { + "iconThemes": [ + { + "id": "vs-seti", + "label": "Seti (Visual Studio Code)", + "path": "./icons/vs-seti-icon-theme.json" + } + ] + } +} \ No newline at end of file diff --git a/extensions/theme-seti/thirdpartynotices.txt b/extensions/theme-seti/thirdpartynotices.txt new file mode 100644 index 00000000000..5faf4bb5a8f --- /dev/null +++ b/extensions/theme-seti/thirdpartynotices.txt @@ -0,0 +1,32 @@ + +THIRD-PARTY SOFTWARE NOTICES AND INFORMATION +For Microsoft vscode-theme-seti + +This file is based on or incorporates material from the projects listed below ("Third Party OSS"). The original copyright +notice and the license under which Microsoft received such Third Party OSS, are set forth below. Such licenses and notice +are provided for informational purposes only. Microsoft licenses the Third Party OSS to you under the licensing terms for +the Microsoft product or service. Microsoft reserves all other rights not expressly granted under this agreement, whether +by implication, estoppel or otherwise.† + +1. Seti UI - A subtle dark colored UI theme for Atom. (https://github.com/jesseweed/seti-ui) + +Copyright (c) 2014 Jesse Weed + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/extensions/typescript/OSSREADME.json b/extensions/typescript/OSSREADME.json index 91e2030ce8e..a62224cfd5c 100644 --- a/extensions/typescript/OSSREADME.json +++ b/extensions/typescript/OSSREADME.json @@ -192,14 +192,15 @@ ] }, { + "isLicense": true, // We override the license since we need a coorect Copyright "name": "typescript", - "version": "1.8.2", + "version": "1.8.10", "license": "Apache2", "repositoryURL": "https://github.com/Microsoft/TypeScript", "description": "The contents of the folder lib is from the TypeScript project https://github.com/Microsoft/TypeScript.", // Reason: LICENSE file does not include Copyright statement "licenseDetail": [ - "Copyright (c) Microsoft Corporation. All rights reserved. ", + "Copyright (c) Microsoft Corporation. All rights reserved.", "", "Apache License", "", diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index f6d2c907c08..d1ba19d4dbb 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -75,11 +75,6 @@ "default": null, "description": "%typescript.tsdk.desc%" }, - "typescript.tsdk_version": { - "type": ["string", "null"], - "default": null, - "description": "%typescript.tsdk_version.desc%" - }, "typescript.tsserver.trace": { "type": "string", "enum": ["off", "messages", "verbose"], diff --git a/extensions/typescript/src/features/hoverProvider.ts b/extensions/typescript/src/features/hoverProvider.ts index 32f7a5a5683..d0c56c80487 100644 --- a/extensions/typescript/src/features/hoverProvider.ts +++ b/extensions/typescript/src/features/hoverProvider.ts @@ -31,7 +31,7 @@ export default class TypeScriptHoverProvider implements HoverProvider { let data = response.body; if (data) { return new Hover( - [data.documentation, { language: 'typescript', value: data.displayString }], + [{ language: 'typescript', value: data.displayString }, data.documentation], new Range(data.start.line - 1, data.start.offset - 1, data.end.line - 1, data.end.offset - 1)); } }, (err) => { diff --git a/extensions/typescript/src/features/workspaceSymbolProvider.ts b/extensions/typescript/src/features/workspaceSymbolProvider.ts index 5b8dd5057da..1fced2f10fb 100644 --- a/extensions/typescript/src/features/workspaceSymbolProvider.ts +++ b/extensions/typescript/src/features/workspaceSymbolProvider.ts @@ -5,7 +5,7 @@ 'use strict'; -import { workspace, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind, Range, Location, CancellationToken } from 'vscode'; +import { workspace, window, Uri, WorkspaceSymbolProvider, SymbolInformation, SymbolKind, Range, Location, CancellationToken } from 'vscode'; import * as Proto from '../protocol'; import { ITypescriptServiceClient } from '../typescriptService'; @@ -30,14 +30,23 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo public provideWorkspaceSymbols(search: string, token :CancellationToken): Promise { // typescript wants to have a resource even when asking - // general questions so we check all open documents for - // one that is typescript'ish + // general questions so we check the active editor. If this + // doesn't match we take the first TS document. let uri: Uri; - let documents = workspace.textDocuments; - for (let document of documents) { - if (document.languageId === this.modeId) { + let editor = window.activeTextEditor; + if (editor) { + let document = editor.document; + if (document && document.languageId === this.modeId) { uri = document.uri; - break; + } + } + if (!uri) { + let documents = workspace.textDocuments; + for (let document of documents) { + if (document.languageId === this.modeId) { + uri = document.uri; + break; + } } } diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index bb15cff7848..c57ddb02862 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -9,7 +9,7 @@ * ------------------------------------------------------------------------------------------ */ 'use strict'; -import { env, languages, commands, workspace, window, Uri, ExtensionContext, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter } from 'vscode'; +import { env, languages, commands, workspace, window, Uri, ExtensionContext, Memento, IndentAction, Diagnostic, DiagnosticCollection, Range, DocumentFilter } from 'vscode'; // This must be the first statement otherwise modules might got loaded with // the wrong locale. @@ -67,7 +67,7 @@ export function activate(context: ExtensionContext): void { modeIds: [MODE_ID_JS, MODE_ID_JSX], extensions: ['.js', '.jsx'] } - ], context.storagePath); + ], context.storagePath, context.globalState); let client = clientHost.serviceClient; @@ -251,7 +251,6 @@ class LanguageProvider { public syntaxDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void { this.syntaxDiagnostics[file] = diagnostics; - this.currentDiagnostics.set(Uri.file(file), diagnostics); } public semanticDiagnosticsReceived(file: string, diagnostics: Diagnostic[]): void { @@ -273,7 +272,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { private languages: LanguageProvider[]; private languagePerId: Map; - constructor(descriptions: LanguageDescription[], storagePath: string) { + constructor(descriptions: LanguageDescription[], storagePath: string, globalState: Memento) { let handleProjectCreateOrDelete = () => { this.client.execute('reloadProjects', null, false); this.triggerAllDiagnostics(); @@ -288,7 +287,7 @@ class TypeScriptServiceClientHost implements ITypescriptServiceClientHost { watcher.onDidDelete(handleProjectCreateOrDelete); watcher.onDidChange(handleProjectChange); - this.client = new TypeScriptServiceClient(this, storagePath); + this.client = new TypeScriptServiceClient(this, storagePath, globalState); this.languages = []; this.languagePerId = Object.create(null); descriptions.forEach(description => { diff --git a/extensions/typescript/src/typescriptServiceClient.ts b/extensions/typescript/src/typescriptServiceClient.ts index 4bd942836b8..8dbf00ccc23 100644 --- a/extensions/typescript/src/typescriptServiceClient.ts +++ b/extensions/typescript/src/typescriptServiceClient.ts @@ -12,7 +12,7 @@ import * as fs from 'fs'; import * as electron from './utils/electron'; import { Reader } from './utils/wireProtocol'; -import { workspace, window, Uri, CancellationToken, OutputChannel } from 'vscode'; +import { workspace, window, Uri, CancellationToken, OutputChannel, Memento, MessageItem } from 'vscode'; import * as Proto from './protocol'; import { ITypescriptServiceClient, ITypescriptServiceClientHost, APIVersion } from './typescriptService'; @@ -66,10 +66,39 @@ namespace Trace { } } +enum MessageAction { + useLocal, + alwaysUseLocal, + useBundled, + doNotCheckAgain +} + +interface MyMessageItem extends MessageItem { + id: MessageAction; +} + + +function openUrl(url: string) { + let cmd: string; + switch (process.platform) { + case 'darwin': + cmd = 'open'; + break; + case 'win32': + cmd = 'start'; + break; + default: + cmd = 'xdg-open'; + } + return cp.exec(cmd + ' ' + url); +} + + export default class TypeScriptServiceClient implements ITypescriptServiceClient { private host: ITypescriptServiceClientHost; private storagePath: string; + private globalState: Memento; private pathSeparator: string; private _onReady: { promise: Promise; resolve: () => void; reject: () => void; }; @@ -94,9 +123,10 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private _apiVersion: APIVersion; private telemetryReporter: TelemetryReporter; - constructor(host: ITypescriptServiceClientHost, storagePath: string) { + constructor(host: ITypescriptServiceClientHost, storagePath: string, globalState: Memento) { this.host = host; this.storagePath = storagePath; + this.globalState = globalState; this.pathSeparator = path.sep; let p = new Promise((resolve, reject) => { @@ -250,74 +280,179 @@ export default class TypeScriptServiceClient implements ITypescriptServiceClient private startService(resendModels: boolean = false): void { let modulePath = path.join(__dirname, '..', 'server', 'typescript', 'lib', 'tsserver.js'); + let checkGlobalVersion = true; + let showVersionStatusItem = false; if (this.tsdk) { + checkGlobalVersion = false; if ((path).isAbsolute(this.tsdk)) { modulePath = path.join(this.tsdk, 'tsserver.js'); } else if (workspace.rootPath) { modulePath = path.join(workspace.rootPath, this.tsdk, 'tsserver.js'); } } - this.info(`Using tsserver from location: ${modulePath}`); - if (!fs.existsSync(modulePath)) { - window.showErrorMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. TypeScript language features will be disabled.', path.dirname(modulePath))); - return; - } + let versionCheckPromise: Thenable = Promise.resolve(modulePath); + const doLocalVersionCheckKey: string = 'doLocalVersionCheck'; - let version = this.getTypeScriptVersion(modulePath); - if (!version) { - version = workspace.getConfiguration().get('typescript.tsdk_version', undefined); - } - if (version) { - this._apiVersion = APIVersion.fromString(version); - } - const label = version || localize('versionNumber.custom' ,'custom'); - const tooltip = modulePath; - VersionStatus.enable(!!this.tsdk); - VersionStatus.setInfo(label, tooltip); - - this.servicePromise = new Promise((resolve, reject) => { - try { - let options: electron.IForkOptions = { - execArgv: [] // [`--debug-brk=5859`] - }; - let value = process.env.TSS_DEBUG; - if (value) { - let port = parseInt(value); - if (!isNaN(port)) { - this.info(`TSServer started in debug mode using port ${port}`); - options.execArgv = [`--debug=${port}`]; - } + if (!this.tsdk && workspace.rootPath && this.globalState.get(doLocalVersionCheckKey, true)) { + let localModulePath = path.join(workspace.rootPath, 'node_modules', 'typescript', 'lib', 'tsserver.js'); + if (fs.existsSync(localModulePath)) { + let localVersion = this.getTypeScriptVersion(localModulePath); + let shippedVersion = this.getTypeScriptVersion(modulePath); + if (localVersion && localVersion !== shippedVersion) { + checkGlobalVersion = false; + versionCheckPromise = window.showInformationMessage( + localize( + 'localTSFound', + 'The workspace folder contains TypeScript version {0}. Do you want to use this version instead of the bundled version {1}?', + localVersion, shippedVersion + ), + ...[{ + title: localize('use', 'Use {0}', localVersion), + id: MessageAction.useLocal + }, + { + title: localize('useBundled', 'Use {0}', shippedVersion), + id: MessageAction.useBundled, + }, + { + title: localize('useAlways', /*'Always use {0}'*/ 'More Information', localVersion), + id: MessageAction.alwaysUseLocal + }, + { + title: localize('doNotCheckAgain', 'Don\'t Check Again'), + id: MessageAction.doNotCheckAgain, + isCloseAffordance: true + }].reverse() + ).then((selected) => { + if (!selected) { + return modulePath; + } + switch(selected.id) { + case MessageAction.useLocal: + showVersionStatusItem = true; + return localModulePath; + case MessageAction.alwaysUseLocal: + window.showInformationMessage(localize('continueWithVersion', 'Continuing with version {0}', shippedVersion)); + openUrl('http://go.microsoft.com/fwlink/?LinkId=826239'); + return modulePath; + case MessageAction.useBundled: + return modulePath; + case MessageAction.doNotCheckAgain: + this.globalState.update(doLocalVersionCheckKey, false); + return modulePath; + default: + return modulePath; + } + }); } - electron.fork(modulePath, [], options, (err: any, childProcess: cp.ChildProcess) => { - if (err) { - this.lastError = err; - this.error('Starting TSServer failed with error.', err); - window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err)); - this.logTelemetry('error', {message: err.message}); - return; - } - this.lastStart = Date.now(); - childProcess.on('error', (err: Error) => { - this.lastError = err; - this.error('TSServer errored with error.', err); - this.serviceExited(false); - }); - childProcess.on('exit', (code: any) => { - this.error(`TSServer exited with code: ${code ? code : 'unknown'}`); - this.serviceExited(true); - }); - this.reader = new Reader(childProcess.stdout, (msg) => { - this.dispatchMessage(msg); - }); - this._onReady.resolve(); - resolve(childProcess); - }); - } catch (error) { - reject(error); } + } + versionCheckPromise.then((modulePath) => { + this.info(`Using tsserver from location: ${modulePath}`); + if (!fs.existsSync(modulePath)) { + window.showErrorMessage(localize('noServerFound', 'The path {0} doesn\'t point to a valid tsserver install. TypeScript language features will be disabled.', path.dirname(modulePath))); + return; + } + + let version = this.getTypeScriptVersion(modulePath); + if (!version) { + version = workspace.getConfiguration().get('typescript.tsdk_version', undefined); + } + if (version) { + this._apiVersion = APIVersion.fromString(version); + } + + + const label = version || localize('versionNumber.custom' ,'custom'); + const tooltip = modulePath; + VersionStatus.enable(!!this.tsdk || showVersionStatusItem); + VersionStatus.setInfo(label, tooltip); + + const doGlobalVersionCheckKey: string = 'doGlobalVersionCheck'; + if (checkGlobalVersion && this.globalState.get(doGlobalVersionCheckKey, true)) { + let tscVersion: string = undefined; + try { + let out = cp.execSync('tsc --version', { encoding: 'utf8' }); + if (out) { + let matches = out.trim().match(/Version\s*(.*)$/); + if (matches && matches.length === 2) { + tscVersion = matches[1]; + } + } + } catch (error) { + } + if (tscVersion && tscVersion !== version) { + window.showInformationMessage( + localize('versionMismatch', 'A version mismatch between the globally installed tsc compiler ({0}) and VS Code\'s language service ({1}) has been detected. This might result in inconsistent compile errors.', tscVersion, version), + ...[{ + title: localize('moreInformation', 'More Information'), + id: 1 + }, + { + title: localize('doNotCheckAgain', 'Don\'t Check Again'), + id: 2, + isCloseAffordance: true + }].reverse() + ).then((selected) => { + if (!selected) { + return; + } + switch (selected.id) { + case 1: + openUrl('http://go.microsoft.com/fwlink/?LinkId=826239'); + break; + case 2: + this.globalState.update(doGlobalVersionCheckKey, false); + break; + } + }); + } + } + + this.servicePromise = new Promise((resolve, reject) => { + try { + let options: electron.IForkOptions = { + execArgv: [] // [`--debug-brk=5859`] + }; + let value = process.env.TSS_DEBUG; + if (value) { + let port = parseInt(value); + if (!isNaN(port)) { + this.info(`TSServer started in debug mode using port ${port}`); + options.execArgv = [`--debug=${port}`]; + } + } + electron.fork(modulePath, [], options, (err: any, childProcess: cp.ChildProcess) => { + if (err) { + this.lastError = err; + this.error('Starting TSServer failed with error.', err); + window.showErrorMessage(localize('serverCouldNotBeStarted', 'TypeScript language server couldn\'t be started. Error message is: {0}', err.message || err)); + this.logTelemetry('error', {message: err.message}); + return; + } + this.lastStart = Date.now(); + childProcess.on('error', (err: Error) => { + this.lastError = err; + this.error('TSServer errored with error.', err); + this.serviceExited(false); + }); + childProcess.on('exit', (code: any) => { + this.error(`TSServer exited with code: ${code ? code : 'unknown'}`); + this.serviceExited(true); + }); + this.reader = new Reader(childProcess.stdout, (msg) => { + this.dispatchMessage(msg); + }); + this._onReady.resolve(); + resolve(childProcess); + }); + } catch (error) { + reject(error); + } + }); + this.serviceStarted(resendModels); }); - this.serviceStarted(resendModels); } private serviceStarted(resendModels: boolean): void { diff --git a/extensions/vscode-api-tests/src/editor.test.ts b/extensions/vscode-api-tests/src/editor.test.ts index 0dc0bf124e0..6cf498ee9c9 100644 --- a/extensions/vscode-api-tests/src/editor.test.ts +++ b/extensions/vscode-api-tests/src/editor.test.ts @@ -6,56 +6,96 @@ 'use strict'; import * as assert from 'assert'; -import {workspace, window, Position, Range} from 'vscode'; +import {workspace, window, Position, Range, commands} from 'vscode'; import {createRandomFile, deleteFile, cleanUp} from './utils'; suite('editor tests', () => { teardown(cleanUp); - test('make edit', () => { - return createRandomFile().then(file => { + function withRandomFileEditor(initialContents:string, run:(editor:vscode.TextEditor, doc:vscode.TextDocument)=>Thenable): Thenable { + return createRandomFile(initialContents).then(file => { return workspace.openTextDocument(file).then(doc => { return window.showTextDocument(doc).then((editor) => { - return editor.edit((builder) => { - builder.insert(new Position(0, 0), 'Hello World'); - }).then(applied => { - assert.ok(applied); - assert.equal(doc.getText(), 'Hello World'); - assert.ok(doc.isDirty); - - return doc.save().then(saved => { - assert.ok(saved); - assert.ok(!doc.isDirty); - + return run(editor, doc).then(_ => { + if (doc.isDirty) { + return doc.save().then(saved => { + assert.ok(saved); + assert.ok(!doc.isDirty); + return deleteFile(file); + }); + } else { return deleteFile(file); - }); + } }); }); }); }); + } + + test('make edit', () => { + return withRandomFileEditor('', (editor, doc) => { + return editor.edit((builder) => { + builder.insert(new Position(0, 0), 'Hello World'); + }).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'Hello World'); + assert.ok(doc.isDirty); + }); + }); }); test('issue #6281: Edits fail to validate ranges correctly before applying', () => { - return createRandomFile('Hello world!').then(file => { - return workspace.openTextDocument(file).then(doc => { - return window.showTextDocument(doc).then((editor) => { - return editor.edit((builder) => { - builder.replace(new Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE), 'new'); - }).then(applied => { - assert.ok(applied); - assert.equal(doc.getText(), 'new'); - assert.ok(doc.isDirty); - - return doc.save().then(saved => { - assert.ok(saved); - assert.ok(!doc.isDirty); - - return deleteFile(file); - }); - }); - }); + return withRandomFileEditor('Hello world!', (editor, doc) => { + return editor.edit((builder) => { + builder.replace(new Range(0, 0, Number.MAX_VALUE, Number.MAX_VALUE), 'new'); + }).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'new'); + assert.ok(doc.isDirty); }); }); }); -}); \ No newline at end of file + + function executeReplace(editor:vscode.TextEditor, range:Range, text:string, undoStopBefore:boolean, undoStopAfter: boolean): Thenable { + return editor.edit((builder) => { + builder.replace(range, text); + }, { undoStopBefore: undoStopBefore, undoStopAfter: undoStopAfter }); + } + + test('TextEditor.edit can control undo/redo stack 1', () => { + return withRandomFileEditor('Hello world!', (editor, doc) => { + return executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'hello world!'); + assert.ok(doc.isDirty); + return executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', false, false); + }).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'hELLO world!'); + assert.ok(doc.isDirty); + return commands.executeCommand('undo'); + }).then(_ => { + assert.equal(doc.getText(), 'Hello world!'); + }); + }); + }); + + test('TextEditor.edit can control undo/redo stack 2', () => { + return withRandomFileEditor('Hello world!', (editor, doc) => { + return executeReplace(editor, new Range(0, 0, 0, 1), 'h', false, false).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'hello world!'); + assert.ok(doc.isDirty); + return executeReplace(editor, new Range(0, 1, 0, 5), 'ELLO', true, false); + }).then(applied => { + assert.ok(applied); + assert.equal(doc.getText(), 'hELLO world!'); + assert.ok(doc.isDirty); + return commands.executeCommand('undo'); + }).then(_ => { + assert.equal(doc.getText(), 'hello world!'); + }); + }); + }); +}); diff --git a/extensions/vscode-api-tests/src/window.test.ts b/extensions/vscode-api-tests/src/window.test.ts index df4b7d080f6..2ac8d8add8e 100644 --- a/extensions/vscode-api-tests/src/window.test.ts +++ b/extensions/vscode-api-tests/src/window.test.ts @@ -206,6 +206,32 @@ suite('window namespace tests', () => { }); }); + test('showQuickPick, canceled by another picker', function () { + + const result = window.showQuickPick(['eins', 'zwei', 'drei'], { ignoreFocusOut: true }).then(result => { + assert.equal(result, undefined); + }); + + const source = new CancellationTokenSource(); + source.cancel(); + window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token); + + return result; + }); + + test('showQuickPick, canceled by input', function () { + + const result = window.showQuickPick(['eins', 'zwei', 'drei'], { ignoreFocusOut: true }).then(result => { + assert.equal(result, undefined); + }); + + const source = new CancellationTokenSource(); + source.cancel(); + window.showInputBox(undefined, source.token); + + return result; + }); + test('editor, selection change kind', () => { return workspace.openTextDocument(join(workspace.rootPath, './far.js')).then(doc => window.showTextDocument(doc)).then(editor => { @@ -225,4 +251,19 @@ suite('window namespace tests', () => { }); }); + + test('createTerminal, Terminal.name', () => { + var terminal = window.createTerminal('foo'); + assert.equal(terminal.name, 'foo'); + + assert.throws(() => { + terminal.name = 'bar'; + }, 'Terminal.name should be readonly'); + }); + + test('createTerminal, immediate Terminal.sendText', () => { + var terminal = window.createTerminal(); + // This should not throw an exception + terminal.sendText('echo "foo"'); + }); }); diff --git a/extensions/vscode-api-tests/src/workspace.test.ts b/extensions/vscode-api-tests/src/workspace.test.ts index e3682d20f90..3ec3b3d5046 100644 --- a/extensions/vscode-api-tests/src/workspace.test.ts +++ b/extensions/vscode-api-tests/src/workspace.test.ts @@ -41,10 +41,10 @@ suite('workspace-namespace', () => { assert.throws(() => config['get'] = 'get-prop'); }); - test('configuration, getConfig/value', () => { - const value = workspace.getConfiguration('farboo.config0'); - assert.equal(Object.keys(value).length, 3); - }); + // test('configuration, getConfig/value', () => { + // const value = workspace.getConfiguration('farboo.config0'); + // assert.equal(Object.keys(value).length, 3); + // }); test('textDocuments', () => { assert.ok(Array.isArray(workspace.textDocuments)); diff --git a/gulpfile.js b/gulpfile.js index 204ac7b60c0..3a90599b4fd 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -29,6 +29,7 @@ const assign = require('object-assign'); const monacodts = require('./build/monaco/api'); const fs = require('fs'); const glob = require('glob'); +const pkg = require('./package.json'); const rootDir = path.join(__dirname, 'src'); const options = require('./src/tsconfig.json').compilerOptions; @@ -215,7 +216,7 @@ gulp.task('mixin', function () { return; } - const url = 'https://github.com/' + repo + '/archive/master.zip'; + const url = `https://github.com/${ repo }/archive/${ pkg.distro }.zip`; const opts = { base: '' }; const username = process.env['VSCODE_MIXIN_USERNAME']; const password = process.env['VSCODE_MIXIN_PASSWORD']; diff --git a/i18n/chs/src/vs/code/electron-main/menus.i18n.json b/i18n/chs/src/vs/code/electron-main/menus.i18n.json index 5825ce03550..76a1e213008 100644 --- a/i18n/chs/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/chs/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "全部保存(&&L)", "miSaveAs": "另存为(&&A)...", "miSelectAll": "全选(&&S)", - "miSelectTheme": "颜色主题(&&C)", + "miSelectColorTheme": "颜色主题(&&C)", "miSplitEditor": "拆分编辑器(&&E)", "miSwitchEditor": "切换编辑器(&&E)", "miSwitchGroup": "切换组(&&G)", diff --git a/i18n/chs/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/chs/src/vs/editor/common/services/modelServiceImpl.i18n.json index 97b24135282..e6fb5879262 100644 --- a/i18n/chs/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/chs/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "请更新设置: \"editor.detectIndentation\" 替换 \"editor.tabSize\": \"auto\" 或 \"editor.insertSpaces\": \"auto\"" + "indentAutoMigrate": "请更新设置: \"editor.detectIndentation\" 替换 \"editor.tabSize\": \"auto\" 或 \"editor.insertSpaces\": \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/chs/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/chs/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/chs/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/chs/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/chs/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..454901a7673 100644 --- a/i18n/chs/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/chs/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "找不到可与此代码版本兼容的 {0} 版本。" +} \ No newline at end of file diff --git a/i18n/chs/src/vs/platform/request/common/request.i18n.json b/i18n/chs/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..c5c8524e89f 100644 --- a/i18n/chs/src/vs/platform/request/common/request.i18n.json +++ b/i18n/chs/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "要使用的代理设置。如果尚未设置,则将从 http_proxy 和 https_proxy 环境变量获取", + "strictSSL": "是否应根据提供的 CA 列表验证代理服务器证书。" +} \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/chs/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/chs/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/chs/src/vs/workbench/electron-browser/integration.i18n.json index cf90c4eca98..77ca69f9d12 100644 --- a/i18n/chs/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/chs/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "复制", "cut": "剪切", + "files": "文件", + "folders": "文件夹", + "openRecentPlaceHolder": "选择要打开的路径(在新窗口中按住 Ctrl 键打开)", + "openRecentPlaceHolderMac": "选择路径(在新窗口中按住 Cmd 键打开)", "paste": "粘贴", "redo": "恢复", "selectAll": "全选", diff --git a/i18n/chs/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/chs/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..481448137ef 100644 --- a/i18n/chs/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "已中止", + "again": "在继续操作之前,请先从“{1}”中删除“{0}”。", + "cancel": "取消", + "cancel2": "取消", + "cantCreateBinFolder": "无法创建 \"/usr/local/bin\"。", + "changeNow": "立即更改", + "continue": "继续", + "editFile": "编辑“{0}”", + "exists": "请删除“{1}”(行 {2})中的别名引用“{0}”,然后重试此操作。", + "install": "在 PATH 中安装“{0}”命令", + "later": "稍后", + "laterInfo": "请记住,你始终可以从命令面板运行“{0}”操作。", + "ok": "确定", + "shellCommand": "Shell 命令", + "successFrom": "已成功从 PATH 卸载 Shell 命令“{0}”。", + "successIn": "已成功在 PATH 中安装了 Shell 命令“{0}”。", + "uninstall": "从 PATH 中卸载“{0}”命令", + "update": "代码需要更改“{0}”shell 命令。是否要立即执行此操作?", + "warnEscalation": "代码将通过 \"osascript\" 提示需要管理员权限才可安装 shell 命令。" +} \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/chs/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 8da8023963d..78cc59d4185 100644 --- a/i18n/chs/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "继续", "deactivateBreakpoints": "停用断点", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "调试: 添加到监视", "debugConsoleAction": "调试控制台", "debugEvaluate": "调试: 评估", "disableAllBreakpoints": "禁用所有断点", diff --git a/i18n/chs/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/chs/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/chs/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/chs/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 8d9049730df..c947c47244e 100644 --- a/i18n/chs/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "名称", + "debugger name": "名称", + "description": "描述", + "details": "详细信息", + "keyboard shortcuts": "键盘快捷方式(&&K)", "license": "许可证", - "noReadme": "无可用自述文件。" + "noReadme": "无可用自述文件。", + "runtime": "运行时", + "setting name": "名称", + "snippets": "代码片段" } \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/chs/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index c4d5594f60a..d150fc50177 100644 --- a/i18n/chs/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "覆盖", + "acceptLocalChanges": "使用本地更改并覆盖磁盘内容", "compareChanges": "比较", "conflictingFileHasChanged": "磁盘上的文件内容已更改,比较编辑器的左侧已刷新。请再次审查和解析。", "discard": "放弃", @@ -13,8 +13,8 @@ "readonlySaveError": "无法保存“{0}”: 文件写保护。选择“覆盖”以删除保护。 ", "resolveSaveConflict": "{0} - 解析保存冲突", "retry": "重试", - "revertLocalChanges": "还原", + "revertLocalChanges": "放弃本地更改,还原为磁盘上的内容", "saveConflictDiffLabel": "{0} - 磁盘上 ↔ {1} 中", "staleSaveError": "无法保存“{0}”: 磁盘上的内容较新。单击 **比较** 以比较你的版本和磁盘上的版本。", - "userGuide": "选择**还原**以放弃所做的更改或使用**覆盖**以将磁盘上的内容替换为所做的更改" + "userGuide": "使用编辑器工具栏中的操作 **撤消** 更改或用更改 **覆盖** 磁盘上的内容" } \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/chs/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index d100e66927c..b5572fc8659 100644 --- a/i18n/chs/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/chs/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "在应用商店中查找更多信息...", + "noIconThemeLabel": "无", "preferences": "首选项", "problemChangingTheme": "加载主题时出现问题: {0}", "selectTheme.label": "颜色主题", diff --git a/i18n/chs/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/chs/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/chs/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/chs/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/chs/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 7a26089460e..f1c6a031f22 100644 --- a/i18n/chs/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/chs/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "无法加载 {0}", "error.cannotloadtheme": "无法加载 {0}", "error.cannotparse": "分析 plist 文件 {0} 时出现问题", "error.cannotparsejson": "分析 JSON 主题文件 {0} 时出现问题", diff --git a/i18n/cht/src/vs/code/electron-main/menus.i18n.json b/i18n/cht/src/vs/code/electron-main/menus.i18n.json index f38ecd5493a..86d5efd8bc2 100644 --- a/i18n/cht/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/cht/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "全部儲存(&&L)", "miSaveAs": "另存新檔(&&A)...", "miSelectAll": "全選(&&S)", - "miSelectTheme": "色彩佈景主題(&&C)", + "miSelectColorTheme": "色彩佈景主題(&&C)", "miSplitEditor": "分割編輯器(&&E)", "miSwitchEditor": "切換編輯器(&&E)", "miSwitchGroup": "切換群組(&&G)", diff --git a/i18n/cht/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/cht/src/vs/editor/common/services/modelServiceImpl.i18n.json index dec878c9c73..804fc567db9 100644 --- a/i18n/cht/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/cht/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "請更新您的設定: `editor.detect Indentation` 會取代 `editor.tabSize`: \"auto\" 或 `editor.insertSpaces`: \"auto\"" + "indentAutoMigrate": "請更新您的設定: `editor.detect Indentation` 會取代 `editor.tabSize`: \"auto\" 或 `editor.insertSpaces`: \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/cht/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/cht/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/cht/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/cht/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/cht/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..68257c60f40 100644 --- a/i18n/cht/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/cht/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "找不到與此 Code 版本相容的 {0} 版本。" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/platform/request/common/request.i18n.json b/i18n/cht/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..0eb2e358110 100644 --- a/i18n/cht/src/vs/platform/request/common/request.i18n.json +++ b/i18n/cht/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "要使用的 Proxy 設定。如果未設定,會從 http_proxy 與 https_proxy 環境變數取得設定。", + "strictSSL": "是否應該針對提供的 CA 清單驗證 Proxy 伺服器憑證。" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/cht/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/cht/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/cht/src/vs/workbench/electron-browser/integration.i18n.json index 87b2094e5d6..9e8cdf97d57 100644 --- a/i18n/cht/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/cht/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "複製", "cut": "剪下", + "files": "檔案", + "folders": "資料夾", + "openRecentPlaceHolder": "選取要開啟的路徑 (按住 Ctrl 鍵以在新視窗開啟)", + "openRecentPlaceHolderMac": "選取路徑 (按住 Cmd 鍵以在新視窗開啟)", "paste": "貼上", "redo": "取消復原", "selectAll": "全選", diff --git a/i18n/cht/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..6f2ae9d5277 100644 --- a/i18n/cht/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "已中止", + "again": "請從 '{1}' 移除 '{0}' 別名後再繼續。", + "cancel": "取消", + "cancel2": "取消", + "cantCreateBinFolder": "無法建立 '/usr/local/bin'。", + "changeNow": "立即變更", + "continue": "繼續", + "editFile": "編輯 '{0}'", + "exists": "請移除參考 '{1}' (第 {2} 行) 中之 '{0}' 的別名,然後重試此動作。", + "install": "在 PATH 中安裝 '{0}' 命令", + "later": "稍後", + "laterInfo": "請記住,您一律可以從命令選擇區執行 '{0}' 動作。", + "ok": "確定", + "shellCommand": "殼層命令", + "successFrom": "已成功從 PATH 解除安裝殼層命令 '{0}'。", + "successIn": "已成功在 PATH 中安裝殼層命令 '{0}'。", + "uninstall": "從 PATH 將 '{0}' 命令解除安裝 ", + "update": "Code 必須變更 '{0}' 殼層命令。要立即執行此作業嗎?", + "warnEscalation": "Code 現在會提示輸入 'osascript' 取得系統管理員權限,以便安裝殼層命令。" +} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/cht/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index a2d6b004445..548cd2b719a 100644 --- a/i18n/cht/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "繼續", "deactivateBreakpoints": "停用中斷點", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "偵錯: 加入監看", "debugConsoleAction": "偵錯主控台", "debugEvaluate": "偵錯: 評估", "disableAllBreakpoints": "停用所有中斷點", diff --git a/i18n/cht/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/cht/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index ef768511e9c..7c391984b8e 100644 --- a/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "名稱", + "debugger name": "名稱", + "description": "描述", + "details": "詳細資料", + "keyboard shortcuts": "鍵盤快速鍵(&&K)", "license": "授權", - "noReadme": "沒有可用的讀我檔案。" + "noReadme": "沒有可用的讀我檔案。", + "runtime": "執行階段", + "setting name": "名稱", + "snippets": "程式碼片段" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/cht/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index 6a34d3ff696..291ec2e34e6 100644 --- a/i18n/cht/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "覆寫", + "acceptLocalChanges": "使用本機變更並覆寫磁碟內容", "compareChanges": "比較", "conflictingFileHasChanged": "磁碟上的檔案內容已變更,而且已重新整理比較編輯器左側。請重新檢閱並加以解決。", "discard": "捨棄", @@ -13,8 +13,8 @@ "readonlySaveError": "無法儲存 '{0}': 檔案有防寫保護。請選取 [覆寫] 以移除保護。", "resolveSaveConflict": "{0} - 解決儲存衝突", "retry": "重試", - "revertLocalChanges": "還原", + "revertLocalChanges": "捨棄本機變更並還原成磁碟上的內容", "saveConflictDiffLabel": "{0} - 磁碟上 ↔ {1} 中", "staleSaveError": "無法儲存 '{0}': 磁碟上的內容較新。請按一下 [比較],比較您的版本與磁碟上的版本。", - "userGuide": "請選取 [還原] 捨棄您的變更;或選取 [覆寫],以您的變更取代磁碟上的內容" + "userGuide": "使用編輯器工具列中的動作來「復原」您的變更,或以您的變更「覆寫」磁碟上的內容" } \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 0ea2bc28e56..f2367c6e915 100644 --- a/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/cht/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "在 Marketplace 可找到更多...", + "noIconThemeLabel": "無", "preferences": "喜好設定", "problemChangingTheme": "載入佈景主題時發生問題: {0}", "selectTheme.label": "色彩佈景主題", diff --git a/i18n/cht/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/cht/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/cht/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/cht/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/cht/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 1396e605d25..4e72bf40b9c 100644 --- a/i18n/cht/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/cht/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "無法載入 {0}", "error.cannotloadtheme": "無法載入 {0}", "error.cannotparse": "剖析 plist 檔案時發生問題: {0}", "error.cannotparsejson": "剖析 JSON 佈景主題檔案時發生問題: {0}", diff --git a/i18n/deu/src/vs/code/electron-main/menus.i18n.json b/i18n/deu/src/vs/code/electron-main/menus.i18n.json index 3239583aa6f..5f079d08e2c 100644 --- a/i18n/deu/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/deu/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "A&&lles speichern", "miSaveAs": "Speichern &&unter...", "miSelectAll": "&&Alles auswählen", - "miSelectTheme": "&&Farbdesign", + "miSelectColorTheme": "&&Farbdesign", "miSplitEditor": "&&Editor teilen", "miSwitchEditor": "&&Editor wechseln", "miSwitchGroup": "&&Gruppe wechseln", diff --git a/i18n/deu/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/deu/src/vs/editor/common/services/modelServiceImpl.i18n.json index 62b9ce82a50..da8500b1afd 100644 --- a/i18n/deu/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/deu/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "Bitte aktualisieren Sie Ihre Einstellungen: \"editor.detectIndentation\" ersetzt \"editor.tabSize\": \"auto\" oder \"editor.insertSpaces\": \"auto\"" + "indentAutoMigrate": "Bitte aktualisieren Sie Ihre Einstellungen: \"editor.detectIndentation\" ersetzt \"editor.tabSize\": \"auto\" oder \"editor.insertSpaces\": \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/deu/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/deu/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/deu/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/deu/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/deu/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..f99598fc253 100644 --- a/i18n/deu/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/deu/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "Eine kompatible Version von {0} mit dieser Version des Codes wurde nicht gefunden." +} \ No newline at end of file diff --git a/i18n/deu/src/vs/platform/request/common/request.i18n.json b/i18n/deu/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..be52abf7e99 100644 --- a/i18n/deu/src/vs/platform/request/common/request.i18n.json +++ b/i18n/deu/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "Die zu verwendende Proxyeinstellung. Wenn diese Option nicht festgelegt wird, wird der Wert aus den Umgebungsvariablen \"http_proxy\" und \"https_proxy\" übernommen.", + "strictSSL": "Gibt an, ob das Proxyserverzertifikat anhand der Liste der bereitgestellten Zertifizierungsstellen überprüft werden soll." +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/browser/actions/openSettings.i18n.json b/i18n/deu/src/vs/workbench/browser/actions/openSettings.i18n.json index 108a20338b2..5fede61d547 100644 --- a/i18n/deu/src/vs/workbench/browser/actions/openSettings.i18n.json +++ b/i18n/deu/src/vs/workbench/browser/actions/openSettings.i18n.json @@ -5,11 +5,11 @@ // Do not edit this file. It is machine generated. { "defaultKeybindings": "Standard-Tastaturkurzbefehle", - "defaultKeybindingsHeader": "Überschreiben Sie Tastenbindungen, indem Sie sie in die Tastenbindungsdatei einfügen.", + "defaultKeybindingsHeader": "Überschreiben Sie Tastenzuordnungen, indem Sie sie in die Tastenzuordnungsdatei einfügen.", "defaultName": "Standardeinstellungen", "defaultSettingsHeader": "Überschreiben Sie Einstellungen, indem Sie sie in die Einstellungsdatei einfügen.", "defaultSettingsHeader2": "Unter http://go.microsoft.com/fwlink/?LinkId=808995 finden Sie die am häufigsten verwendeten Einstellungen.", - "emptyKeybindingsHeader": "Platzieren Sie Ihre Tastenbindungen in dieser Datei, um die Standardwerte zu überschreiben.", + "emptyKeybindingsHeader": "Platzieren Sie Ihre Tastenzuordnungen in dieser Datei, um die Standardwerte zu überschreiben.", "emptySettingsHeader": "Platzieren Sie Ihre Einstellungen in dieser Datei, um die Standardeinstellungen zu überschreiben.", "emptySettingsHeader1": "Platzieren Sie Ihre Einstellungen in dieser Datei, um Standard- und Benutzereinstellungen zu überschreiben.", "fail.createSettings": "{0} ({1}) kann nicht erstellt werden.", diff --git a/i18n/deu/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/deu/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/deu/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/deu/src/vs/workbench/electron-browser/integration.i18n.json index cd66a933e56..3aff4d86d2f 100644 --- a/i18n/deu/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/deu/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "Kopieren", "cut": "Ausschneiden", + "files": "Dateien", + "folders": "Ordner", + "openRecentPlaceHolder": "Wählen Sie einen zu öffnenden Pfad aus (halten Sie STRG gedrückt, um ein neues Fenster zu öffnen).", + "openRecentPlaceHolderMac": "Wählen Sie einen Pfad aus (halten Sie die BEFEHLSTASTE gedrückt, um ein neues Fenster zu öffnen).", "paste": "Einfügen", "redo": "Wiederholen", "selectAll": "Alles auswählen", diff --git a/i18n/deu/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..f0a7e810cd3 100644 --- a/i18n/deu/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "Abgebrochen", + "again": "Bitte entfernen Sie den {0}-Alias aus \"{1}\", bevor Sie fortfahren.", + "cancel": "Abbrechen", + "cancel2": "Abbrechen", + "cantCreateBinFolder": "/usr/local/bin kann nicht erstellt werden.", + "changeNow": "Jetzt ändern", + "continue": "Weiter", + "editFile": "{0} bearbeiten", + "exists": "Bitte entfernen Sie den Alias in \"{1}\" (Zeile {2}), der auf \"{0}\" verweist, und wiederholen Sie diese Aktion.", + "install": "Befehl \"{0}\" in \"PATH\" installieren", + "later": "Später", + "laterInfo": "Denken Sie daran, dass Sie die Aktion \"{0}\" immer über die Befehlspalette ausführen können.", + "ok": "OK", + "shellCommand": "Shellbefehl", + "successFrom": "Der Shellbefehl \"{0}\" wurde erfolgreich aus \"PATH\" deinstalliert.", + "successIn": "Der Shellbefehl \"{0}\" wurde erfolgreich in \"PATH\" installiert.", + "uninstall": "Befehl \"{0}\" aus \"PATH\" deinstallieren", + "update": "Der Code muss den Shellbefehl \"{0}\" ändern. Möchten Sie diesen Vorgang jetzt ausführen?", + "warnEscalation": "Der Code fordert nun mit \"osascript\" zur Eingabe von Administratorberechtigungen auf, um den Shellbefehl zu installieren." +} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/deu/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 118bd2d2638..744c0143a77 100644 --- a/i18n/deu/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "Weiter", "deactivateBreakpoints": "Haltepunkte deaktivieren", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "Debuggen: Zur Überwachung hinzufügen", "debugConsoleAction": "Debugkonsole", "debugEvaluate": "Debuggen: Auswerten", "disableAllBreakpoints": "Alle Haltepunkte deaktivieren", diff --git a/i18n/deu/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/deu/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index f2b16eea542..970c53c49bf 100644 --- a/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "Name", + "debugger name": "Name", + "description": "Beschreibung", + "details": "Details", + "keyboard shortcuts": "&&Tastenkombinationen", "license": "Lizenz", - "noReadme": "Keine INFODATEI verfügbar." + "noReadme": "Keine INFODATEI verfügbar.", + "runtime": "Laufzeit", + "setting name": "Name", + "snippets": "Codeausschnitte" } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/deu/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index 9bf3939a661..ff1e037629e 100644 --- a/i18n/deu/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "Überschreiben", + "acceptLocalChanges": "Lokale Änderungen verwenden und Datenträgerinhalte überschreiben", "compareChanges": "Vergleichen", "conflictingFileHasChanged": "Der Inhalt der Datei auf dem Datenträger hat sich geändert, und die linke Seite des Vergleichs-Editors wurde aktualisiert. Bitte überprüfen Sie die Datei, und lösen Sie Konflikte erneut auf.", "discard": "Verwerfen", @@ -13,8 +13,8 @@ "readonlySaveError": "Fehler beim Speichern von \"{0}\": Die Datei ist schreibgeschützt. Wählen Sie 'Überschreiben' aus, um den Schutz aufzuheben.", "resolveSaveConflict": "{0} – Speicherkonflikt auflösen", "retry": "Wiederholen", - "revertLocalChanges": "Wiederherstellen", + "revertLocalChanges": "Lokale Änderungen verwerfen und Inhalt auf dem Datenträger wiederherstellen", "saveConflictDiffLabel": "{0}– auf Datenträger ↔ in{1}", "staleSaveError": "Fehler beim Speichern von \"{0}\": Der Inhalt auf dem Datenträger ist neuer. Klicken Sie auf **Vergleichen**, um Ihre Version mit der Version auf dem Datenträger zu vergleichen.", - "userGuide": "Wählen Sie **Wiederherstellen** aus, um Ihre Änderungen zu verwerfen, oder **Überschreiben**, um den Inhalt auf dem Datenträger durch Ihre Änderungen zu ersetzen." + "userGuide": "Verwenden Sie die Aktionen in der Editor-Symbolleiste, um Ihre Änderungen **rückgängig zu machen** oder den Inhalt auf dem Datenträger mit Ihren Änderungen zu **überschreiben**." } \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 49a04d1fac9..07edb010ee5 100644 --- a/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/deu/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "Weitere in Marketplace suchen...", + "noIconThemeLabel": "Keine", "preferences": "Einstellungen", "problemChangingTheme": "Problem beim Laden des Designs: {0}", "selectTheme.label": "Farbdesign", diff --git a/i18n/deu/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/deu/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/deu/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/deu/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/deu/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index fd082a541af..d5e42f094a0 100644 --- a/i18n/deu/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/deu/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "\"{0}\" kann nicht geladen werden.", "error.cannotloadtheme": "\"{0}\" kann nicht geladen werden.", "error.cannotparse": "Probleme beim Analysieren der PLIST-Datei: {0}", "error.cannotparsejson": "Probleme beim Analysieren der JSON-Designdatei: {0}", diff --git a/i18n/esn/src/vs/code/electron-main/menus.i18n.json b/i18n/esn/src/vs/code/electron-main/menus.i18n.json index 7abd214d37e..9f8635608db 100644 --- a/i18n/esn/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/esn/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "Guardar t&&odo", "miSaveAs": "Guardar &&como...", "miSelectAll": "&&Seleccionar todo", - "miSelectTheme": "&&Tema de color", + "miSelectColorTheme": "&&Tema de color", "miSplitEditor": "Dividir &&editor", "miSwitchEditor": "Cambiar &&editor", "miSwitchGroup": "Cambiar &&grupo", diff --git a/i18n/esn/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/esn/src/vs/editor/common/services/modelServiceImpl.i18n.json index 14d769e98dd..90e7452fd91 100644 --- a/i18n/esn/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/esn/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "Actualice la configuración: `editor.detectIndentation` reemplaza a `editor.tabSize`: \"auto\" o `editor.insertSpaces`: \"auto\"" + "indentAutoMigrate": "Actualice la configuración: `editor.detectIndentation` reemplaza a `editor.tabSize`: \"auto\" o `editor.insertSpaces`: \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/esn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/esn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/esn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/esn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/esn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..a46b51d7b92 100644 --- a/i18n/esn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/esn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "No se encontró una versión de {0} compatible con esta versión de Code." +} \ No newline at end of file diff --git a/i18n/esn/src/vs/platform/request/common/request.i18n.json b/i18n/esn/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..c0ae03c5855 100644 --- a/i18n/esn/src/vs/platform/request/common/request.i18n.json +++ b/i18n/esn/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "El valor del proxy que se debe utilizar. Si no se establece, se tomará de las variables de entorno http_proxy y https_proxy", + "strictSSL": "Indica si el certificado del servidor proxy debe comprobarse en la lista de entidades de certificación proporcionada." +} \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/esn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/esn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/esn/src/vs/workbench/electron-browser/integration.i18n.json index a21c29a98d2..c38b4747f37 100644 --- a/i18n/esn/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/esn/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "Copiar", "cut": "Cortar", + "files": "archivos", + "folders": "carpetas", + "openRecentPlaceHolder": "Seleccione una ruta de acceso para abrir (mantenga presionada la tecla Ctrl para abrirla en una nueva ventana)", + "openRecentPlaceHolderMac": "Seleccione una ruta de acceso (mantenga presionada la tecla Cmd para abrirla en una nueva ventana)", "paste": "Pegar", "redo": "Rehacer", "selectAll": "Seleccionar todo", diff --git a/i18n/esn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/esn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..b274cef029d 100644 --- a/i18n/esn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "Anulado", + "again": "Quite el alias '{0}' de '{1}' antes de continuar.", + "cancel": "Cancelar", + "cancel2": "Cancelar", + "cantCreateBinFolder": "No se puede crear \"/usr/local/bin\".", + "changeNow": "Cambiar ahora", + "continue": "Continuar", + "editFile": "Editar '{0}'", + "exists": "Quite el alias que hace referencia a '{0}' en '{1}' (línea {2}) y pruebe a realizar esta acción de nuevo.", + "install": "Instalar el comando '{0}' en PATH", + "later": "Más tarde", + "laterInfo": "Recuerde que siempre puede ejecutar la acción '{0}' desde la paleta de comandos.", + "ok": "Aceptar", + "shellCommand": "Comando shell", + "successFrom": "El comando shell '{0}' se desinstaló correctamente de PATH.", + "successIn": "El comando shell '{0}' se instaló correctamente en PATH.", + "uninstall": "Desinstalar el comando '{0}' de PATH", + "update": "El código debe cambiar el comando shell '{0}'. ¿Quiere hacerlo ahora?", + "warnEscalation": "Ahora el código solicitará privilegios de administrador con \"osascript\" para instalar el comando shell." +} \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/esn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index fa7d0c319ec..dc74d9d05e2 100644 --- a/i18n/esn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "Continuar", "deactivateBreakpoints": "Desactivar puntos de interrupción", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "Depuración: Agregar a inspección", "debugConsoleAction": "Consola de depuración", "debugEvaluate": "Depuración: Evaluar", "disableAllBreakpoints": "Deshabilitar todos los puntos de interrupción", diff --git a/i18n/esn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/esn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/esn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/esn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 7cdbceed429..4f80b96f8c1 100644 --- a/i18n/esn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "Nombre", + "debugger name": "Nombre", + "description": "Descripción", + "details": "Detalles", + "keyboard shortcuts": "&&Métodos abreviados de teclado", "license": "Licencia", - "noReadme": "No hay ningún archivo LÉAME disponible." + "noReadme": "No hay ningún archivo LÉAME disponible.", + "runtime": "Motor en tiempo de ejecución", + "setting name": "Nombre", + "snippets": "Fragmentos" } \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/esn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index fe5275dfdd6..2ce0e31f5be 100644 --- a/i18n/esn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "Sobrescribir", + "acceptLocalChanges": "Usar cambios locales y sobrescribir contenido del disco", "compareChanges": "Comparar", "conflictingFileHasChanged": "El contenido del archivo en el disco ha cambiado y se ha actualizado la parte izquierda del editor de comparación. Revíselo y resuelva de nuevo.", "discard": "Descartar", @@ -13,8 +13,8 @@ "readonlySaveError": "No se pudo guardar '{0}': El archivo está protegido contra escritura. Seleccione \"Sobrescribir\" para quitar la protección.", "resolveSaveConflict": "{0} - Resolver conflicto al guardar", "retry": "Reintentar", - "revertLocalChanges": "Revertir", + "revertLocalChanges": "Descartar cambios locales y revertir al contenido del disco", "saveConflictDiffLabel": "{0} - en el disco ↔ en {1}", "staleSaveError": "No se pudo guardar '{0}': El contenido del disco es más reciente. Haga clic en **Comparar** para comparar su versión con la que hay en el disco.", - "userGuide": "Seleccione **Revertir** para descartar los cambios o **Sobrescribir** para reemplazar el contenido del disco por los cambios" + "userGuide": "Use las acciones de la barra de herramientas del editor para **deshacer** los cambios o **sobrescribir** el contenido del disco con sus cambios." } \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index bae20cc903e..38bf37ee3c1 100644 --- a/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/esn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "Encontrará más en Marketplace...", + "noIconThemeLabel": "Ninguno", "preferences": "Preferencias", "problemChangingTheme": "Problema al cargar el tema: {0}", "selectTheme.label": "Tema de color", diff --git a/i18n/esn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/esn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/esn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/esn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/esn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index db8cf6d47a6..d1bd63bc307 100644 --- a/i18n/esn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/esn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "No se puede cargar {0}", "error.cannotloadtheme": "No se puede cargar {0}", "error.cannotparse": "Problemas al analizar el archivo plist: {0}", "error.cannotparsejson": "Problemas al analizar el archivo de tema JSON: {0}", diff --git a/i18n/fra/src/vs/code/electron-main/menus.i18n.json b/i18n/fra/src/vs/code/electron-main/menus.i18n.json index 652af2869cc..7aeea0178fb 100644 --- a/i18n/fra/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/fra/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "Enregistrer to&&ut", "miSaveAs": "Enregistrer &&sous...", "miSelectAll": "&&Sélectionner tout", - "miSelectTheme": "Thème de &&couleur", + "miSelectColorTheme": "Thème de &&couleur", "miSplitEditor": "Fractionner l'édit&&eur", "miSwitchEditor": "Changer d'é&&diteur", "miSwitchGroup": "Changer de gr&&oupe", diff --git a/i18n/fra/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/fra/src/vs/editor/common/services/modelServiceImpl.i18n.json index 594e0ba1a7b..9d083658185 100644 --- a/i18n/fra/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/fra/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "Mettez à jour vos paramètres : 'editor.detectIndentation' remplace 'editor.tabSize': \"auto\" ou 'editor.insertSpaces': \"auto\"" + "indentAutoMigrate": "Mettez à jour vos paramètres : 'editor.detectIndentation' remplace 'editor.tabSize': \"auto\" ou 'editor.insertSpaces': \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/fra/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/fra/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/fra/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/fra/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/fra/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..461f8c836b4 100644 --- a/i18n/fra/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/fra/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "Version compatible de {0} introuvable avec cette version de Code." +} \ No newline at end of file diff --git a/i18n/fra/src/vs/platform/request/common/request.i18n.json b/i18n/fra/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..d5d7b323840 100644 --- a/i18n/fra/src/vs/platform/request/common/request.i18n.json +++ b/i18n/fra/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "Paramètre de proxy à utiliser. S'il n'est pas défini, il est récupéré à partir des variables d'environnement http_proxy et https_proxy", + "strictSSL": "Spécifie si le certificat de serveur proxy doit être vérifié par rapport à la liste des autorités de certification fournies." +} \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/fra/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/fra/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/fra/src/vs/workbench/electron-browser/integration.i18n.json index 479f7c25f15..0b62855b485 100644 --- a/i18n/fra/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/fra/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "Copier", "cut": "Couper", + "files": "fichiers", + "folders": "dossiers", + "openRecentPlaceHolder": "Sélectionner un chemin à ouvrir (maintenir la touche Ctrl enfoncée pour l'ouvrir dans une nouvelle fenêtre)", + "openRecentPlaceHolderMac": "Sélectionner un chemin (maintenir la touche Cmd enfoncée pour l'ouvrir dans une nouvelle fenêtre)", "paste": "Coller", "redo": "Rétablir", "selectAll": "Tout Sélectionner", diff --git a/i18n/fra/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/fra/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..b15b85c3e20 100644 --- a/i18n/fra/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "Abandonné", + "again": "Supprimez l'alias '{0}' dans '{1}' avant de continuer.", + "cancel": "Annuler", + "cancel2": "Annuler", + "cantCreateBinFolder": "Impossible de créer '/usr/local/bin'.", + "changeNow": "Changer maintenant", + "continue": "Continuer", + "editFile": "Modifier '{0}'", + "exists": "Supprimez l'alias faisant référence à '{0}' dans '{1}' (ligne {2}), puis réessayez cette action.", + "install": "Installer la commande '{0}' dans PATH", + "later": "Plus tard", + "laterInfo": "N'oubliez pas que vous pouvez toujours exécuter l'action '{0}' à partir de la palette de commandes.", + "ok": "OK", + "shellCommand": "Commande d'interpréteur de commandes", + "successFrom": "La commande d'interpréteur de commandes '{0}' a été correctement désinstallée à partir de PATH.", + "successIn": "La commande d'interpréteur de commandes '{0}' a été correctement installée dans PATH.", + "uninstall": "Désinstaller la commande '{0}' de PATH", + "update": "Code doit changer la commande d'interpréteur de commandes '{0}'. Voulez-vous le faire maintenant ?", + "warnEscalation": "Code va maintenant demander avec 'osascript' des privilèges d'administrateur pour installer la commande d'interpréteur de commandes." +} \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/fra/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index b80fb434271..4f584f44d27 100644 --- a/i18n/fra/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "Continuer", "deactivateBreakpoints": "Désactiver les points d'arrêt", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "Déboguer : ajouter à la fenêtre Espion", "debugConsoleAction": "Console de débogage", "debugEvaluate": "Déboguer : évaluer", "disableAllBreakpoints": "Désactiver tous les points d'arrêt", diff --git a/i18n/fra/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/fra/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/fra/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/fra/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 67c43681a72..f8f264019e3 100644 --- a/i18n/fra/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "Nom", + "debugger name": "Nom", + "description": "Description", + "details": "Détails", + "keyboard shortcuts": "Racco&&urcis clavier", "license": "Licence", - "noReadme": "Aucun fichier README disponible." + "noReadme": "Aucun fichier README disponible.", + "runtime": "Runtime", + "setting name": "Nom", + "snippets": "Extraits" } \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/fra/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index 88f96521ca5..811f66dd719 100644 --- a/i18n/fra/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "Remplacer", + "acceptLocalChanges": "Utiliser les modifications locales et remplacer le contenu du disque", "compareChanges": "Comparer", "conflictingFileHasChanged": "Le contenu du fichier sur disque a changé. Le côté gauche de l'éditeur de comparaison a été actualisé. Passez le contenu en revue, puis résolvez à nouveau les conflits.", "discard": "Abandonner", @@ -13,8 +13,8 @@ "readonlySaveError": "Échec de l'enregistrement de '{0}' : le fichier est protégé en écriture. Sélectionnez 'Remplacer' pour supprimer la protection.", "resolveSaveConflict": "{0} - Résoudre le conflit d'enregistrement", "retry": "Réessayer", - "revertLocalChanges": "Rétablir", + "revertLocalChanges": "Ignorer les modifications locales et restaurer le contenu sur disque", "saveConflictDiffLabel": "{0} - sur disque ↔ dans {1}", "staleSaveError": "Échec de l'enregistrement de '{0}' : le contenu sur disque est plus récent. Cliquez sur **Comparer** pour comparer votre version à celle située sur le disque.", - "userGuide": "Sélectionnez **Rétablir** pour abandonner vos modifications ou **Remplacer** pour remplacer le contenu sur disque par vos modifications" + "userGuide": "Utilisez les actions de la barre d'outils de l'éditeur pour **annuler** vos modifications, ou pour **remplacer** le contenu sur disque par le contenu incluant vos changements" } \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/fra/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index ea3f46af8a9..aa227d11f0f 100644 --- a/i18n/fra/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/fra/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "En trouver davantage sur Marketplace...", + "noIconThemeLabel": "Aucun", "preferences": "Préférences", "problemChangingTheme": "Problème de chargement du thème : {0}", "selectTheme.label": "Thème de couleur", diff --git a/i18n/fra/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/fra/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/fra/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/fra/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/fra/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 302396e2087..dfc37060f25 100644 --- a/i18n/fra/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/fra/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "Impossible de charger {0}", "error.cannotloadtheme": "Impossible de charger {0}", "error.cannotparse": "Problèmes durant l'analyse du fichier plist : {0}", "error.cannotparsejson": "Problèmes durant l'analyse du fichier de thème JSON : {0}", diff --git a/i18n/ita/extensions/typescript/out/typescriptServiceClient.i18n.json b/i18n/ita/extensions/typescript/out/typescriptServiceClient.i18n.json index 062ddc904bc..02f5c883500 100644 --- a/i18n/ita/extensions/typescript/out/typescriptServiceClient.i18n.json +++ b/i18n/ita/extensions/typescript/out/typescriptServiceClient.i18n.json @@ -6,7 +6,7 @@ { "channelName": "TypeScript", "noServerFound": "Il percorso {0} non punta a un'installazione valida di tsserver. Le funzionalità del linguaggio TypeScript verranno disabilitate.", - "serverCouldNotBeStarted": "Non è stato possibile avviare il linguaggio TypeScript. Messaggio di errore: {0}", + "serverCouldNotBeStarted": "Non è stato possibile avviare il server di linguaggio TypeScript. Messaggio di errore: {0}", "serverDied": "Il servizio di linguaggio Typescript è stato arrestato in modo imprevisto per cinque volte negli ultimi cinque minuti. Provare ad aprire una segnalazione bug.", "serverDiedAfterStart": "Il servizio di linguaggio TypeScript è stato arrestato in modo imprevisto per cinque volte dopo che è stato avviato e non verrà riavviato. Aprire una segnalazione bug.", "versionNumber.custom": "personalizzato" diff --git a/i18n/ita/src/vs/code/electron-main/menus.i18n.json b/i18n/ita/src/vs/code/electron-main/menus.i18n.json index c285f811783..5e60b17a6b2 100644 --- a/i18n/ita/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/ita/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "Salva &&tutto", "miSaveAs": "Salva &&con nome...", "miSelectAll": "&&Seleziona tutto", - "miSelectTheme": "&&Tema colori", + "miSelectColorTheme": "&&Tema colori", "miSplitEditor": "Dividi &&editor", "miSwitchEditor": "Cambia &&editor", "miSwitchGroup": "Cambia &&gruppo", diff --git a/i18n/ita/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/ita/src/vs/editor/common/services/modelServiceImpl.i18n.json index 381cc0574a8..d44cf96f046 100644 --- a/i18n/ita/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/ita/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "Aggiornare le impostazioni: `editor.detectIndentation` sostituisce `editor.tabSize`: \"auto\" o `editor.insertSpaces`: \"auto\"" + "indentAutoMigrate": "Aggiornare le impostazioni: `editor.detectIndentation` sostituisce `editor.tabSize`: \"auto\" o `editor.insertSpaces`: \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/ita/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/ita/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/ita/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..dd36704e339 100644 --- a/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/ita/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "Non è stata trovata una versione di {0} compatibile con questa versione di Visual Studio Code." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/platform/request/common/request.i18n.json b/i18n/ita/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..98d4a8ba4f3 100644 --- a/i18n/ita/src/vs/platform/request/common/request.i18n.json +++ b/i18n/ita/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "Impostazione proxy da usare. Se non è impostata, verrà ottenuta dalle variabili di ambiente http_proxy e https_proxy", + "strictSSL": "Indica se il certificato del server proxy deve essere verificato in base all'elenco di CA specificate." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/ita/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/ita/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/ita/src/vs/workbench/electron-browser/integration.i18n.json index 0232551c4b3..f2f7a72a924 100644 --- a/i18n/ita/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/ita/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "Copia", "cut": "Taglia", + "files": "file", + "folders": "cartelle", + "openRecentPlaceHolder": "Selezionare un percorso da aprire (tenere premuto CTRL per aprirlo in una nuova finestra)", + "openRecentPlaceHolderMac": "Selezionare un percorso (tenere premuto CMD per aprirlo in una nuova finestra)", "paste": "Incolla", "redo": "Ripristina", "selectAll": "Seleziona tutto", diff --git a/i18n/ita/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..757130c5260 100644 --- a/i18n/ita/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "Operazione interrotta", + "again": "Rimuovere l'alias '{0}' da '{1}' prima di continuare.", + "cancel": "Annulla", + "cancel2": "Annulla", + "cantCreateBinFolder": "Non è possibile creare '/usr/local/bin'.", + "changeNow": "Cambia ora", + "continue": "Continua", + "editFile": "Modifica '{0}'", + "exists": "Rimuovere l'alias che fa riferimento a '{0}' in '{1}' (riga {2}) e ripetere l'azione.", + "install": "Installa il comando '{0}' in PATH", + "later": "In seguito", + "laterInfo": "Nota: è sempre possibile eseguire l'azione '{0}' dal riquadro comandi.", + "ok": "OK", + "shellCommand": "Comando della shell", + "successFrom": "Il comando della shell '{0}' è stato disinstallato da PATH.", + "successIn": "Il comando della shell '{0}' è stato installato in PATH.", + "uninstall": "Disinstalla il comando '{0}' da PATH", + "update": "Visual Studio Code deve modificare il comando della shell '{0}'. Eseguire questa operazione?", + "warnEscalation": "Visual Studio Code eseguirà 'osascript' per richiedere i privilegi di amministratore per installare il comando della shell." +} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/ita/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 71a3f1fbe4c..3c87f5fd44d 100644 --- a/i18n/ita/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "Continua", "deactivateBreakpoints": "Disattiva punti di interruzione", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "Debug: Aggiungi a espressione di controllo", "debugConsoleAction": "Console di debug", "debugEvaluate": "Debug: Valuta", "disableAllBreakpoints": "Disabilita tutti i punti di interruzione", diff --git a/i18n/ita/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/ita/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 2f5a03dac06..b10f440d58b 100644 --- a/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "Nome", + "debugger name": "Nome", + "description": "Descrizione", + "details": "Dettagli", + "keyboard shortcuts": "&&Tasti di scelta rapida", "license": "Licenza", - "noReadme": "File LEGGIMI non disponibile." + "noReadme": "File LEGGIMI non disponibile.", + "runtime": "Runtime", + "setting name": "Nome", + "snippets": "Frammenti" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/ita/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index 514ad175c8f..fa8ea10d96f 100644 --- a/i18n/ita/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "Sovrascrivi", + "acceptLocalChanges": "Usa modifiche locali e sovrascrivi contenuto del disco", "compareChanges": "Confronta", "conflictingFileHasChanged": "Il contenuto del file su disco è cambiato e il lato sinistro dell'editor di confronto è stato aggiornato. Rivedere e risolvere di nuovo.", "discard": "Rimuovi", @@ -13,8 +13,8 @@ "readonlySaveError": "Non è stato possibile salvare '{0}': il file è protetto da scrittura. Selezionare 'Sovrascrivi' per rimuovere la protezione.", "resolveSaveConflict": "{0} - Risolvi conflitto di salvataggio", "retry": "Riprova", - "revertLocalChanges": "Ripristina", + "revertLocalChanges": "Rimuovi modifiche locali e ripristina contenuto su disco", "saveConflictDiffLabel": "{0} - su disco ↔ in {1}", "staleSaveError": "Non è stato possibile salvare '{0}': il contenuto sul disco è più recente. Fare clic su **Confronta** per confrontare la versione corrente con quella sul disco.", - "userGuide": "Selezionare **Ripristina** per annullare le modifiche oppure **Sovrascrivi** per sostituire il contenuto su disco con le modifiche" + "userGuide": "Usare le azioni della barra degli strumenti dell'editor per **annullare** le modifiche oppure **sovrascrivere** il contenuto su disco con le modifiche" } \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 865f9be77b3..f3df83e8b00 100644 --- a/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/ita/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "Cerca altro nel Marketplace...", + "noIconThemeLabel": "Nessuno", "preferences": "Preferenze", "problemChangingTheme": "Problema durante il caricamento del tema: {0}", "selectTheme.label": "Tema colori", diff --git a/i18n/ita/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/ita/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/ita/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/ita/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/ita/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 260f15efa46..f5942d67f45 100644 --- a/i18n/ita/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/ita/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "Non è possibile caricare {0}", "error.cannotloadtheme": "Non è possibile caricare {0}", "error.cannotparse": "Problemi durante l'analisi del file plist: {0}", "error.cannotparsejson": "Problemi durante l'analisi del file di tema di JSON: {0}", diff --git a/i18n/jpn/src/vs/code/electron-main/menus.i18n.json b/i18n/jpn/src/vs/code/electron-main/menus.i18n.json index 08cfb2a87ec..3526a096dff 100644 --- a/i18n/jpn/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/jpn/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "すべて保存(&&L)", "miSaveAs": "名前を付けて保存(&&A)...", "miSelectAll": "すべて選択(&&S)", - "miSelectTheme": "配色テーマ(&&C)", + "miSelectColorTheme": "配色テーマ(&&C)", "miSplitEditor": "エディターを分割(&&E)", "miSwitchEditor": "エディターの切り替え(&&E)", "miSwitchGroup": "グループの切り替え(&&G)", diff --git a/i18n/jpn/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/jpn/src/vs/editor/common/services/modelServiceImpl.i18n.json index 3ca695feb23..409151d5796 100644 --- a/i18n/jpn/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/jpn/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "設定を更新してください: `editor.detectIndentation` は `editor.tabSize`: \"auto\" または `editor.insertSpaces`: \"auto\" を置き換えます" + "indentAutoMigrate": "設定を更新してください: `editor.detectIndentation` は `editor.tabSize`: \"auto\" または `editor.insertSpaces`: \"auto\" を置き換えます", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/editor/contrib/format/common/formatActions.i18n.json b/i18n/jpn/src/vs/editor/contrib/format/common/formatActions.i18n.json index ab3299baa08..1774a740981 100644 --- a/i18n/jpn/src/vs/editor/contrib/format/common/formatActions.i18n.json +++ b/i18n/jpn/src/vs/editor/contrib/format/common/formatActions.i18n.json @@ -4,5 +4,5 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "formatAction.label": "コードの書式設定" + "formatAction.label": "コードのフォーマット" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/jpn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/jpn/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/jpn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/jpn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..d46cd1d5c0e 100644 --- a/i18n/jpn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/jpn/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "Code のこのバージョンと互換性のある {0} のバージョンが見つかりませんでした。" +} \ No newline at end of file diff --git a/i18n/jpn/src/vs/platform/request/common/request.i18n.json b/i18n/jpn/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..ff5ad3f02ae 100644 --- a/i18n/jpn/src/vs/platform/request/common/request.i18n.json +++ b/i18n/jpn/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "使用するプロキシ設定。設定されていない場合、環境変数 http_proxy および https_proxy から取得されます。", + "strictSSL": "提供された CA の一覧と照らしてプロキシ サーバーの証明書を確認するかどうか。" +} \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/jpn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/jpn/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/jpn/src/vs/workbench/electron-browser/integration.i18n.json index 18a8dcaced3..7a5e0a25566 100644 --- a/i18n/jpn/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/jpn/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "コピー", "cut": "切り取り", + "files": "ファイル", + "folders": "フォルダー", + "openRecentPlaceHolder": "パスを選択して開く (Ctrl キーを押しながら新しいウィンドウで開く)", + "openRecentPlaceHolderMac": "パスを選択 (Cmd キーを押しながら新しいウィンドウで開く)", "paste": "貼り付け", "redo": "やり直し", "selectAll": "すべて選択", diff --git a/i18n/jpn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/jpn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..1d730282563 100644 --- a/i18n/jpn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "中止されました", + "again": "別名 {0}' を '{1}' から削除してから続行してください。", + "cancel": "キャンセル", + "cancel2": "キャンセル", + "cantCreateBinFolder": "'/usr/local/bin' を作成できません。", + "changeNow": "今すぐ変更", + "continue": "続行", + "editFile": "'{0}' の編集", + "exists": "'{1}' 内の '{0}' (行 {2}) を参照する別名を削除してから、この操作をやり直してください。", + "install": "PATH 内に '{0}' コマンドをインストールします", + "later": "後で", + "laterInfo": "いつでもコマンド パレットから '{0}' アクションを実行できることに留意してください。", + "ok": "OK", + "shellCommand": "シェル コマンド", + "successFrom": "シェル コマンド '{0}' が PATH から正常にアンインストールされました。", + "successIn": "シェル コマンド '{0}' が PATH に正常にインストールされました。", + "uninstall": "'{0}' コマンドを PATH からアンインストールします", + "update": "Code '{0}' シェル コマンドを変更する必要があります。今すぐ実行してもよろしいですか?", + "warnEscalation": "管理者特権でシェル コマンドをインストールできるように、Code が 'osascript' のプロンプトを出します" +} \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/jpn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 9880d658f30..b22db929734 100644 --- a/i18n/jpn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "続行", "deactivateBreakpoints": "ブレークポイントの非アクティブ化", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "デバッグ: ウォッチに追加", "debugConsoleAction": "デバッグ コンソール", "debugEvaluate": "デバッグ: 評価", "disableAllBreakpoints": "すべてのブレークポイントを無効にする", diff --git a/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/jpn/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/jpn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 11080c99e23..c01445b815d 100644 --- a/i18n/jpn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "名前", + "debugger name": "名前", + "description": "説明", + "details": "詳細", + "keyboard shortcuts": "キーボード ショートカット(&&K)", "license": "ライセンス", - "noReadme": "利用できる README はありません。" + "noReadme": "利用できる README はありません。", + "runtime": "ランタイム", + "setting name": "名前", + "snippets": "スニペット" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/jpn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index af7c8691e92..43096b4b661 100644 --- a/i18n/jpn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "上書き", + "acceptLocalChanges": "ローカルでの変更を使ってディスクの内容を上書きします", "compareChanges": "比較", "conflictingFileHasChanged": "ディスク上のファイルの内容が変更され、比較エディターの左側が最新の情報に更新されました。確認して再度解決してください。", "discard": "破棄", @@ -13,8 +13,8 @@ "readonlySaveError": "'{0}' の保存に失敗しました。ファイルが書き込み禁止になっています。[上書き] を選択して保護を解除してください。", "resolveSaveConflict": "{0} - 保存時の競合の解決", "retry": "再試行", - "revertLocalChanges": "元に戻す", + "revertLocalChanges": "ローカルでの変更を破棄してディスクの内容に戻します", "saveConflictDiffLabel": "{0} - ディスク上 ↔ {1} 内", "staleSaveError": "'{0} の保存に失敗しました。ディスクの内容の方が新しくなっています。[比較] をクリックしてご使用のバージョンをディスク上のバージョンと比較してください。", - "userGuide": "[元に戻す] を選択して変更を破棄するか、または [上書き] を選択してディスクの内容を変更内容に置き換えてください" + "userGuide": "エディター ツール バーの操作で、変更を [元に戻す] か、ディスクの内容を変更内容で [上書き] します" } \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/jpn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 33344074333..2311c64a3c3 100644 --- a/i18n/jpn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/jpn/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "Marketplace でさらに探す...", + "noIconThemeLabel": "なし", "preferences": "基本設定", "problemChangingTheme": "テーマの読み込み中に問題が発生しました: {0}", "selectTheme.label": "配色テーマ", diff --git a/i18n/jpn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/jpn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/jpn/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/jpn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/jpn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index b92525cd606..20973e40913 100644 --- a/i18n/jpn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/jpn/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "{0} を読み込めません", "error.cannotloadtheme": "{0} を読み込めません", "error.cannotparse": "plist ファイルの解析中に問題が発生しました: {0}", "error.cannotparsejson": "JSON テーマ ファイルの解析中に問題が発生しました: {0}", diff --git a/i18n/kor/src/vs/code/electron-main/menus.i18n.json b/i18n/kor/src/vs/code/electron-main/menus.i18n.json index 601c2073d78..a763667ae92 100644 --- a/i18n/kor/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/kor/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "모두 저장(&&L)", "miSaveAs": "다른 이름으로 저장(&&A)...", "miSelectAll": "모두 선택(&&S)", - "miSelectTheme": "색 테마(&&C)", + "miSelectColorTheme": "색 테마(&&C)", "miSplitEditor": "편집기 분할(&&E)", "miSwitchEditor": "편집기 전환(&&E)", "miSwitchGroup": "그룹 전환(&&G)", diff --git a/i18n/kor/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/kor/src/vs/editor/common/services/modelServiceImpl.i18n.json index 2848d3e2c10..9665e78488b 100644 --- a/i18n/kor/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/kor/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "설정 업데이트 필요: `editor.detectIndentation`은 `editor.tabSize`를 바꿈: \"auto\" 또는 `editor.insertSpaces`: \"auto\"" + "indentAutoMigrate": "설정 업데이트 필요: `editor.detectIndentation`은 `editor.tabSize`를 바꿈: \"auto\" 또는 `editor.insertSpaces`: \"auto\"", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/kor/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/kor/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/kor/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..6486bf096b4 100644 --- a/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/kor/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "이 버전의 코드에서 {0}의 호환 버전을 찾을 수 없습니다." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/platform/request/common/request.i18n.json b/i18n/kor/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..81c4be2abc5 100644 --- a/i18n/kor/src/vs/platform/request/common/request.i18n.json +++ b/i18n/kor/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "사용할 프록시 설정입니다. 설정되지 않으면 http_proxy 및 https_proxy 환경 변수에서 가져옵니다.", + "strictSSL": "제공된 CA 목록에 대해 프록시 서버 인증서를 확인해야 하는지 여부를 나타냅니다." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/kor/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/kor/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/kor/src/vs/workbench/electron-browser/integration.i18n.json index 51d7fa7ff91..e04a1f39d8f 100644 --- a/i18n/kor/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/kor/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "복사", "cut": "잘라내기", + "files": "파일", + "folders": "폴더", + "openRecentPlaceHolder": "열기 경로 선택(새 창에서 열려면 Ctrl 키를 길게 누름)", + "openRecentPlaceHolderMac": "경로 선택(새 창에서 열려면 Cmd 키를 길게 누름)", "paste": "붙여넣기", "redo": "다시 실행", "selectAll": "모두 선택", diff --git a/i18n/kor/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..5e7629158ff 100644 --- a/i18n/kor/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "중단됨", + "again": "계속하려면 '{1}'에서 '{0}' 별칭을 제거하세요.", + "cancel": "취소", + "cancel2": "취소", + "cantCreateBinFolder": "'/usr/local/bin'을 만들 수 없습니다.", + "changeNow": "지금 변경", + "continue": "계속", + "editFile": "'{0}' 편집", + "exists": "'{1}'(줄 {2})에서 '{0}'을(를) 참조하는 별칭을 제거하고 이 작업을 다시 시도하세요.", + "install": "PATH에 '{0}' 명령 설치", + "later": "나중에", + "laterInfo": "명령 팔레트에서 '{0}' 작업을 항상 실행할 수 있습니다.", + "ok": "확인", + "shellCommand": "셸 명령", + "successFrom": "셸 명령 '{0}'이(가) PATH에서 제거되었습니다.", + "successIn": "셸 명령 '{0}'이(가) PATH에 설치되었습니다.", + "uninstall": "PATH에서 '{0}' 명령 제거", + "update": "코드에서 '{0}' 셸 명령을 변경해야 합니다. 지금 변경할까요?", + "warnEscalation": "이제 코드에서 'osascript'를 사용하여 관리자에게 셸 명령을 설치할 권한이 있는지를 묻습니다." +} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/kor/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 338871e15b5..093f3284257 100644 --- a/i18n/kor/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "계속", "deactivateBreakpoints": "중단점 비활성화", "debugActionLabelAndKeybinding": "{0}({1})", + "debugAddToWatch": "디버그: 조사식에 추가", "debugConsoleAction": "디버그 콘솔", "debugEvaluate": "디버그: 평가", "disableAllBreakpoints": "모든 중단점 해제", diff --git a/i18n/kor/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/kor/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index 756508d34fd..33409bab6b2 100644 --- a/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "이름", + "debugger name": "이름", + "description": "설명", + "details": "세부 정보", + "keyboard shortcuts": "바로 가기 키(&&K)", "license": "라이선스", - "noReadme": "사용 가능한 추가 정보가 없습니다." + "noReadme": "사용 가능한 추가 정보가 없습니다.", + "runtime": "런타임", + "setting name": "이름", + "snippets": "코드 조각" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/kor/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index 396d9b89b19..dea8a739f68 100644 --- a/i18n/kor/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "덮어쓰기", + "acceptLocalChanges": "로컬 변경 사용 및 디스크 콘텐츠 덮어쓰기", "compareChanges": "비교", "conflictingFileHasChanged": "디스크에 있는 파일의 내용이 변경되고 비교 편집기의 왼쪽이 새로 고쳐졌습니다. 검토하고 다시 확인하세요.", "discard": "삭제", @@ -13,8 +13,8 @@ "readonlySaveError": "'{0}'을(를) 저장하지 못했습니다. 파일이 쓰기 보호되어 있습니다. 보호를 제거하려면 '덮어쓰기'를 선택하세요.", "resolveSaveConflict": "{0} - 저장 충돌 해결", "retry": "다시 시도", - "revertLocalChanges": "되돌리기", + "revertLocalChanges": "로컬 변경을 취소하고 디스크의 콘텐츠로 되돌리기", "saveConflictDiffLabel": "{0} - 디스크 ↔ {1}에 있음", "staleSaveError": "'{0}'을(를) 저장하지 못했습니다. 디스크의 내용이 최신 버전입니다. 버전을 디스크에 있는 버전과 비교하려면 **비교**를 클릭하세요.", - "userGuide": "변경 내용을 취소하려면 **되돌리기**를 선택하고, 디스크의 콘텐츠를 변경 내용으로 바꾸려면 **덮어쓰기**를 선택하세요." + "userGuide": "편집기 도구 모음의 작업을 사용하여 변경 내용을 **실행 취소**하거나 디스크의 콘텐츠를 변경 내용으로 **덮어쓰기**" } \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 7b0038766cc..a359ed136c8 100644 --- a/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/kor/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "마켓플레이스에서 더 찾기...", + "noIconThemeLabel": "없음", "preferences": "기본 설정", "problemChangingTheme": "테마를 로드하는 중 문제가 발생했습니다. {0}", "selectTheme.label": "색 테마", diff --git a/i18n/kor/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/kor/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/kor/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/kor/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/kor/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 611b581b6dd..305d317f88e 100644 --- a/i18n/kor/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/kor/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "{0}을(를) 로드할 수 없습니다.", "error.cannotloadtheme": "{0}을(를) 로드할 수 없습니다.", "error.cannotparse": "plist 파일을 구문 분석하는 중 문제 발생: {0}", "error.cannotparsejson": "JSON 테마 파일을 구문 분석하는 중 문제 발생: {0}", diff --git a/i18n/rus/src/vs/code/electron-main/menus.i18n.json b/i18n/rus/src/vs/code/electron-main/menus.i18n.json index 2f532a5ce63..1e0af1ba1dd 100644 --- a/i18n/rus/src/vs/code/electron-main/menus.i18n.json +++ b/i18n/rus/src/vs/code/electron-main/menus.i18n.json @@ -79,7 +79,7 @@ "miSaveAll": "Сохранить &&все", "miSaveAs": "Сохранить &&как...", "miSelectAll": "&&Выделить все", - "miSelectTheme": "Цветовая &&тема", + "miSelectColorTheme": "Цветовая &&тема", "miSplitEditor": "Разделить &&редактор", "miSwitchEditor": "Переключить р&&едактор", "miSwitchGroup": "Переключить &&группу", diff --git a/i18n/rus/src/vs/editor/common/services/modelServiceImpl.i18n.json b/i18n/rus/src/vs/editor/common/services/modelServiceImpl.i18n.json index 20df22f9fb6..0800b64b655 100644 --- a/i18n/rus/src/vs/editor/common/services/modelServiceImpl.i18n.json +++ b/i18n/rus/src/vs/editor/common/services/modelServiceImpl.i18n.json @@ -4,5 +4,6 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "indentAutoMigrate": "Измените параметры: editor.detectIndentation заменяет editor.tabSize: auto или editor.insertSpaces: auto" + "indentAutoMigrate": "Измените параметры: editor.detectIndentation заменяет editor.tabSize: auto или editor.insertSpaces: auto", + "sourceAndDiagMessage": "[{0}] {1}" } \ No newline at end of file diff --git a/i18n/rus/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json b/i18n/rus/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/rus/src/vs/editor/contrib/suggest/browser/suggestController.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json b/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json index 8b6ad71cd4e..06bdba7a55d 100644 --- a/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json +++ b/i18n/rus/src/vs/platform/extensionManagement/node/extensionGalleryService.i18n.json @@ -3,4 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "noCompatible": "Не удалось найти версию {0}, совместимую с этой версией кода." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/platform/request/common/request.i18n.json b/i18n/rus/src/vs/platform/request/common/request.i18n.json index 8b6ad71cd4e..689a2b7ba15 100644 --- a/i18n/rus/src/vs/platform/request/common/request.i18n.json +++ b/i18n/rus/src/vs/platform/request/common/request.i18n.json @@ -3,4 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "httpConfigurationTitle": "HTTP", + "proxy": "Используемый параметр прокси. Если он не задан, он будет взят из переменных среды http_proxy и https_proxy.", + "strictSSL": "Должен ли сертификат прокси-сервера проверяться по списку предоставленных ЦС." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json b/i18n/rus/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/rus/src/vs/workbench/browser/parts/quickopen/quickopen.contribution.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/electron-browser/integration.i18n.json b/i18n/rus/src/vs/workbench/electron-browser/integration.i18n.json index 7e9ce1cb2bd..13a4385b5fb 100644 --- a/i18n/rus/src/vs/workbench/electron-browser/integration.i18n.json +++ b/i18n/rus/src/vs/workbench/electron-browser/integration.i18n.json @@ -6,6 +6,10 @@ { "copy": "Копировать", "cut": "Вырезать", + "files": "файлы", + "folders": "папки", + "openRecentPlaceHolder": "Выбрать папку для открытия (удерживайте клавишу CTRL, чтобы открыть ее в новом окне)", + "openRecentPlaceHolderMac": "Выбрать путь (удерживайте клавишу CMD, чтобы открыть в новом окне)", "paste": "Вставить", "redo": "Вернуть", "selectAll": "Выбрать все", diff --git a/i18n/rus/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json index 8b6ad71cd4e..505f04c2774 100644 --- a/i18n/rus/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/cli/electron-browser/cli.contribution.i18n.json @@ -3,4 +3,24 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. -{} \ No newline at end of file +{ + "aborted": "Прервано", + "again": "Перед тем как продолжить, удалите псевдоним \"{0}\" из \"{1}\".", + "cancel": "Отмена", + "cancel2": "Отмена", + "cantCreateBinFolder": "Не удается создать папку \"/usr/local/bin\".", + "changeNow": "Изменить сейчас", + "continue": "Продолжить", + "editFile": "Изменить \"{0}\"", + "exists": "Удалите псевдоним, содержащий ссылку на \"{0}\", из \"{1}\" (строка {2}) и повторите это действие.", + "install": "Установить путь к команде \"{0}\" в PATH", + "later": "Позже", + "laterInfo": "Помните, что вы всегда можете выполнить действие \"{0}\" из палитры команд.", + "ok": "ОК", + "shellCommand": "Команда оболочки", + "successFrom": "Путь к команде оболочки \"{0}\" успешно удален из PATH.", + "successIn": "Путь к команде оболочки \"{0}\" успешно установлен в PATH.", + "uninstall": "Удалить путь к команде \"{0}\" из PATH", + "update": "Редактору Code необходимо изменить команду оболочки \"{0}\". Сделать это сейчас?", + "warnEscalation": "Редактор Code запросит права администратора для установки команды оболочки с помощью osascript." +} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/debug/browser/debugActions.i18n.json b/i18n/rus/src/vs/workbench/parts/debug/browser/debugActions.i18n.json index 9c7301c4032..b2e54ba4c7f 100644 --- a/i18n/rus/src/vs/workbench/parts/debug/browser/debugActions.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/debug/browser/debugActions.i18n.json @@ -14,6 +14,7 @@ "continueDebug": "Продолжить", "deactivateBreakpoints": "Отключить точки останова", "debugActionLabelAndKeybinding": "{0} ({1})", + "debugAddToWatch": "Отладка: добавить контрольное значение", "debugConsoleAction": "Консоль отладки", "debugEvaluate": "Отладка: вычисление", "disableAllBreakpoints": "Отключить все точки останова", diff --git a/i18n/rus/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/rus/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json index e237b5ffd4a..ed547a0fbfc 100644 --- a/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.i18n.json @@ -4,6 +4,14 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "command name": "Имя", + "debugger name": "Имя", + "description": "Описание", + "details": "Подробности", + "keyboard shortcuts": "&&Сочетания клавиш", "license": "Лицензия", - "noReadme": "Файл сведений недоступен." + "noReadme": "Файл сведений недоступен.", + "runtime": "Среда выполнения", + "setting name": "Имя", + "snippets": "Фрагменты" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json b/i18n/rus/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json index a12825b2476..438d12c0f06 100644 --- a/i18n/rus/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/files/browser/saveErrorHandler.i18n.json @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { - "acceptLocalChanges": "Перезаписать", + "acceptLocalChanges": "Использовать локальные изменения и перезаписать содержимое на диске", "compareChanges": "Сравнить", "conflictingFileHasChanged": "Содержимое файла на диске изменилось, и левая часть редактора сравнения обновилась. Проверьте данные и устраните проблему.", "discard": "Отмена", @@ -13,8 +13,8 @@ "readonlySaveError": "Не удалось сохранить \"{0}\": файл защищен от записи. Чтобы снять защиту, нажмите \"Перезаписать\".", "resolveSaveConflict": "{0} — разрешение конфликта сохранения", "retry": "Повторить попытку", - "revertLocalChanges": "Отменить", + "revertLocalChanges": "Отменить локальные изменения и вернуться к содержимому на диске", "saveConflictDiffLabel": "{0} — на диске ↔ в {1}", "staleSaveError": "Не удалось сохранить \"{0}\": содержимое на диске более новое. Чтобы сравнить свою версию с версией на диске, нажмите **Сравнить**.", - "userGuide": "Используйте команду **Отменить** для отмены изменений или **Перезаписать** для замены содержимого на диске" + "userGuide": "Используйте команды на панели инструментов для **отмены** изменений или **перезаписи** содержимого на диске" } \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json b/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json index 65fd75e3d90..7a237c9f15d 100644 --- a/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json +++ b/i18n/rus/src/vs/workbench/parts/themes/electron-browser/themes.contribution.i18n.json @@ -5,6 +5,7 @@ // Do not edit this file. It is machine generated. { "findMore": "Подробнее на Marketplace...", + "noIconThemeLabel": "Нет", "preferences": "Параметры", "problemChangingTheme": "Проблема с загрузкой темы: {0}", "selectTheme.label": "Цветовая тема", diff --git a/i18n/rus/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json b/i18n/rus/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json new file mode 100644 index 00000000000..8b6ad71cd4e --- /dev/null +++ b/i18n/rus/src/vs/workbench/services/configuration/node/configurationEditingService.i18n.json @@ -0,0 +1,6 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +// Do not edit this file. It is machine generated. +{} \ No newline at end of file diff --git a/i18n/rus/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json b/i18n/rus/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json index 9979cd23e25..27243d0ceea 100644 --- a/i18n/rus/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json +++ b/i18n/rus/src/vs/workbench/services/themes/electron-browser/themeService.i18n.json @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ // Do not edit this file. It is machine generated. { + "error.cannotloadicontheme": "Не удается загрузить {0}.", "error.cannotloadtheme": "Не удается загрузить {0}.", "error.cannotparse": "Проблемы при анализе файла PLIST: {0}.", "error.cannotparsejson": "Возникли проблемы при анализе файла JSON THEME: {0}.", diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 9c8eac28e70..4266bd833e9 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "code-oss-dev", - "version": "1.4.0", + "version": "1.6.0", "dependencies": { "agent-base": { "version": "1.0.2", @@ -9,7 +9,7 @@ }, "anymatch": { "version": "1.3.0", - "from": "anymatch@>=1.1.0 <2.0.0", + "from": "anymatch@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.0.tgz" }, "applicationinsights": { @@ -38,29 +38,44 @@ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz" }, "async-each": { - "version": "0.1.6", - "from": "async-each@>=0.1.5 <0.2.0", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-0.1.6.tgz" + "version": "1.0.1", + "from": "async-each@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz" + }, + "balanced-match": { + "version": "0.4.2", + "from": "balanced-match@>=0.4.1 <0.5.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz" }, "binary-extensions": { - "version": "1.4.0", + "version": "1.6.0", "from": "binary-extensions@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.4.0.tgz" + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.6.0.tgz" }, - "bindings": { - "version": "1.2.1", - "from": "bindings@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz" + "brace-expansion": { + "version": "1.1.6", + "from": "brace-expansion@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.6.tgz" }, "braces": { - "version": "1.8.3", + "version": "1.8.5", "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.3.tgz" + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz" + }, + "buffer-shims": { + "version": "1.0.0", + "from": "buffer-shims@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz" }, "chokidar": { - "version": "1.0.5", - "from": "chokidar@1.0.5", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.0.5.tgz" + "version": "1.6.0", + "from": "bpasero/chokidar#vscode", + "resolved": "git://github.com/bpasero/chokidar.git#8b64fda5a22cc9850f1346d12302051c5ba65b10" + }, + "concat-map": { + "version": "0.0.1", + "from": "concat-map@0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" }, "core-util-is": { "version": "1.0.2", @@ -83,9 +98,9 @@ "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz" }, "expand-range": { - "version": "1.8.1", + "version": "1.8.2", "from": "expand-range@>=1.8.1 <2.0.0", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.1.tgz" + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz" }, "extend": { "version": "3.0.0", @@ -134,8 +149,13 @@ }, "fsevents": { "version": "0.3.8", - "from": "fsevents@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-0.3.8.tgz" + "from": "bpasero/fsevents#vscode", + "resolved": "git://github.com/bpasero/fsevents.git#fe2aaccaaffbd69a23374cf46a8c6bafe8e51b01" + }, + "gc-signals": { + "version": "0.0.1", + "from": "gc-signals@0.0.1", + "resolved": "https://registry.npmjs.org/gc-signals/-/gc-signals-0.0.1.tgz" }, "getmac": { "version": "1.0.7", @@ -145,31 +165,12 @@ "glob-base": { "version": "0.3.0", "from": "glob-base@>=0.3.0 <0.4.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - } - } + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz" }, "glob-parent": { - "version": "1.3.0", - "from": "glob-parent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-1.3.0.tgz", - "dependencies": { - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - } - } + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz" }, "graceful-fs": { "version": "4.1.2", @@ -193,7 +194,7 @@ }, "inherits": { "version": "2.0.1", - "from": "inherits@>=2.0.1 <2.1.0", + "from": "inherits@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" }, "is-binary-path": { @@ -202,9 +203,9 @@ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz" }, "is-buffer": { - "version": "1.1.3", + "version": "1.1.4", "from": "is-buffer@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.3.tgz" + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" }, "is-dotfile": { "version": "1.0.2", @@ -227,9 +228,9 @@ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz" }, "is-glob": { - "version": "1.1.3", - "from": "is-glob@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-1.1.3.tgz" + "version": "2.0.1", + "from": "is-glob@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" }, "is-number": { "version": "2.1.0", @@ -247,45 +248,33 @@ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz" }, "isarray": { - "version": "0.0.1", - "from": "isarray@0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" + "version": "1.0.0", + "from": "isarray@1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz" }, "isobject": { - "version": "2.0.0", + "version": "2.1.0", "from": "isobject@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.0.0.tgz" + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz" }, "kind-of": { - "version": "3.0.2", + "version": "3.0.4", "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.2.tgz" - }, - "lru-cache": { - "version": "2.7.3", - "from": "lru-cache@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz" + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.0.4.tgz" }, "micromatch": { - "version": "2.3.7", + "version": "2.3.11", "from": "micromatch@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.7.tgz", - "dependencies": { - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - } - } + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz" }, "minimatch": { - "version": "0.2.14", - "from": "minimatch@>=0.2.12 <0.3.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" + "version": "3.0.3", + "from": "minimatch@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.3.tgz" }, "minimist": { "version": "1.2.0", - "from": "minimist@latest", + "from": "minimist@1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz" }, "ms": { @@ -294,9 +283,9 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz" }, "nan": { - "version": "2.2.1", - "from": "nan@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz" + "version": "2.4.0", + "from": "nan@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.4.0.tgz" }, "native-keymap": { "version": "0.1.2", @@ -314,21 +303,14 @@ "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.0.tgz" }, "oniguruma": { - "version": "6.0.1", + "version": "6.1.0", "from": "oniguruma@>=6.0.1 <7.0.0", - "resolved": "https://registry.npmjs.org/oniguruma/-/oniguruma-6.0.1.tgz" + "resolved": "https://registry.npmjs.org/oniguruma/-/oniguruma-6.1.0.tgz" }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "dependencies": { - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz" - } - } + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz" }, "path-is-absolute": { "version": "1.0.0", @@ -345,6 +327,11 @@ "from": "preserve@>=0.2.0 <0.3.0", "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz" }, + "process-nextick-args": { + "version": "1.0.7", + "from": "process-nextick-args@>=1.0.6 <1.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz" + }, "pty.js": { "version": "0.3.0", "from": "https://github.com/Tyriar/pty.js/tarball/fffbf86eb9e8051b5b2be4ba9c7b07faa018ce8d", @@ -354,6 +341,11 @@ "version": "1.2.1", "from": "extend@>=1.2.1 <1.3.0", "resolved": "https://registry.npmjs.org/extend/-/extend-1.2.1.tgz" + }, + "nan": { + "version": "2.2.1", + "from": "nan@2.2.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.2.1.tgz" } } }, @@ -363,14 +355,14 @@ "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.5.tgz" }, "readable-stream": { - "version": "1.0.34", - "from": "readable-stream@>=1.0.26-2 <1.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz" + "version": "2.1.5", + "from": "readable-stream@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.1.5.tgz" }, "readdirp": { - "version": "1.4.0", - "from": "readdirp@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-1.4.0.tgz" + "version": "2.1.0", + "from": "readdirp@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz" }, "regex-cache": { "version": "0.4.3", @@ -392,10 +384,10 @@ "from": "semver@4.3.6", "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz" }, - "sigmund": { + "set-immediate-shim": { "version": "1.0.1", - "from": "sigmund@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz" + "from": "set-immediate-shim@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz" }, "string_decoder": { "version": "0.10.31", @@ -407,10 +399,15 @@ "from": "typechecker@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz" }, + "util-deprecate": { + "version": "1.0.2", + "from": "util-deprecate@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz" + }, "vscode-debugprotocol": { - "version": "1.11.0", - "from": "vscode-debugprotocol@1.11.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.11.0.tgz" + "version": "1.13.0-pre.3", + "from": "vscode-debugprotocol@1.13.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.13.0-pre.3.tgz" }, "vscode-textmate": { "version": "2.1.1", @@ -428,7 +425,7 @@ "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.0.tgz" }, "xterm": { - "version": "1.0.0", + "version": "1.1.3", "from": "git+https://github.com/sourcelair/xterm.js.git#220828f", "resolved": "git+https://github.com/sourcelair/xterm.js.git#220828f7db9932acbd3198be5b97d81f5a7b6880" }, diff --git a/package.json b/package.json index 5db0f8f31d8..bc346f0683d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,8 @@ { "name": "code-oss-dev", - "version": "1.5.0", + "version": "1.6.0", "electronVersion": "0.37.6", + "distro": "221e07c30a60acb4752cc6ba35e5ac4481d8b9d5", "author": { "name": "Microsoft Corporation" }, @@ -15,8 +16,9 @@ }, "dependencies": { "applicationinsights": "0.15.6", - "chokidar": "1.0.5", + "chokidar": "bpasero/chokidar#vscode", "emmet": "1.3.1", + "gc-signals": "^0.0.1", "getmac": "1.0.7", "graceful-fs": "4.1.2", "http-proxy-agent": "0.2.7", @@ -27,7 +29,7 @@ "pty.js": "https://github.com/Tyriar/pty.js/tarball/fffbf86eb9e8051b5b2be4ba9c7b07faa018ce8d", "fast-plist": "0.1.1", "semver": "4.3.6", - "vscode-debugprotocol": "1.11.0", + "vscode-debugprotocol": "1.13.0-pre.3", "vscode-textmate": "2.1.1", "winreg": "1.2.0", "xterm": "git+https://github.com/sourcelair/xterm.js.git#220828f", @@ -42,6 +44,7 @@ "cson-parser": "^1.3.3", "debounce": "^1.0.0", "documentdb": "^1.5.1", + "eslint": "^3.4.0", "event-stream": "^3.1.7", "express": "^4.13.1", "ghooks": "1.0.3", @@ -61,7 +64,6 @@ "gulp-replace": "^0.5.4", "gulp-shell": "^0.5.2", "gulp-sourcemaps": "^1.6.0", - "gulp-symdest": "^1.0.0", "gulp-tsb": "^1.10.1", "gulp-tslint": "^4.3.0", "gulp-uglify": "^1.4.1", @@ -89,6 +91,7 @@ "uglify-js": "2.4.8", "underscore": "^1.8.2", "vinyl": "^0.4.5", + "vinyl-fs": "^2.4.3", "vscode-nls-dev": "^1.0.0" }, "repository": { diff --git a/scripts/test.bat b/scripts/test.bat index 7b580cbeac1..e55af32e6c0 100644 --- a/scripts/test.bat +++ b/scripts/test.bat @@ -8,6 +8,11 @@ if not "%BUILD_BUILDID%" == "" ( set ELECTRON_NO_ATTACH_CONSOLE=1 ) +rem APPVEYOR Builds +if not "%APPVEYOR%" == "" ( + set ELECTRON_NO_ATTACH_CONSOLE=1 +) + pushd %~dp0\.. for /f "tokens=2 delims=:," %%a in ('findstr /R /C:"\"nameShort\":.*" product.json') do set NAMESHORT=%%~a diff --git a/src/main.js b/src/main.js index fa36b95f26c..0f05e631398 100644 --- a/src/main.js +++ b/src/main.js @@ -12,6 +12,10 @@ var path = require('path'); var minimist = require('minimist'); var paths = require('./paths'); +var args = minimist(process.argv, { + string: ['user-data-dir', 'locale'] +}); + function stripComments(content) { var regexp = /("(?:[^\\\"]*(?:\\.)?)*")|('(?:[^\\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; var result = content.replace(regexp, function (match, m1, m2, m3, m4) { @@ -39,16 +43,7 @@ function stripComments(content) { } function getNLSConfiguration() { - var locale = undefined; - var localeOpts = '--locale'; - for (var i = 0; i < process.argv.length; i++) { - var arg = process.argv[i]; - if (arg.slice(0, localeOpts.length) == localeOpts) { - var segments = arg.split('='); - locale = segments[1]; - break; - } - } + var locale = args['locale']; if (!locale) { var userData = app.getPath('userData'); @@ -127,8 +122,7 @@ try { } // Set userData path before app 'ready' event -var argv = minimist(process.argv, { string: ['user-data-dir'] }); -var userData = argv['user-data-dir'] || paths.getDefaultUserDataPath(process.platform); +var userData = args['user-data-dir'] || paths.getDefaultUserDataPath(process.platform); app.setPath('userData', userData); // Mac: when someone drops a file to the not-yet running VSCode, the open-file event fires even before diff --git a/src/paths.js b/src/paths.js index 8373cde04d5..693547888ab 100644 --- a/src/paths.js +++ b/src/paths.js @@ -9,7 +9,7 @@ var pkg = require('../package.json'); function getAppDataPath(platform) { switch (platform) { - case 'win32': return process.env['APPDATA']; + case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming'); case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support'); case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config'); default: throw new Error('Platform not supported'); diff --git a/src/typings/gc-signals.d.ts b/src/typings/gc-signals.d.ts new file mode 100644 index 00000000000..cc982d1260c --- /dev/null +++ b/src/typings/gc-signals.d.ts @@ -0,0 +1,19 @@ +declare module 'gc-signals' { + export interface GCSignal { + } + /** + * Create a new GC signal. When being garbage collected the passed + * value is stored for later consumption. + */ + export const GCSignal: { + new (id: number): GCSignal; + }; + /** + * Consume ids of garbage collected signals. + */ + export function consumeSignals(): number[]; + export function onDidGarbageCollectSignals(callback: (ids: number[]) => any): { + dispose(): void; + }; + export function trackGarbageCollection(obj: any, id: number): number; +} \ No newline at end of file diff --git a/src/vs/base/browser/htmlContentRenderer.ts b/src/vs/base/browser/htmlContentRenderer.ts index 6443eb85e99..de6c34c290e 100644 --- a/src/vs/base/browser/htmlContentRenderer.ts +++ b/src/vs/base/browser/htmlContentRenderer.ts @@ -20,15 +20,9 @@ export interface RenderOptions { codeBlockRenderer?: (modeId: string, value: string) => string | TPromise; } -export function renderMarkedString(markedStrings: MarkedString[], options: RenderOptions = {}): Node { - let htmlContentElements = markedStrings.map(value => { - if (typeof value === 'string') { - return { markdown: value }; - } else if (typeof value === 'object') { - return { code: value }; - }; - }); - return renderHtml(htmlContentElements, options); +export function renderMarkedString(markedString: MarkedString, options: RenderOptions = {}): Node { + const htmlContentElement = typeof markedString === 'string' ? { markdown: markedString } : { code: markedString }; + return renderHtml(htmlContentElement, options); } /** diff --git a/src/vs/base/browser/ui/button/button.css b/src/vs/base/browser/ui/button/button.css index 61e35cbdd56..e77131c5f4d 100644 --- a/src/vs/base/browser/ui/button/button.css +++ b/src/vs/base/browser/ui/button/button.css @@ -26,23 +26,26 @@ } /*Theming support*/ -.vs .monaco-button:not(.disabled):hover { + +.monaco-button:not(.disabled):hover { background-color: #DDD } -.monaco-text-button:not(.disabled):hover { - background: #006BB3; -} - -.monaco-text-button:not(.disabled):active { - background: #005F9E; -} - -.vs-dark .monaco-text-button { - background: #0E639C; -} - .vs-dark .monaco-button:not(.disabled):hover, .hc-black .monaco-button:not(.disabled):hover { background-color: #2f3334; +} + +.monaco-button.monaco-text-button:not(.disabled):hover, +.vs-dark .monaco-button.monaco-text-button:not(.disabled):hover, +.hc-black .monaco-button.monaco-text-button:not(.disabled):hover { + background: #006BB3; +} + +.monaco-button.monaco-text-button:not(.disabled):active { + background: #005F9E; +} + +.vs-dark .monaco-button.monaco-text-button { + background: #0E639C; } \ No newline at end of file diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 3a04a40662c..c3e56260d46 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -87,6 +87,7 @@ export class ListView implements IDisposable { this.scrollableElement = new ScrollableElement(this.rowsContainer, { canUseTranslate3d: false, + alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: ScrollbarVisibility.Auto, useShadows: getOrDefault(options, o => o.useShadows, DefaultOptions.useShadows), diff --git a/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts b/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts index d4340ec2c4b..787bb9570d0 100644 --- a/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts +++ b/src/vs/base/browser/ui/resourceviewer/resourceViewer.ts @@ -13,6 +13,7 @@ import paths = require('vs/base/common/paths'); import {Builder, $} from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import {BoundedLinkedMap} from 'vs/base/common/map'; // Known media mimes that we can handle const mapExtToMediaMimes = { @@ -67,6 +68,29 @@ export interface IResourceDescriptor { resource: URI; name: string; size: number; + etag: string; +} + +// Chrome is caching images very aggressively and so we use the ETag information to find out if +// we need to bypass the cache or not. We could always bypass the cache everytime we show the image +// however that has very bad impact on memory consumption because each time the image gets shown, +// memory grows (see also https://github.com/electron/electron/issues/6275) +const IMAGE_RESOURCE_ETAG_CACHE = new BoundedLinkedMap<{ etag: string, src: string }>(100); +function imageSrc(descriptor: IResourceDescriptor): string { + const src = descriptor.resource.toString(); + + let cached = IMAGE_RESOURCE_ETAG_CACHE.get(src); + if (!cached) { + cached = { etag: descriptor.etag, src }; + IMAGE_RESOURCE_ETAG_CACHE.set(src, cached); + } + + if (cached.etag !== descriptor.etag) { + cached.etag = descriptor.etag; + cached.src = `${src}?${Date.now()}`; // bypass cache with this trick + } + + return cached.src; } /** @@ -98,9 +122,8 @@ export class ResourceViewer { $(container) .empty() .addClass('image') - .img({ - src: descriptor.resource.toString() // disabled due to https://github.com/electron/electron/issues/6275 + '?' + Date.now() // We really want to avoid the browser from caching this resource, so we add a fake query param that is unique - }).on(DOM.EventType.LOAD, (e, img) => { + .img({ src: imageSrc(descriptor) }) + .on(DOM.EventType.LOAD, (e, img) => { const imgElement = img.getHTMLElement(); if (imgElement.naturalWidth > imgElement.width || imgElement.naturalHeight > imgElement.height) { $(container).addClass('oversized'); @@ -119,47 +142,6 @@ export class ResourceViewer { }); } - // Embed Object (only PDF for now) - else if (false /* PDF is currently not supported in Electron it seems */ && mime.indexOf('pdf') >= 0) { - $(container) - .empty() - .element('object') - .attr({ - data: descriptor.resource.toString(), // disabled due to https://github.com/electron/electron/issues/6275 + '?' + Date.now(), // We really want to avoid the browser from caching this resource, so we add a fake query param that is unique - width: '100%', - height: '100%', - type: mime - }); - } - - // Embed Audio (if supported in browser) - else if (false /* disabled due to unknown impact on memory usage */ && mime.indexOf('audio/') >= 0) { - $(container) - .empty() - .element('audio') - .attr({ - src: descriptor.resource.toString(), // disabled due to https://github.com/electron/electron/issues/6275 + '?' + Date.now(), // We really want to avoid the browser from caching this resource, so we add a fake query param that is unique - text: nls.localize('missingAudioSupport', "Sorry but playback of audio files is not supported."), - controls: 'controls' - }).on(DOM.EventType.LOAD, () => { - scrollbar.scanDomNode(); - }); - } - - // Embed Video (if supported in browser) - else if (false /* disabled due to unknown impact on memory usage */ && mime.indexOf('video/') >= 0) { - $(container) - .empty() - .element('video') - .attr({ - src: descriptor.resource.toString(), // disabled due to https://github.com/electron/electron/issues/6275 + '?' + Date.now(), // We really want to avoid the browser from caching this resource, so we add a fake query param that is unique - text: nls.localize('missingVideoSupport', "Sorry but playback of video files is not supported."), - controls: 'controls' - }).on(DOM.EventType.LOAD, () => { - scrollbar.scanDomNode(); - }); - } - // Handle generic Binary Files else { $(container) diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index 6677ae3a6c4..f42246f9cf5 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -285,8 +285,10 @@ export class ScrollableElement extends Widget { } } - e.preventDefault(); - e.stopPropagation(); + if (this._options.alwaysConsumeMouseWheel || this._shouldRender) { + e.preventDefault(); + e.stopPropagation(); + } } private _onDidScroll(e:ScrollEvent): void { @@ -416,6 +418,7 @@ function resolveOptions(opts: ScrollableElementCreationOptions): ScrollableEleme useShadows: (typeof opts.useShadows !== 'undefined' ? opts.useShadows : true), handleMouseWheel: (typeof opts.handleMouseWheel !== 'undefined' ? opts.handleMouseWheel : true), flipAxes: (typeof opts.flipAxes !== 'undefined' ? opts.flipAxes : false), + alwaysConsumeMouseWheel: (typeof opts.alwaysConsumeMouseWheel !== 'undefined' ? opts.alwaysConsumeMouseWheel : false), scrollYToX: (typeof opts.scrollYToX !== 'undefined' ? opts.scrollYToX : false), mouseWheelScrollSensitivity: (typeof opts.mouseWheelScrollSensitivity !== 'undefined' ? opts.mouseWheelScrollSensitivity : 1), arrowSize: (typeof opts.arrowSize !== 'undefined' ? opts.arrowSize : 11), diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts index 72f519e48ad..1db0602cde2 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElementOptions.ts @@ -40,6 +40,11 @@ export interface ScrollableElementCreationOptions { * Defaults to false. */ scrollYToX?: boolean; + /** + * Always consume mouse wheel events, even when scrolling is no longer possible. + * Defaults to false. + */ + alwaysConsumeMouseWheel?: boolean; /** * A multiplier to be used on the `deltaX` and `deltaY` of mouse wheel scroll events. * Defaults to 1. @@ -117,6 +122,7 @@ export interface ScrollableElementResolvedOptions { handleMouseWheel: boolean; flipAxes: boolean; scrollYToX: boolean; + alwaysConsumeMouseWheel: boolean; mouseWheelScrollSensitivity: number; arrowSize: number; listenOnDomNode: HTMLElement; diff --git a/src/vs/base/browser/ui/splitview/splitview.css b/src/vs/base/browser/ui/splitview/splitview.css index 822a28f78ae..353a927c87f 100644 --- a/src/vs/base/browser/ui/splitview/splitview.css +++ b/src/vs/base/browser/ui/splitview/splitview.css @@ -87,7 +87,7 @@ } /* High Contrast Theming */ -.hc-black .monaco-split-view > .split-view-view > .header { +.hc-black .monaco-split-view > .split-view-view:not(:first-child) > .header { border-top: 1px solid #6FC3DF; } diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index 41c8cf24a8d..eca09a7988e 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -23,11 +23,13 @@ export function toThenable(arg: T | Thenable): Thenable { } } -export function asWinJsPromise(callback: (token: CancellationToken) => T | Thenable): TPromise { +export function asWinJsPromise(callback: (token: CancellationToken) => T | TPromise | Thenable): TPromise { let source = new CancellationTokenSource(); - return new TPromise((resolve, reject) => { + return new TPromise((resolve, reject, progress) => { let item = callback(source.token); - if (isThenable(item)) { + if (TPromise.is(item)) { + item.then(resolve, reject, progress); + } else if (isThenable(item)) { item.then(resolve, reject); } else { resolve(item); @@ -41,15 +43,15 @@ export function asWinJsPromise(callback: (token: CancellationToken) => T | Th * Hook a cancellation token to a WinJS Promise */ export function wireCancellationToken(token: CancellationToken, promise: TPromise, resolveAsUndefinedWhenCancelled?: boolean): Thenable { - token.onCancellationRequested(() => promise.cancel()); + const subscription = token.onCancellationRequested(() => promise.cancel()); if (resolveAsUndefinedWhenCancelled) { - return promise.then(undefined, err => { + promise = promise.then(undefined, err => { if (!errors.isPromiseCanceledError(err)) { return TPromise.wrapError(err); } }); } - return promise; + return always(promise, () => subscription.dispose()); } export interface ITask { diff --git a/src/vs/base/common/color.ts b/src/vs/base/common/color.ts new file mode 100644 index 00000000000..a532c64cc86 --- /dev/null +++ b/src/vs/base/common/color.ts @@ -0,0 +1,245 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as Object from 'vs/base/common/objects'; + +export interface RGBA { r: number; g: number; b: number; a: number; } +export interface HSLA { h: number; s: number; l: number; a: number; } + +/** + * Converts an Hex color value to RGB. + * returns r, g, and b are contained in the set [0, 255] + */ +function hex2rgba(hex: string): RGBA { + function parseHex(str: string) { + return parseInt('0x' + str); + } + if (hex.charAt(0) === '#' && hex.length >= 7) { + let r = parseHex(hex.substr(1, 2)); + let g = parseHex(hex.substr(3, 2)); + let b = parseHex(hex.substr(5, 2)); + let a = hex.length === 9 ? parseHex(hex.substr(7, 2)) / 0xff : 1; + return { r, g, b, a }; + } + return { r: 255, g: 0, b: 0, a: 1 }; +} + +/** + * Converts an RGB color value to HSL. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes r, g, and b are contained in the set [0, 255] and + * returns h in the set [0, 360], s, and l in the set [0, 1]. + */ +function rgba2hsla(rgba: RGBA): HSLA { + let r = rgba.r / 255; + let g = rgba.g / 255; + let b = rgba.b / 255; + let a = rgba.a === void 0 ? rgba.a : 1; + + let max = Math.max(r, g, b), min = Math.min(r, g, b); + let h = 0, s = 0, l = Math.round(((min + max) / 2) * 1000) / 1000, chroma = max - min; + + if (chroma > 0) { + s = Math.min(Math.round((l <= 0.5 ? chroma / (2 * l) : chroma / (2 - (2 * l))) * 1000) / 1000, 1); + switch (max) { + case r: h = (g - b) / chroma + (g < b ? 6 : 0); break; + case g: h = (b - r) / chroma + 2; break; + case b: h = (r - g) / chroma + 4; break; + } + h *= 60; + h = Math.round(h); + } + return { h, s, l, a }; +} + +/** + * Converts an HSL color value to RGB. Conversion formula + * adapted from http://en.wikipedia.org/wiki/HSL_color_space. + * Assumes h in the set [0, 360] s, and l are contained in the set [0, 1] and + * returns r, g, and b in the set [0, 255]. + */ +function hsla2rgba(hsla: HSLA): RGBA { + let h = hsla.h / 360; + let s = Math.min(hsla.s, 1); + let l = Math.min(hsla.l, 1); + let a = hsla.a === void 0 ? hsla.a : 1; + let r, g, b; + + if (s === 0) { + r = g = b = l; // achromatic + } else { + let hue2rgb = function hue2rgb(p, q, t) { + if (t < 0) { + t += 1; + } + if (t > 1) { + t -= 1; + } + if (t < 1 / 6) { + return p + (q - p) * 6 * t; + } + if (t < 1 / 2) { + return q; + } + if (t < 2 / 3) { + return p + (q - p) * (2 / 3 - t) * 6; + } + return p; + }; + + let q = l < 0.5 ? l * (1 + s) : l + s - l * s; + let p = 2 * l - q; + r = hue2rgb(p, q, h + 1 / 3); + g = hue2rgb(p, q, h); + b = hue2rgb(p, q, h - 1 / 3); + } + + return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255), a }; +} + +export class Color { + + private rgba: RGBA; + private hsla: HSLA; + private str: string; + + constructor(arg: string | RGBA) { + this.rgba = typeof arg === 'string' ? hex2rgba(arg) : arg; + this.str = null; + } + + /** + * http://www.w3.org/TR/WCAG20/#relativeluminancedef + * Returns the number in the set [0, 1]. O => Darkest Black. 1 => Lightest white. + */ + public getLuminosity(): number { + let luminosityFor = function (color): number { + let c = color / 255; + return (c <= 0.03928) ? c / 12.92 : Math.pow(((c + 0.055) / 1.055), 2.4); + }; + let R = luminosityFor(this.rgba.r); + let G = luminosityFor(this.rgba.g); + let B = luminosityFor(this.rgba.b); + let luminosity = 0.2126 * R + 0.7152 * G + 0.0722 * B; + return Math.round(luminosity * 10000) / 10000; + } + + /** + * http://www.w3.org/TR/WCAG20/#contrast-ratiodef + * Returns the contrast ration number in the set [1, 21]. + */ + public getContrast(another: Color): number { + let lum1 = this.getLuminosity(); + let lum2 = another.getLuminosity(); + return lum1 > lum2 ? (lum1 + 0.05) / (lum2 + 0.05) : (lum2 + 0.05) / (lum1 + 0.05); + } + + /** + * http://24ways.org/2010/calculating-color-contrast + * Return 'true' if darker color otherwise 'false' + */ + public isDarker(): boolean { + var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; + return yiq < 128; + } + + /** + * http://24ways.org/2010/calculating-color-contrast + * Return 'true' if lighter color otherwise 'false' + */ + public isLighter(): boolean { + var yiq = (this.rgba.r * 299 + this.rgba.g * 587 + this.rgba.b * 114) / 1000; + return yiq >= 128; + } + + public isLighterThan(another: Color): boolean { + let lum1 = this.getLuminosity(); + let lum2 = another.getLuminosity(); + return lum1 > lum2; + } + + public isDarkerThan(another: Color): boolean { + let lum1 = this.getLuminosity(); + let lum2 = another.getLuminosity(); + return lum1 < lum2; + } + + public lighten(factor: number): Color { + let hsl = this.toHSLA(); + hsl.l += hsl.l * factor; + return new Color(hsla2rgba(hsl)); + } + + public darken(factor: number): Color { + let hsl = this.toHSLA(); + hsl.l -= hsl.l * factor; + return new Color(hsla2rgba(hsl)); + } + + public transparent(factor: number): Color { + let p = this.rgba; + return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor }); + } + + public opposite(): Color { + return new Color({ + r: 255 - this.rgba.r, + g: 255 - this.rgba.g, + b: 255 - this.rgba.b, + a: this.rgba.a + }); + } + + public toString(): string { + if (!this.str) { + let p = this.rgba; + this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; + } + return this.str; + } + + public toHSLA(): HSLA { + if (!this.hsla) { + this.hsla = rgba2hsla(this.rgba); + } + return Object.clone(this.hsla); + } + + public toRGBA(): RGBA { + return Object.clone(this.rgba); + } + + public static fromRGBA(rgba: RGBA): Color { + return new Color(rgba); + } + + public static fromHex(hex: string): Color { + return new Color(hex); + } + + public static fromHSLA(hsla: HSLA): Color { + return new Color(hsla2rgba(hsla)); + } + + public static getLighterColor(of: Color, relative: Color, factor?: number): Color { + if (of.isLighterThan(relative)) { + return of; + } + factor = factor ? factor : 0.5; + let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity(); + factor = factor * (lum2 - lum1) / lum2; + return of.lighten(factor); + } + + public static getDarkerColor(of: Color, relative: Color, factor?: number): Color { + if (of.isDarkerThan(relative)) { + return of; + } + factor = factor ? factor : 0.5; + let lum1 = of.getLuminosity(), lum2 = relative.getLuminosity(); + factor = factor * (lum1 - lum2) / lum1; + return of.darken(factor); + } +} \ No newline at end of file diff --git a/src/vs/base/common/errorMessage.ts b/src/vs/base/common/errorMessage.ts new file mode 100644 index 00000000000..98080b8e980 --- /dev/null +++ b/src/vs/base/common/errorMessage.ts @@ -0,0 +1,242 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import nls = require('vs/nls'); +import objects = require('vs/base/common/objects'); +import types = require('vs/base/common/types'); +import arrays = require('vs/base/common/arrays'); +import strings = require('vs/base/common/strings'); +import {IXHRResponse} from 'vs/base/common/http'; + +export interface IConnectionErrorData { + status: number; + statusText?: string; + responseText?: string; +} + +/** + * The base class for all connection errors originating from XHR requests. + */ +export class ConnectionError implements Error { + public status: number; + public statusText: string; + public responseText: string; + public errorMessage: string; + public errorCode: string; + public errorObject: any; + public name: string; + + constructor(mixin: IConnectionErrorData); + constructor(request: IXHRResponse); + constructor(arg: any) { + this.status = arg.status; + this.statusText = arg.statusText; + this.name = 'ConnectionError'; + + try { + this.responseText = arg.responseText; + } catch (e) { + this.responseText = ''; + } + + this.errorMessage = null; + this.errorCode = null; + this.errorObject = null; + + if (this.responseText) { + try { + let errorObj = JSON.parse(this.responseText); + this.errorMessage = errorObj.message; + this.errorCode = errorObj.code; + this.errorObject = errorObj; + } catch (error) { + // Ignore + } + } + } + + public get message(): string { + return this.connectionErrorToMessage(this, false); + } + + public get verboseMessage(): string { + return this.connectionErrorToMessage(this, true); + } + + private connectionErrorDetailsToMessage(error: ConnectionError, verbose: boolean): string { + let errorCode = error.errorCode; + let errorMessage = error.errorMessage; + + if (errorCode !== null && errorMessage !== null) { + return nls.localize( + { + key: 'message', + comment: [ + '{0} represents the error message', + '{1} represents the error code' + ] + }, + "{0}. Error code: {1}", + strings.rtrim(errorMessage, '.'), errorCode); + } + + if (errorMessage !== null) { + return errorMessage; + } + + if (verbose && error.responseText !== null) { + return error.responseText; + } + + return null; + } + + private connectionErrorToMessage(error: ConnectionError, verbose: boolean): string { + let details = this.connectionErrorDetailsToMessage(error, verbose); + + // Status Code based Error + if (error.status === 401) { + if (details !== null) { + return nls.localize( + { + key: 'error.permission.verbose', + comment: [ + '{0} represents detailed information why the permission got denied' + ] + }, + "Permission Denied (HTTP {0})", + details); + } + + return nls.localize('error.permission', "Permission Denied"); + } + + // Return error details if present + if (details) { + return details; + } + + // Fallback to HTTP Status and Code + if (error.status > 0 && error.statusText !== null) { + if (verbose && error.responseText !== null && error.responseText.length > 0) { + return nls.localize('error.http.verbose', "{0} (HTTP {1}: {2})", error.statusText, error.status, error.responseText); + } + + return nls.localize('error.http', "{0} (HTTP {1})", error.statusText, error.status); + } + + // Finally its an Unknown Connection Error + if (verbose && error.responseText !== null && error.responseText.length > 0) { + return nls.localize('error.connection.unknown.verbose', "Unknown Connection Error ({0})", error.responseText); + } + + return nls.localize('error.connection.unknown', "An unknown connection error occurred. Either you are no longer connected to the internet or the server you are connected to is offline."); + } +} + +// Bug: Can not subclass a JS Type. Do it manually (as done in WinJS.Class.derive) +objects.derive(Error, ConnectionError); + +function xhrToErrorMessage(xhr: IConnectionErrorData, verbose: boolean): string { + let ce = new ConnectionError(xhr); + if (verbose) { + return ce.verboseMessage; + } else { + return ce.message; + } +} + +function exceptionToErrorMessage(exception: any, verbose: boolean): string { + if (exception.message) { + if (verbose && (exception.stack || exception.stacktrace)) { + return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), exception.stack || exception.stacktrace); + } + + return detectSystemErrorMessage(exception); + } + + return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); +} + +function detectSystemErrorMessage(exception: any): string { + + // See https://nodejs.org/api/errors.html#errors_class_system_error + if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') { + return nls.localize('nodeExceptionMessage', "A system error occured ({0})", exception.message); + } + + return exception.message; +} + +/** + * Tries to generate a human readable error message out of the error. If the verbose parameter + * is set to true, the error message will include stacktrace details if provided. + * @returns A string containing the error message. + */ +export function toErrorMessage(error: any = null, verbose: boolean = false): string { + if (!error) { + return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); + } + + if (Array.isArray(error)) { + let errors: any[] = arrays.coalesce(error); + let msg = toErrorMessage(errors[0], verbose); + + if (errors.length > 1) { + return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length); + } + + return msg; + } + + if (types.isString(error)) { + return error; + } + + if (!types.isUndefinedOrNull(error.status)) { + return xhrToErrorMessage(error, verbose); + } + + if (error.detail) { + let detail = error.detail; + + if (detail.error) { + if (detail.error && !types.isUndefinedOrNull(detail.error.status)) { + return xhrToErrorMessage(detail.error, verbose); + } + + if (types.isArray(detail.error)) { + for (let i = 0; i < detail.error.length; i++) { + if (detail.error[i] && !types.isUndefinedOrNull(detail.error[i].status)) { + return xhrToErrorMessage(detail.error[i], verbose); + } + } + } + + else { + return exceptionToErrorMessage(detail.error, verbose); + } + } + + if (detail.exception) { + if (!types.isUndefinedOrNull(detail.exception.status)) { + return xhrToErrorMessage(detail.exception, verbose); + } + + return exceptionToErrorMessage(detail.exception, verbose); + } + } + + if (error.stack) { + return exceptionToErrorMessage(error, verbose); + } + + if (error.message) { + return error.message; + } + + return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); +} diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 24ac4ae2794..ee0a4c84e79 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -4,14 +4,9 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import nls = require('vs/nls'); -import objects = require('vs/base/common/objects'); import platform = require('vs/base/common/platform'); import types = require('vs/base/common/types'); -import arrays = require('vs/base/common/arrays'); -import strings = require('vs/base/common/strings'); import {IAction} from 'vs/base/common/actions'; -import {IXHRResponse} from 'vs/base/common/http'; import Severity from 'vs/base/common/severity'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -93,12 +88,6 @@ export function onUnexpectedPromiseError(promise: TPromise): TPromise { return promise.then(null, onUnexpectedError); } -export interface IConnectionErrorData { - status: number; - statusText?: string; - responseText?: string; -} - export function transformErrorForSerialization(error: any): any { if (error instanceof Error) { let {name, message} = error; @@ -115,230 +104,6 @@ export function transformErrorForSerialization(error: any): any { return error; } -/** - * The base class for all connection errors originating from XHR requests. - */ -export class ConnectionError implements Error { - public status: number; - public statusText: string; - public responseText: string; - public errorMessage: string; - public errorCode: string; - public errorObject: any; - public name: string; - - constructor(mixin: IConnectionErrorData); - constructor(request: IXHRResponse); - constructor(arg: any) { - this.status = arg.status; - this.statusText = arg.statusText; - this.name = 'ConnectionError'; - - try { - this.responseText = arg.responseText; - } catch (e) { - this.responseText = ''; - } - - this.errorMessage = null; - this.errorCode = null; - this.errorObject = null; - - if (this.responseText) { - try { - let errorObj = JSON.parse(this.responseText); - this.errorMessage = errorObj.message; - this.errorCode = errorObj.code; - this.errorObject = errorObj; - } catch (error) { - // Ignore - } - } - } - - public get message(): string { - return this.connectionErrorToMessage(this, false); - } - - public get verboseMessage(): string { - return this.connectionErrorToMessage(this, true); - } - - private connectionErrorDetailsToMessage(error: ConnectionError, verbose: boolean): string { - let errorCode = error.errorCode; - let errorMessage = error.errorMessage; - - if (errorCode !== null && errorMessage !== null) { - return nls.localize( - { - key: 'message', - comment: [ - '{0} represents the error message', - '{1} represents the error code' - ] - }, - "{0}. Error code: {1}", - strings.rtrim(errorMessage, '.'), errorCode); - } - - if (errorMessage !== null) { - return errorMessage; - } - - if (verbose && error.responseText !== null) { - return error.responseText; - } - - return null; - } - - private connectionErrorToMessage(error: ConnectionError, verbose: boolean): string { - let details = this.connectionErrorDetailsToMessage(error, verbose); - - // Status Code based Error - if (error.status === 401) { - if (details !== null) { - return nls.localize( - { - key: 'error.permission.verbose', - comment: [ - '{0} represents detailed information why the permission got denied' - ] - }, - "Permission Denied (HTTP {0})", - details); - } - - return nls.localize('error.permission', "Permission Denied"); - } - - // Return error details if present - if (details) { - return details; - } - - // Fallback to HTTP Status and Code - if (error.status > 0 && error.statusText !== null) { - if (verbose && error.responseText !== null && error.responseText.length > 0) { - return nls.localize('error.http.verbose', "{0} (HTTP {1}: {2})", error.statusText, error.status, error.responseText); - } - - return nls.localize('error.http', "{0} (HTTP {1})", error.statusText, error.status); - } - - // Finally its an Unknown Connection Error - if (verbose && error.responseText !== null && error.responseText.length > 0) { - return nls.localize('error.connection.unknown.verbose', "Unknown Connection Error ({0})", error.responseText); - } - - return nls.localize('error.connection.unknown', "An unknown connection error occurred. Either you are no longer connected to the internet or the server you are connected to is offline."); - } -} - -// Bug: Can not subclass a JS Type. Do it manually (as done in WinJS.Class.derive) -objects.derive(Error, ConnectionError); - -function xhrToErrorMessage(xhr: IConnectionErrorData, verbose: boolean): string { - let ce = new ConnectionError(xhr); - if (verbose) { - return ce.verboseMessage; - } else { - return ce.message; - } -} - -function exceptionToErrorMessage(exception: any, verbose: boolean): string { - if (exception.message) { - if (verbose && (exception.stack || exception.stacktrace)) { - return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), exception.stack || exception.stacktrace); - } - - return detectSystemErrorMessage(exception); - } - - return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); -} - -function detectSystemErrorMessage(exception: any): string { - - // See https://nodejs.org/api/errors.html#errors_class_system_error - if (typeof exception.code === 'string' && typeof exception.errno === 'number' && typeof exception.syscall === 'string') { - return nls.localize('nodeExceptionMessage', "A system error occured ({0})", exception.message); - } - - return exception.message; -} - -/** - * Tries to generate a human readable error message out of the error. If the verbose parameter - * is set to true, the error message will include stacktrace details if provided. - * @returns A string containing the error message. - */ -export function toErrorMessage(error: any = null, verbose: boolean = false): string { - if (!error) { - return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); - } - - if (Array.isArray(error)) { - let errors: any[] = arrays.coalesce(error); - let msg = toErrorMessage(errors[0], verbose); - - if (errors.length > 1) { - return nls.localize('error.moreErrors', "{0} ({1} errors in total)", msg, errors.length); - } - - return msg; - } - - if (types.isString(error)) { - return error; - } - - if (!types.isUndefinedOrNull(error.status)) { - return xhrToErrorMessage(error, verbose); - } - - if (error.detail) { - let detail = error.detail; - - if (detail.error) { - if (detail.error && !types.isUndefinedOrNull(detail.error.status)) { - return xhrToErrorMessage(detail.error, verbose); - } - - if (types.isArray(detail.error)) { - for (let i = 0; i < detail.error.length; i++) { - if (detail.error[i] && !types.isUndefinedOrNull(detail.error[i].status)) { - return xhrToErrorMessage(detail.error[i], verbose); - } - } - } - - else { - return exceptionToErrorMessage(detail.error, verbose); - } - } - - if (detail.exception) { - if (!types.isUndefinedOrNull(detail.exception.status)) { - return xhrToErrorMessage(detail.exception, verbose); - } - - return exceptionToErrorMessage(detail.exception, verbose); - } - } - - if (error.stack) { - return exceptionToErrorMessage(error, verbose); - } - - if (error.message) { - return error.message; - } - - return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details."); -} - const canceledName = 'Canceled'; /** @@ -361,22 +126,22 @@ export function canceled(): Error { * Returns an error that signals something is not implemented. */ export function notImplemented(): Error { - return new Error(nls.localize('notImplementedError', "Not Implemented")); + return new Error('Not Implemented'); } export function illegalArgument(name?: string): Error { if (name) { - return new Error(nls.localize('illegalArgumentError', "Illegal argument: {0}", name)); + return new Error(`Illegal argument: ${name}`); } else { - return new Error(nls.localize('illegalArgumentError2', "Illegal argument")); + return new Error('Illegal argument'); } } export function illegalState(name?: string): Error { if (name) { - return new Error(nls.localize('illegalStateError', "Illegal state: {0}", name)); + return new Error(`Illegal state: ${name}`); } else { - return new Error(nls.localize('illegalStateError2', "Illegal state")); + return new Error('Illegal state'); } } @@ -386,14 +151,6 @@ export function readonly(name?: string): Error { : new Error('readonly property cannot be changed'); } -export function loaderError(err: Error): Error { - if (platform.isWeb) { - return new Error(nls.localize('loaderError', "Failed to load a required file. Either you are no longer connected to the internet or the server you are connected to is offline. Please refresh the browser to try again.")); - } - - return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))); -} - export interface IErrorOptions { severity?: Severity; actions?: IAction[]; diff --git a/src/vs/base/common/htmlContent.ts b/src/vs/base/common/htmlContent.ts index 3943f6f8114..24d3d8dbbde 100644 --- a/src/vs/base/common/htmlContent.ts +++ b/src/vs/base/common/htmlContent.ts @@ -67,8 +67,8 @@ function markedStringEqual(a:MarkedString, b:MarkedString): boolean { ); } -export function textAsCodeBlock(text: string) : MarkedString { - return { language: 'string', value: text }; +export function textToMarkedString(text: string) : MarkedString { + return text.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&'); // escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash } diff --git a/src/vs/base/common/mime.ts b/src/vs/base/common/mime.ts index 650a2c99e28..272e14bd738 100644 --- a/src/vs/base/common/mime.ts +++ b/src/vs/base/common/mime.ts @@ -252,5 +252,5 @@ export function suggestFilename(theMime: string, prefix: string): string { } } - return null; + return prefix; // without any known extension, just return the prefix } \ No newline at end of file diff --git a/src/vs/base/common/strings.ts b/src/vs/base/common/strings.ts index 9b392e32010..c20ff1172a8 100644 --- a/src/vs/base/common/strings.ts +++ b/src/vs/base/common/strings.ts @@ -174,14 +174,21 @@ export function endsWith(haystack: string, needle: string): boolean { } } -export function createRegExp(searchString: string, isRegex: boolean, matchCase: boolean, wholeWord: boolean, global:boolean): RegExp { +export interface RegExpOptions { + matchCase?: boolean; + wholeWord?: boolean; + multiline?: boolean; + global?: boolean; +} + +export function createRegExp(searchString: string, isRegex: boolean, options: RegExpOptions = {}): RegExp { if (searchString === '') { throw new Error('Cannot create regex from empty string'); } if (!isRegex) { searchString = searchString.replace(/[\-\\\{\}\*\+\?\|\^\$\.\,\[\]\(\)\#\s]/g, '\\$&'); } - if (wholeWord) { + if (options.wholeWord) { if (!/\B/.test(searchString.charAt(0))) { searchString = '\\b' + searchString; } @@ -190,12 +197,15 @@ export function createRegExp(searchString: string, isRegex: boolean, matchCase: } } let modifiers = ''; - if (global) { + if (options.global) { modifiers += 'g'; } - if (!matchCase) { + if (!options.matchCase) { modifiers += 'i'; } + if (options.multiline) { + modifiers += 'm'; + } return new RegExp(searchString, modifiers); } diff --git a/src/vs/base/common/uuid.ts b/src/vs/base/common/uuid.ts index 9ff483760c2..675167c31e8 100644 --- a/src/vs/base/common/uuid.ts +++ b/src/vs/base/common/uuid.ts @@ -39,8 +39,7 @@ class V4UUID extends ValueUUID { private static _timeHighBits = ['8', '9', 'a', 'b']; private static _oneOf(array:string[]):string { - var idx = Math.floor(array.length * Math.random()); - return array[idx]; + return array[Math.floor(array.length * Math.random())]; } private static _randomHex():string { @@ -92,22 +91,27 @@ class V4UUID extends ValueUUID { /** * An empty UUID that contains only zeros. */ -export var empty:UUID = new ValueUUID('00000000-0000-0000-0000-000000000000'); +export const empty:UUID = new ValueUUID('00000000-0000-0000-0000-000000000000'); export function v4():UUID { return new V4UUID(); } -var _UUIDPattern = /^[0-9A-Fa-f]{8}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{4}-[0-9A-Fa-f]{12}$/; +const _UUIDPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + +export function isUUID(value: string): boolean { + return _UUIDPattern.test(value); +} /** * Parses a UUID that is of the format xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx. * @param value A uuid string. */ export function parse(value:string):UUID { - if(!_UUIDPattern.test(value)) { + if(!isUUID(value)) { throw new Error('invalid uuid'); } + return new ValueUUID(value); } diff --git a/src/vs/base/common/worker/workerClient.ts b/src/vs/base/common/worker/workerClient.ts index 61fef76290d..86a5676b330 100644 --- a/src/vs/base/common/worker/workerClient.ts +++ b/src/vs/base/common/worker/workerClient.ts @@ -227,7 +227,9 @@ export class WorkerClient { } private _postMessage(msg:any): void { - this._worker.postMessage(stringify(msg)); + if (this._worker) { + this._worker.postMessage(stringify(msg)); + } } private _onSerializedMessage(msg:string): void { diff --git a/src/vs/base/node/stdFork.ts b/src/vs/base/node/stdFork.ts index 030a884e889..3f5a4a3f7f3 100644 --- a/src/vs/base/node/stdFork.ts +++ b/src/vs/base/node/stdFork.ts @@ -109,7 +109,9 @@ export function fork(modulePath: string, args: string[], options: IForkOpts, cal if (serverClosed) { return; } + serverClosed = true; + process.removeListener('exit', closeServer); stdOutServer.close(); stdErrServer.close(); }; diff --git a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts index ad55b7551fb..f0873cc1ba6 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenModel.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenModel.ts @@ -269,6 +269,10 @@ export class QuickOpenEntry { return { labelHighlights, descriptionHighlights }; } + + public isFile(): boolean { + return false; // TODO@Ben debt with editor history merging + } } export class QuickOpenEntryItem extends QuickOpenEntry { diff --git a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts index d5d7a26817d..fa86665bc95 100644 --- a/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts +++ b/src/vs/base/parts/quickopen/browser/quickOpenWidget.ts @@ -12,7 +12,6 @@ import browser = require('vs/base/browser/browser'); import {EventType} from 'vs/base/common/events'; import types = require('vs/base/common/types'); import errors = require('vs/base/common/errors'); -import uuid = require('vs/base/common/uuid'); import {IQuickNavigateConfiguration, IAutoFocus, IEntryRunContext, IModel, Mode} from 'vs/base/parts/quickopen/common/quickOpen'; import {Filter, Renderer, DataSource, IModelProvider, AccessibilityProvider} from 'vs/base/parts/quickopen/browser/quickOpenViewer'; import {Dimension, Builder, $} from 'vs/base/browser/builder'; @@ -44,7 +43,6 @@ export interface IQuickOpenOptions { inputPlaceHolder: string; inputAriaLabel?: string; actionProvider?: IActionProvider; - enableAnimations?: boolean; } export interface IShowOptions { @@ -92,7 +90,6 @@ export class QuickOpenWidget implements IModelProvider { private isLoosingFocus: boolean; private callbacks: IQuickOpenCallbacks; private toUnbind: IDisposable[]; - private currentInputToken: string; private quickNavigateConfiguration: IQuickNavigateConfiguration; private container: HTMLElement; private treeElement: HTMLElement; @@ -100,6 +97,7 @@ export class QuickOpenWidget implements IModelProvider { private usageLogger: IQuickOpenUsageLogger; private layoutDimensions: Dimension; private model: IModel; + private inputChangingTimeoutHandle: number; constructor(container: HTMLElement, callbacks: IQuickOpenCallbacks, options: IQuickOpenOptions, usageLogger?: IQuickOpenUsageLogger) { this.toUnbind = []; @@ -444,9 +442,6 @@ export class QuickOpenWidget implements IModelProvider { // Adjust UI for quick navigate mode if (this.quickNavigateConfiguration) { this.inputContainer.hide(); - if (this.options.enableAnimations) { - this.treeContainer.removeClass('transition'); - } this.builder.show(); this.tree.DOMFocus(); } @@ -454,9 +449,6 @@ export class QuickOpenWidget implements IModelProvider { // Otherwise use normal UI else { this.inputContainer.show(); - if (this.options.enableAnimations) { - this.treeContainer.addClass('transition'); - } this.builder.show(); this.inputBox.focus(); } @@ -492,32 +484,25 @@ export class QuickOpenWidget implements IModelProvider { } private setInputAndLayout(input: IModel, autoFocus: IAutoFocus): void { + this.treeContainer.style({ height: `${this.getHeight(input)}px` }); - // Use a generated token to avoid race conditions from setting input - let currentInputToken = uuid.generateUuid(); - this.currentInputToken = currentInputToken; + this.tree.setInput(null).then(() => { + this.model = input; - // setInput and Layout - this.setTreeHeightForInput(input).then(() => { - if (this.currentInputToken === currentInputToken) { - this.tree.setInput(null).then(() => { - this.model = input; + // ARIA + this.inputElement.setAttribute('aria-haspopup', String(input && input.entries && input.entries.length > 0)); - // ARIA - this.inputElement.setAttribute('aria-haspopup', String(input && input.entries && input.entries.length > 0)); + return this.tree.setInput(input); + }).done(() => { - return this.tree.setInput(input); - }).done(() => { - // Indicate entries to tree - this.tree.layout(); + // Indicate entries to tree + this.tree.layout(); - // Handle auto focus - if (input && input.entries.some(e => this.isElementVisible(input, e))) { - this.autoFocus(input, autoFocus); - } - }, errors.onUnexpectedError); + // Handle auto focus + if (input && input.entries.some(e => this.isElementVisible(input, e))) { + this.autoFocus(input, autoFocus); } - }); + }, errors.onUnexpectedError); } private isElementVisible(input: IModel, e: T): boolean { @@ -596,54 +581,22 @@ export class QuickOpenWidget implements IModelProvider { } // Apply height & Refresh - this.setTreeHeightForInput(input).then(() => { - this.tree.refresh().done(() => { + this.treeContainer.style({ height: `${this.getHeight(input)}px` }); + this.tree.refresh().done(() => { - // Indicate entries to tree - this.tree.layout(); + // Indicate entries to tree + this.tree.layout(); - let doAutoFocus = autoFocus && input && input.entries.some(e => this.isElementVisible(input, e)); - if (doAutoFocus && !autoFocus.autoFocusPrefixMatch) { - doAutoFocus = !this.tree.getFocus(); // if auto focus is not for prefix matches, we do not want to change what the user has focussed already - } + let doAutoFocus = autoFocus && input && input.entries.some(e => this.isElementVisible(input, e)); + if (doAutoFocus && !autoFocus.autoFocusPrefixMatch) { + doAutoFocus = !this.tree.getFocus(); // if auto focus is not for prefix matches, we do not want to change what the user has focussed already + } - // Handle auto focus - if (doAutoFocus) { - this.autoFocus(input, autoFocus); - } - }, errors.onUnexpectedError); - }); - } - - private setTreeHeightForInput(input: IModel): TPromise { - let newHeight = this.getHeight(input) + 'px'; - let oldHeight = this.treeContainer.style('height'); - - // Apply - this.treeContainer.style({ height: newHeight }); - - // Return instantly if we don't CSS transition or the height is the same as old - if (!this.treeContainer.hasClass('transition') || oldHeight === newHeight) { - return TPromise.as(null); - } - - // Otherwise return promise that only fulfills when the CSS transition has ended - return new TPromise((c, e) => { - let unbind: IDisposable[] = []; - let complete = false; - let completeHandler = () => { - if (!complete) { - complete = true; - - unbind = dispose(unbind); - - c(null); - } - }; - - this.treeContainer.once('webkitTransitionEnd', completeHandler, unbind); - this.treeContainer.once('transitionend', completeHandler, unbind); - }); + // Handle auto focus + if (doAutoFocus) { + this.autoFocus(input, autoFocus); + } + }, errors.onUnexpectedError); } private getHeight(input: IModel): number { @@ -761,6 +714,11 @@ export class QuickOpenWidget implements IModelProvider { return; } + // If the input changes, indicate this to the tree + if (!!this.getInput()) { + this.onInputChanging(); + } + // Adapt tree height to entries and apply input this.setInputAndLayout(input, autoFocus); @@ -770,6 +728,20 @@ export class QuickOpenWidget implements IModelProvider { } } + private onInputChanging(): void { + if (this.inputChangingTimeoutHandle) { + clearTimeout(this.inputChangingTimeoutHandle); + this.inputChangingTimeoutHandle = null; + } + + // when the input is changing in quick open, we indicate this as CSS class to the widget + // for a certain timeout. this helps reducing some hectic UI updates when input changes quickly + this.builder.addClass('content-changing'); + this.inputChangingTimeoutHandle = setTimeout(() => { + this.builder.removeClass('content-changing'); + }, 500); + } + public getInput(): IModel { return this.tree.getInput(); } diff --git a/src/vs/base/parts/quickopen/browser/quickopen.css b/src/vs/base/parts/quickopen/browser/quickopen.css index ddd5fe0dd44..92e2e617f4d 100644 --- a/src/vs/base/parts/quickopen/browser/quickopen.css +++ b/src/vs/base/parts/quickopen/browser/quickopen.css @@ -37,16 +37,10 @@ .quick-open-widget .quick-open-tree { line-height: 1.8em; - height: 0; /* need an initial height to trigger transition on first open */ } -.quick-open-widget .quick-open-tree.transition { - -webkit-transition: height 100ms ease-in; - -ms-transition: height 100ms ease-in; - -moz-transition: height 100ms ease-in; - -khtml-transition: height 100ms ease-in; - -o-transition: height 100ms ease-in; - transition: height 100ms ease-in; +.quick-open-widget.content-changing .quick-open-tree .monaco-scrollable-element .slider { + display: none; /* scrollbar slider causes some hectic updates when input changes quickly, so hide it while quick open changes */ } .quick-open-widget .quick-open-tree .quick-open-entry { diff --git a/src/vs/base/parts/tree/browser/collapsed-dark.svg b/src/vs/base/parts/tree/browser/collapsed-dark.svg new file mode 100755 index 00000000000..cf5c3641aa7 --- /dev/null +++ b/src/vs/base/parts/tree/browser/collapsed-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/collapsed-hc.svg b/src/vs/base/parts/tree/browser/collapsed-hc.svg new file mode 100644 index 00000000000..145c763338f --- /dev/null +++ b/src/vs/base/parts/tree/browser/collapsed-hc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/collapsed.svg b/src/vs/base/parts/tree/browser/collapsed.svg new file mode 100755 index 00000000000..3a63808c358 --- /dev/null +++ b/src/vs/base/parts/tree/browser/collapsed.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/expanded-dark.svg b/src/vs/base/parts/tree/browser/expanded-dark.svg new file mode 100755 index 00000000000..73d41e63990 --- /dev/null +++ b/src/vs/base/parts/tree/browser/expanded-dark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/expanded-hc.svg b/src/vs/base/parts/tree/browser/expanded-hc.svg new file mode 100644 index 00000000000..d38d4abc89e --- /dev/null +++ b/src/vs/base/parts/tree/browser/expanded-hc.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/expanded.svg b/src/vs/base/parts/tree/browser/expanded.svg new file mode 100755 index 00000000000..75f73adbb02 --- /dev/null +++ b/src/vs/base/parts/tree/browser/expanded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/base/parts/tree/browser/loading-hc.svg b/src/vs/base/parts/tree/browser/loading-hc.svg new file mode 100644 index 00000000000..9d4fa14a907 --- /dev/null +++ b/src/vs/base/parts/tree/browser/loading-hc.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index 3f029712b7f..56377a213e5 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -44,11 +44,7 @@ .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content { position: relative; - -moz-transition: opacity 0.15s ease-out; - -webkit-transition: opacity 0.15s ease-out; - -ms-transition: opacity 0.15s ease-out; - -o-transition: opacity 0.15s ease-out; - transition: opacity 0.15s ease-out; + height: 100%; } .monaco-tree-drag-image { @@ -70,76 +66,23 @@ /* Expansion */ -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before, -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { +.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { + content: ' '; position: absolute; display: block; - content: ""; - width: 0; - height: 0; - border-style: solid; -} - -/* Expansion: collapsed */ - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { - border-width: 4px; - border-left-width: 5px; - top: 8px; - left: -10px; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { - border-width: 2px; - border-left-width: 2px; - top: 10px; - left: -9px; -} - -.monaco-tree.no-row-padding .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { - left: 10px; -} - -.monaco-tree.no-row-padding .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { - left: 11px; -} - -/* Expansion: expanded */ - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { - border-width: 6px 6px 0 5px; - top: 9px; + background: url('collapsed.svg') 50% 50% no-repeat; + width: 16px; + height: 100%; + top: 0; left: -16px; } -.monaco-tree.no-row-padding .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { - left: 4px; +.monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { + background-image: url('expanded.svg'); } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:after { - border-width: 0; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected.expanded > .content:before { - border-left-width: 5px; -} - -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected.expanded > .content:before { - border-left-width: 5px; -} - -/* Refreshing */ .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { - background: url('loading.svg') 50% 50% no-repeat; - width: 10px; - height: 10px; - border: none; - top: 7px; - left: -12px; -} - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:after { - background: none; + background-image: url('loading.svg'); } /* Highlighted */ @@ -148,8 +91,8 @@ opacity: 0.3; } - /* Bare row */ + .monaco-tree.bare .monaco-tree-wrapper.drop-target, .monaco-tree.bare .monaco-tree-row { color: inherit !important; @@ -157,6 +100,7 @@ } /* Default style */ + .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { background-color: #DCEBFC; } .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { background-color: #4FA7FF; color: white; } .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #3399FF; color: white; } @@ -165,20 +109,8 @@ .monaco-tree .monaco-tree-wrapper.drop-target, .monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background-color: #DDECFF !important; color: inherit !important; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { border-color: transparent; border-left-color: #A6A6A6; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { border-color: transparent; border-left-color: #F6F6F6; } -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected.has-children > .content:before { border-left-color: white; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected.has-children > .content:before { border-left-color: #646465; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children:hover:not(.selected):not(.focused) > .content:after { border-left-color: #f0f0f0; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected.has-children > .content:after { border-left-color: #CCCEDB; } -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected.has-children > .content:after { border-left-color: #3399FF; } - -.monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { border-color: transparent; border-right-color: #646465; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:after { border-color: transparent; } -.monaco-tree .monaco-tree-rows > .monaco-tree-row.selected.expanded > .content:before { border-left-color: transparent; } -.monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected.expanded > .content:before { border-right-color: white; border-left-color: transparent; } - /* VS Dark */ + .vs-dark .monaco-tree.focused .monaco-tree-row.focused:not(.highlighted) { background-color: #073655; } .vs-dark .monaco-tree.focused .monaco-tree-row.selected:not(.highlighted) { background-color: #0E639C; color: white; } .vs-dark .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused.selected:not(.highlighted) { background-color: #094771; color: white; } @@ -187,17 +119,20 @@ .vs-dark .monaco-tree-wrapper.drop-target, .vs-dark .monaco-tree .monaco-tree-row.drop-target { background-color: #383B3D !important; color: inherit !important; } -/* VS Dark twistie */ -.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { border-left-color: #D4D4D4; } -.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { border-left-color: #252526; } -.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { border-left-color: transparent; border-right-color: #D4D4D4; } -.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children:hover:not(.selected):not(.focused) > .content:after { border-left-color: #2A2D2E; } +.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { + background-image: url('collapsed-dark.svg'); +} + +.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { + background-image: url('expanded-dark.svg'); +} .vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { background-image: url('loading-dark.svg'); } /* High Contrast Theming */ + .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row { background: none !important; border: 1px solid transparent; } .hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.focused:not(.highlighted) { border: 1px dotted #f38518; } .hc-black .monaco-tree.focused .monaco-tree-rows > .monaco-tree-row.selected:not(.highlighted) { border: 1px solid #f38518; } @@ -207,20 +142,15 @@ .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.drop-target { background: none !important; border: 1px dashed #f38518; } .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { - border: none; - content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTYgNHY4bDQtNC00LTR6bTEgMi40MTRsMS41ODYgMS41ODYtMS41ODYgMS41ODZ2LTMuMTcyeiIvPjwvc3ZnPg=="); - top: 2px; - left: -16px; -} - -.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { - border: none; + background-image: url('collapsed-hc.svg'); } .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.expanded > .content:before { - border: none; - content: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTExIDEwLjA3aC01LjY1Nmw1LjY1Ni01LjY1NnY1LjY1NnoiLz48L3N2Zz4="); - top: 3px; + background-image: url('expanded-hc.svg'); +} + +.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { + background-image: url('loading-hc.svg'); } .monaco-tree-action.collapse-all { diff --git a/src/vs/base/parts/tree/browser/treeView.ts b/src/vs/base/parts/tree/browser/treeView.ts index eec80162bf2..3a830ba9c78 100644 --- a/src/vs/base/parts/tree/browser/treeView.ts +++ b/src/vs/base/parts/tree/browser/treeView.ts @@ -489,6 +489,7 @@ export class TreeView extends HeightMap { this.wrapper.className = 'monaco-tree-wrapper'; this.scrollableElement = new ScrollableElement(this.wrapper, { canUseTranslate3d: false, + alwaysConsumeMouseWheel: true, horizontal: ScrollbarVisibility.Hidden, vertical: (typeof context.options.verticalScrollMode !== 'undefined' ? context.options.verticalScrollMode : ScrollbarVisibility.Auto), useShadows: context.options.useShadows, diff --git a/src/vs/base/test/common/color.test.ts b/src/vs/base/test/common/color.test.ts new file mode 100644 index 00000000000..67d2469f11e --- /dev/null +++ b/src/vs/base/test/common/color.test.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {Color} from 'vs/base/common/color'; +import * as assert from 'assert'; + +suite('Color', () => { + + test('rgba2hsla', function () { + assert.deepEqual({ h: 0, s: 0, l: 0, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 0, s: 0, l: 1, a: 1 }, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).toHSLA()); + + assert.deepEqual({ h: 0, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 120, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 240, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).toHSLA()); + + assert.deepEqual({ h: 60, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 180, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).toHSLA()); + assert.deepEqual({ h: 300, s: 1, l: 0.5, a: 1 }, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).toHSLA()); + + assert.deepEqual({ h: 0, s: 0, l: 0.753, a: 1 }, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).toHSLA()); + + assert.deepEqual({ h: 0, s: 0, l: 0.502, a: 1 }, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).toHSLA()); + assert.deepEqual({ h: 0, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 60, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 120, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).toHSLA()); + assert.deepEqual({ h: 300, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).toHSLA()); + assert.deepEqual({ h: 180, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).toHSLA()); + assert.deepEqual({ h: 240, s: 1, l: 0.251, a: 1 }, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).toHSLA()); + }); + + test('hsla2rgba', function () { + assert.deepEqual({ r: 0, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0, a: 1 }).toRGBA()); + assert.deepEqual({ r: 255, g: 255, b: 255, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 1, a: 1 }).toRGBA()); + + assert.deepEqual({ r: 255, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 1, l: 0.5, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 255, b: 0, a: 1 }, Color.fromHSLA({ h: 120, s: 1, l: 0.5, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 0, b: 255, a: 1 }, Color.fromHSLA({ h: 240, s: 1, l: 0.5, a: 1 }).toRGBA()); + + assert.deepEqual({ r: 255, g: 255, b: 0, a: 1 }, Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 255, b: 255, a: 1 }, Color.fromHSLA({ h: 180, s: 1, l: 0.5, a: 1 }).toRGBA()); + assert.deepEqual({ r: 255, g: 0, b: 255, a: 1 }, Color.fromHSLA({ h: 300, s: 1, l: 0.5, a: 1 }).toRGBA()); + + assert.deepEqual({ r: 192, g: 192, b: 192, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }).toRGBA()); + + assert.deepEqual({ r: 128, g: 128, b: 128, a: 1 }, Color.fromHSLA({ h: 0, s: 0, l: 0.502, a: 1 }).toRGBA()); + assert.deepEqual({ r: 128, g: 0, b: 0, a: 1 }, Color.fromHSLA({ h: 0, s: 1, l: 0.251, a: 1 }).toRGBA()); + assert.deepEqual({ r: 128, g: 128, b: 0, a: 1 }, Color.fromHSLA({ h: 60, s: 1, l: 0.251, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 128, b: 0, a: 1 }, Color.fromHSLA({ h: 120, s: 1, l: 0.251, a: 1 }).toRGBA()); + assert.deepEqual({ r: 128, g: 0, b: 128, a: 1 }, Color.fromHSLA({ h: 300, s: 1, l: 0.251, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 128, b: 128, a: 1 }, Color.fromHSLA({ h: 180, s: 1, l: 0.251, a: 1 }).toRGBA()); + assert.deepEqual({ r: 0, g: 0, b: 128, a: 1 }, Color.fromHSLA({ h: 240, s: 1, l: 0.251, a: 1 }).toRGBA()); + }); + + test('hex2rgba', function () { + assert.deepEqual({ r: 0, g: 0, b: 0, a: 1 }, Color.fromHex('#000000').toRGBA()); + assert.deepEqual({ r: 255, g: 255, b: 255, a: 1 }, Color.fromHex('#FFFFFF').toRGBA()); + + assert.deepEqual({ r: 255, g: 0, b: 0, a: 1 }, Color.fromHex('#FF0000').toRGBA()); + assert.deepEqual({ r: 0, g: 255, b: 0, a: 1 }, Color.fromHex('#00FF00').toRGBA()); + assert.deepEqual({ r: 0, g: 0, b: 255, a: 1 }, Color.fromHex('#0000FF').toRGBA()); + + assert.deepEqual({ r: 255, g: 255, b: 0, a: 1 }, Color.fromHex('#FFFF00').toRGBA()); + assert.deepEqual({ r: 0, g: 255, b: 255, a: 1 }, Color.fromHex('#00FFFF').toRGBA()); + assert.deepEqual({ r: 255, g: 0, b: 255, a: 1 }, Color.fromHex('#FF00FF').toRGBA()); + + assert.deepEqual({ r: 192, g: 192, b: 192, a: 1 }, Color.fromHex('#C0C0C0').toRGBA()); + + assert.deepEqual({ r: 128, g: 128, b: 128, a: 1 }, Color.fromHex('#808080').toRGBA()); + assert.deepEqual({ r: 128, g: 0, b: 0, a: 1 }, Color.fromHex('#800000').toRGBA()); + assert.deepEqual({ r: 128, g: 128, b: 0, a: 1 }, Color.fromHex('#808000').toRGBA()); + assert.deepEqual({ r: 0, g: 128, b: 0, a: 1 }, Color.fromHex('#008000').toRGBA()); + assert.deepEqual({ r: 128, g: 0, b: 128, a: 1 }, Color.fromHex('#800080').toRGBA()); + assert.deepEqual({ r: 0, g: 128, b: 128, a: 1 }, Color.fromHex('#008080').toRGBA()); + assert.deepEqual({ r: 0, g: 0, b: 128, a: 1 }, Color.fromHex('#000080').toRGBA()); + }); + + test('isLighterColor', function () { + let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }); + + assert.ok(color1.isLighterThan(color2)); + + // Abyss theme + assert.ok(Color.fromHex('#770811').isLighterThan(Color.fromHex('#000c18'))); + }); + + test('getLighterColor', function () { + let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }); + + assert.deepEqual(color1.toHSLA(), Color.getLighterColor(color1, color2).toHSLA()); + assert.deepEqual({ h: 0, s: 0, l: 0.914, a: 1 }, Color.getLighterColor(color2, color1).toHSLA()); + assert.deepEqual({ h: 0, s: 0, l: 0.851, a: 1 }, Color.getLighterColor(color2, color1, 0.3).toHSLA()); + assert.deepEqual({ h: 0, s: 0, l: 0.98, a: 1 }, Color.getLighterColor(color2, color1, 0.7).toHSLA()); + assert.deepEqual({ h: 0, s: 0, l: 1, a: 1 }, Color.getLighterColor(color2, color1, 1).toHSLA()); + + }); + + test('isDarkerColor', function () { + let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }); + + assert.ok(color2.isDarkerThan(color1)); + + }); + + test('getDarkerColor', function () { + let color1 = Color.fromHSLA({ h: 60, s: 1, l: 0.5, a: 1 }), color2 = Color.fromHSLA({ h: 0, s: 0, l: 0.753, a: 1 }); + + assert.deepEqual(color2.toHSLA(), Color.getDarkerColor(color2, color1).toHSLA()); + assert.deepEqual({ h: 60, s: 1, l: 0.392, a: 1 }, Color.getDarkerColor(color1, color2).toHSLA()); + assert.deepEqual({ h: 60, s: 1, l: 0.435, a: 1 }, Color.getDarkerColor(color1, color2, 0.3).toHSLA()); + assert.deepEqual({ h: 60, s: 1, l: 0.349, a: 1 }, Color.getDarkerColor(color1, color2, 0.7).toHSLA()); + assert.deepEqual({ h: 60, s: 1, l: 0.284, a: 1 }, Color.getDarkerColor(color1, color2, 1).toHSLA()); + + // Abyss theme + assert.deepEqual({ h: 355, s: 0.874, l: 0.157, a: 1 }, Color.getDarkerColor(Color.fromHex('#770811'), Color.fromHex('#000c18'), 0.4).toHSLA()); + }); + + test('luminosity', function () { + assert.deepEqual(0, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(1, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.2126, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.7152, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.0722, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.9278, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.7874, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).getLuminosity()); + assert.deepEqual(0.2848, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.5271, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).getLuminosity()); + + assert.deepEqual(0.2159, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.0459, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.2003, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.1544, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.0615, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.17, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.0156, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).getLuminosity()); + }); + + test('contrast', function () { + assert.deepEqual(0, Color.fromRGBA({ r: 0, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(1, Color.fromRGBA({ r: 255, g: 255, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.2126, Color.fromRGBA({ r: 255, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.7152, Color.fromRGBA({ r: 0, g: 255, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.0722, Color.fromRGBA({ r: 0, g: 0, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.9278, Color.fromRGBA({ r: 255, g: 255, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.7874, Color.fromRGBA({ r: 0, g: 255, b: 255, a: 1 }).getLuminosity()); + assert.deepEqual(0.2848, Color.fromRGBA({ r: 255, g: 0, b: 255, a: 1 }).getLuminosity()); + + assert.deepEqual(0.5271, Color.fromRGBA({ r: 192, g: 192, b: 192, a: 1 }).getLuminosity()); + + assert.deepEqual(0.2159, Color.fromRGBA({ r: 128, g: 128, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.0459, Color.fromRGBA({ r: 128, g: 0, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.2003, Color.fromRGBA({ r: 128, g: 128, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.1544, Color.fromRGBA({ r: 0, g: 128, b: 0, a: 1 }).getLuminosity()); + assert.deepEqual(0.0615, Color.fromRGBA({ r: 128, g: 0, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.17, Color.fromRGBA({ r: 0, g: 128, b: 128, a: 1 }).getLuminosity()); + assert.deepEqual(0.0156, Color.fromRGBA({ r: 0, g: 0, b: 128, a: 1 }).getLuminosity()); + }); + +}); diff --git a/src/vs/base/test/common/errors.test.ts b/src/vs/base/test/common/errors.test.ts index 1e7f7200559..22472c8d745 100644 --- a/src/vs/base/test/common/errors.test.ts +++ b/src/vs/base/test/common/errors.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { toErrorMessage } from 'vs/base/common/errors'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; suite('Errors', () => { test('Get Error Message', function () { diff --git a/src/vs/base/worker/defaultWorkerFactory.ts b/src/vs/base/worker/defaultWorkerFactory.ts index abfa5b9935c..87d36ddc78b 100644 --- a/src/vs/base/worker/defaultWorkerFactory.ts +++ b/src/vs/base/worker/defaultWorkerFactory.ts @@ -10,7 +10,7 @@ import {logOnceWebWorkerWarning, IWorker, IWorkerCallback, IWorkerFactory} from import * as dom from 'vs/base/browser/dom'; function defaultGetWorkerUrl(workerId:string, label:string): string { - return require.toUrl('./' + workerId); + return require.toUrl('./' + workerId) + '#' + label; } var getWorkerUrl = flags.getCrossOriginWorkerScriptUrl || defaultGetWorkerUrl; @@ -41,7 +41,9 @@ class WebWorker implements IWorker { } public postMessage(msg:string): void { - this.worker.postMessage(msg); + if (this.worker) { + this.worker.postMessage(msg); + } } public dispose(): void { @@ -128,10 +130,12 @@ export class DefaultWorkerFactory implements IWorkerFactory { private static LAST_WORKER_ID = 0; + private _label: string; private _fallbackToIframe:boolean; private _webWorkerFailedBeforeError:any; - constructor(fallbackToIframe:boolean) { + constructor(label:string, fallbackToIframe:boolean) { + this._label = label; this._fallbackToIframe = fallbackToIframe; this._webWorkerFailedBeforeError = false; } @@ -145,7 +149,7 @@ export class DefaultWorkerFactory implements IWorkerFactory { } try { - return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => { + return new WebWorker(moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => { logOnceWebWorkerWarning(err); this._webWorkerFailedBeforeError = err; onErrorCallback(err); @@ -159,7 +163,7 @@ export class DefaultWorkerFactory implements IWorkerFactory { if (this._webWorkerFailedBeforeError) { throw this._webWorkerFailedBeforeError; } - return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback, (err) => { + return new WebWorker(moduleId, workerId, this._label || 'anonymous' + workerId, onMessageCallback, (err) => { logOnceWebWorkerWarning(err); this._webWorkerFailedBeforeError = err; onErrorCallback(err); diff --git a/src/vs/base/worker/workerMain.ts b/src/vs/base/worker/workerMain.ts index 717ca10fdf7..461c9a52374 100644 --- a/src/vs/base/worker/workerMain.ts +++ b/src/vs/base/worker/workerMain.ts @@ -18,14 +18,16 @@ let loadCode = function(moduleId) { require([moduleId], function(ws) { - let messageHandler = ws.create((msg:any) => { - (self).postMessage(msg); - }, null); + setTimeout(function() { + let messageHandler = ws.create((msg:any) => { + (self).postMessage(msg); + }, null); - self.onmessage = (e) => messageHandler.onmessage(e.data); - while(beforeReadyMessages.length > 0) { - self.onmessage(beforeReadyMessages.shift()); - } + self.onmessage = (e) => messageHandler.onmessage(e.data); + while(beforeReadyMessages.length > 0) { + self.onmessage(beforeReadyMessages.shift()); + } + }, 0); }); }; diff --git a/src/vs/code/electron-main/auto-updater.win32.ts b/src/vs/code/electron-main/auto-updater.win32.ts index c5613d59b7f..a32e78e0504 100644 --- a/src/vs/code/electron-main/auto-updater.win32.ts +++ b/src/vs/code/electron-main/auto-updater.win32.ts @@ -44,7 +44,7 @@ export class Win32AutoUpdaterImpl extends EventEmitter { } get cachePath(): TPromise { - let result = path.join(tmpdir(), 'vscode-update'); + const result = path.join(tmpdir(), 'vscode-update'); return new TPromise((c, e) => mkdirp(result, null, err => err ? e(err) : c(result))); } diff --git a/src/vs/code/electron-main/env.ts b/src/vs/code/electron-main/env.ts index 57f83264419..43bd79b713e 100644 --- a/src/vs/code/electron-main/env.ts +++ b/src/vs/code/electron-main/env.ts @@ -17,7 +17,7 @@ import URI from 'vs/base/common/uri'; import * as types from 'vs/base/common/types'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import product, { IProductConfiguration } from 'vs/platform/product'; -import { parseArgs, ParsedArgs } from 'vs/code/node/argv'; +import { parseArgs, ParsedArgs } from 'vs/platform/environment/node/argv'; export interface IProcessEnvironment { [key: string]: string; @@ -91,7 +91,7 @@ export class EnvService implements IEnvService { this._appKeybindingsPath = path.join(this._appSettingsHome, 'keybindings.json'); // Remove the Electron executable - let [, ...args] = process.argv; + const [, ...args] = process.argv; // If dev, remove the first non-option argument: it's the app location if (!this.isBuilt) { @@ -205,14 +205,14 @@ export interface IParsedPath { } export function parseLineAndColumnAware(rawPath: string): IParsedPath { - let segments = rawPath.split(':'); // C:\file.txt:: + const segments = rawPath.split(':'); // C:\file.txt:: let path: string; let line: number = null; let column: number = null; segments.forEach(segment => { - let segmentAsNumber = Number(segment); + const segmentAsNumber = Number(segment); if (!types.isNumber(segmentAsNumber)) { path = !!path ? [path, segment].join(':') : segment; // a colon can well be part of a path (e.g. C:\...) } else if (line === null) { @@ -234,7 +234,7 @@ export function parseLineAndColumnAware(rawPath: string): IParsedPath { } function toLineAndColumnPath(parsedPath: IParsedPath): string { - let segments = [parsedPath.path]; + const segments = [parsedPath.path]; if (types.isNumber(parsedPath.line)) { segments.push(String(parsedPath.line)); diff --git a/src/vs/code/electron-main/lifecycle.ts b/src/vs/code/electron-main/lifecycle.ts index 7c4c9880856..66f3e5aae83 100644 --- a/src/vs/code/electron-main/lifecycle.ts +++ b/src/vs/code/electron-main/lifecycle.ts @@ -119,7 +119,7 @@ export class LifecycleService implements ILifecycleService { // Window Before Closing: Main -> Renderer vscodeWindow.win.on('close', (e) => { - let windowId = vscodeWindow.id; + const windowId = vscodeWindow.id; this.logService.log('Lifecycle#window-before-close', windowId); // The window already acknowledged to be closed @@ -155,9 +155,9 @@ export class LifecycleService implements ILifecycleService { this.logService.log('Lifecycle#unload()', vscodeWindow.id); return new TPromise((c) => { - let oneTimeEventToken = this.oneTimeListenerTokenGenerator++; - let oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken; - let oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken; + const oneTimeEventToken = this.oneTimeListenerTokenGenerator++; + const oneTimeOkEvent = 'vscode:ok' + oneTimeEventToken; + const oneTimeCancelEvent = 'vscode:cancel' + oneTimeEventToken; ipc.once(oneTimeOkEvent, () => { c(false); // no veto diff --git a/src/vs/code/electron-main/log.ts b/src/vs/code/electron-main/log.ts index e7e0667f986..c281e1a8e38 100644 --- a/src/vs/code/electron-main/log.ts +++ b/src/vs/code/electron-main/log.ts @@ -19,7 +19,7 @@ export class MainLogService implements ILogService { _serviceBrand: any; - constructor( @IEnvService private envService: IEnvService) { + constructor(@IEnvService private envService: IEnvService) { } log(...args: any[]): void { diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index 9140ae96f97..0d0b3e365f0 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -10,7 +10,7 @@ import * as fs from 'original-fs'; import { app, ipcMain as ipc } from 'electron'; import { assign } from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; -import { parseMainProcessArgv, ParsedArgs } from 'vs/code/node/argv'; +import { parseMainProcessArgv, ParsedArgs } from 'vs/platform/environment/node/argv'; import { mkdirp } from 'vs/base/node/pfs'; import { IProcessEnvironment, IEnvService, EnvService } from 'vs/code/electron-main/env'; import { IWindowsService, WindowsManager } from 'vs/code/electron-main/windows'; @@ -69,14 +69,14 @@ function main(accessor: ServicesAccessor, mainIpcServer: Server, userEnv: IProce const windowsService = accessor.get(IWindowsService); const lifecycleService = accessor.get(ILifecycleService); const updateService = accessor.get(IUpdateService); - const configurationService = >accessor.get(IConfigurationService); + const configurationService = accessor.get(IConfigurationService) as ConfigurationService; // We handle uncaught exceptions here to prevent electron from opening a dialog to the user process.on('uncaughtException', (err: any) => { if (err) { // take only the message and stack property - let friendlyError = { + const friendlyError = { message: err.message, stack: err.stack }; diff --git a/src/vs/code/electron-main/menus.ts b/src/vs/code/electron-main/menus.ts index 535de3080b5..6ab11aa7c2e 100644 --- a/src/vs/code/electron-main/menus.ts +++ b/src/vs/code/electron-main/menus.ts @@ -11,7 +11,7 @@ import * as platform from 'vs/base/common/platform'; import * as arrays from 'vs/base/common/arrays'; import * as env from 'vs/code/electron-main/env'; import { ipcMain as ipc, app, shell, dialog, Menu, MenuItem } from 'electron'; -import { IWindowsService, WindowsManager, IOpenedPathsList } from 'vs/code/electron-main/windows'; +import { IWindowsService } from 'vs/code/electron-main/windows'; import { IPath, VSCodeWindow } from 'vs/code/electron-main/window'; import { IStorageService } from 'vs/code/electron-main/storage'; import { IUpdateService, State as UpdateState } from 'vs/code/electron-main/update-manager'; @@ -20,10 +20,10 @@ import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; export function generateNewIssueUrl(baseUrl: string, name: string, version: string, commit: string, date: string): string { - const osVersion = `${ os.type() } ${ os.arch() } ${ os.release() }`; + const osVersion = `${os.type()} ${os.arch()} ${os.release()}`; const queryStringPrefix = baseUrl.indexOf('?') === -1 ? '?' : '&'; const body = encodeURIComponent( -`- VSCode Version: ${name} ${version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'}) + `- VSCode Version: ${name} ${version} (${product.commit || 'Commit unknown'}, ${product.date || 'Date unknown'}) - OS Version: ${osVersion} Steps to Reproduce: @@ -32,7 +32,7 @@ Steps to Reproduce: 2.` ); - return `${ baseUrl }${queryStringPrefix}body=${body}`; + return `${baseUrl}${queryStringPrefix}body=${body}`; } interface IResolvedKeybinding { @@ -44,7 +44,7 @@ export class VSCodeMenu { private static lastKnownKeybindingsMapStorageKey = 'lastKnownKeybindings'; - private static MAX_RECENT_ENTRIES = 10; + private static MAX_MENU_RECENT_ENTRIES = 10; private isQuitting: boolean; private appMenuInstalled: boolean; @@ -97,7 +97,7 @@ export class VSCodeMenu { // Fill hash map of resolved keybindings let needsMenuUpdate = false; keybindings.forEach((keybinding) => { - let accelerator = new Keybinding(keybinding.binding)._toElectronAccelerator(); + const accelerator = new Keybinding(keybinding.binding)._toElectronAccelerator(); if (accelerator) { this.mapResolvedKeybindingToActionId[keybinding.id] = accelerator; if (this.mapLastKnownKeybindingToActionId[keybinding.id] !== accelerator) { @@ -153,7 +153,6 @@ export class VSCodeMenu { } private onOpen(path: IPath): void { - this.addToOpenedPathsList(path.filePath || path.workspacePath, !!path.filePath); this.updateMenu(); } @@ -166,47 +165,47 @@ export class VSCodeMenu { private install(): void { // Menus - let menubar = new Menu(); + const menubar = new Menu(); // Mac: Application let macApplicationMenuItem: Electron.MenuItem; if (platform.isMacintosh) { - let applicationMenu = new Menu(); + const applicationMenu = new Menu(); macApplicationMenuItem = new MenuItem({ label: this.envService.product.nameShort, submenu: applicationMenu }); this.setMacApplicationMenu(applicationMenu); } // File - let fileMenu = new Menu(); - let fileMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu }); + const fileMenu = new Menu(); + const fileMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mFile', comment: ['&& denotes a mnemonic'] }, "&&File")), submenu: fileMenu }); this.setFileMenu(fileMenu); // Edit - let editMenu = new Menu(); - let editMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu }); + const editMenu = new Menu(); + const editMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mEdit', comment: ['&& denotes a mnemonic'] }, "&&Edit")), submenu: editMenu }); this.setEditMenu(editMenu); // View - let viewMenu = new Menu(); - let viewMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu }); + const viewMenu = new Menu(); + const viewMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mView', comment: ['&& denotes a mnemonic'] }, "&&View")), submenu: viewMenu }); this.setViewMenu(viewMenu); // Goto - let gotoMenu = new Menu(); - let gotoMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu }); + const gotoMenu = new Menu(); + const gotoMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mGoto', comment: ['&& denotes a mnemonic'] }, "&&Go")), submenu: gotoMenu }); this.setGotoMenu(gotoMenu); // Mac: Window let macWindowMenuItem: Electron.MenuItem; if (platform.isMacintosh) { - let windowMenu = new Menu(); + const windowMenu = new Menu(); macWindowMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize('mWindow', "Window")), submenu: windowMenu, role: 'window' }); this.setMacWindowMenu(windowMenu); } // Help - let helpMenu = new Menu(); - let helpMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' }); + const helpMenu = new Menu(); + const helpMenuItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'mHelp', comment: ['&& denotes a mnemonic'] }, "&&Help")), submenu: helpMenu, role: 'help' }); this.setHelpMenu(helpMenu); // Menu Structure @@ -231,76 +230,23 @@ export class VSCodeMenu { if (platform.isMacintosh && !this.appMenuInstalled) { this.appMenuInstalled = true; - let dockMenu = new Menu(); + const dockMenu = new Menu(); dockMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), click: () => this.windowsService.openNewWindow() })); app.dock.setMenu(dockMenu); } } - private addToOpenedPathsList(path?: string, isFile?: boolean): void { - if (!path) { - return; - } - - let mru = this.getOpenedPathsList(); - if (isFile) { - mru.files.unshift(path); - mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase()); - } else { - mru.folders.unshift(path); - mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase()); - } - - // Make sure its bounded - mru.folders = mru.folders.slice(0, VSCodeMenu.MAX_RECENT_ENTRIES); - mru.files = mru.files.slice(0, VSCodeMenu.MAX_RECENT_ENTRIES); - - this.storageService.setItem(WindowsManager.openedPathsListStorageKey, mru); - } - - private removeFromOpenedPathsList(path: string): void { - let mru = this.getOpenedPathsList(); - - let index = mru.files.indexOf(path); - if (index >= 0) { - mru.files.splice(index, 1); - } - - index = mru.folders.indexOf(path); - if (index >= 0) { - mru.folders.splice(index, 1); - } - - this.storageService.setItem(WindowsManager.openedPathsListStorageKey, mru); - } - - private clearOpenedPathsList(): void { - this.storageService.setItem(WindowsManager.openedPathsListStorageKey, { folders: [], files: [] }); - app.clearRecentDocuments(); - - this.updateMenu(); - } - - private getOpenedPathsList(): IOpenedPathsList { - let mru = this.storageService.getItem(WindowsManager.openedPathsListStorageKey); - if (!mru) { - mru = { folders: [], files: [] }; - } - - return mru; - } - private setMacApplicationMenu(macApplicationMenu: Electron.Menu): void { - let about = new MenuItem({ label: nls.localize('mAbout', "About {0}", this.envService.product.nameLong), role: 'about' }); - let checkForUpdates = this.getUpdateMenuItems(); - let preferences = this.getPreferencesMenu(); - let hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", this.envService.product.nameLong), role: 'hide', accelerator: 'Command+H' }); - let hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' }); - let showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); - let quit = new MenuItem({ label: nls.localize('miQuit', "Quit {0}", this.envService.product.nameLong), click: () => this.quit(), accelerator: 'Command+Q' }); + const about = new MenuItem({ label: nls.localize('mAbout', "About {0}", this.envService.product.nameLong), role: 'about' }); + const checkForUpdates = this.getUpdateMenuItems(); + const preferences = this.getPreferencesMenu(); + const hide = new MenuItem({ label: nls.localize('mHide', "Hide {0}", this.envService.product.nameLong), role: 'hide', accelerator: 'Command+H' }); + const hideOthers = new MenuItem({ label: nls.localize('mHideOthers', "Hide Others"), role: 'hideothers', accelerator: 'Command+Alt+H' }); + const showAll = new MenuItem({ label: nls.localize('mShowAll', "Show All"), role: 'unhide' }); + const quit = new MenuItem({ label: nls.localize('miQuit', "Quit {0}", this.envService.product.nameLong), click: () => this.quit(), accelerator: 'Command+Q' }); - let actions = [about]; + const actions = [about]; actions.push(...checkForUpdates); actions.push(...[ __separator__(), @@ -317,7 +263,7 @@ export class VSCodeMenu { } private setFileMenu(fileMenu: Electron.Menu): void { - let hasNoWindows = (this.windowsService.getWindowCount() === 0); + const hasNoWindows = (this.windowsService.getWindowCount() === 0); let newFile: Electron.MenuItem; if (hasNoWindows) { @@ -326,8 +272,8 @@ export class VSCodeMenu { newFile = this.createMenuItem(nls.localize({ key: 'miNewFile', comment: ['&& denotes a mnemonic'] }, "&&New File"), 'workbench.action.files.newUntitledFile'); } - let open = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), accelerator: this.getAccelerator('workbench.action.files.openFileFolder'), click: () => this.windowsService.openFileFolderPicker() }); - let openFolder = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), accelerator: this.getAccelerator('workbench.action.files.openFolder'), click: () => this.windowsService.openFolderPicker() }); + const open = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpen', comment: ['&& denotes a mnemonic'] }, "&&Open...")), accelerator: this.getAccelerator('workbench.action.files.openFileFolder'), click: () => this.windowsService.openFileFolderPicker() }); + const openFolder = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenFolder', comment: ['&& denotes a mnemonic'] }, "Open &&Folder...")), accelerator: this.getAccelerator('workbench.action.files.openFolder'), click: () => this.windowsService.openFolderPicker() }); let openFile: Electron.MenuItem; if (hasNoWindows) { @@ -336,24 +282,24 @@ export class VSCodeMenu { openFile = this.createMenuItem(nls.localize({ key: 'miOpenFile', comment: ['&& denotes a mnemonic'] }, "&&Open File..."), 'workbench.action.files.openFile'); } - let openRecentMenu = new Menu(); + const openRecentMenu = new Menu(); this.setOpenRecentMenu(openRecentMenu); - let openRecent = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 }); + const openRecent = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miOpenRecent', comment: ['&& denotes a mnemonic'] }, "Open &&Recent")), submenu: openRecentMenu, enabled: openRecentMenu.items.length > 0 }); - let saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save', this.windowsService.getWindowCount() > 0); - let saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs', this.windowsService.getWindowCount() > 0); - let saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll', this.windowsService.getWindowCount() > 0); + const saveFile = this.createMenuItem(nls.localize({ key: 'miSave', comment: ['&& denotes a mnemonic'] }, "&&Save"), 'workbench.action.files.save', this.windowsService.getWindowCount() > 0); + const saveFileAs = this.createMenuItem(nls.localize({ key: 'miSaveAs', comment: ['&& denotes a mnemonic'] }, "Save &&As..."), 'workbench.action.files.saveAs', this.windowsService.getWindowCount() > 0); + const saveAllFiles = this.createMenuItem(nls.localize({ key: 'miSaveAll', comment: ['&& denotes a mnemonic'] }, "Save A&&ll"), 'workbench.action.files.saveAll', this.windowsService.getWindowCount() > 0); - let preferences = this.getPreferencesMenu(); + const preferences = this.getPreferencesMenu(); - let newWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), accelerator: this.getAccelerator('workbench.action.newWindow'), click: () => this.windowsService.openNewWindow() }); - let revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Revert F&&ile"), 'workbench.action.files.revert', this.windowsService.getWindowCount() > 0); - let closeWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), accelerator: this.getAccelerator('workbench.action.closeWindow'), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 }); + const newWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miNewWindow', comment: ['&& denotes a mnemonic'] }, "&&New Window")), accelerator: this.getAccelerator('workbench.action.newWindow'), click: () => this.windowsService.openNewWindow() }); + const revertFile = this.createMenuItem(nls.localize({ key: 'miRevert', comment: ['&& denotes a mnemonic'] }, "Revert F&&ile"), 'workbench.action.files.revert', this.windowsService.getWindowCount() > 0); + const closeWindow = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miCloseWindow', comment: ['&& denotes a mnemonic'] }, "Close &&Window")), accelerator: this.getAccelerator('workbench.action.closeWindow'), click: () => this.windowsService.getLastActiveWindow().win.close(), enabled: this.windowsService.getWindowCount() > 0 }); - let closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder'); - let closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "Close &&Editor"), 'workbench.action.closeActiveEditor'); + const closeFolder = this.createMenuItem(nls.localize({ key: 'miCloseFolder', comment: ['&& denotes a mnemonic'] }, "Close &&Folder"), 'workbench.action.closeFolder'); + const closeEditor = this.createMenuItem(nls.localize({ key: 'miCloseEditor', comment: ['&& denotes a mnemonic'] }, "Close &&Editor"), 'workbench.action.closeActiveEditor'); - let exit = this.createMenuItem(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit"), () => this.quit()); + const exit = this.createMenuItem(nls.localize({ key: 'miExit', comment: ['&& denotes a mnemonic'] }, "E&&xit"), () => this.quit()); arrays.coalesce([ newFile, @@ -380,14 +326,14 @@ export class VSCodeMenu { } private getPreferencesMenu(): Electron.MenuItem { - let userSettings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&User Settings"), 'workbench.action.openGlobalSettings'); - let workspaceSettings = this.createMenuItem(nls.localize({ key: 'miOpenWorkspaceSettings', comment: ['&& denotes a mnemonic'] }, "&&Workspace Settings"), 'workbench.action.openWorkspaceSettings'); - let kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings'); - let snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets'); - let colorThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme'); - let iconThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme"), 'workbench.action.selectIconTheme'); + const userSettings = this.createMenuItem(nls.localize({ key: 'miOpenSettings', comment: ['&& denotes a mnemonic'] }, "&&User Settings"), 'workbench.action.openGlobalSettings'); + const workspaceSettings = this.createMenuItem(nls.localize({ key: 'miOpenWorkspaceSettings', comment: ['&& denotes a mnemonic'] }, "&&Workspace Settings"), 'workbench.action.openWorkspaceSettings'); + const kebindingSettings = this.createMenuItem(nls.localize({ key: 'miOpenKeymap', comment: ['&& denotes a mnemonic'] }, "&&Keyboard Shortcuts"), 'workbench.action.openGlobalKeybindings'); + const snippetsSettings = this.createMenuItem(nls.localize({ key: 'miOpenSnippets', comment: ['&& denotes a mnemonic'] }, "User &&Snippets"), 'workbench.action.openSnippets'); + const colorThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectColorTheme', comment: ['&& denotes a mnemonic'] }, "&&Color Theme"), 'workbench.action.selectTheme'); + const iconThemeSelection = this.createMenuItem(nls.localize({ key: 'miSelectIconTheme', comment: ['&& denotes a mnemonic'] }, "File &&Icon Theme"), 'workbench.action.selectIconTheme'); - let preferencesMenu = new Menu(); + const preferencesMenu = new Menu(); preferencesMenu.append(userSettings); preferencesMenu.append(workspaceSettings); preferencesMenu.append(__separator__()); @@ -405,7 +351,7 @@ export class VSCodeMenu { // If the user selected to exit from an extension development host window, do not quit, but just // close the window unless this is the last window that is opened. - let vscodeWindow = this.windowsService.getFocusedWindow(); + const vscodeWindow = this.windowsService.getFocusedWindow(); if (vscodeWindow && vscodeWindow.isPluginDevelopmentHost && this.windowsService.getWindowCount() > 1) { vscodeWindow.win.close(); } @@ -423,42 +369,38 @@ export class VSCodeMenu { private setOpenRecentMenu(openRecentMenu: Electron.Menu): void { openRecentMenu.append(this.createMenuItem(nls.localize({ key: 'miReopenClosedEditor', comment: ['&& denotes a mnemonic'] }, "&&Reopen Closed Editor"), 'workbench.action.reopenClosedEditor')); - let recentList = this.getOpenedPathsList(); + const {folders, files} = this.windowsService.getRecentPathsList(); // Folders - if (recentList.folders.length > 0) { + if (folders.length > 0) { openRecentMenu.append(__separator__()); - recentList.folders.forEach((folder, index) => { - if (index < VSCodeMenu.MAX_RECENT_ENTRIES) { - openRecentMenu.append(this.createOpenRecentMenuItem(folder)); - } - }); + + for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < folders.length; i++) { + openRecentMenu.append(this.createOpenRecentMenuItem(folders[i])); + } } // Files - let files = recentList.files; if (files.length > 0) { openRecentMenu.append(__separator__()); - files.forEach((file, index) => { - if (index < VSCodeMenu.MAX_RECENT_ENTRIES) { - openRecentMenu.append(this.createOpenRecentMenuItem(file)); - } - }); + for (let i = 0; i < VSCodeMenu.MAX_MENU_RECENT_ENTRIES && i < files.length; i++) { + openRecentMenu.append(this.createOpenRecentMenuItem(files[i])); + } } - if (recentList.folders.length || files.length) { + if (folders.length || files.length) { openRecentMenu.append(__separator__()); - openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => this.clearOpenedPathsList() })); + openRecentMenu.append(new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miClearItems', comment: ['&& denotes a mnemonic'] }, "&&Clear Items")), click: () => { this.windowsService.clearRecentPathsList(); this.updateMenu(); } })); } } private createOpenRecentMenuItem(path: string): Electron.MenuItem { return new MenuItem({ label: unMnemonicLabel(path), click: () => { - let success = !!this.windowsService.open({ cli: this.envService.cliArgs, pathsToOpen: [path] }); + const success = !!this.windowsService.open({ cli: this.envService.cliArgs, pathsToOpen: [path] }); if (!success) { - this.removeFromOpenedPathsList(path); + this.windowsService.removeFromRecentPathsList(path); this.updateMenu(); } } @@ -466,7 +408,7 @@ export class VSCodeMenu { } private createRoleMenuItem(label: string, actionId: string, role: Electron.MenuItemRole): Electron.MenuItem { - let options: Electron.MenuItemOptions = { + const options: Electron.MenuItemOptions = { label: mnemonicLabel(label), accelerator: this.getAccelerator(actionId), role, @@ -500,10 +442,10 @@ export class VSCodeMenu { selectAll = this.createMenuItem(nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"), 'editor.action.selectAll'); } - let find = this.createMenuItem(nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), 'actions.find'); - let replace = this.createMenuItem(nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), 'editor.action.startFindReplaceAction'); - let findInFiles = this.createMenuItem(nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), 'workbench.view.search'); - let replaceInFiles = this.createMenuItem(nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files"), 'workbench.action.replaceInFiles'); + const find = this.createMenuItem(nls.localize({ key: 'miFind', comment: ['&& denotes a mnemonic'] }, "&&Find"), 'actions.find'); + const replace = this.createMenuItem(nls.localize({ key: 'miReplace', comment: ['&& denotes a mnemonic'] }, "&&Replace"), 'editor.action.startFindReplaceAction'); + const findInFiles = this.createMenuItem(nls.localize({ key: 'miFindInFiles', comment: ['&& denotes a mnemonic'] }, "Find &&in Files"), 'workbench.view.search'); + const replaceInFiles = this.createMenuItem(nls.localize({ key: 'miReplaceInFiles', comment: ['&& denotes a mnemonic'] }, "Replace &&in Files"), 'workbench.action.replaceInFiles'); [ undo, @@ -523,34 +465,34 @@ export class VSCodeMenu { } private setViewMenu(viewMenu: Electron.Menu): void { - let explorer = this.createMenuItem(nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), 'workbench.view.explorer'); - let search = this.createMenuItem(nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), 'workbench.view.search'); - let git = this.createMenuItem(nls.localize({ key: 'miViewGit', comment: ['&& denotes a mnemonic'] }, "&&Git"), 'workbench.view.git'); - let debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug'); - let extensions = this.createMenuItem(nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), 'workbench.view.extensions'); - let output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput'); - let debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl'); - let integratedTerminal = this.createMenuItem(nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal"), 'workbench.action.terminal.toggleTerminal'); - let problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems'); + const explorer = this.createMenuItem(nls.localize({ key: 'miViewExplorer', comment: ['&& denotes a mnemonic'] }, "&&Explorer"), 'workbench.view.explorer'); + const search = this.createMenuItem(nls.localize({ key: 'miViewSearch', comment: ['&& denotes a mnemonic'] }, "&&Search"), 'workbench.view.search'); + const git = this.createMenuItem(nls.localize({ key: 'miViewGit', comment: ['&& denotes a mnemonic'] }, "&&Git"), 'workbench.view.git'); + const debug = this.createMenuItem(nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug"), 'workbench.view.debug'); + const extensions = this.createMenuItem(nls.localize({ key: 'miViewExtensions', comment: ['&& denotes a mnemonic'] }, "E&&xtensions"), 'workbench.view.extensions'); + const output = this.createMenuItem(nls.localize({ key: 'miToggleOutput', comment: ['&& denotes a mnemonic'] }, "&&Output"), 'workbench.action.output.toggleOutput'); + const debugConsole = this.createMenuItem(nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console"), 'workbench.debug.action.toggleRepl'); + const integratedTerminal = this.createMenuItem(nls.localize({ key: 'miToggleIntegratedTerminal', comment: ['&& denotes a mnemonic'] }, "&&Integrated Terminal"), 'workbench.action.terminal.toggleTerminal'); + const problems = this.createMenuItem(nls.localize({ key: 'miMarker', comment: ['&& denotes a mnemonic'] }, "&&Problems"), 'workbench.actions.view.problems'); - let commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands'); + const commands = this.createMenuItem(nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette..."), 'workbench.action.showCommands'); - let fullscreen = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), accelerator: this.getAccelerator('workbench.action.toggleFullScreen'), click: () => this.windowsService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsService.getWindowCount() > 0 }); - let toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar'); - let splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor'); - let toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility'); - let moveSidebar = this.createMenuItem(nls.localize({ key: 'miMoveSidebar', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar"), 'workbench.action.toggleSidebarPosition'); - let togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel'); - let toggleStatusbar = this.createMenuItem(nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar"), 'workbench.action.toggleStatusbarVisibility'); + const fullscreen = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miToggleFullScreen', comment: ['&& denotes a mnemonic'] }, "Toggle &&Full Screen")), accelerator: this.getAccelerator('workbench.action.toggleFullScreen'), click: () => this.windowsService.getLastActiveWindow().toggleFullScreen(), enabled: this.windowsService.getWindowCount() > 0 }); + const toggleMenuBar = this.createMenuItem(nls.localize({ key: 'miToggleMenuBar', comment: ['&& denotes a mnemonic'] }, "Toggle Menu &&Bar"), 'workbench.action.toggleMenuBar'); + const splitEditor = this.createMenuItem(nls.localize({ key: 'miSplitEditor', comment: ['&& denotes a mnemonic'] }, "Split &&Editor"), 'workbench.action.splitEditor'); + const toggleSidebar = this.createMenuItem(nls.localize({ key: 'miToggleSidebar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Side Bar"), 'workbench.action.toggleSidebarVisibility'); + const moveSidebar = this.createMenuItem(nls.localize({ key: 'miMoveSidebar', comment: ['&& denotes a mnemonic'] }, "&&Move Side Bar"), 'workbench.action.toggleSidebarPosition'); + const togglePanel = this.createMenuItem(nls.localize({ key: 'miTogglePanel', comment: ['&& denotes a mnemonic'] }, "Toggle &&Panel"), 'workbench.action.togglePanel'); + const toggleStatusbar = this.createMenuItem(nls.localize({ key: 'miToggleStatusbar', comment: ['&& denotes a mnemonic'] }, "&&Toggle Status Bar"), 'workbench.action.toggleStatusbarVisibility'); const toggleWordWrap = this.createMenuItem(nls.localize({ key: 'miToggleWordWrap', comment: ['&& denotes a mnemonic'] }, "Toggle &&Word Wrap"), 'editor.action.toggleWordWrap'); const toggleRenderWhitespace = this.createMenuItem(nls.localize({ key: 'miToggleRenderWhitespace', comment: ['&& denotes a mnemonic'] }, "Toggle &&Render Whitespace"), 'editor.action.toggleRenderWhitespace'); const toggleRenderControlCharacters = this.createMenuItem(nls.localize({ key: 'miToggleRenderControlCharacters', comment: ['&& denotes a mnemonic'] }, "Toggle &&Control Characters"), 'editor.action.toggleRenderControlCharacter'); - let zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn'); - let zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut'); - let resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset'); + const zoomIn = this.createMenuItem(nls.localize({ key: 'miZoomIn', comment: ['&& denotes a mnemonic'] }, "&&Zoom In"), 'workbench.action.zoomIn'); + const zoomOut = this.createMenuItem(nls.localize({ key: 'miZoomOut', comment: ['&& denotes a mnemonic'] }, "Zoom O&&ut"), 'workbench.action.zoomOut'); + const resetZoom = this.createMenuItem(nls.localize({ key: 'miZoomReset', comment: ['&& denotes a mnemonic'] }, "&&Reset Zoom"), 'workbench.action.zoomReset'); arrays.coalesce([ commands, @@ -586,15 +528,15 @@ export class VSCodeMenu { } private setGotoMenu(gotoMenu: Electron.Menu): void { - let back = this.createMenuItem(nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), 'workbench.action.navigateBack'); - let forward = this.createMenuItem(nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), 'workbench.action.navigateForward'); + const back = this.createMenuItem(nls.localize({ key: 'miBack', comment: ['&& denotes a mnemonic'] }, "&&Back"), 'workbench.action.navigateBack'); + const forward = this.createMenuItem(nls.localize({ key: 'miForward', comment: ['&& denotes a mnemonic'] }, "&&Forward"), 'workbench.action.navigateForward'); - let switchEditorMenu = new Menu(); + const switchEditorMenu = new Menu(); - let nextEditor = this.createMenuItem(nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor"), 'workbench.action.nextEditor'); - let previousEditor = this.createMenuItem(nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor"), 'workbench.action.previousEditor'); - let nextEditorInGroup = this.createMenuItem(nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group"), 'workbench.action.openNextRecentlyUsedEditorInGroup'); - let previousEditorInGroup = this.createMenuItem(nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group"), 'workbench.action.openPreviousRecentlyUsedEditorInGroup'); + const nextEditor = this.createMenuItem(nls.localize({ key: 'miNextEditor', comment: ['&& denotes a mnemonic'] }, "&&Next Editor"), 'workbench.action.nextEditor'); + const previousEditor = this.createMenuItem(nls.localize({ key: 'miPreviousEditor', comment: ['&& denotes a mnemonic'] }, "&&Previous Editor"), 'workbench.action.previousEditor'); + const nextEditorInGroup = this.createMenuItem(nls.localize({ key: 'miNextEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Used Editor in Group"), 'workbench.action.openNextRecentlyUsedEditorInGroup'); + const previousEditorInGroup = this.createMenuItem(nls.localize({ key: 'miPreviousEditorInGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Used Editor in Group"), 'workbench.action.openPreviousRecentlyUsedEditorInGroup'); [ nextEditor, @@ -604,15 +546,15 @@ export class VSCodeMenu { previousEditorInGroup ].forEach(item => switchEditorMenu.append(item)); - let switchEditor = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor")), submenu: switchEditorMenu, enabled: true }); + const switchEditor = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchEditor', comment: ['&& denotes a mnemonic'] }, "Switch &&Editor")), submenu: switchEditorMenu, enabled: true }); - let switchGroupMenu = new Menu(); + const switchGroupMenu = new Menu(); - let focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "&&Left Group"), 'workbench.action.focusFirstEditorGroup'); - let focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "&&Center Group"), 'workbench.action.focusSecondEditorGroup'); - let focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "&&Right Group"), 'workbench.action.focusThirdEditorGroup'); - let nextGroup = this.createMenuItem(nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), 'workbench.action.focusNextGroup'); - let previousGroup = this.createMenuItem(nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), 'workbench.action.focusPreviousGroup'); + const focusFirstGroup = this.createMenuItem(nls.localize({ key: 'miFocusFirstGroup', comment: ['&& denotes a mnemonic'] }, "&&Left Group"), 'workbench.action.focusFirstEditorGroup'); + const focusSecondGroup = this.createMenuItem(nls.localize({ key: 'miFocusSecondGroup', comment: ['&& denotes a mnemonic'] }, "&&Center Group"), 'workbench.action.focusSecondEditorGroup'); + const focusThirdGroup = this.createMenuItem(nls.localize({ key: 'miFocusThirdGroup', comment: ['&& denotes a mnemonic'] }, "&&Right Group"), 'workbench.action.focusThirdEditorGroup'); + const nextGroup = this.createMenuItem(nls.localize({ key: 'miNextGroup', comment: ['&& denotes a mnemonic'] }, "&&Next Group"), 'workbench.action.focusNextGroup'); + const previousGroup = this.createMenuItem(nls.localize({ key: 'miPreviousGroup', comment: ['&& denotes a mnemonic'] }, "&&Previous Group"), 'workbench.action.focusPreviousGroup'); [ focusFirstGroup, @@ -623,12 +565,13 @@ export class VSCodeMenu { previousGroup ].forEach(item => switchGroupMenu.append(item)); - let switchGroup = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group")), submenu: switchGroupMenu, enabled: true }); + const switchGroup = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miSwitchGroup', comment: ['&& denotes a mnemonic'] }, "Switch &&Group")), submenu: switchGroupMenu, enabled: true }); - let gotoFile = this.createMenuItem(nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File..."), 'workbench.action.quickOpen'); - let gotoSymbol = this.createMenuItem(nls.localize({ key: 'miGotoSymbol', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol..."), 'workbench.action.gotoSymbol'); - let gotoDefinition = this.createMenuItem(nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), 'editor.action.goToDeclaration'); - let gotoLine = this.createMenuItem(nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line..."), 'workbench.action.gotoLine'); + const gotoFile = this.createMenuItem(nls.localize({ key: 'miGotoFile', comment: ['&& denotes a mnemonic'] }, "Go to &&File..."), 'workbench.action.quickOpen'); + const gotoSymbolInFile = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File..."), 'workbench.action.gotoSymbol'); + const gotoSymbolInWorkspace = this.createMenuItem(nls.localize({ key: 'miGotoSymbolInWorkspace', comment: ['&& denotes a mnemonic'] }, "Go to Symbol in &&Workspace..."), 'workbench.action.showAllSymbols'); + const gotoDefinition = this.createMenuItem(nls.localize({ key: 'miGotoDefinition', comment: ['&& denotes a mnemonic'] }, "Go to &&Definition"), 'editor.action.goToDeclaration'); + const gotoLine = this.createMenuItem(nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line..."), 'workbench.action.gotoLine'); [ back, @@ -638,16 +581,17 @@ export class VSCodeMenu { switchGroup, __separator__(), gotoFile, - gotoSymbol, + gotoSymbolInFile, + gotoSymbolInWorkspace, gotoDefinition, gotoLine ].forEach(item => gotoMenu.append(item)); } private setMacWindowMenu(macWindowMenu: Electron.Menu): void { - let minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsService.getWindowCount() > 0 }); - let close = new MenuItem({ label: nls.localize('mClose', "Close"), role: 'close', accelerator: 'Command+W', enabled: this.windowsService.getWindowCount() > 0 }); - let bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsService.getWindowCount() > 0 }); + const minimize = new MenuItem({ label: nls.localize('mMinimize', "Minimize"), role: 'minimize', accelerator: 'Command+M', enabled: this.windowsService.getWindowCount() > 0 }); + const close = new MenuItem({ label: nls.localize('mClose', "Close"), role: 'close', accelerator: 'Command+W', enabled: this.windowsService.getWindowCount() > 0 }); + const bringAllToFront = new MenuItem({ label: nls.localize('mBringToFront', "Bring All to Front"), role: 'front', enabled: this.windowsService.getWindowCount() > 0 }); [ minimize, @@ -658,14 +602,14 @@ export class VSCodeMenu { } private toggleDevTools(): void { - let w = this.windowsService.getFocusedWindow(); + const w = this.windowsService.getFocusedWindow(); if (w && w.win) { w.win.webContents.toggleDevTools(); } } private setHelpMenu(helpMenu: Electron.Menu): void { - let toggleDevToolsItem = new MenuItem({ + const toggleDevToolsItem = new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miToggleDevTools', comment: ['&& denotes a mnemonic'] }, "&&Toggle Developer Tools")), accelerator: this.getAccelerator('workbench.action.toggleDevTools'), click: () => this.toggleDevTools(), @@ -685,7 +629,7 @@ export class VSCodeMenu { this.envService.product.licenseUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miLicense', comment: ['&& denotes a mnemonic'] }, "&&View License")), click: () => { if (platform.language) { - let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + const queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; this.openUrl(`${this.envService.product.licenseUrl}${queryArgChar}lang=${platform.language}`, 'openLicenseUrl'); } else { this.openUrl(this.envService.product.licenseUrl, 'openLicenseUrl'); @@ -695,7 +639,7 @@ export class VSCodeMenu { this.envService.product.privacyStatementUrl ? new MenuItem({ label: mnemonicLabel(nls.localize({ key: 'miPrivacyStatement', comment: ['&& denotes a mnemonic'] }, "&&Privacy Statement")), click: () => { if (platform.language) { - let queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; + const queryArgChar = this.envService.product.licenseUrl.indexOf('?') > 0 ? '&' : '?'; this.openUrl(`${this.envService.product.privacyStatementUrl}${queryArgChar}lang=${platform.language}`, 'openPrivacyStatement'); } else { this.openUrl(this.envService.product.privacyStatementUrl, 'openPrivacyStatement'); @@ -724,7 +668,7 @@ export class VSCodeMenu { return []; case UpdateState.UpdateDownloaded: - let update = this.updateService.availableUpdate; + const update = this.updateService.availableUpdate; return [new MenuItem({ label: nls.localize('miRestartToUpdate', "Restart To Update..."), click: () => { this.reportMenuActionTelemetry('RestartToUpdate'); @@ -745,14 +689,14 @@ export class VSCodeMenu { })]; } - let updateAvailableLabel = platform.isWindows + const updateAvailableLabel = platform.isWindows ? nls.localize('miDownloadingUpdate', "Downloading Update...") : nls.localize('miInstallingUpdate', "Installing Update..."); return [new MenuItem({ label: updateAvailableLabel, enabled: false })]; default: - let result = [new MenuItem({ + const result = [new MenuItem({ label: nls.localize('miCheckForUpdates', "Check For Updates..."), click: () => setTimeout(() => { this.reportMenuActionTelemetry('CheckForUpdate'); this.updateService.checkForUpdates(true); @@ -766,16 +710,16 @@ export class VSCodeMenu { private createMenuItem(label: string, actionId: string, enabled?: boolean): Electron.MenuItem; private createMenuItem(label: string, click: () => void, enabled?: boolean): Electron.MenuItem; private createMenuItem(arg1: string, arg2: any, arg3?: boolean): Electron.MenuItem { - let label = mnemonicLabel(arg1); - let click: () => void = (typeof arg2 === 'function') ? arg2 : () => this.windowsService.sendToFocused('vscode:runAction', arg2); - let enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0; + const label = mnemonicLabel(arg1); + const click: () => void = (typeof arg2 === 'function') ? arg2 : () => this.windowsService.sendToFocused('vscode:runAction', arg2); + const enabled = typeof arg3 === 'boolean' ? arg3 : this.windowsService.getWindowCount() > 0; let actionId: string; if (typeof arg2 === 'string') { actionId = arg2; } - let options: Electron.MenuItemOptions = { + const options: Electron.MenuItemOptions = { label: label, accelerator: this.getAccelerator(actionId), click: click, @@ -791,7 +735,7 @@ export class VSCodeMenu { accelerator: this.getAccelerator(actionId), enabled: this.windowsService.getWindowCount() > 0, click: () => { - let windowInFocus = this.windowsService.getFocusedWindow(); + const windowInFocus = this.windowsService.getFocusedWindow(); if (!windowInFocus) { return; } @@ -807,7 +751,7 @@ export class VSCodeMenu { private getAccelerator(actionId: string): string { if (actionId) { - let resolvedKeybinding = this.mapResolvedKeybindingToActionId[actionId]; + const resolvedKeybinding = this.mapResolvedKeybindingToActionId[actionId]; if (resolvedKeybinding) { return resolvedKeybinding; // keybinding is fully resolved } @@ -816,7 +760,7 @@ export class VSCodeMenu { this.actionIdKeybindingRequests.push(actionId); // keybinding needs to be resolved } - let lastKnownKeybinding = this.mapLastKnownKeybindingToActionId[actionId]; + const lastKnownKeybinding = this.mapLastKnownKeybindingToActionId[actionId]; return lastKnownKeybinding; // return the last known keybining (chance of mismatch is very low unless it changed) } @@ -825,7 +769,7 @@ export class VSCodeMenu { } private openAboutDialog(): void { - let lastActiveWindow = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow(); + const lastActiveWindow = this.windowsService.getFocusedWindow() || this.windowsService.getLastActiveWindow(); dialog.showMessageBox(lastActiveWindow && lastActiveWindow.win, { title: this.envService.product.nameLong, diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 89b7557932c..3348239f100 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -26,7 +26,7 @@ function _spawnSharedProcess(options: ISharedProcessOptions): cp.ChildProcess { } if (options.debugPort) { - execArgv.push(`--debug=${ options.debugPort }`); + execArgv.push(`--debug=${options.debugPort}`); } const result = cp.fork(boostrapPath, ['--type=SharedProcess'], { env, execArgv }); diff --git a/src/vs/code/electron-main/storage.ts b/src/vs/code/electron-main/storage.ts index 943d4a93679..decedc16d3a 100644 --- a/src/vs/code/electron-main/storage.ts +++ b/src/vs/code/electron-main/storage.ts @@ -7,19 +7,13 @@ import * as path from 'path'; import * as fs from 'original-fs'; -import { EventEmitter } from 'events'; import { IEnvService } from 'vs/code/electron-main/env'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -const EventTypes = { - STORE: 'store' -}; - export const IStorageService = createDecorator('storageService'); export interface IStorageService { _serviceBrand: any; - onStore(clb: (key: string, oldValue: T, newValue: T) => void): () => void; getItem(key: string, defaultValue?: T): T; setItem(key: string, data: any): void; removeItem(key: string): void; @@ -31,18 +25,11 @@ export class StorageService implements IStorageService { private dbPath: string; private database: any = null; - private eventEmitter = new EventEmitter(); constructor(@IEnvService private envService: IEnvService) { this.dbPath = path.join(envService.appHome, 'storage.json'); } - onStore(clb: (key: string, oldValue: T, newValue: T) => void): () => void { - this.eventEmitter.addListener(EventTypes.STORE, clb); - - return () => this.eventEmitter.removeListener(EventTypes.STORE, clb); - } - getItem(key: string, defaultValue?: T): T { if (!this.database) { this.database = this.load(); @@ -68,11 +55,8 @@ export class StorageService implements IStorageService { } } - let oldValue = this.database[key]; this.database[key] = data; this.save(); - - this.eventEmitter.emit(EventTypes.STORE, key, oldValue, data); } removeItem(key: string): void { @@ -81,11 +65,8 @@ export class StorageService implements IStorageService { } if (this.database[key]) { - let oldValue = this.database[key]; delete this.database[key]; this.save(); - - this.eventEmitter.emit(EventTypes.STORE, key, oldValue, null); } } diff --git a/src/vs/code/electron-main/update-manager.ts b/src/vs/code/electron-main/update-manager.ts index c82e6191e7e..c065b832d31 100644 --- a/src/vs/code/electron-main/update-manager.ts +++ b/src/vs/code/electron-main/update-manager.ts @@ -131,7 +131,7 @@ export class UpdateManager extends EventEmitter implements IUpdateService { }); this.raw.on('update-downloaded', (event: any, releaseNotes: string, version: string, date: Date, url: string, rawQuitAndUpdate: () => void) => { - let data: IUpdate = { + const data: IUpdate = { releaseNotes: releaseNotes, version: version, date: date, @@ -231,7 +231,9 @@ export class UpdateManager extends EventEmitter implements IUpdateService { } private getUpdateChannel(): string { - const channel = this.configurationService.getConfiguration('update.channel') || 'default'; + const config = this.configurationService.getConfiguration<{ channel: string; }>('update'); + const channel = config && config.channel; + return channel === 'none' ? null : this.envService.quality; } diff --git a/src/vs/code/electron-main/window.ts b/src/vs/code/electron-main/window.ts index 66ac054e468..74cf9590d8b 100644 --- a/src/vs/code/electron-main/window.ts +++ b/src/vs/code/electron-main/window.ts @@ -14,7 +14,7 @@ import { TPromise, TValueCallback } from 'vs/base/common/winjs.base'; import { ICommandLineArguments, IEnvService, IProcessEnvironment } from 'vs/code/electron-main/env'; import { ILogService } from 'vs/code/electron-main/log'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { parseArgs } from 'vs/code/node/argv'; +import { parseArgs } from 'vs/platform/environment/node/argv'; export interface IWindowState { width?: number; @@ -361,6 +361,11 @@ export class VSCodeWindow { this._readyState = ReadyState.NAVIGATING; } + // Make sure to clear any previous edited state + if (platform.isMacintosh && this._win.isDocumentEdited()) { + this._win.setDocumentEdited(false); + } + // Load URL this._win.loadURL(this.getUrl(config)); diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index 719a82bbe90..3e814a2a0b3 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -10,6 +10,7 @@ import * as fs from 'original-fs'; import * as platform from 'vs/base/common/platform'; import * as nls from 'vs/nls'; import * as paths from 'vs/base/common/paths'; +import * as types from 'vs/base/common/types'; import * as arrays from 'vs/base/common/arrays'; import { assign, mixin } from 'vs/base/common/objects'; import { EventEmitter } from 'events'; @@ -56,7 +57,7 @@ interface IWindowsState { openedFolders: IWindowState[]; } -export interface IOpenedPathsList { +export interface IRecentPathsList { folders: string[]; files: string[]; } @@ -108,14 +109,18 @@ export interface IWindowsService { getWindowById(windowId: number): VSCodeWindow; getWindows(): VSCodeWindow[]; getWindowCount(): number; + getRecentPathsList(): IRecentPathsList; + removeFromRecentPathsList(path: string); + clearRecentPathsList(): void; } export class WindowsManager implements IWindowsService { _serviceBrand: any; - public static openedPathsListStorageKey = 'openedPathsList'; + private static MAX_TOTAL_RECENT_ENTRIES = 100; + private static recentPathsListStorageKey = 'openedPathsList'; private static workingDirPickerStorageKey = 'pickerWorkingDir'; private static windowsStateStorageKey = 'windowsState'; @@ -210,7 +215,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:workbenchLoaded', (event, windowId: number) => { this.logService.log('IPC#vscode-workbenchLoaded'); - let win = this.getWindowById(windowId); + const win = this.getWindowById(windowId); if (win) { win.setReady(); @@ -240,7 +245,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:closeFolder', (event, windowId: number) => { this.logService.log('IPC#vscode-closeFolder'); - let win = this.getWindowById(windowId); + const win = this.getWindowById(windowId); if (win) { this.open({ cli: this.envService.cliArgs, forceEmpty: true, windowToUse: win }); } @@ -255,7 +260,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:reloadWindow', (event, windowId: number) => { this.logService.log('IPC#vscode:reloadWindow'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { this.reload(vscodeWindow); } @@ -264,7 +269,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:toggleFullScreen', (event, windowId: number) => { this.logService.log('IPC#vscode:toggleFullScreen'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.toggleFullScreen(); } @@ -273,7 +278,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:setFullScreen', (event, windowId: number, fullscreen: boolean) => { this.logService.log('IPC#vscode:setFullScreen'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.setFullScreen(fullscreen); } @@ -282,7 +287,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:toggleDevTools', (event, windowId: number) => { this.logService.log('IPC#vscode:toggleDevTools'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.webContents.toggleDevTools(); } @@ -291,7 +296,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:openDevTools', (event, windowId: number) => { this.logService.log('IPC#vscode:openDevTools'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.webContents.openDevTools(); vscodeWindow.win.show(); @@ -301,7 +306,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:setRepresentedFilename', (event, windowId: number, fileName: string) => { this.logService.log('IPC#vscode:setRepresentedFilename'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.setRepresentedFilename(fileName); } @@ -310,7 +315,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:setMenuBarVisibility', (event, windowId: number, visibility: boolean) => { this.logService.log('IPC#vscode:setMenuBarVisibility'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.setMenuBarVisibility(visibility); } @@ -319,7 +324,7 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:flashFrame', (event, windowId: number) => { this.logService.log('IPC#vscode:flashFrame'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.flashFrame(!vscodeWindow.win.isFocused()); } @@ -328,9 +333,9 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:openRecent', (event, windowId: number) => { this.logService.log('IPC#vscode:openRecent'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { - const recents = this.getRecentlyOpenedPaths(vscodeWindow.config.workspacePath, vscodeWindow.config.filesToOpen); + const recents = this.getRecentPathsList(vscodeWindow.config.workspacePath, vscodeWindow.config.filesToOpen); vscodeWindow.send('vscode:openRecent', recents.files, recents.folders); } @@ -339,16 +344,25 @@ export class WindowsManager implements IWindowsService { ipc.on('vscode:focusWindow', (event, windowId: number) => { this.logService.log('IPC#vscode:focusWindow'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.win.focus(); } }); + ipc.on('vscode:showWindow', (event, windowId: number) => { + this.logService.log('IPC#vscode:showWindow'); + + let vscodeWindow = this.getWindowById(windowId); + if (vscodeWindow) { + vscodeWindow.win.show(); + } + }); + ipc.on('vscode:setDocumentEdited', (event, windowId: number, edited: boolean) => { this.logService.log('IPC#vscode:setDocumentEdited'); - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow && vscodeWindow.win.isDocumentEdited() !== edited) { vscodeWindow.win.setDocumentEdited(edited); } @@ -358,8 +372,8 @@ export class WindowsManager implements IWindowsService { this.logService.log('IPC#vscode:toggleMenuBar'); // Update in settings - let menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false); - let newMenuBarHidden = !menuBarHidden; + const menuBarHidden = this.storageService.getItem(VSCodeWindow.menuBarHiddenKey, false); + const newMenuBarHidden = !menuBarHidden; this.storageService.setItem(VSCodeWindow.menuBarHiddenKey, newMenuBarHidden); // Update across windows @@ -367,7 +381,7 @@ export class WindowsManager implements IWindowsService { // Inform user if menu bar is now hidden if (newMenuBarHidden) { - let vscodeWindow = this.getWindowById(windowId); + const vscodeWindow = this.getWindowById(windowId); if (vscodeWindow) { vscodeWindow.send('vscode:showInfoMessage', nls.localize('hiddenMenuBar', "You can still access the menu bar by pressing the **Alt** key.")); } @@ -375,7 +389,7 @@ export class WindowsManager implements IWindowsService { }); ipc.on('vscode:broadcast', (event, windowId: number, target: string, broadcast: { channel: string; payload: any; }) => { - if (broadcast.channel && broadcast.payload) { + if (broadcast.channel && !types.isUndefinedOrNull(broadcast.payload)) { this.logService.log('IPC#vscode:broadcast', target, broadcast.channel, broadcast.payload); // Handle specific events on main side @@ -398,9 +412,9 @@ export class WindowsManager implements IWindowsService { }); ipc.on('vscode:log', (event, logEntry: ILogEntry) => { - let args = []; + const args = []; try { - let parsed = JSON.parse(logEntry.arguments); + const parsed = JSON.parse(logEntry.arguments); args.push(...Object.getOwnPropertyNames(parsed).map(o => parsed[o])); } catch (error) { args.push(logEntry.arguments); @@ -418,6 +432,14 @@ export class WindowsManager implements IWindowsService { } }); + ipc.on('vscode:switchWindow', (event, windowId: number) => { + const windows = this.getWindows(); + const window = this.getWindowById(windowId); + window.send('vscode:switchWindow', windows.map(w => { + return {path: w.openedWorkspacePath, title: w.win.getTitle(), id: w.id}; + })); + }); + this.updateService.on('update-downloaded', (update: IUpdate) => { this.sendToFocused('vscode:telemetry', { eventName: 'update:downloaded', data: { version: update.version } }); @@ -494,7 +516,7 @@ export class WindowsManager implements IWindowsService { public reload(win: VSCodeWindow, cli?: ICommandLineArguments): void { // Only reload when the window has not vetoed this - this.lifecycleService.unload(win).done((veto) => { + this.lifecycleService.unload(win).done(veto => { if (!veto) { win.reload(cli); } @@ -503,16 +525,16 @@ export class WindowsManager implements IWindowsService { public open(openConfig: IOpenConfiguration): VSCodeWindow[] { let iPathsToOpen: IPath[]; - let usedWindows: VSCodeWindow[] = []; + const usedWindows: VSCodeWindow[] = []; // Find paths from provided paths if any if (openConfig.pathsToOpen && openConfig.pathsToOpen.length > 0) { - iPathsToOpen = openConfig.pathsToOpen.map((pathToOpen) => { - let iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto); + iPathsToOpen = openConfig.pathsToOpen.map(pathToOpen => { + const iPath = this.toIPath(pathToOpen, false, openConfig.cli && openConfig.cli.goto); // Warn if the requested path to open does not exist if (!iPath) { - let options: Electron.ShowMessageBoxOptions = { + const options: Electron.ShowMessageBoxOptions = { title: this.envService.product.nameLong, type: 'info', buttons: [nls.localize('ok', "OK")], @@ -521,7 +543,7 @@ export class WindowsManager implements IWindowsService { noLink: true }; - let activeWindow = BrowserWindow.getFocusedWindow(); + const activeWindow = BrowserWindow.getFocusedWindow(); if (activeWindow) { dialog.showMessageBox(activeWindow, options); } else { @@ -547,19 +569,19 @@ export class WindowsManager implements IWindowsService { // Otherwise infer from command line arguments else { - let ignoreFileNotFound = openConfig.cli.paths.length > 0; // we assume the user wants to create this file from command line + const ignoreFileNotFound = openConfig.cli.paths.length > 0; // we assume the user wants to create this file from command line iPathsToOpen = this.cliToPaths(openConfig.cli, ignoreFileNotFound); } let filesToOpen: IPath[] = []; let filesToDiff: IPath[] = []; - let foldersToOpen = iPathsToOpen.filter((iPath) => iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath); - let emptyToOpen = iPathsToOpen.filter((iPath) => !iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath); - let extensionsToInstall = iPathsToOpen.filter((iPath) => iPath.installExtensionPath).map(ipath => ipath.filePath); - let filesToCreate = iPathsToOpen.filter((iPath) => !!iPath.filePath && iPath.createFilePath && !iPath.installExtensionPath); + let foldersToOpen = iPathsToOpen.filter(iPath => iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath); + let emptyToOpen = iPathsToOpen.filter(iPath => !iPath.workspacePath && !iPath.filePath && !iPath.installExtensionPath); + let extensionsToInstall = iPathsToOpen.filter(iPath => iPath.installExtensionPath).map(ipath => ipath.filePath); + let filesToCreate = iPathsToOpen.filter(iPath => !!iPath.filePath && iPath.createFilePath && !iPath.installExtensionPath); // Diff mode needs special care - let candidates = iPathsToOpen.filter((iPath) => !!iPath.filePath && !iPath.createFilePath && !iPath.installExtensionPath); + const candidates = iPathsToOpen.filter(iPath => !!iPath.filePath && !iPath.createFilePath && !iPath.installExtensionPath); if (openConfig.diffMode) { if (candidates.length === 2) { filesToDiff = candidates; @@ -578,7 +600,7 @@ export class WindowsManager implements IWindowsService { // Handle files to open/diff or to create when we dont open a folder if (!foldersToOpen.length && (filesToOpen.length > 0 || filesToCreate.length > 0 || filesToDiff.length > 0 || extensionsToInstall.length > 0)) { - // Let the user settings override how files are open in a new window or same window unless we are forced + // const the user settings override how files are open in a new window or same window unless we are forced let openFilesInNewWindow: boolean; if (openConfig.forceNewWindow) { openFilesInNewWindow = true; @@ -593,15 +615,11 @@ export class WindowsManager implements IWindowsService { } // Open Files in last instance if any and flag tells us so - let lastActiveWindow = this.getLastActiveWindow(); + const lastActiveWindow = this.getLastActiveWindow(); if (!openFilesInNewWindow && lastActiveWindow) { lastActiveWindow.focus(); - lastActiveWindow.ready().then((readyWindow) => { - readyWindow.send('vscode:openFiles', { - filesToOpen: filesToOpen, - filesToCreate: filesToCreate, - filesToDiff: filesToDiff - }); + lastActiveWindow.ready().then(readyWindow => { + readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff }); if (extensionsToInstall.length) { readyWindow.send('vscode:installExtensions', { extensionsToInstall }); @@ -614,7 +632,7 @@ export class WindowsManager implements IWindowsService { // Otherwise open instance with files else { configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, null, filesToOpen, filesToCreate, filesToDiff, extensionsToInstall); - let browserWindow = this.openInBrowserWindow(configuration, true /* new window */); + const browserWindow = this.openInBrowserWindow(configuration, true /* new window */); usedWindows.push(browserWindow); openConfig.forceNewWindow = true; // any other folders to open must open in new window then @@ -626,16 +644,12 @@ export class WindowsManager implements IWindowsService { if (foldersToOpen.length > 0) { // Check for existing instances - let windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map((iPath) => this.findWindow(iPath.workspacePath))); + const windowsOnWorkspacePath = arrays.coalesce(foldersToOpen.map(iPath => this.findWindow(iPath.workspacePath))); if (windowsOnWorkspacePath.length > 0) { - let browserWindow = windowsOnWorkspacePath[0]; + const browserWindow = windowsOnWorkspacePath[0]; browserWindow.focus(); // just focus one of them - browserWindow.ready().then((readyWindow) => { - readyWindow.send('vscode:openFiles', { - filesToOpen: filesToOpen, - filesToCreate: filesToCreate, - filesToDiff: filesToDiff - }); + browserWindow.ready().then(readyWindow => { + readyWindow.send('vscode:openFiles', { filesToOpen, filesToCreate, filesToDiff }); if (extensionsToInstall.length) { readyWindow.send('vscode:installExtensions', { extensionsToInstall }); @@ -654,13 +668,13 @@ export class WindowsManager implements IWindowsService { } // Open remaining ones - foldersToOpen.forEach((folderToOpen) => { - if (windowsOnWorkspacePath.some((win) => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) { + foldersToOpen.forEach(folderToOpen => { + if (windowsOnWorkspacePath.some(win => this.isPathEqual(win.openedWorkspacePath, folderToOpen.workspacePath))) { return; // ignore folders that are already open } configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli, folderToOpen.workspacePath, filesToOpen, filesToCreate, filesToDiff, extensionsToInstall); - let browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); + const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); usedWindows.push(browserWindow); // Reset these because we handled them @@ -676,84 +690,78 @@ export class WindowsManager implements IWindowsService { // Handle empty if (emptyToOpen.length > 0) { emptyToOpen.forEach(() => { - let configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli); - let browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); + const configuration = this.toConfiguration(this.getWindowUserEnv(openConfig), openConfig.cli); + const browserWindow = this.openInBrowserWindow(configuration, openInNewWindow, openInNewWindow ? void 0 : openConfig.windowToUse); usedWindows.push(browserWindow); openInNewWindow = true; // any other folders to open must open in new window then }); } - // Remember in recent document list - iPathsToOpen.forEach((iPath) => { - if (iPath.filePath || iPath.workspacePath) { - app.addRecentDocument(iPath.filePath || iPath.workspacePath); - } - }); + // Remember in recent document list (unless this opens for extension development) + if (!openConfig.cli.extensionDevelopmentPath) { + iPathsToOpen.forEach(iPath => { + if (iPath.filePath || iPath.workspacePath) { + app.addRecentDocument(iPath.filePath || iPath.workspacePath); + this.addToRecentPathsList(iPath.filePath || iPath.workspacePath, !!iPath.filePath); + } + }); + } // Emit events - iPathsToOpen.forEach((iPath) => this.eventEmitter.emit(EventTypes.OPEN, iPath)); + iPathsToOpen.forEach(iPath => this.eventEmitter.emit(EventTypes.OPEN, iPath)); return arrays.distinct(usedWindows); } - private getWindowUserEnv(openConfig: IOpenConfiguration): IProcessEnvironment { - return assign({}, this.initialUserEnv, openConfig.userEnv || {}); - } - - public openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void { - - // Reload an existing plugin development host window on the same path - // We currently do not allow more than one extension development window - // on the same plugin path. - let res = WindowsManager.WINDOWS.filter((w) => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath)); - if (res && res.length === 1) { - this.reload(res[0], openConfig.cli); - res[0].focus(); // make sure it gets focus and is restored - + private addToRecentPathsList(path?: string, isFile?: boolean): void { + if (!path) { return; } - // Fill in previously opened workspace unless an explicit path is provided and we are not unit testing - if (openConfig.cli.paths.length === 0 && !openConfig.cli.extensionTestsPath) { - let workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath; - if (workspaceToOpen) { - openConfig.cli.paths = [workspaceToOpen]; - } + const mru = this.getRecentPathsList(); + if (isFile) { + mru.files.unshift(path); + mru.files = arrays.distinct(mru.files, (f) => platform.isLinux ? f : f.toLowerCase()); + } else { + mru.folders.unshift(path); + mru.folders = arrays.distinct(mru.folders, (f) => platform.isLinux ? f : f.toLowerCase()); } - // Make sure we are not asked to open a path that is already opened - if (openConfig.cli.paths.length > 0) { - res = WindowsManager.WINDOWS.filter((w) => w.openedWorkspacePath && openConfig.cli.paths.indexOf(w.openedWorkspacePath) >= 0); - if (res.length) { - openConfig.cli.paths = []; - } + // Make sure its bounded + mru.folders = mru.folders.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); + mru.files = mru.files.slice(0, WindowsManager.MAX_TOTAL_RECENT_ENTRIES); + + this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru); + } + + public removeFromRecentPathsList(path: string): void { + const mru = this.getRecentPathsList(); + + let index = mru.files.indexOf(path); + if (index >= 0) { + mru.files.splice(index, 1); } - // Open it - this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli.paths.length === 0 }); + index = mru.folders.indexOf(path); + if (index >= 0) { + mru.folders.splice(index, 1); + } + + this.storageService.setItem(WindowsManager.recentPathsListStorageKey, mru); } - private toConfiguration(userEnv: IProcessEnvironment, cli: ICommandLineArguments, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[], extensionsToInstall?: string[]): IWindowConfiguration { - let configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI - configuration.appRoot = this.envService.appRoot; - configuration.execPath = process.execPath; - configuration.userEnv = userEnv; - configuration.workspacePath = workspacePath; - configuration.filesToOpen = filesToOpen; - configuration.filesToCreate = filesToCreate; - configuration.filesToDiff = filesToDiff; - configuration.extensionsToInstall = extensionsToInstall; - - return configuration; + public clearRecentPathsList(): void { + this.storageService.setItem(WindowsManager.recentPathsListStorageKey, { folders: [], files: [] }); + app.clearRecentDocuments(); } - private getRecentlyOpenedPaths(workspacePath?: string, filesToOpen?: IPath[]): IOpenedPathsList { + public getRecentPathsList(workspacePath?: string, filesToOpen?: IPath[]): IRecentPathsList { let files: string[]; let folders: string[]; // Get from storage - let storedRecents = this.storageService.getItem(WindowsManager.openedPathsListStorageKey); + const storedRecents = this.storageService.getItem(WindowsManager.recentPathsListStorageKey); if (storedRecents) { files = storedRecents.files || []; folders = storedRecents.folders || []; @@ -776,13 +784,60 @@ export class WindowsManager implements IWindowsService { files = arrays.distinct(files); folders = arrays.distinct(folders); - // Make sure it is bounded - files = files.slice(0, 10); - folders = folders.slice(0, 10); - return { files, folders }; } + private getWindowUserEnv(openConfig: IOpenConfiguration): IProcessEnvironment { + return assign({}, this.initialUserEnv, openConfig.userEnv || {}); + } + + public openPluginDevelopmentHostWindow(openConfig: IOpenConfiguration): void { + + // Reload an existing plugin development host window on the same path + // We currently do not allow more than one extension development window + // on the same plugin path. + let res = WindowsManager.WINDOWS.filter(w => w.config && this.isPathEqual(w.config.extensionDevelopmentPath, openConfig.cli.extensionDevelopmentPath)); + if (res && res.length === 1) { + this.reload(res[0], openConfig.cli); + res[0].focus(); // make sure it gets focus and is restored + + return; + } + + // Fill in previously opened workspace unless an explicit path is provided and we are not unit testing + if (openConfig.cli.paths.length === 0 && !openConfig.cli.extensionTestsPath) { + const workspaceToOpen = this.windowsState.lastPluginDevelopmentHostWindow && this.windowsState.lastPluginDevelopmentHostWindow.workspacePath; + if (workspaceToOpen) { + openConfig.cli.paths = [workspaceToOpen]; + } + } + + // Make sure we are not asked to open a path that is already opened + if (openConfig.cli.paths.length > 0) { + res = WindowsManager.WINDOWS.filter(w => w.openedWorkspacePath && openConfig.cli.paths.indexOf(w.openedWorkspacePath) >= 0); + if (res.length) { + openConfig.cli.paths = []; + } + } + + // Open it + this.open({ cli: openConfig.cli, forceNewWindow: true, forceEmpty: openConfig.cli.paths.length === 0 }); + } + + private toConfiguration(userEnv: IProcessEnvironment, cli: ICommandLineArguments, workspacePath?: string, filesToOpen?: IPath[], filesToCreate?: IPath[], filesToDiff?: IPath[], extensionsToInstall?: string[]): IWindowConfiguration { + const configuration: IWindowConfiguration = mixin({}, cli); // inherit all properties from CLI + configuration.appRoot = this.envService.appRoot; + configuration.execPath = process.execPath; + configuration.userEnv = userEnv; + configuration.workspacePath = workspacePath; + configuration.filesToOpen = filesToOpen; + configuration.filesToCreate = filesToCreate; + configuration.filesToDiff = filesToDiff; + configuration.extensionsToInstall = extensionsToInstall; + + return configuration; + } + private toIPath(anyPath: string, ignoreFileNotFound?: boolean, gotoLineMode?: boolean): IPath { if (!anyPath) { return null; @@ -794,9 +849,9 @@ export class WindowsManager implements IWindowsService { anyPath = parsedPath.path; } - let candidate = path.normalize(anyPath); + const candidate = path.normalize(anyPath); try { - let candidateStat = fs.statSync(candidate); + const candidateStat = fs.statSync(candidate); if (candidateStat) { return candidateStat.isFile() ? { @@ -834,11 +889,11 @@ export class WindowsManager implements IWindowsService { reopenFolders = (windowConfig && windowConfig.reopenFolders) || ReopenFoldersSetting.ONE; } - let lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath; + const lastActiveFolder = this.windowsState.lastActiveWindow && this.windowsState.lastActiveWindow.workspacePath; // Restore all if (reopenFolders === ReopenFoldersSetting.ALL) { - let lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath); + const lastOpenedFolders = this.windowsState.openedFolders.map(o => o.workspacePath); // If we have a last active folder, move it to the end if (lastActiveFolder) { @@ -855,7 +910,7 @@ export class WindowsManager implements IWindowsService { } } - let iPaths = candidates.map((candidate) => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter((path) => !!path); + const iPaths = candidates.map(candidate => this.toIPath(candidate, ignoreFileNotFound, cli.goto)).filter(path => !!path); if (iPaths.length > 0) { return iPaths; } @@ -904,7 +959,7 @@ export class WindowsManager implements IWindowsService { // Some configuration things get inherited if the window is being reused and we are // in plugin development host mode. These options are all development related. - let currentWindowConfig = vscodeWindow.config; + const currentWindowConfig = vscodeWindow.config; if (!configuration.extensionDevelopmentPath && currentWindowConfig && !!currentWindowConfig.extensionDevelopmentPath) { configuration.extensionDevelopmentPath = currentWindowConfig.extensionDevelopmentPath; configuration.verbose = currentWindowConfig.verbose; @@ -915,7 +970,7 @@ export class WindowsManager implements IWindowsService { } // Only load when the window has not vetoed this - this.lifecycleService.unload(vscodeWindow).done((veto) => { + this.lifecycleService.unload(vscodeWindow).done(veto => { if (!veto) { // Load it @@ -935,14 +990,14 @@ export class WindowsManager implements IWindowsService { // Known Folder - load from stored settings if any if (configuration.workspacePath) { - let stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState); + const stateForWorkspace = this.windowsState.openedFolders.filter(o => this.isPathEqual(o.workspacePath, configuration.workspacePath)).map(o => o.uiState); if (stateForWorkspace.length) { return stateForWorkspace[0]; } } // First Window - let lastActive = this.getLastActiveWindow(); + const lastActive = this.getLastActiveWindow(); if (!lastActive && this.windowsState.lastActiveWindow) { return this.windowsState.lastActiveWindow.uiState; } @@ -953,7 +1008,7 @@ export class WindowsManager implements IWindowsService { // We want the new window to open on the same display that the last active one is in let displayToUse: Electron.Display; - let displays = screen.getAllDisplays(); + const displays = screen.getAllDisplays(); // Single Display if (displays.length === 1) { @@ -965,7 +1020,7 @@ export class WindowsManager implements IWindowsService { // on mac there is 1 menu per window so we need to use the monitor where the cursor currently is if (platform.isMacintosh) { - let cursorPoint = screen.getCursorScreenPoint(); + const cursorPoint = screen.getCursorScreenPoint(); displayToUse = screen.getDisplayNearestPoint(cursorPoint); } @@ -980,7 +1035,7 @@ export class WindowsManager implements IWindowsService { } } - let defaultState = defaultWindowState(); + const defaultState = defaultWindowState(); defaultState.x = displayToUse.bounds.x + (displayToUse.bounds.width / 2) - (defaultState.width / 2); defaultState.y = displayToUse.bounds.y + (displayToUse.bounds.height / 2) - (defaultState.height / 2); @@ -992,8 +1047,8 @@ export class WindowsManager implements IWindowsService { return state; } - let existingWindowBounds = WindowsManager.WINDOWS.map((win) => win.getBounds()); - while (existingWindowBounds.some((b) => b.x === state.x || b.y === state.y)) { + const existingWindowBounds = WindowsManager.WINDOWS.map(win => win.getBounds()); + while (existingWindowBounds.some(b => b.x === state.x || b.y === state.y)) { state.x += 30; state.y += 30; } @@ -1022,8 +1077,8 @@ export class WindowsManager implements IWindowsService { } private getFileOrFolderPaths(options: INativeOpenDialogOptions, clb: (paths: string[]) => void): void { - let workingDir = options.path ||  this.storageService.getItem(WindowsManager.workingDirPickerStorageKey); - let focussedWindow = this.getFocusedWindow(); + const workingDir = options.path || this.storageService.getItem(WindowsManager.workingDirPickerStorageKey); + const focussedWindow = this.getFocusedWindow(); let pickerProperties: ('openFile' | 'openDirectory' | 'multiSelections' | 'createDirectory')[]; if (options.pickFiles && options.pickFolders) { @@ -1035,7 +1090,7 @@ export class WindowsManager implements IWindowsService { dialog.showOpenDialog(focussedWindow && focussedWindow.win, { defaultPath: workingDir, properties: pickerProperties - }, (paths) => { + }, paths => { if (paths && paths.length > 0) { // Remember path in storage for next time @@ -1050,7 +1105,7 @@ export class WindowsManager implements IWindowsService { } public focusLastActive(cli: ICommandLineArguments): VSCodeWindow { - let lastActive = this.getLastActiveWindow(); + const lastActive = this.getLastActiveWindow(); if (lastActive) { lastActive.focus(); @@ -1066,8 +1121,8 @@ export class WindowsManager implements IWindowsService { public getLastActiveWindow(): VSCodeWindow { if (WindowsManager.WINDOWS.length) { - let lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map((w) => w.lastFocusTime)); - let res = WindowsManager.WINDOWS.filter((w) => w.lastFocusTime === lastFocussedDate); + const lastFocussedDate = Math.max.apply(Math, WindowsManager.WINDOWS.map(w => w.lastFocusTime)); + const res = WindowsManager.WINDOWS.filter(w => w.lastFocusTime === lastFocussedDate); if (res && res.length) { return res[0]; } @@ -1080,15 +1135,15 @@ export class WindowsManager implements IWindowsService { if (WindowsManager.WINDOWS.length) { // Sort the last active window to the front of the array of windows to test - let windowsToTest = WindowsManager.WINDOWS.slice(0); - let lastActiveWindow = this.getLastActiveWindow(); + const windowsToTest = WindowsManager.WINDOWS.slice(0); + const lastActiveWindow = this.getLastActiveWindow(); if (lastActiveWindow) { windowsToTest.splice(windowsToTest.indexOf(lastActiveWindow), 1); windowsToTest.unshift(lastActiveWindow); } // Find it - let res = windowsToTest.filter((w) => { + const res = windowsToTest.filter(w => { // match on workspace if (typeof w.openedWorkspacePath === 'string' && (this.isPathEqual(w.openedWorkspacePath, workspacePath))) { @@ -1134,7 +1189,7 @@ export class WindowsManager implements IWindowsService { } public sendToAll(channel: string, payload: any, windowIdsToIgnore?: number[]): void { - WindowsManager.WINDOWS.forEach((w) => { + WindowsManager.WINDOWS.forEach(w => { if (windowIdsToIgnore && windowIdsToIgnore.indexOf(w.id) >= 0) { return; // do not send if we are instructed to ignore it } @@ -1144,7 +1199,7 @@ export class WindowsManager implements IWindowsService { } public getFocusedWindow(): VSCodeWindow { - let win = BrowserWindow.getFocusedWindow(); + const win = BrowserWindow.getFocusedWindow(); if (win) { return this.getWindowById(win.id); } @@ -1153,7 +1208,7 @@ export class WindowsManager implements IWindowsService { } public getWindowById(windowId: number): VSCodeWindow { - let res = WindowsManager.WINDOWS.filter((w) => w.id === windowId); + const res = WindowsManager.WINDOWS.filter(w => w.id === windowId); if (res && res.length === 1) { return res[0]; } @@ -1181,7 +1236,7 @@ export class WindowsManager implements IWindowsService { message: nls.localize('appStalled', "The window is no longer responding"), detail: nls.localize('appStalledDetail', "You can reopen or close the window or keep waiting."), noLink: true - }, (result) => { + }, result => { if (result === 0) { vscodeWindow.reload(); } else if (result === 2) { @@ -1200,7 +1255,7 @@ export class WindowsManager implements IWindowsService { message: nls.localize('appCrashed', "The window has crashed"), detail: nls.localize('appCrashedDetail', "We are sorry for the inconvenience! You can reopen the window to continue where you left off."), noLink: true - }, (result) => { + }, result => { if (result === 0) { vscodeWindow.reload(); } else if (result === 1) { @@ -1217,7 +1272,7 @@ export class WindowsManager implements IWindowsService { } // On Window close, update our stored state of this window - let state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() }; + const state: IWindowState = { workspacePath: win.openedWorkspacePath, uiState: win.serializeWindowState() }; if (win.isPluginDevelopmentHost) { this.windowsState.lastPluginDevelopmentHostWindow = state; } else { @@ -1237,7 +1292,7 @@ export class WindowsManager implements IWindowsService { win.dispose(); // Remove from our list so that Electron can clean it up - let index = WindowsManager.WINDOWS.indexOf(win); + const index = WindowsManager.WINDOWS.indexOf(win); WindowsManager.WINDOWS.splice(index, 1); // Emit @@ -1267,4 +1322,4 @@ export class WindowsManager implements IWindowsService { return pathA === pathB; } -} \ No newline at end of file +} diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 33dcb46bb57..3a506b9d96a 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -6,7 +6,8 @@ import { spawn } from 'child_process'; import { TPromise } from 'vs/base/common/winjs.base'; import { assign } from 'vs/base/common/objects'; -import { parseCLIProcessArgv, buildHelpMessage, ParsedArgs } from 'vs/code/node/argv'; +import { parseCLIProcessArgv, buildHelpMessage, ParsedArgs } from 'vs/platform/environment/node/argv'; +import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; function shouldSpawnCliProcess(argv: ParsedArgs): boolean { @@ -28,9 +29,9 @@ export function main(argv: string[]): TPromise { } if (args.help) { - console.log(buildHelpMessage(pkg.version)); + console.log(buildHelpMessage(product.nameLong, product.applicationName, pkg.version)); } else if (args.version) { - console.log(pkg.version); + console.log(`${ pkg.version } (${ product.commit })`); } else if (shouldSpawnCliProcess(args)) { const mainCli = new TPromise(c => require(['vs/code/node/cliProcessMain'], c)); return mainCli.then(cli => cli.main(args)); diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index 1ad830e39fe..c6aba650b72 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; import * as path from 'path'; -import { ParsedArgs } from 'vs/code/node/argv'; +import { ParsedArgs } from 'vs/platform/environment/node/argv'; import { TPromise } from 'vs/base/common/winjs.base'; import { sequence } from 'vs/base/common/async'; import { IPager } from 'vs/base/common/paging'; diff --git a/src/vs/code/node/sharedProcessMain.ts b/src/vs/code/node/sharedProcessMain.ts index 9f4069163ea..d7ede781b9b 100644 --- a/src/vs/code/node/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcessMain.ts @@ -14,7 +14,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { EnvironmentService } from 'vs/platform/environment/node/environmentService'; -import { parseArgs } from 'vs/code/node/argv'; +import { parseArgs } from 'vs/platform/environment/node/argv'; import { IEventService } from 'vs/platform/event/common/event'; import { EventService } from 'vs/platform/event/common/eventService'; import { ExtensionManagementChannel } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; @@ -108,7 +108,7 @@ function main(server: Server): void { server.registerChannel('extensions', channel); // eventually clean up old extensions - setTimeout(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(), 5000); + setTimeout(() => (extensionManagementService as ExtensionManagementService).removeDeprecatedExtensions(), 100); }); }); } diff --git a/src/vs/code/test/node/argv.test.ts b/src/vs/code/test/node/argv.test.ts index 685ca737cf9..dec7e806bdd 100644 --- a/src/vs/code/test/node/argv.test.ts +++ b/src/vs/code/test/node/argv.test.ts @@ -5,7 +5,7 @@ 'use strict'; import assert = require('assert'); -import {formatOptions} from 'vs/code/node/argv'; +import {formatOptions} from 'vs/platform/environment/node/argv'; suite('formatOptions', () => { test('Text should display small columns correctly', () => { diff --git a/src/vs/css.js b/src/vs/css.js index 45d7eb7bb59..4e1ad32966e 100644 --- a/src/vs/css.js +++ b/src/vs/css.js @@ -236,9 +236,7 @@ var CSSLoaderPlugin; CSSPlugin.prototype.load = function (name, req, load, config) { config = config || {}; var myConfig = config['vs/css'] || {}; - if (myConfig.inlineResources) { - global.inlineResources = true; - } + global.inlineResources = myConfig.inlineResources; var cssUrl = req.toUrl(name + '.css'); this.cssLoader.load(name, cssUrl, function (contents) { // Contents has the CSS file contents if we are in a build @@ -277,7 +275,7 @@ var CSSLoaderPlugin; ], entries = global.cssPluginEntryPoints[moduleName]; for (var i = 0; i < entries.length; i++) { if (global.inlineResources) { - contents.push(Utilities.rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents)); + contents.push(Utilities.rewriteOrInlineUrls(entries[i].fsPath, entries[i].moduleName, moduleName, entries[i].contents, global.inlineResources === 'base64')); } else { contents.push(Utilities.rewriteUrls(entries[i].moduleName, moduleName, entries[i].contents)); @@ -428,7 +426,7 @@ var CSSLoaderPlugin; return Utilities.relativePath(newFile, absoluteUrl); }); }; - Utilities.rewriteOrInlineUrls = function (originalFileFSPath, originalFile, newFile, contents) { + Utilities.rewriteOrInlineUrls = function (originalFileFSPath, originalFile, newFile, contents, forceBase64) { var fs = require.nodeRequire('fs'); var path = require.nodeRequire('path'); return this._replaceURL(contents, function (url) { @@ -444,7 +442,7 @@ var CSSLoaderPlugin; global.cssInlinedResources.push(normalizedFSPath); var MIME = /\.svg$/.test(url) ? 'image/svg+xml' : 'image/png'; var DATA = ';base64,' + fileContents.toString('base64'); - if (/\.svg$/.test(url)) { + if (!forceBase64 && /\.svg$/.test(url)) { // .svg => url encode as explained at https://codepen.io/tigt/post/optimizing-svgs-in-data-uris var newText = fileContents.toString() .replace(/"/g, '\'') diff --git a/src/vs/editor/browser/codeEditor.ts b/src/vs/editor/browser/codeEditor.ts index daec7c89ee4..9374587ae6c 100644 --- a/src/vs/editor/browser/codeEditor.ts +++ b/src/vs/editor/browser/codeEditor.ts @@ -7,15 +7,18 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {ICommandService} from 'vs/platform/commands/common/commands'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; -import * as editorCommon from 'vs/editor/common/editorCommon'; +import {IEditorOptions} from 'vs/editor/common/editorCommon'; +import {IEditorContributionCtor} from 'vs/editor/browser/editorBrowser'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {CodeEditorWidget} from 'vs/editor/browser/widget/codeEditorWidget'; +import {EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; export class CodeEditor extends CodeEditorWidget { constructor( domElement:HTMLElement, - options:editorCommon.IEditorOptions, + options:IEditorOptions, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @ICommandService commandService: ICommandService, @@ -24,4 +27,11 @@ export class CodeEditor extends CodeEditorWidget { super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService); } + protected _getContributions(): IEditorContributionCtor[] { + return [].concat(EditorBrowserRegistry.getEditorContributions()).concat(CommonEditorRegistry.getEditorContributions()); + } + + protected _getActions(): EditorAction[] { + return CommonEditorRegistry.getEditorActions(); + } } diff --git a/src/vs/editor/browser/config/configuration.ts b/src/vs/editor/browser/config/configuration.ts index f5f82b4ff4d..56cbb755aa4 100644 --- a/src/vs/editor/browser/config/configuration.ts +++ b/src/vs/editor/browser/config/configuration.ts @@ -271,6 +271,8 @@ export class Configuration extends CommonEditorConfiguration { extra += 'ie '; } else if (browser.isFirefox) { extra += 'ff '; + } else if (browser.isEdge) { + extra += 'edge '; } if (browser.isIE9) { extra += 'ie9 '; diff --git a/src/vs/editor/browser/controller/keyboardHandler.ts b/src/vs/editor/browser/controller/keyboardHandler.ts index 6341ee3386a..a8040a835af 100644 --- a/src/vs/editor/browser/controller/keyboardHandler.ts +++ b/src/vs/editor/browser/controller/keyboardHandler.ts @@ -109,12 +109,14 @@ export class KeyboardHandler extends ViewEventHandler implements IDisposable { let cs = dom.getComputedStyle(this.textArea.actual); if (browser.isFirefox) { // computedStyle.font is empty in Firefox... - context.font = `${cs.fontStyle} ${cs.fontVariant} ${cs.fontWeight} ${cs.fontStretch} ${cs.fontSize} / ${cs.lineHeight} '${cs.fontFamily}'`; + context.font = `${cs.fontStyle} ${cs.fontVariant} ${cs.fontWeight} ${cs.fontStretch} ${cs.fontSize} / ${cs.lineHeight} ${cs.fontFamily}`; + let metrics = context.measureText(e.data); + StyleMutator.setWidth(this.textArea.actual, metrics.width + 2); // +2 for Japanese... } else { context.font = cs.font; + let metrics = context.measureText(e.data); + StyleMutator.setWidth(this.textArea.actual, metrics.width); } - let metrics = context.measureText(e.data); - StyleMutator.setWidth(this.textArea.actual, metrics.width); } })); diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index c136eee58ef..e4a42059522 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -14,7 +14,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import {ViewEventHandler} from 'vs/editor/common/viewModel/viewEventHandler'; import {MouseTargetFactory} from 'vs/editor/browser/controller/mouseTarget'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import {TimeoutTimer} from 'vs/base/common/async'; +import {TimeoutTimer, RunOnceScheduler} from 'vs/base/common/async'; import {ViewContext} from 'vs/editor/common/view/viewContext'; import {VisibleRange} from 'vs/editor/common/view/renderingContext'; import {EditorMouseEventFactory, GlobalEditorMouseMoveMonitor, EditorMouseEvent} from 'vs/editor/browser/editorDom'; @@ -131,6 +131,7 @@ export class MouseHandler extends ViewEventHandler implements IDisposable { protected mouseTargetFactory: MouseTargetFactory; protected listenersToRemove:IDisposable[]; private toDispose:IDisposable[]; + private _asyncFocus: RunOnceScheduler; private _mouseDownOperation: MouseDownOperation; private lastMouseLeaveTime:number; @@ -155,6 +156,8 @@ export class MouseHandler extends ViewEventHandler implements IDisposable { ); this.toDispose = []; + this._asyncFocus = new RunOnceScheduler(() => this.viewHelper.focusTextArea(), 0); + this.toDispose.push(this._asyncFocus); this.lastMouseLeaveTime = -1; @@ -214,6 +217,11 @@ export class MouseHandler extends ViewEventHandler implements IDisposable { this._mouseDownOperation.onCursorSelectionChanged(e); return false; } + private _isFocused = false; + public onViewFocusChanged(isFocused:boolean): boolean { + this._isFocused = isFocused; + return false; + } // --- end event handlers protected _createMouseTarget(e:EditorMouseEvent, testEventTarget:boolean): editorBrowser.IMouseTarget { @@ -280,17 +288,11 @@ export class MouseHandler extends ViewEventHandler implements IDisposable { } var focus = () => { - if (browser.isIE11orEarlier) { - // IE does not want to focus when coming in from the browser's address bar - if ((e.browserEvent).fromElement) { - e.preventDefault(); - this.viewHelper.focusTextArea(); - } else { - // TODO@Alex -> cancel this if focus is lost - setTimeout(() => { - this.viewHelper.focusTextArea(); - }); - } + // In IE11, if the focus is in the browser's address bar and + // then you click in the editor, calling preventDefault() + // will not move focus properly (focus remains the address bar) + if (browser.isIE11orEarlier && !this._isFocused) { + this._asyncFocus.schedule(); } else { e.preventDefault(); this.viewHelper.focusTextArea(); diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 4059b8e9a9c..6a0cc8f5be3 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -294,6 +294,11 @@ export class MouseTargetFactory { if (lineNumberAttribute && columnAttribute) { return this.createMouseTargetFromViewCursor(t, parseInt(lineNumberAttribute, 10), parseInt(columnAttribute, 10), mouseColumn); } + } else { + // Hit testing completely failed... + let possibleLineNumber = this._viewHelper.getLineNumberAtVerticalOffset(mouseVerticalOffset); + let maxColumn = this._context.model.getLineMaxColumn(possibleLineNumber); + return new MouseTarget(t, MouseTargetType.CONTENT_EMPTY, mouseColumn, new Position(possibleLineNumber, maxColumn)); } } diff --git a/src/vs/editor/browser/editor.all.ts b/src/vs/editor/browser/editor.all.ts index c7b54af660a..0ae86cff88c 100644 --- a/src/vs/editor/browser/editor.all.ts +++ b/src/vs/editor/browser/editor.all.ts @@ -8,7 +8,6 @@ import 'vs/editor/browser/widget/codeEditorWidget'; import 'vs/editor/browser/widget/diffEditorWidget'; -import 'vs/editor/contrib/accessibility/browser/accessibility'; import 'vs/editor/contrib/clipboard/browser/clipboard'; import 'vs/editor/contrib/codelens/browser/codelens'; import 'vs/editor/contrib/comment/common/comment'; @@ -41,7 +40,6 @@ import 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode'; import 'vs/editor/contrib/toggleWordWrap/common/toggleWordWrap'; import 'vs/css!vs/editor/contrib/wordHighlighter/browser/wordHighlighter'; import 'vs/editor/contrib/wordHighlighter/common/wordHighlighter'; -import 'vs/editor/contrib/defineKeybinding/browser/defineKeybinding'; import 'vs/editor/contrib/folding/browser/folding'; import 'vs/editor/contrib/indentation/common/indentation'; diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index be1811a72f5..00448c5ba4e 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -8,7 +8,7 @@ import {IEventEmitter} from 'vs/base/common/eventEmitter'; import {IDisposable} from 'vs/base/common/lifecycle'; import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {IMouseEvent} from 'vs/base/browser/mouseEvent'; -import {IInstantiationService, IConstructorSignature1} from 'vs/platform/instantiation/common/instantiation'; +import {IConstructorSignature1} from 'vs/platform/instantiation/common/instantiation'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {Position} from 'vs/editor/common/core/position'; import {Range} from 'vs/editor/common/core/range'; @@ -385,19 +385,7 @@ export interface IEditorMouseEvent { /** * @internal */ -export type ISimpleEditorContributionCtor = IConstructorSignature1; - -/** - * An editor contribution descriptor that will be used to construct editor contributions - * @internal - */ -export interface IEditorContributionDescriptor { - /** - * Create an instance of the contribution - */ - createInstance(instantiationService:IInstantiationService, editor:ICodeEditor): editorCommon.IEditorContribution; -} - +export type IEditorContributionCtor = IConstructorSignature1; /** * An overview ruler diff --git a/src/vs/editor/browser/editorBrowserExtensions.ts b/src/vs/editor/browser/editorBrowserExtensions.ts index b4df735ec27..6e07a20e005 100644 --- a/src/vs/editor/browser/editorBrowserExtensions.ts +++ b/src/vs/editor/browser/editorBrowserExtensions.ts @@ -4,54 +4,40 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IInstantiationService, IConstructorSignature1} from 'vs/platform/instantiation/common/instantiation'; import {Registry} from 'vs/platform/platform'; -import {IEditorContribution} from 'vs/editor/common/editorCommon'; -import {ICodeEditor, IEditorContributionDescriptor, ISimpleEditorContributionCtor} from 'vs/editor/browser/editorBrowser'; +import {IEditorContributionCtor} from 'vs/editor/browser/editorBrowser'; + +export function editorContribution(ctor:IEditorContributionCtor): void { + EditorContributionRegistry.INSTANCE.registerEditorBrowserContribution(ctor); +} export namespace EditorBrowserRegistry { - // --- Editor Contributions - export function registerEditorContribution(ctor:ISimpleEditorContributionCtor): void { - (Registry.as(Extensions.EditorContributions)).registerEditorBrowserContribution(ctor); - } - export function getEditorContributions(): IEditorContributionDescriptor[] { - return (Registry.as(Extensions.EditorContributions)).getEditorBrowserContributions(); + export function getEditorContributions(): IEditorContributionCtor[] { + return EditorContributionRegistry.INSTANCE.getEditorBrowserContributions(); } } -class SimpleEditorContributionDescriptor implements IEditorContributionDescriptor { - private _ctor:ISimpleEditorContributionCtor; - - constructor(ctor:ISimpleEditorContributionCtor) { - this._ctor = ctor; - } - - public createInstance(instantiationService:IInstantiationService, editor:ICodeEditor): IEditorContribution { - // cast added to help the compiler, can remove once IConstructorSignature1 has been removed - return instantiationService.createInstance(> this._ctor, editor); - } -} - -// Editor extension points -var Extensions = { +const Extensions = { EditorContributions: 'editor.contributions' }; class EditorContributionRegistry { - private editorContributions: IEditorContributionDescriptor[]; + public static INSTANCE = new EditorContributionRegistry(); + + private editorContributions: IEditorContributionCtor[]; constructor() { this.editorContributions = []; } - public registerEditorBrowserContribution(ctor:ISimpleEditorContributionCtor): void { - this.editorContributions.push(new SimpleEditorContributionDescriptor(ctor)); + public registerEditorBrowserContribution(ctor:IEditorContributionCtor): void { + this.editorContributions.push(ctor); } - public getEditorBrowserContributions(): IEditorContributionDescriptor[] { + public getEditorBrowserContributions(): IEditorContributionCtor[] { return this.editorContributions.slice(0); } } -Registry.add(Extensions.EditorContributions, new EditorContributionRegistry()); +Registry.add(Extensions.EditorContributions, EditorContributionRegistry.INSTANCE); diff --git a/src/vs/editor/browser/services/codeEditorServiceImpl.ts b/src/vs/editor/browser/services/codeEditorServiceImpl.ts index 0bd26ac4387..070a393c7ee 100644 --- a/src/vs/editor/browser/services/codeEditorServiceImpl.ts +++ b/src/vs/editor/browser/services/codeEditorServiceImpl.ts @@ -302,7 +302,7 @@ class DecorationRenderHelper { if (typeof opts !== 'undefined') { DecorationRenderHelper.collectBorderSettingsCSSText(opts, cssTextArr); if (typeof opts.contentIconPath !== 'undefined') { - cssTextArr.push(strings.format(this._CSS_MAP.contentIconPath, URI.file(opts.contentIconPath).toString())); + cssTextArr.push(strings.format(this._CSS_MAP.contentIconPath, URI.parse(opts.contentIconPath).toString())); } if (typeof opts.contentText !== 'undefined') { let escaped = opts.contentText.replace(/\"/g, '\\\"'); diff --git a/src/vs/editor/browser/standalone/colorizer.ts b/src/vs/editor/browser/standalone/colorizer.ts index a4ce6633487..3101d2c672c 100644 --- a/src/vs/editor/browser/standalone/colorizer.ts +++ b/src/vs/editor/browser/standalone/colorizer.ts @@ -5,6 +5,7 @@ 'use strict'; import {RunOnceScheduler} from 'vs/base/common/async'; +import {IDisposable} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {IModel} from 'vs/editor/common/editorCommon'; import {ILineTokens, IMode} from 'vs/editor/common/modes'; @@ -25,46 +26,65 @@ export class Colorizer { public static colorizeElement(modeService:IModeService, domNode:HTMLElement, options:IColorizerElementOptions): TPromise { options = options || {}; - var theme = options.theme || 'vs'; - var mimeType = options.mimeType || domNode.getAttribute('lang') || domNode.getAttribute('data-lang'); + let theme = options.theme || 'vs'; + let mimeType = options.mimeType || domNode.getAttribute('lang') || domNode.getAttribute('data-lang'); if (!mimeType) { console.error('Mode not detected'); return; } - var text = domNode.firstChild.nodeValue; + let text = domNode.firstChild.nodeValue; domNode.className += 'monaco-editor ' + theme; - var render = (str:string) => { + let render = (str:string) => { domNode.innerHTML = str; }; return this.colorize(modeService, text, mimeType, options).then(render, (err) => console.error(err), render); } + private static _tokenizationSupportChangedPromise(target:IMode): TPromise { + let listener: IDisposable = null; + let stopListening = () => { + if (listener) { + listener.dispose(); + listener = null; + } + }; + + return new TPromise((c, e, p) => { + listener = target.addSupportChangedListener((e) => { + if (e.tokenizationSupport) { + stopListening(); + c(void 0); + } + }); + }, stopListening); + } + public static colorize(modeService:IModeService, text:string, mimeType:string, options:IColorizerOptions): TPromise { options = options || {}; if (typeof options.tabSize === 'undefined') { options.tabSize = 4; } - var lines = text.split('\n'), - c: (v:string)=>void, - e: (err:any)=>void, - p: (v:string)=>void, - isCancelled = false, - mode: IMode; + let lines = text.split('\n'); + let c: (v:string)=>void; + let e: (err:any)=>void; + let p: (v:string)=>void; + let isCanceled = false; + let mode: IMode; - var result = new TPromise((_c, _e, _p) => { + let result = new TPromise((_c, _e, _p) => { c = _c; e = _e; p = _p; }, () => { - isCancelled = true; + isCanceled = true; }); - var colorize = new RunOnceScheduler(() => { - if (isCancelled) { + let colorize = new RunOnceScheduler(() => { + if (isCanceled) { return; } - var r = actualColorize(lines, mode, options.tabSize); + let r = actualColorize(lines, mode, options.tabSize); if (r.retokenize.length > 0) { // There are retokenization requests r.retokenize.forEach((p) => p.then(scheduleColorize)); @@ -74,7 +94,7 @@ export class Colorizer { c(r.result); } }, 0); - var scheduleColorize = () => colorize.schedule(); + let scheduleColorize = () => colorize.schedule(); modeService.getOrCreateMode(mimeType).then((_mode) => { if (!_mode) { @@ -82,7 +102,15 @@ export class Colorizer { return; } if (!_mode.tokenizationSupport) { - e('Mode found ("' + _mode.getId() + '"), but does not support tokenization.'); + // wait 500ms for mode to load, then give up + TPromise.any([this._tokenizationSupportChangedPromise(_mode), TPromise.timeout(500)]).then(_ => { + if (!_mode.tokenizationSupport) { + e('Mode found ("' + _mode.getId() + '"), but does not support tokenization.'); + return; + } + mode = _mode; + scheduleColorize(); + }); return; } mode = _mode; @@ -93,7 +121,7 @@ export class Colorizer { } public static colorizeLine(line:string, tokens:ViewLineToken[], tabSize:number = 4): string { - var renderResult = renderLine(new RenderLineInput( + let renderResult = renderLine(new RenderLineInput( line, tabSize, 0, @@ -106,9 +134,9 @@ export class Colorizer { } public static colorizeModelLine(model:IModel, lineNumber:number, tabSize:number = 4): string { - var content = model.getLineContent(lineNumber); - var tokens = model.getLineTokens(lineNumber, false); - var inflatedTokens = tokens.inflate(); + let content = model.getLineContent(lineNumber); + let tokens = model.getLineTokens(lineNumber, false); + let inflatedTokens = tokens.inflate(); return this.colorizeLine(content, inflatedTokens, tabSize); } } @@ -120,7 +148,7 @@ interface IActualColorizeResult { } function actualColorize(lines:string[], mode:IMode, tabSize:number): IActualColorizeResult { - var tokenization = mode.tokenizationSupport, + let tokenization = mode.tokenizationSupport, html:string[] = [], state = tokenization.getInitialState(), i:number, diff --git a/src/vs/editor/browser/standalone/media/standalone-tokens.css b/src/vs/editor/browser/standalone/media/standalone-tokens.css index de100c8b704..98b56b06771 100644 --- a/src/vs/editor/browser/standalone/media/standalone-tokens.css +++ b/src/vs/editor/browser/standalone/media/standalone-tokens.css @@ -14,6 +14,10 @@ color: deepskyblue; } +.monaco-editor-hover p { + margin: 0; +} + /*.monaco-editor.vs [tabindex="0"]:focus { outline: 1px solid rgba(0, 122, 204, 0.4); outline-offset: -1px; diff --git a/src/vs/editor/browser/standalone/simpleServices.ts b/src/vs/editor/browser/standalone/simpleServices.ts index a7cd30e3c4e..5c68fb3292e 100644 --- a/src/vs/editor/browser/standalone/simpleServices.ts +++ b/src/vs/editor/browser/standalone/simpleServices.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {toErrorMessage} from 'vs/base/common/errors'; import {EventEmitter} from 'vs/base/common/eventEmitter'; import {Schemas} from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; @@ -12,8 +11,8 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IConfigurationService, IConfigurationServiceEvent, IConfigurationValue, getConfigurationValue} from 'vs/platform/configuration/common/configuration'; import {IEditor, IEditorInput, IEditorOptions, IEditorService, IResourceInput, ITextEditorModel, Position} from 'vs/platform/editor/common/editor'; import {AbstractExtensionService, ActivatedExtension} from 'vs/platform/extensions/common/abstractExtensionService'; -import {IExtensionDescription} from 'vs/platform/extensions/common/extensions'; -import {ICommandService, ICommandHandler} from 'vs/platform/commands/common/commands'; +import {IExtensionDescription, IExtensionService} from 'vs/platform/extensions/common/extensions'; +import {ICommandService, ICommand, ICommandHandler} from 'vs/platform/commands/common/commands'; import {KeybindingService} from 'vs/platform/keybinding/browser/keybindingServiceImpl'; import {IOSupport} from 'vs/platform/keybinding/common/keybindingResolver'; import {IKeybindingItem} from 'vs/platform/keybinding/common/keybinding'; @@ -24,6 +23,9 @@ import {ICodeEditor, IDiffEditor} from 'vs/editor/browser/editorBrowser'; import {Selection} from 'vs/editor/common/core/selection'; import Event, {Emitter} from 'vs/base/common/event'; import {getDefaultValues as getDefaultConfiguration} from 'vs/platform/configuration/common/model'; +import {CommandService} from 'vs/platform/commands/common/commandService'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {IProgressService, IProgressRunner} from 'vs/platform/progress/common/progress'; export class SimpleEditor implements IEditor { @@ -41,6 +43,7 @@ export class SimpleEditor implements IEditor { public getControl():editorCommon.IEditor { return this._widget; } public getSelection():Selection { return this._widget.getSelection(); } public focus():void { this._widget.focus(); } + public isVisible():boolean { return true; } public withTypedEditor(codeEditorCallback:(editor:ICodeEditor)=>T, diffEditorCallback:(editor:IDiffEditor)=>T): T { if (this._widget.getEditorType() === editorCommon.EditorType.ICodeEditor) { @@ -62,6 +65,10 @@ export class SimpleModel extends EventEmitter implements ITextEditorModel { this.model = model; } + public load(): TPromise { + return TPromise.as(this); + } + public get textEditorModel():editorCommon.IModel { return this.model; } @@ -162,6 +169,26 @@ export class SimpleEditorService implements IEditorService { } } +export class SimpleProgressService implements IProgressService { + _serviceBrand: any; + + private static NULL_PROGRESS_RUNNER:IProgressRunner = { + done: () => {}, + total: () => {}, + worked: () => {} + }; + + show(infinite: boolean, delay?: number): IProgressRunner; + show(total: number, delay?: number): IProgressRunner; + show(): IProgressRunner { + return SimpleProgressService.NULL_PROGRESS_RUNNER; + } + + showWhile(promise: TPromise, delay?: number): TPromise { + return null; + } +} + export class SimpleMessageService implements IMessageService { public _serviceBrand: any; @@ -171,7 +198,7 @@ export class SimpleMessageService implements IMessageService { switch(sev) { case Severity.Error: - console.error(toErrorMessage(message, true)); + console.error(message); break; case Severity.Warning: console.warn(message); @@ -198,11 +225,32 @@ export class SimpleMessageService implements IMessageService { } } +export class StandaloneCommandService extends CommandService { + + private _dynamicCommands: { [id: string]: ICommand; }; + + constructor( + instantiationService: IInstantiationService, + extensionService: IExtensionService + ) { + super(instantiationService, extensionService); + + this._dynamicCommands = Object.create(null); + } + + public addCommand(id:string, command:ICommand): void { + this._dynamicCommands[id] = command; + } + + protected _getCommand(id:string): ICommand { + return super._getCommand(id) || this._dynamicCommands[id]; + } +} + export class StandaloneKeybindingService extends KeybindingService { private static LAST_GENERATED_ID = 0; private _dynamicKeybindings: IKeybindingItem[]; - private _dynamicCommands: { [id: string]: ICommandHandler }; constructor( contextKeyService: IContextKeyService, @@ -213,7 +261,6 @@ export class StandaloneKeybindingService extends KeybindingService { super(contextKeyService, commandService, messageService); this._dynamicKeybindings = []; - this._dynamicCommands = Object.create(null); this._beginListening(domNode); } @@ -230,7 +277,15 @@ export class StandaloneKeybindingService extends KeybindingService { weight1: 1000, weight2: 0 }); - this._dynamicCommands[commandId] = handler; + + let commandService = this._commandService; + if (commandService instanceof StandaloneCommandService) { + commandService.addCommand(commandId, { + handler: handler + }); + } else { + throw new Error('Unknown command service!'); + } this.updateResolver(); return commandId; } @@ -238,10 +293,6 @@ export class StandaloneKeybindingService extends KeybindingService { protected _getExtraKeybindings(isFirstTime:boolean): IKeybindingItem[] { return this._dynamicKeybindings; } - - protected _getCommandHandler(commandId:string): ICommandHandler { - return super._getCommandHandler(commandId) || this._dynamicCommands[commandId]; - } } export class SimpleExtensionService extends AbstractExtensionService { diff --git a/src/vs/editor/browser/standalone/standaloneCodeEditor.ts b/src/vs/editor/browser/standalone/standaloneCodeEditor.ts index 9c6ed0740a1..c8e24481318 100644 --- a/src/vs/editor/browser/standalone/standaloneCodeEditor.ts +++ b/src/vs/editor/browser/standalone/standaloneCodeEditor.ts @@ -16,7 +16,7 @@ import {IActionDescriptor, ICodeEditorWidgetCreationOptions, IDiffEditorOptions, import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; import {StandaloneKeybindingService} from 'vs/editor/browser/standalone/simpleServices'; -import {IEditorContextViewService, IEditorOverrideServices, ensureStaticPlatformServices, getOrCreateStaticServices} from 'vs/editor/browser/standalone/standaloneServices'; +import {IEditorContextViewService} from 'vs/editor/browser/standalone/standaloneServices'; import {CodeEditor} from 'vs/editor/browser/codeEditor'; import {DiffEditorWidget} from 'vs/editor/browser/widget/diffEditorWidget'; import {ICodeEditor, IDiffEditor} from 'vs/editor/browser/editorBrowser'; @@ -65,7 +65,7 @@ export class StandaloneEditor extends CodeEditor implements IStandaloneCodeEdito constructor( domElement:HTMLElement, options:IEditorConstructionOptions, - toDispose: IDisposable[], + toDispose: IDisposable, @IInstantiationService instantiationService: IInstantiationService, @ICodeEditorService codeEditorService: ICodeEditorService, @ICommandService commandService: ICommandService, @@ -81,7 +81,7 @@ export class StandaloneEditor extends CodeEditor implements IStandaloneCodeEdito } this._contextViewService = contextViewService; - this._toDispose2 = toDispose; + this._toDispose2 = [toDispose]; let model: IModel = null; if (typeof options.model === 'undefined') { @@ -154,7 +154,7 @@ export class StandaloneEditor extends CodeEditor implements IStandaloneCodeEdito _postDetachModelCleanup(detachedModel:IModel): void { super._postDetachModelCleanup(detachedModel); if (detachedModel && this._ownsModel) { - detachedModel.destroy(); + detachedModel.dispose(); this._ownsModel = false; } } @@ -169,7 +169,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon constructor( domElement:HTMLElement, options:IDiffEditorConstructionOptions, - toDispose: IDisposable[], + toDispose: IDisposable, @IInstantiationService instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, @IKeybindingService keybindingService: IKeybindingService, @@ -184,7 +184,7 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon this._contextViewService = contextViewService; - this._toDispose2 = toDispose; + this._toDispose2 = [toDispose]; this._contextViewService.setContainer(this._containerDomElement); } @@ -230,34 +230,3 @@ export class StandaloneDiffEditor extends DiffEditorWidget implements IStandalon } } } - -export var startup = (function() { - - var modesRegistryInitialized = false; - var setupServicesCalled = false; - - return { - initStaticServicesIfNecessary: function() { - if (modesRegistryInitialized) { - return; - } - modesRegistryInitialized = true; - getOrCreateStaticServices(); - }, - - setupServices: function(services: IEditorOverrideServices): IEditorOverrideServices { - if (setupServicesCalled) { - console.error('Call to monaco.editor.setupServices is ignored because it was called before'); - return; - } - setupServicesCalled = true; - if (modesRegistryInitialized) { - console.error('Call to monaco.editor.setupServices is ignored because other API was called before'); - return; - } - - return ensureStaticPlatformServices(services); - } - }; - -})(); diff --git a/src/vs/editor/browser/standalone/standaloneEditor.ts b/src/vs/editor/browser/standalone/standaloneEditor.ts index a5334880b00..ab59657aa90 100644 --- a/src/vs/editor/browser/standalone/standaloneEditor.ts +++ b/src/vs/editor/browser/standalone/standaloneEditor.ts @@ -7,43 +7,58 @@ import 'vs/css!./media/standalone-tokens'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ContentWidgetPositionPreference, OverlayWidgetPositionPreference} from 'vs/editor/browser/editorBrowser'; -import {ShallowCancelThenPromise} from 'vs/base/common/async'; -import {StandaloneEditor, IStandaloneCodeEditor, StandaloneDiffEditor, IStandaloneDiffEditor, startup, IEditorConstructionOptions, IDiffEditorConstructionOptions} from 'vs/editor/browser/standalone/standaloneCodeEditor'; +import {StandaloneEditor, IStandaloneCodeEditor, StandaloneDiffEditor, IStandaloneDiffEditor, IEditorConstructionOptions, IDiffEditorConstructionOptions} from 'vs/editor/browser/standalone/standaloneCodeEditor'; import {ScrollbarVisibility} from 'vs/base/common/scrollable'; -import {IEditorOverrideServices, ensureDynamicPlatformServices, ensureStaticPlatformServices} from 'vs/editor/browser/standalone/standaloneServices'; +import {IEditorOverrideServices, DynamicStandaloneServices, StaticServices} from 'vs/editor/browser/standalone/standaloneServices'; import {IDisposable} from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; -import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; -import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; -import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; import {OpenerService} from 'vs/platform/opener/browser/openerService'; +import {IOpenerService} from 'vs/platform/opener/common/opener'; import {IModel} from 'vs/editor/common/editorCommon'; -import {IModelService} from 'vs/editor/common/services/modelService'; import {Colorizer, IColorizerElementOptions, IColorizerOptions} from 'vs/editor/browser/standalone/colorizer'; import {SimpleEditorService} from 'vs/editor/browser/standalone/simpleServices'; import * as modes from 'vs/editor/common/modes'; -import {EditorWorkerClient} from 'vs/editor/common/services/editorWorkerServiceImpl'; +import {IWebWorkerOptions, MonacoWebWorker, createWebWorker as actualCreateWebWorker} from 'vs/editor/common/services/webWorker'; import {IMarkerData} from 'vs/platform/markers/common/markers'; import {DiffNavigator} from 'vs/editor/contrib/diffNavigator/common/diffNavigator'; - -function shallowClone(obj:T): T { - let r:T = {}; - if (obj) { - let keys = Object.keys(obj); - for (let i = 0, len = keys.length; i < len; i++) { - let key = keys[i]; - r[key] = obj[key]; - } - } - return r; -} +import {IEditorService} from 'vs/platform/editor/common/editor'; +import {ICommandService} from 'vs/platform/commands/common/commands'; +import {IContextViewService} from 'vs/platform/contextview/browser/contextView'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; +import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; +import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; +import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; /** * @internal */ -export function setupServices(services: IEditorOverrideServices): IEditorOverrideServices { - return startup.setupServices(services); +export function setupServices(overrides: IEditorOverrideServices): any { + return StaticServices.init(overrides); +} + +function withAllStandaloneServices(domElement:HTMLElement, override:IEditorOverrideServices, callback:(services:DynamicStandaloneServices)=>T): T { + let services = new DynamicStandaloneServices(domElement, override); + + // The editorService is a lovely beast. It needs to point back to the code editor instance... + let simpleEditorService: SimpleEditorService = null; + if (!services.has(IEditorService)) { + simpleEditorService = new SimpleEditorService(); + services.set(IEditorService, simpleEditorService); + } + + if (!services.has(IOpenerService)) { + services.set(IOpenerService, new OpenerService(services.get(IEditorService), services.get(ICommandService))); + } + + let result = callback(services); + + if (simpleEditorService) { + simpleEditorService.setEditor(result); + } + + return result; } /** @@ -51,28 +66,20 @@ export function setupServices(services: IEditorOverrideServices): IEditorOverrid * `domElement` should be empty (not contain other dom nodes). * The editor will read the size of `domElement`. */ -export function create(domElement:HTMLElement, options?:IEditorConstructionOptions, services?:IEditorOverrideServices):IStandaloneCodeEditor { - startup.initStaticServicesIfNecessary(); - - services = shallowClone(services); - var editorService: SimpleEditorService = null; - if (!services || !services.editorService) { - editorService = new SimpleEditorService(); - services.editorService = editorService; - } - - if (!services.openerService) { - services.openerService = new OpenerService(editorService, services.commandService); - } - - var t = prepareServices(domElement, services); - var result = t.ctx.instantiationService.createInstance(StandaloneEditor, domElement, options, t.toDispose); - - if (editorService) { - editorService.setEditor(result); - } - - return result; +export function create(domElement:HTMLElement, options?:IEditorConstructionOptions, override?:IEditorOverrideServices):IStandaloneCodeEditor { + return withAllStandaloneServices(domElement, override, (services) => { + return new StandaloneEditor( + domElement, + options, + services, + services.get(IInstantiationService), + services.get(ICodeEditorService), + services.get(ICommandService), + services.get(IContextKeyService), + services.get(IKeybindingService), + services.get(IContextViewService) + ); + }); } /** @@ -80,24 +87,19 @@ export function create(domElement:HTMLElement, options?:IEditorConstructionOptio * `domElement` should be empty (not contain other dom nodes). * The editor will read the size of `domElement`. */ -export function createDiffEditor(domElement:HTMLElement, options?:IDiffEditorConstructionOptions, services?: IEditorOverrideServices):IStandaloneDiffEditor { - startup.initStaticServicesIfNecessary(); - - services = shallowClone(services); - var editorService: SimpleEditorService = null; - if (!services || !services.editorService) { - editorService = new SimpleEditorService(); - services.editorService = editorService; - } - - var t = prepareServices(domElement, services); - var result = t.ctx.instantiationService.createInstance(StandaloneDiffEditor, domElement, options, t.toDispose); - - if (editorService) { - editorService.setEditor(result); - } - - return result; +export function createDiffEditor(domElement:HTMLElement, options?:IDiffEditorConstructionOptions, override?: IEditorOverrideServices):IStandaloneDiffEditor { + return withAllStandaloneServices(domElement, override, (services) => { + return new StandaloneDiffEditor( + domElement, + options, + services, + services.get(IInstantiationService), + services.get(IContextKeyService), + services.get(IKeybindingService), + services.get(IContextViewService), + services.get(IEditorWorkerService) + ); + }); } export interface IDiffNavigator { @@ -117,30 +119,8 @@ export function createDiffNavigator(diffEditor:IStandaloneDiffEditor, opts?:IDif return new DiffNavigator(diffEditor, opts); } -function prepareServices(domElement: HTMLElement, services: IEditorOverrideServices): { ctx: IEditorOverrideServices; toDispose: IDisposable[]; } { - services = ensureStaticPlatformServices(services); - var toDispose = ensureDynamicPlatformServices(domElement, services); - - var collection = new ServiceCollection(); - for (var legacyServiceId in services) { - if (services.hasOwnProperty(legacyServiceId)) { - let id = createDecorator(legacyServiceId); - let service = services[legacyServiceId]; - collection.set(id, service); - } - } - services.instantiationService = new InstantiationService(collection); - - return { - ctx: services, - toDispose: toDispose - }; -} - function doCreateModel(value:string, mode:TPromise, uri?:URI): IModel { - let modelService = ensureStaticPlatformServices(null).modelService; - - return modelService.createModel(value, mode, uri); + return StaticServices.modelService.get().createModel(value, mode, uri); } /** @@ -148,12 +128,8 @@ function doCreateModel(value:string, mode:TPromise, uri?:URI): IMod * You can specify the language that should be set for this model or let the language be inferred from the `uri`. */ export function createModel(value:string, language?:string, uri?:URI): IModel { - startup.initStaticServicesIfNecessary(); - value = value || ''; - let modeService = ensureStaticPlatformServices(null).modeService; - if (!language) { let path = uri ? uri.path : null; @@ -163,73 +139,58 @@ export function createModel(value:string, language?:string, uri?:URI): IModel { firstLine = value.substring(0, firstLF); } - return doCreateModel(value, modeService.getOrCreateModeByFilenameOrFirstLine(path, firstLine), uri); + return doCreateModel(value, StaticServices.modeService.get().getOrCreateModeByFilenameOrFirstLine(path, firstLine), uri); } - return doCreateModel(value, modeService.getOrCreateMode(language), uri); + return doCreateModel(value, StaticServices.modeService.get().getOrCreateMode(language), uri); } /** * Change the language for a model. */ export function setModelLanguage(model:IModel, language:string): void { - startup.initStaticServicesIfNecessary(); - let modeService = ensureStaticPlatformServices(null).modeService; - - model.setMode(modeService.getOrCreateMode(language)); + model.setMode(StaticServices.modeService.get().getOrCreateMode(language)); } /** * Set the markers for a model. */ export function setModelMarkers(model:IModel, owner:string, markers: IMarkerData[]): void { - startup.initStaticServicesIfNecessary(); - var markerService = ensureStaticPlatformServices(null).markerService; - markerService.changeOne(owner, model.uri, markers); + StaticServices.markerService.get().changeOne(owner, model.uri, markers); } /** * Get the model that has `uri` if it exists. */ export function getModel(uri: URI): IModel { - startup.initStaticServicesIfNecessary(); - var modelService = ensureStaticPlatformServices(null).modelService; - return modelService.getModel(uri); + return StaticServices.modelService.get().getModel(uri); } /** * Get all the created models. */ export function getModels(): IModel[] { - startup.initStaticServicesIfNecessary(); - var modelService = ensureStaticPlatformServices(null).modelService; - return modelService.getModels(); + return StaticServices.modelService.get().getModels(); } /** * Emitted when a model is created. */ export function onDidCreateModel(listener:(model:IModel)=>void): IDisposable { - startup.initStaticServicesIfNecessary(); - var modelService = ensureStaticPlatformServices(null).modelService; - return modelService.onModelAdded(listener); + return StaticServices.modelService.get().onModelAdded(listener); } /** * Emitted right before a model is disposed. */ export function onWillDisposeModel(listener:(model:IModel)=>void): IDisposable { - startup.initStaticServicesIfNecessary(); - var modelService = ensureStaticPlatformServices(null).modelService; - return modelService.onModelRemoved(listener); + return StaticServices.modelService.get().onModelRemoved(listener); } /** * Emitted when a different language is set to a model. */ export function onDidChangeModelLanguage(listener:(e:{ model: IModel; oldLanguage: string; })=>void): IDisposable { - startup.initStaticServicesIfNecessary(); - var modelService = ensureStaticPlatformServices(null).modelService; - return modelService.onModelModeChanged((e) => { + return StaticServices.modelService.get().onModelModeChanged((e) => { listener({ model: e.model, oldLanguage: e.oldModeId @@ -242,129 +203,31 @@ export function onDidChangeModelLanguage(listener:(e:{ model: IModel; oldLanguag * @internal */ export function getOrCreateMode(modeId: string):TPromise { - startup.initStaticServicesIfNecessary(); - var modeService = ensureStaticPlatformServices(null).modeService; - - return modeService.getOrCreateMode(modeId); + return StaticServices.modeService.get().getOrCreateMode(modeId); } -/** - * A web worker that can provide a proxy to an arbitrary file. - */ -export interface MonacoWebWorker { - /** - * Terminate the web worker, thus invalidating the returned proxy. - */ - dispose(): void; - /** - * Get a proxy to the arbitrary loaded code. - */ - getProxy(): TPromise; - /** - * Synchronize (send) the models at `resources` to the web worker, - * making them available in the monaco.worker.getMirrorModels(). - */ - withSyncedResources(resources: URI[]): TPromise; -} -/** - * @internal - */ -export class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { - - private _foreignModuleId: string; - private _foreignModuleCreateData: any; - private _foreignProxy: TPromise; - - /** - * @internal - */ - constructor(modelService: IModelService, opts:IWebWorkerOptions) { - super(modelService); - this._foreignModuleId = opts.moduleId; - this._foreignModuleCreateData = opts.createData || null; - this._foreignProxy = null; - } - - private _getForeignProxy(): TPromise { - if (!this._foreignProxy) { - this._foreignProxy = new ShallowCancelThenPromise(this._getProxy().then((proxy) => { - return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData).then((foreignMethods) => { - this._foreignModuleId = null; - this._foreignModuleCreateData = null; - - let proxyMethodRequest = (method:string, args:any[]): TPromise => { - return proxy.fmr(method, args); - }; - - let createProxyMethod = (method:string, proxyMethodRequest:(method:string, args:any[])=>TPromise): Function => { - return function () { - let args = Array.prototype.slice.call(arguments, 0); - return proxyMethodRequest(method, args); - }; - }; - - let foreignProxy = {}; - for (let i = 0; i < foreignMethods.length; i++) { - foreignProxy[foreignMethods[i]] = createProxyMethod(foreignMethods[i], proxyMethodRequest); - } - - return foreignProxy; - }); - })); - } - return this._foreignProxy; - } - - public getProxy(): TPromise { - return this._getForeignProxy(); - } - - public withSyncedResources(resources: URI[]): TPromise { - return this._withSyncedResources(resources).then(_ => this.getProxy()); - } -} - -export interface IWebWorkerOptions { - /** - * The AMD moduleId to load. - * It should export a function `create` that should return the exported proxy. - */ - moduleId: string; - /** - * The data to send over when calling create on the module. - */ - createData?: any; -} /** * Create a new web worker that has model syncing capabilities built in. * Specify an AMD module to load that will `create` an object that will be proxied. */ export function createWebWorker(opts:IWebWorkerOptions): MonacoWebWorker { - startup.initStaticServicesIfNecessary(); - let staticPlatformServices = ensureStaticPlatformServices(null); - let modelService = staticPlatformServices.modelService; - - return new MonacoWebWorkerImpl(modelService, opts); + return actualCreateWebWorker(StaticServices.modelService.get(), opts); } /** * Colorize the contents of `domNode` using attribute `data-lang`. */ export function colorizeElement(domNode:HTMLElement, options:IColorizerElementOptions): TPromise { - startup.initStaticServicesIfNecessary(); - var modeService = ensureStaticPlatformServices(null).modeService; - return Colorizer.colorizeElement(modeService, domNode, options); + return Colorizer.colorizeElement(StaticServices.modeService.get(), domNode, options); } /** * Colorize `text` using language `languageId`. */ export function colorize(text:string, languageId:string, options:IColorizerOptions): TPromise { - startup.initStaticServicesIfNecessary(); - var modeService = ensureStaticPlatformServices(null).modeService; - return Colorizer.colorize(modeService, text, languageId, options); + return Colorizer.colorize(StaticServices.modeService.get(), text, languageId, options); } /** diff --git a/src/vs/editor/browser/standalone/standaloneLanguages.ts b/src/vs/editor/browser/standalone/standaloneLanguages.ts index e95da74c61f..5faaca7524f 100644 --- a/src/vs/editor/browser/standalone/standaloneLanguages.ts +++ b/src/vs/editor/browser/standalone/standaloneLanguages.ts @@ -11,9 +11,8 @@ import {ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegist import {ModesRegistry} from 'vs/editor/common/modes/modesRegistry'; import {IMonarchLanguage} from 'vs/editor/common/modes/monarch/monarchTypes'; import {ILanguageExtensionPoint} from 'vs/editor/common/services/modeService'; -import {ensureStaticPlatformServices} from 'vs/editor/browser/standalone/standaloneServices'; +import {StaticServices} from 'vs/editor/browser/standalone/standaloneServices'; import * as modes from 'vs/editor/common/modes'; -import {startup} from './standaloneCodeEditor'; import {LanguageConfiguration} from 'vs/editor/common/modes/languageConfigurationRegistry'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {Position} from 'vs/editor/common/core/position'; @@ -68,22 +67,16 @@ export function setLanguageConfiguration(languageId:string, configuration:Langua * Set the tokens provider for a language (manual implementation). */ export function setTokensProvider(languageId:string, provider:modes.TokensProvider): IDisposable { - startup.initStaticServicesIfNecessary(); - let staticPlatformServices = ensureStaticPlatformServices(null); - return staticPlatformServices.modeService.registerTokenizationSupport2(languageId, provider); + return StaticServices.modeService.get().registerTokenizationSupport2(languageId, provider); } /** * Set the tokens provider for a language (monarch implementation). */ export function setMonarchTokensProvider(languageId:string, languageDef:IMonarchLanguage): IDisposable { - startup.initStaticServicesIfNecessary(); - let staticPlatformServices = ensureStaticPlatformServices(null); let lexer = compile(languageId, languageDef); - let modeService = staticPlatformServices.modeService; - - return modeService.registerTokenizationSupport(languageId, (mode) => { - return createTokenizationSupport(modeService, mode, lexer); + return StaticServices.modeService.get().registerTokenizationSupport(languageId, (mode) => { + return createTokenizationSupport(StaticServices.modeService.get(), mode, lexer); }); } @@ -149,9 +142,7 @@ export function registerCodeLensProvider(languageId:string, provider:modes.CodeL export function registerCodeActionProvider(languageId:string, provider:CodeActionProvider): IDisposable { return modes.CodeActionProviderRegistry.register(languageId, { provideCodeActions: (model:editorCommon.IReadOnlyModel, range:Range, token: CancellationToken): modes.CodeAction[] | Thenable => { - startup.initStaticServicesIfNecessary(); - var markerService = ensureStaticPlatformServices(null).markerService; - let markers = markerService.read({resource: model.uri }).filter(m => { + let markers = StaticServices.markerService.get().read({resource: model.uri }).filter(m => { return Range.areIntersectingOrTouching(m, range); }); return provider.provideCodeActions(model, range, { markers }, token); diff --git a/src/vs/editor/browser/standalone/standaloneServices.ts b/src/vs/editor/browser/standalone/standaloneServices.ts index 5408ac69bbd..d1fa71183ab 100644 --- a/src/vs/editor/browser/standalone/standaloneServices.ts +++ b/src/vs/editor/browser/standalone/standaloneServices.ts @@ -4,22 +4,19 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IDisposable} from 'vs/base/common/lifecycle'; +import {Disposable} from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {ContextMenuService} from 'vs/platform/contextview/browser/contextMenuService'; import {IContextMenuService, IContextViewService} from 'vs/platform/contextview/browser/contextView'; import {ContextViewService} from 'vs/platform/contextview/browser/contextViewService'; -import {IEditorService} from 'vs/platform/editor/common/editor'; import {IEventService} from 'vs/platform/event/common/event'; import {EventService} from 'vs/platform/event/common/eventService'; import {IExtensionService} from 'vs/platform/extensions/common/extensions'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {createDecorator, IInstantiationService, ServiceIdentifier} from 'vs/platform/instantiation/common/instantiation'; import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; import {ICommandService} from 'vs/platform/commands/common/commands'; -import {CommandService} from 'vs/platform/commands/common/commandService'; -import {IOpenerService} from 'vs/platform/opener/common/opener'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import {MarkerService} from 'vs/platform/markers/common/markerService'; @@ -37,7 +34,10 @@ import {MainThreadModeServiceImpl} from 'vs/editor/common/services/modeServiceIm import {IModelService} from 'vs/editor/common/services/modelService'; import {ModelServiceImpl} from 'vs/editor/common/services/modelServiceImpl'; import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServiceImpl'; -import {SimpleConfigurationService, SimpleMessageService, SimpleExtensionService, StandaloneKeybindingService} from 'vs/editor/browser/standalone/simpleServices'; +import { + SimpleConfigurationService, SimpleMessageService, SimpleExtensionService, + StandaloneKeybindingService, StandaloneCommandService, SimpleProgressService +} from 'vs/editor/browser/standalone/simpleServices'; import {ContextKeyService} from 'vs/platform/contextkey/browser/contextKeyService'; import {IMenuService} from 'vs/platform/actions/common/actions'; import {MenuService} from 'vs/platform/actions/common/menuService'; @@ -50,256 +50,159 @@ export interface IEditorContextViewService extends IContextViewService { } export interface IEditorOverrideServices { - /** - * @internal - */ - compatWorkerService?: ICompatWorkerService; - /** - * @internal - */ - modeService?: IModeService; - /** - * @internal - */ - extensionService?:IExtensionService; - /** - * @internal - */ - instantiationService?:IInstantiationService; - /** - * @internal - */ - messageService?:IMessageService; - /** - * @internal - */ - markerService?:IMarkerService; - /** - * @internal - */ - menuService?:IMenuService; - /** - * @internal - */ - editorService?:IEditorService; - /** - * @internal - */ - commandService?:ICommandService; - /** - * @internal - */ - openerService?:IOpenerService; - /** - * @internal - */ - contextKeyService?:IContextKeyService; - /** - * @internal - */ - keybindingService?:IKeybindingService; - /** - * @internal - */ - contextService?:IWorkspaceContextService; - /** - * @internal - */ - contextViewService?:IEditorContextViewService; - /** - * @internal - */ - contextMenuService?:IContextMenuService; - /** - * @internal - */ - telemetryService?:ITelemetryService; - /** - * @internal - */ - eventService?:IEventService; - /** - * @internal - */ - storageService?:IStorageService; - /** - * @internal - */ - configurationService?:IConfigurationService; - /** - * @internal - */ - progressService?:IProgressService; - /** - * @internal - */ - modelService?: IModelService; - /** - * @internal - */ - codeEditorService?: ICodeEditorService; - /** - * @internal - */ - editorWorkerService?: IEditorWorkerService; } -export interface IStaticServices { - configurationService: IConfigurationService; - compatWorkerService: ICompatWorkerService; - modeService: IModeService; - extensionService: IExtensionService; - markerService: IMarkerService; - menuService: IMenuService; - contextService: IWorkspaceContextService; - messageService: IMessageService; - telemetryService: ITelemetryService; - modelService: IModelService; - codeEditorService: ICodeEditorService; - editorWorkerService: IEditorWorkerService; - eventService: IEventService; - storageService: IStorageService; - commandService: ICommandService; - instantiationService: IInstantiationService; -} +export module StaticServices { -function shallowClone(obj:T): T { - let r:T = {}; - if (obj) { - let keys = Object.keys(obj); - for (let i = 0, len = keys.length; i < len; i++) { - let key = keys[i]; - r[key] = obj[key]; + const _serviceCollection = new ServiceCollection(); + + export class LazyStaticService { + private _serviceId: ServiceIdentifier; + private _factory: (overrides: IEditorOverrideServices) => T; + private _value: T; + + public get id() { return this._serviceId; } + + constructor(serviceId:ServiceIdentifier, factory:(overrides: IEditorOverrideServices) => T) { + this._serviceId = serviceId; + this._factory = factory; + this._value = null; } - } - return r; -} -export function ensureStaticPlatformServices(services: IEditorOverrideServices): IEditorOverrideServices { - services = shallowClone(services); - - var statics = getOrCreateStaticServices(services); - - let keys = Object.keys(statics); - for (let i = 0, len = keys.length; i < len; i++) { - let serviceId = keys[i]; - if (!services.hasOwnProperty(serviceId)) { - services[serviceId] = statics[serviceId]; + public get(overrides?: IEditorOverrideServices): T { + if (!this._value) { + if (overrides) { + this._value = overrides[this._serviceId.toString()]; + } + if (!this._value) { + this._value = this._factory(overrides); + } + if (!this._value) { + throw new Error('Service ' + this._serviceId + ' is missing!'); + } + _serviceCollection.set(this._serviceId, this._value); + } + return this._value; } } - return services; -} + let _all: LazyStaticService[] = []; -export function ensureDynamicPlatformServices(domElement:HTMLElement, services: IEditorOverrideServices): IDisposable[] { - let r:IDisposable[] = []; - - let contextKeyService:IContextKeyService; - if (typeof services.contextKeyService === 'undefined') { - contextKeyService = new ContextKeyService(services.configurationService); - r.push(contextKeyService); - services.contextKeyService = contextKeyService; - } else { - contextKeyService = services.contextKeyService; - } - if (typeof services.keybindingService === 'undefined') { - let keybindingService = new StandaloneKeybindingService(contextKeyService, services.commandService, services.messageService, domElement); - r.push(keybindingService); - services.keybindingService = keybindingService; + function define(serviceId:ServiceIdentifier, factory:(overrides: IEditorOverrideServices) => T): LazyStaticService { + let r = new LazyStaticService(serviceId, factory); + _all.push(r); + return r; } - let contextViewService:IEditorContextViewService; - if (typeof services.contextViewService === 'undefined') { - contextViewService = new ContextViewService(domElement, services.telemetryService, services.messageService); - r.push(contextViewService); - services.contextViewService = contextViewService; - } else { - contextViewService = services.contextViewService; + export function init(overrides: IEditorOverrideServices): [ServiceCollection, IInstantiationService] { + // Create a fresh service collection + let result = new ServiceCollection(); + + // Initialize the service collection with the overrides + for (let serviceId in overrides) { + if (overrides.hasOwnProperty(serviceId)) { + result.set(createDecorator(serviceId), overrides[serviceId]); + } + } + + // Make sure the same static services are present in all service collections + _all.forEach(service => result.set(service.id, service.get(overrides))); + + // Ensure the collection gets the correct instantiation service + let instantiationService = new InstantiationService(result, true); + result.set(IInstantiationService, instantiationService); + + return [result, instantiationService]; } - if (typeof services.contextMenuService === 'undefined') { - let contextMenuService = new ContextMenuService(domElement, services.telemetryService, services.messageService, contextViewService); - r.push(contextMenuService); - services.contextMenuService = contextMenuService; - } + export const instantiationService = define(IInstantiationService, () => new InstantiationService(_serviceCollection, true)); - return r; -} - -// The static services represents a map of services that once 1 editor has been created must be used for all subsequent editors -var staticServices: IStaticServices = null; -export function getOrCreateStaticServices(services?: IEditorOverrideServices): IStaticServices { - if (staticServices) { - return staticServices; - } - services = services || {}; - - let serviceCollection = new ServiceCollection(); - const instantiationService = new InstantiationService(serviceCollection, true); - - let contextService = services.contextService || new WorkspaceContextService({ + export const contextService = define(IWorkspaceContextService, () => new WorkspaceContextService({ resource: URI.from({ scheme: 'inmemory', authority: 'model', path: '/' }) - }); - serviceCollection.set(IWorkspaceContextService, contextService); + })); - let telemetryService = services.telemetryService || NullTelemetryService; - serviceCollection.set(ITelemetryService, telemetryService); + export const telemetryService = define(ITelemetryService, () => NullTelemetryService); - let eventService = services.eventService || new EventService(); - serviceCollection.set(IEventService, eventService); + export const eventService = define(IEventService, () => new EventService()); - let configurationService = services.configurationService || new SimpleConfigurationService(); - serviceCollection.set(IConfigurationService, configurationService); + export const configurationService = define(IConfigurationService, () => new SimpleConfigurationService()); - let messageService = services.messageService || new SimpleMessageService(); - serviceCollection.set(IMessageService, messageService); + export const messageService = define(IMessageService, () => new SimpleMessageService()); - let extensionService = services.extensionService || new SimpleExtensionService(); - serviceCollection.set(IExtensionService, extensionService); + export const extensionService = define(IExtensionService, () => new SimpleExtensionService()); - let commandService = services.commandService || new CommandService(instantiationService, extensionService); - serviceCollection.set(ICommandService, commandService); + export const markerService = define(IMarkerService, () => new MarkerService()); - let markerService = services.markerService || new MarkerService(); - serviceCollection.set(IMarkerService, markerService); + export const modeService = define(IModeService, (o) => new MainThreadModeServiceImpl(instantiationService.get(o), extensionService.get(o), configurationService.get(o))); - let modeService = services.modeService || new MainThreadModeServiceImpl(instantiationService, extensionService, configurationService); - serviceCollection.set(IModeService, modeService); + export const modelService = define(IModelService, (o) => new ModelServiceImpl(markerService.get(o), configurationService.get(o), messageService.get(o))); - let modelService = services.modelService || new ModelServiceImpl(markerService, configurationService, messageService); - serviceCollection.set(IModelService, modelService); + export const compatWorkerService = define(ICompatWorkerService, (o) => new MainThreadCompatWorkerService(modelService.get(o))); - let compatWorkerService = services.compatWorkerService || new MainThreadCompatWorkerService(modelService); - serviceCollection.set(ICompatWorkerService, compatWorkerService); + export const editorWorkerService = define(IEditorWorkerService, (o) => new EditorWorkerServiceImpl(modelService.get(o))); - let editorWorkerService = services.editorWorkerService || new EditorWorkerServiceImpl(modelService); - serviceCollection.set(IEditorWorkerService, editorWorkerService); + export const codeEditorService = define(ICodeEditorService, () => new CodeEditorServiceImpl()); - let codeEditorService = services.codeEditorService || new CodeEditorServiceImpl(); - serviceCollection.set(ICodeEditorService, codeEditorService); + export const progressService = define(IProgressService, () => new SimpleProgressService()); - let menuService = services.menuService || new MenuService(extensionService, commandService); - serviceCollection.set(IMenuService, menuService); - - staticServices = { - configurationService: configurationService, - extensionService: extensionService, - commandService: commandService, - compatWorkerService: compatWorkerService, - modeService: modeService, - markerService: markerService, - menuService: menuService, - contextService: contextService, - telemetryService: telemetryService, - messageService: messageService, - modelService: modelService, - codeEditorService: codeEditorService, - editorWorkerService: editorWorkerService, - eventService: eventService, - storageService: services.storageService || NullStorageService, - instantiationService: instantiationService - }; - - return staticServices; + export const storageService = define(IStorageService, () => NullStorageService); } +export class DynamicStandaloneServices extends Disposable { + + private _serviceCollection: ServiceCollection; + private _instantiationService: IInstantiationService; + + constructor(domElement:HTMLElement, overrides: IEditorOverrideServices) { + super(); + + const [_serviceCollection, _instantiationService] = StaticServices.init(overrides); + this._serviceCollection = _serviceCollection; + this._instantiationService = _instantiationService; + + const configurationService = this.get(IConfigurationService); + const extensionService = this.get(IExtensionService); + const messageService = this.get(IMessageService); + const telemetryService = this.get(ITelemetryService); + + let ensure = (serviceId:ServiceIdentifier, factory:() => T): T => { + let value:T = null; + if (overrides) { + value = overrides[serviceId.toString()]; + } + if (!value) { + value = factory(); + } + this._serviceCollection.set(serviceId, value); + return value; + }; + + let contextKeyService = ensure(IContextKeyService, () => this._register(new ContextKeyService(configurationService))); + + let commandService = ensure(ICommandService, () => new StandaloneCommandService(this._instantiationService, extensionService)); + + ensure(IKeybindingService, () => this._register(new StandaloneKeybindingService(contextKeyService, commandService, messageService, domElement))); + + let contextViewService = ensure(IContextViewService, () => this._register(new ContextViewService(domElement, telemetryService, messageService))); + + ensure(IContextMenuService, () => this._register(new ContextMenuService(domElement, telemetryService, messageService, contextViewService))); + + ensure(IMenuService, () => new MenuService(extensionService, commandService)); + } + + public get(serviceId:ServiceIdentifier): T { + let r = this._serviceCollection.get(serviceId); + if (!r) { + throw new Error('Missing service ' + serviceId); + } + return r; + } + + public set(serviceId:ServiceIdentifier, instance: T): void { + this._serviceCollection.set(serviceId, instance); + } + + public has(serviceId:ServiceIdentifier): boolean { + return this._serviceCollection.has(serviceId); + } +} diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index 249de085fb0..006a158b653 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -11,7 +11,6 @@ import * as timer from 'vs/base/common/timer'; import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import {StyleMutator} from 'vs/base/browser/styleMutator'; -import {IContextKey, IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import {ICommandService} from 'vs/platform/commands/common/commands'; import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -47,8 +46,6 @@ import {ViewLinesViewportData} from 'vs/editor/common/viewLayout/viewLinesViewpo import {IRenderingContext} from 'vs/editor/common/view/renderingContext'; import {IPointerHandlerHelper} from 'vs/editor/browser/controller/mouseHandler'; -import EditorContextKeys = editorCommon.EditorContextKeys; - export class View extends ViewEventHandler implements editorBrowser.IView, IDisposable { private eventDispatcher:ViewEventDispatcher; @@ -90,10 +87,7 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp private accumulatedModelEvents: EmitterEvent[]; private _renderAnimationFrame: IDisposable; - private _editorTextFocusContextKey: IContextKey; - constructor( - contextKeyService: IContextKeyService, commandService: ICommandService, configuration:Configuration, model:IViewModel, @@ -135,7 +129,7 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp (eventHandler:IViewEventHandler) => this.eventDispatcher.removeEventHandler(eventHandler) ); - this.createTextArea(contextKeyService); + this.createTextArea(); this.createViewParts(); // Keyboard handler @@ -172,10 +166,9 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp } } - private createTextArea(contextKeyService: IContextKeyService): void { + private createTextArea(): void { // Text Area (The focus will always be in the textarea when the cursor is blinking) this.textArea = document.createElement('textarea'); - this._editorTextFocusContextKey = EditorContextKeys.TextFocus.bindTo(contextKeyService); this.textArea.className = editorBrowser.ClassNames.TEXTAREA; this.textArea.setAttribute('wrap', 'off'); this.textArea.setAttribute('autocorrect', 'off'); @@ -479,10 +472,8 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp public onViewFocusChanged(isFocused:boolean): boolean { dom.toggleClass(this.domNode, 'focused', isFocused); if (isFocused) { - this._editorTextFocusContextKey.set(true); this.outgoingEventBus.emit(editorCommon.EventType.ViewFocusGained, {}); } else { - this._editorTextFocusContextKey.reset(); this.outgoingEventBus.emit(editorCommon.EventType.ViewFocusLost, {}); } return false; @@ -531,7 +522,6 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp this.viewParts = []; this.layoutProvider.dispose(); - this._editorTextFocusContextKey.reset(); } // --- begin Code Editor APIs diff --git a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts index 2e5bb1e972a..05e582adac3 100644 --- a/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts +++ b/src/vs/editor/browser/viewParts/currentLineHighlight/currentLineHighlight.ts @@ -16,6 +16,7 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { private _context:ViewContext; private _lineHeight:number; private _readOnly:boolean; + private _renderLineHighlight:boolean; private _layoutProvider:ILayoutProvider; private _selectionIsEmpty:boolean; private _primaryCursorIsInEditableRange:boolean; @@ -28,6 +29,7 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { this._context = context; this._lineHeight = this._context.configuration.editor.lineHeight; this._readOnly = this._context.configuration.editor.readOnly; + this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight; this._layoutProvider = layoutProvider; @@ -87,6 +89,9 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { if (e.readOnly) { this._readOnly = this._context.configuration.editor.readOnly; } + if (e.viewInfo.renderLineHighlight) { + this._renderLineHighlight = this._context.configuration.editor.viewInfo.renderLineHighlight; + } if (e.layoutInfo) { this._contentWidth = this._context.configuration.editor.layoutInfo.contentWidth; } @@ -129,6 +134,6 @@ export class CurrentLineHighlightOverlay extends DynamicViewOverlay { } private _shouldShowCurrentLine(): boolean { - return this._selectionIsEmpty && this._primaryCursorIsInEditableRange && !this._readOnly; + return this._renderLineHighlight && this._selectionIsEmpty && this._primaryCursorIsInEditableRange && !this._readOnly; } } diff --git a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts index 2e0e8fb674c..997ab474114 100644 --- a/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts +++ b/src/vs/editor/browser/viewParts/overviewRuler/overviewRuler.ts @@ -24,10 +24,6 @@ export class OverviewRuler extends ViewEventHandler implements IOverviewRuler { this._context.addEventHandler(this); } - public destroy(): void { - this.dispose(); - } - public dispose(): void { this._context.removeEventHandler(this); this._overviewRuler.dispose(); diff --git a/src/vs/editor/browser/viewParts/selections/selections.css b/src/vs/editor/browser/viewParts/selections/selections.css index 44701f7ab86..4c1c38ee310 100644 --- a/src/vs/editor/browser/viewParts/selections/selections.css +++ b/src/vs/editor/browser/viewParts/selections/selections.css @@ -21,9 +21,11 @@ .monaco-editor.hc-black .top-right-radius { border-top-right-radius: 0; } .monaco-editor.hc-black .bottom-right-radius { border-bottom-right-radius: 0; } -.monaco-editor.vs .view-overlays .selected-text { background: #E5EBF1; } -.monaco-editor.vs .view-overlays.focused .selected-text { background: #ADD6FF; } -.monaco-editor.vs-dark .view-overlays .selected-text { background: #3A3D41; } -.monaco-editor.vs-dark .view-overlays.focused .selected-text { background: #264F78; } -.monaco-editor.hc-black .view-overlays .selected-text { background: none; } -.monaco-editor.hc-black .view-overlays.focused .selected-text { background: white; border-radius: 0; } +.monaco-editor.vs .view-overlays .selected-text { background: #E5EBF1; } +.monaco-editor.vs .view-overlays.focused .selected-text { background: #ADD6FF; } +.monaco-editor.vs-dark .view-overlays .selected-text { background: #3A3D41; } +.monaco-editor.vs-dark .view-overlays.focused .selected-text { background: #264F78; } +.monaco-editor.hc-black .view-overlays .selected-text { background: none; } +.monaco-editor.hc-black .view-overlays.focused .selected-text { background: white; } +.monaco-editor.ie.hc-black .view-overlays.focused .selected-text { background: none; border: 2px solid #f38518; } +.monaco-editor.edge.hc-black .view-overlays.focused .selected-text { background: none; border: 2px solid #f38518; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css index 6a5cde5843b..964f54368a7 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.css @@ -59,33 +59,33 @@ @keyframes cursor-smooth { 0%, 20% { - opacity: 0; + opacity: 1; } 60%, 100% { - opacity: 1; + opacity: 0; } } @keyframes cursor-phase { 0%, 20% { - opacity: 0; + opacity: 1; } 90%, 100% { - opacity: 1; + opacity: 0; } } @keyframes cursor-expand { 0%, 20% { - transform: scaleY(0); + transform: scaleY(1); } 80%, 100% { - transform: scaleY(1); + transform: scaleY(0); } } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 57b1a6ab777..8adb5ce1c4d 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -18,11 +18,10 @@ import {CommonEditorConfiguration} from 'vs/editor/common/config/commonEditorCon import {Range} from 'vs/editor/common/core/range'; import {Selection} from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {Configuration} from 'vs/editor/browser/config/configuration'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; import {Colorizer} from 'vs/editor/browser/standalone/colorizer'; import {View} from 'vs/editor/browser/view/viewImpl'; import {Disposable, IDisposable} from 'vs/base/common/lifecycle'; @@ -30,7 +29,7 @@ import Event, {Emitter} from 'vs/base/common/event'; import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent'; import {InternalEditorAction} from 'vs/editor/common/editorAction'; -export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser.ICodeEditor { +export abstract class CodeEditorWidget extends CommonCodeEditor implements editorBrowser.ICodeEditor { public onMouseUp(listener: (e:editorBrowser.IEditorMouseEvent)=>void): IDisposable { return this.addListener2(editorCommon.EventType.MouseUp, listener); @@ -90,10 +89,8 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. let hasFocus = this._focusTracker.hasFocus(); if (hasFocus) { - this._editorFocusContextKey.set(true); this.emit(editorCommon.EventType.EditorFocus, {}); } else { - this._editorFocusContextKey.reset(); this.emit(editorCommon.EventType.EditorBlur, {}); } }); @@ -101,17 +98,18 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. this.contentWidgets = {}; this.overlayWidgets = {}; - let contributionDescriptors = [].concat(EditorBrowserRegistry.getEditorContributions()).concat(CommonEditorRegistry.getEditorContributions()); - for (let i = 0, len = contributionDescriptors.length; i < len; i++) { + let contributions = this._getContributions(); + for (let i = 0, len = contributions.length; i < len; i++) { + let ctor = contributions[i]; try { - let contribution = contributionDescriptors[i].createInstance(this._instantiationService, this); + let contribution = this._instantiationService.createInstance(ctor, this); this._contributions[contribution.getId()] = contribution; } catch (err) { onUnexpectedError(err); } } - CommonEditorRegistry.getEditorActions().forEach((action) => { + this._getActions().forEach((action) => { let internalAction = new InternalEditorAction(action, this, this._instantiationService, this._contextKeyService); this._actions[internalAction.id] = internalAction; }); @@ -119,6 +117,9 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. this._codeEditorService.addCodeEditor(this); } + protected abstract _getContributions(): editorBrowser.IEditorContributionCtor[]; + protected abstract _getActions(): EditorAction[]; + protected _createConfiguration(options:editorCommon.ICodeEditorWidgetCreationOptions): CommonEditorConfiguration { return new Configuration(options, this.domElement); } @@ -309,7 +310,7 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. } public hasWidgetFocus(): boolean { - return this._focusTracker.hasFocus(); + return this._focusTracker && this._focusTracker.hasFocus(); } public addContentWidget(widget: editorBrowser.IContentWidget): void { @@ -506,7 +507,6 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. protected _createView(): void { this._view = new View( - this._contextKeyService, this._commandService, this._configuration, this.viewModel, diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index c73fc66f728..a3d24a4fc21 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -381,8 +381,8 @@ export class DiffEditorWidget extends EventEmitter implements editorBrowser.IDif this._originalOverviewRuler.dispose(); this._modifiedOverviewRuler.dispose(); - this.originalEditor.destroy(); - this.modifiedEditor.destroy(); + this.originalEditor.dispose(); + this.modifiedEditor.dispose(); this._strategy.dispose(); diff --git a/src/vs/editor/browser/widget/media/editor.css b/src/vs/editor/browser/widget/media/editor.css index 586ffaa0185..b812108a658 100644 --- a/src/vs/editor/browser/widget/media/editor.css +++ b/src/vs/editor/browser/widget/media/editor.css @@ -22,6 +22,8 @@ overflow: visible; -webkit-text-size-adjust: 100%; -ms-high-contrast-adjust: none; + -webkit-font-feature-settings: "liga" off, "calt" off; + font-feature-settings: "liga" off, "calt" off; } .monaco-editor.enable-ligatures { -webkit-font-feature-settings: "liga" on, "calt" on; @@ -112,11 +114,11 @@ top: 0; } -/* -------------------- Highlight a line -------------------- */ +/* -------------------- Highlight a range -------------------- */ -.monaco-editor.vs .lineHighlight { background: rgba(253, 255, 0, 0.2); } -.monaco-editor.vs-dark .lineHighlight { background: rgba(243, 240, 245, 0.2); } -.monaco-editor.hc-black .lineHighlight { background: rgba(243, 240, 245, 0.2); } +.monaco-editor.vs .rangeHighlight { background: rgba(253, 255, 0, 0.2); } +.monaco-editor.vs-dark .rangeHighlight { background: rgba(255, 255, 255, 0.044); } +.monaco-editor.hc-black .rangeHighlight { border: 1px dotted #f38518; } /* -------------------- Squigglies -------------------- */ diff --git a/src/vs/editor/common/commands/shiftCommand.ts b/src/vs/editor/common/commands/shiftCommand.ts index 63e2e77aee6..ec65fed4d55 100644 --- a/src/vs/editor/common/commands/shiftCommand.ts +++ b/src/vs/editor/common/commands/shiftCommand.ts @@ -51,9 +51,9 @@ export class ShiftCommand implements ICommand { } public getEditOperations(model: ITokenizedModel, builder: IEditOperationBuilder): void { - let startLine = this._selection.startLineNumber, - endLine = this._selection.endLineNumber, - _SPACE = ' '.charCodeAt(0); + let startLine = this._selection.startLineNumber; + let endLine = this._selection.endLineNumber; + let _SPACE = ' '.charCodeAt(0); if (this._selection.endColumn === 1 && startLine !== endLine) { endLine = endLine - 1; diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 7cb38bc6054..75d7dc1e11a 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -6,7 +6,7 @@ import {onUnexpectedError} from 'vs/base/common/errors'; import {EventEmitter, IEventEmitter} from 'vs/base/common/eventEmitter'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; +import {Disposable, IDisposable, dispose} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {ServicesAccessor, IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; @@ -84,8 +84,8 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom protected id:number; - _lifetimeDispose: IDisposable[]; - _configuration:CommonEditorConfiguration; + protected _lifetimeDispose: IDisposable[]; + protected _configuration:CommonEditorConfiguration; protected _contributions:{ [key:string]:editorCommon.IEditorContribution; }; protected _actions:{ [key:string]:editorCommon.IEditorAction; }; @@ -107,13 +107,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom private _decorationTypeKeysToIds: {[decorationTypeKey:string]:string[]}; private _decorationTypeSubtypes: {[decorationTypeKey:string]:{ [subtype:string]:boolean}}; - private _editorIdContextKey: IContextKey; - protected _editorFocusContextKey: IContextKey; - private _editorTabMovesFocusKey: IContextKey; - private _editorReadonly: IContextKey; - private _hasMultipleSelectionsKey: IContextKey; - private _hasNonEmptySelectionKey: IContextKey; - private _langIdKey: IContextKey; constructor( domElement: IContextKeyServiceTarget, @@ -130,15 +123,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom // listeners that are kept during the whole editor lifetime this._lifetimeDispose = []; - this._contextKeyService = contextKeyService.createScoped(this.domElement); - this._editorIdContextKey = this._contextKeyService.createKey('editorId', this.getId()); - this._editorFocusContextKey = EditorContextKeys.Focus.bindTo(this._contextKeyService); - this._editorTabMovesFocusKey = EditorContextKeys.TabMovesFocus.bindTo(this._contextKeyService); - this._editorReadonly = EditorContextKeys.ReadOnly.bindTo(this._contextKeyService); - this._hasMultipleSelectionsKey = EditorContextKeys.HasMultipleSelections.bindTo(this._contextKeyService); - this._hasNonEmptySelectionKey = EditorContextKeys.HasNonEmptySelection.bindTo(this._contextKeyService); - this._langIdKey = EditorContextKeys.LanguageId.bindTo(this._contextKeyService); - this._lifetimeDispose.push(new EditorModeContext(this, this._contextKeyService)); this._decorationTypeKeysToIds = {}; this._decorationTypeSubtypes = {}; @@ -149,19 +133,14 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom } this._configuration = this._createConfiguration(options); - if (this._configuration.editor.tabFocusMode) { - this._editorTabMovesFocusKey.set(true); - } - this._editorReadonly.set(this._configuration.editor.readOnly); this._lifetimeDispose.push(this._configuration.onDidChange((e) => { - if (this._configuration.editor.tabFocusMode) { - this._editorTabMovesFocusKey.set(true); - } else { - this._editorTabMovesFocusKey.reset(); - } this.emit(editorCommon.EventType.ConfigurationChanged, e); })); + this._contextKeyService = contextKeyService.createScoped(this.domElement); + this._lifetimeDispose.push(new EditorContextKeysManager(this, this._contextKeyService)); + this._lifetimeDispose.push(new EditorModeContext(this, this._contextKeyService)); + this._instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this._contextKeyService])); this._attachModel(null); @@ -214,8 +193,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public updateOptions(newOptions:editorCommon.IEditorOptions): void { this._configuration.updateOptions(newOptions); - this._editorReadonly.set(this._configuration.editor.readOnly); - this._editorTabMovesFocusKey.set(this._configuration.editor.tabFocusMode); } public getConfiguration(): editorCommon.InternalEditorOptions { @@ -228,8 +205,8 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public getValue(options:{ preserveBOM:boolean; lineEnding:string; }=null): string { if (this.model) { - var preserveBOM:boolean = (options && options.preserveBOM) ? true : false; - var eolPreference = editorCommon.EndOfLinePreference.TextDefined; + let preserveBOM:boolean = (options && options.preserveBOM) ? true : false; + let eolPreference = editorCommon.EndOfLinePreference.TextDefined; if (options && options.lineEnding && options.lineEnding === '\n') { eolPreference = editorCommon.EndOfLinePreference.LF; } else if (options && options.lineEnding && options.lineEnding === '\r\n') { @@ -256,10 +233,10 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom return; } - var detachedModel = this._detachModel(); + let detachedModel = this._detachModel(); this._attachModel(model); - var e: editorCommon.IModelChangedEvent = { + let e: editorCommon.IModelChangedEvent = { oldModelUrl: detachedModel ? detachedModel.uri : null, newModelUrl: model ? model.uri : null }; @@ -315,9 +292,9 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom if (!Range.isIRange(range)) { throw new Error('Invalid arguments'); } - var validatedRange = this.model.validateRange(range); + let validatedRange = this.model.validateRange(range); - var revealRangeEvent: editorCommon.ICursorRevealRangeEvent = { + let revealRangeEvent: editorCommon.ICursorRevealRangeEvent = { range: validatedRange, viewRange: null, verticalType: verticalType, @@ -401,9 +378,9 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom if (!this.cursor) { return null; } - var selections = this.cursor.getSelections(); - var result:Selection[] = []; - for (var i = 0, len = selections.length; i < len; i++) { + let selections = this.cursor.getSelections(); + let result:Selection[] = []; + for (let i = 0, len = selections.length; i < len; i++) { result[i] = selections[i].clone(); } return result; @@ -414,8 +391,8 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public setSelection(selection:editorCommon.ISelection, reveal?:boolean, revealVerticalInCenter?:boolean, revealHorizontal?:boolean): void; public setSelection(editorSelection:Selection, reveal?:boolean, revealVerticalInCenter?:boolean, revealHorizontal?:boolean): void; public setSelection(something:any, reveal:boolean = false, revealVerticalInCenter:boolean = false, revealHorizontal:boolean = false): void { - var isSelection = Selection.isISelection(something); - var isRange = Range.isIRange(something); + let isSelection = Selection.isISelection(something); + let isRange = Range.isIRange(something); if (!isSelection && !isRange) { throw new Error('Invalid arguments'); @@ -425,7 +402,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom this._setSelectionImpl(something, reveal, revealVerticalInCenter, revealHorizontal); } else if (isRange) { // act as if it was an IRange - var selection:editorCommon.ISelection = { + let selection:editorCommon.ISelection = { selectionStartLineNumber: something.startLineNumber, selectionStartColumn: something.startColumn, positionLineNumber: something.endLineNumber, @@ -439,7 +416,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom if (!this.cursor) { return; } - var selection = new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn); + let selection = new Selection(sel.selectionStartLineNumber, sel.selectionStartColumn, sel.positionLineNumber, sel.positionColumn); this.cursor.setSelections('api', [selection]); if (reveal) { this.revealRange(selection, revealVerticalInCenter, revealHorizontal); @@ -492,7 +469,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom if (!ranges || ranges.length === 0) { throw new Error('Invalid arguments'); } - for (var i = 0, len = ranges.length; i < len; i++) { + for (let i = 0, len = ranges.length; i < len; i++) { if (!Selection.isISelection(ranges[i])) { throw new Error('Invalid arguments'); } @@ -567,7 +544,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public trigger(source:string, handlerId:string, payload:any): void { payload = payload || {}; - var candidate = this.getAction(handlerId); + let candidate = this.getAction(handlerId); if (candidate !== null) { TPromise.as(candidate.run()).done(null, onUnexpectedError); } else { @@ -728,7 +705,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom return this._configuration.editor.layoutInfo; } - _attachModel(model:editorCommon.IModel): void { + protected _attachModel(model:editorCommon.IModel): void { this.model = model ? model : null; this.listenersToRemove = []; this.viewModel = null; @@ -736,18 +713,17 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom if (this.model) { this.domElement.setAttribute('data-mode-id', this.model.getMode().getId()); - this._langIdKey.set(this.model.getMode().getId()); this._configuration.setIsDominatedByLongLines(this.model.isDominatedByLongLines()); this.model.onBeforeAttached(); - var hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( + let hardWrappingLineMapperFactory = new CharacterHardWrappingLineMapperFactory( this._configuration.editor.wrappingInfo.wordWrapBreakBeforeCharacters, this._configuration.editor.wrappingInfo.wordWrapBreakAfterCharacters, this._configuration.editor.wrappingInfo.wordWrapBreakObtrusiveCharacters ); - var linesCollection = new SplitLinesCollection( + let linesCollection = new SplitLinesCollection( this.model, hardWrappingLineMapperFactory, this.model.getOptions().tabSize, @@ -764,7 +740,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom () => this.getCenteredRangeInViewport() ); - var viewModelHelper:IViewModelHelper = { + let viewModelHelper:IViewModelHelper = { viewModel: this.viewModel, getCurrentCompletelyVisibleViewLinesRangeInViewport: () => { return this.viewModel.convertModelRangeToViewRange(this.getCompletelyVisibleLinesRangeInViewport()); @@ -808,9 +784,9 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom this._createView(); this.listenersToRemove.push(this._getViewInternalEventBus().addBulkListener2((events) => { - for (var i = 0, len = events.length; i < len; i++) { - var eventType = events[i].getType(); - var e = events[i].getData(); + for (let i = 0, len = events.length; i < len; i++) { + let eventType = events[i].getType(); + let e = events[i].getData(); switch (eventType) { case editorCommon.EventType.ViewFocusGained: @@ -860,15 +836,15 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom break; default: -// console.warn("Unhandled view event: ", e); + // console.warn("Unhandled view event: ", e); } } })); this.listenersToRemove.push(this.model.addBulkListener((events) => { - for (var i = 0, len = events.length; i < len; i++) { - var eventType = events[i].getType(); - var e = events[i].getData(); + for (let i = 0, len = events.length; i < len; i++) { + let eventType = events[i].getType(); + let e = events[i].getData(); switch (eventType) { case editorCommon.EventType.ModelDecorationsChanged: @@ -877,7 +853,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom case editorCommon.EventType.ModelModeChanged: this.domElement.setAttribute('data-mode-id', this.model.getMode().getId()); - this._langIdKey.set(this.model.getMode().getId()); this.emit(editorCommon.EventType.ModelModeChanged, e); break; @@ -903,60 +878,27 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom break; default: -// console.warn("Unhandled model event: ", e); + // console.warn("Unhandled model event: ", e); } } })); - var _hasNonEmptySelection = (e: editorCommon.ICursorSelectionChangedEvent) => { - var allSelections = [e.selection].concat(e.secondarySelections); - return allSelections.some(s => !s.isEmpty()); - }; - this.listenersToRemove.push(this.cursor.addBulkListener2((events) => { - var updateHasMultipleCursors = false, - hasMultipleCursors = false, - updateHasNonEmptySelection = false, - hasNonEmptySelection = false; - - for (var i = 0, len = events.length; i < len; i++) { - var eventType = events[i].getType(); - var e = events[i].getData(); + for (let i = 0, len = events.length; i < len; i++) { + let eventType = events[i].getType(); + let e = events[i].getData(); switch (eventType) { case editorCommon.EventType.CursorPositionChanged: - var cursorPositionChangedEvent = e; - updateHasMultipleCursors = true; - hasMultipleCursors = (cursorPositionChangedEvent.secondaryPositions.length > 0); this.emit(editorCommon.EventType.CursorPositionChanged, e); break; case editorCommon.EventType.CursorSelectionChanged: - var cursorSelectionChangedEvent = e; - updateHasMultipleCursors = true; - hasMultipleCursors = (cursorSelectionChangedEvent.secondarySelections.length > 0); - updateHasNonEmptySelection = true; - hasNonEmptySelection = _hasNonEmptySelection(cursorSelectionChangedEvent); this.emit(editorCommon.EventType.CursorSelectionChanged, e); break; default: -// console.warn("Unhandled cursor event: ", e); - } - } - - if (updateHasMultipleCursors) { - if (hasMultipleCursors) { - this._hasMultipleSelectionsKey.set(true); - } else { - this._hasMultipleSelectionsKey.reset(); - } - } - if (updateHasNonEmptySelection) { - if (hasNonEmptySelection) { - this._hasNonEmptySelectionKey.set(true); - } else { - this._hasNonEmptySelectionKey.reset(); + // console.warn("Unhandled cursor event: ", e); } } })); @@ -971,7 +913,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom protected abstract _getViewInternalEventBus(): IEventEmitter; - _postDetachModelCleanup(detachedModel:editorCommon.IModel): void { + protected _postDetachModelCleanup(detachedModel:editorCommon.IModel): void { if (detachedModel) { this._decorationTypeKeysToIds = {}; if (this._decorationTypeSubtypes) { @@ -1006,7 +948,7 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom this.viewModel = null; } - var result = this.model; + let result = this.model; this.model = null; this.domElement.removeAttribute('data-mode-id'); @@ -1017,4 +959,68 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom protected abstract _registerDecorationType(key:string, options: editorCommon.IDecorationRenderOptions, parentTypeKey?: string): void; protected abstract _removeDecorationType(key:string): void; protected abstract _resolveDecorationOptions(typeKey:string, writable: boolean): editorCommon.IModelDecorationOptions; -} \ No newline at end of file +} + +class EditorContextKeysManager extends Disposable { + + private _editor:CommonCodeEditor; + + private _editorId: IContextKey; + private _editorFocus: IContextKey; + private _editorTextFocus: IContextKey; + private _editorTabMovesFocus: IContextKey; + private _editorReadonly: IContextKey; + private _hasMultipleSelections: IContextKey; + private _hasNonEmptySelection: IContextKey; + + constructor( + editor:CommonCodeEditor, + contextKeyService: IContextKeyService + ) { + super(); + + this._editor = editor; + + this._editorId = contextKeyService.createKey('editorId', editor.getId()); + this._editorFocus = EditorContextKeys.Focus.bindTo(contextKeyService); + this._editorTextFocus = EditorContextKeys.TextFocus.bindTo(contextKeyService); + this._editorTabMovesFocus = EditorContextKeys.TabMovesFocus.bindTo(contextKeyService); + this._editorReadonly = EditorContextKeys.ReadOnly.bindTo(contextKeyService); + this._hasMultipleSelections = EditorContextKeys.HasMultipleSelections.bindTo(contextKeyService); + this._hasNonEmptySelection = EditorContextKeys.HasNonEmptySelection.bindTo(contextKeyService); + + this._register(this._editor.onDidChangeConfiguration(() => this._updateFromConfig())); + this._register(this._editor.onDidChangeCursorSelection(() => this._updateFromSelection())); + this._register(this._editor.onDidFocusEditor(() => this._updateFromFocus())); + this._register(this._editor.onDidBlurEditor(() => this._updateFromFocus())); + this._register(this._editor.onDidFocusEditorText(() => this._updateFromFocus())); + this._register(this._editor.onDidBlurEditorText(() => this._updateFromFocus())); + + this._updateFromConfig(); + this._updateFromSelection(); + this._updateFromFocus(); + } + + private _updateFromConfig(): void { + let config = this._editor.getConfiguration(); + + this._editorTabMovesFocus.set(config.tabFocusMode); + this._editorReadonly.set(config.readOnly); + } + + private _updateFromSelection(): void { + let selections = this._editor.getSelections(); + if (!selections) { + this._hasMultipleSelections.reset(); + this._hasNonEmptySelection.reset(); + } else { + this._hasMultipleSelections.set(selections.length > 1); + this._hasNonEmptySelection.set(selections.some(s => !s.isEmpty())); + } + } + + private _updateFromFocus(): void { + this._editorFocus.set(this._editor.hasWidgetFocus()); + this._editorTextFocus.set(this._editor.isFocused()); + } +} diff --git a/src/vs/editor/common/config/commonEditorConfig.ts b/src/vs/editor/common/config/commonEditorConfig.ts index 4d66ca9959a..6151b38a212 100644 --- a/src/vs/editor/common/config/commonEditorConfig.ts +++ b/src/vs/editor/common/config/commonEditorConfig.ts @@ -149,6 +149,7 @@ class InternalEditorOptionsHelper { ): editorCommon.InternalEditorOptions { let wrappingColumn = toInteger(opts.wrappingColumn, -1); + let wordWrap = toBoolean(opts.wordWrap); let stopRenderingLineAfter:number; if (typeof opts.stopRenderingLineAfter !== 'undefined') { @@ -197,6 +198,12 @@ class InternalEditorOptionsHelper { isViewportWrapping: true, wrappingColumn: Math.max(1, Math.floor((layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth) / fontInfo.typicalHalfwidthCharacterWidth)) }; + } else if (wrappingColumn > 0 && wordWrap === true) { + // Enable smart viewport wrapping + bareWrappingInfo = { + isViewportWrapping: true, + wrappingColumn: Math.min(wrappingColumn, Math.floor((layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth) / fontInfo.typicalHalfwidthCharacterWidth)) + }; } else if (wrappingColumn > 0) { // Wrapping is enabled bareWrappingInfo = { @@ -247,6 +254,7 @@ class InternalEditorOptionsHelper { renderWhitespace: toBoolean(opts.renderWhitespace), renderControlCharacters: toBoolean(opts.renderControlCharacters), renderIndentGuides: toBoolean(opts.renderIndentGuides), + renderLineHighlight: toBoolean(opts.renderLineHighlight), scrollbar: scrollbar, }); @@ -584,13 +592,14 @@ let editorConfiguration:IConfigurationNode = { }, 'editor.fontWeight' : { 'type': 'string', + 'enum': ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'], 'default': DefaultConfig.editor.fontWeight, 'description': nls.localize('fontWeight', "Controls the font weight.") }, 'editor.fontSize' : { 'type': 'number', 'default': DefaultConfig.editor.fontSize, - 'description': nls.localize('fontSize', "Controls the font size.") + 'description': nls.localize('fontSize', "Controls the font size in pixels.") }, 'editor.lineHeight' : { 'type': 'number', @@ -654,6 +663,11 @@ let editorConfiguration:IConfigurationNode = { 'minimum': -1, 'description': nls.localize('wrappingColumn', "Controls after how many characters the editor will wrap to the next line. Setting this to 0 turns on viewport width wrapping (word wrapping). Setting this to -1 forces the editor to never wrap.") }, + 'editor.wordWrap' : { + 'type': 'boolean', + 'default': DefaultConfig.editor.wordWrap, + 'description': nls.localize('wordWrap', "Controls if lines should wrap. The lines will wrap at min(editor.wrappingColumn, viewportWidthInColumns).") + }, 'editor.wrappingIndent' : { 'type': 'string', 'enum': ['none', 'same', 'indent'], @@ -769,6 +783,11 @@ let editorConfiguration:IConfigurationNode = { default: DefaultConfig.editor.renderIndentGuides, description: nls.localize('renderIndentGuides', "Controls whether the editor should render indent guides") }, + 'editor.renderLineHighlight': { + 'type': 'boolean', + default: DefaultConfig.editor.renderLineHighlight, + description: nls.localize('renderLineHighlight', "Controls whether the editor should render the current line highlight") + }, 'editor.codeLens' : { 'type': 'boolean', 'default': DefaultConfig.editor.codeLens, @@ -815,4 +834,4 @@ if (platform.isLinux) { }; } -configurationRegistry.registerConfiguration(editorConfiguration); \ No newline at end of file +configurationRegistry.registerConfiguration(editorConfiguration); diff --git a/src/vs/editor/common/config/config.ts b/src/vs/editor/common/config/config.ts index e06267c18df..ad40f48b688 100644 --- a/src/vs/editor/common/config/config.ts +++ b/src/vs/editor/common/config/config.ts @@ -97,7 +97,10 @@ export abstract class EditorCommand extends Command { } protected runEditorCommand(accessor:ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: any): void { - this._callback(controllerGetter(editor)); + let controller = controllerGetter(editor); + if (controller) { + this._callback(controllerGetter(editor)); + } } }; } @@ -155,8 +158,10 @@ function getActiveEditor(accessor: ServicesAccessor): editorCommon.ICommonCodeEd // Substitute for (editor instanceof ICodeEditor) if (editor && typeof editor.getEditorType === 'function') { - let codeEditor = editor; - return codeEditor; + if (editor.getEditorType() === editorCommon.EditorType.ICodeEditor) { + return editor; + } + return (editor).getModifiedEditor(); } } diff --git a/src/vs/editor/common/config/defaultConfig.ts b/src/vs/editor/common/config/defaultConfig.ts index 2e8b9c5a166..e915e7bb177 100644 --- a/src/vs/editor/common/config/defaultConfig.ts +++ b/src/vs/editor/common/config/defaultConfig.ts @@ -66,6 +66,7 @@ class ConfigClass implements IConfiguration { scrollBeyondLastLine: true, automaticLayout: false, wrappingColumn: 300, + wordWrap: false, wrappingIndent: 'same', wordWrapBreakBeforeCharacters: '([{‘“〈《「『【〔([{「£¥$£¥++', wordWrapBreakAfterCharacters: ' \t})]?|&,;¢°′″‰℃、。。、¢,.:;?!%・・ゝゞヽヾーァィゥェォッャュョヮヵヶぁぃぅぇぉっゃゅょゎゕゖㇰㇱㇲㇳㇴㇵㇶㇷㇸㇹㇺㇻㇼㇽㇾㇿ々〻ァィゥェォャュョッー’”〉》」』】〕)]}」', @@ -93,6 +94,7 @@ class ConfigClass implements IConfiguration { renderWhitespace: false, renderControlCharacters: false, renderIndentGuides: false, + renderLineHighlight: true, useTabStops: true, fontFamily: ( diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index d19ec0de937..6b0d36f477e 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -1426,8 +1426,7 @@ export class Cursor extends EventEmitter { private _replacePreviousChar(ctx: IMultipleCursorOperationContext): boolean { let text = ctx.eventData.text; let replaceCharCnt = ctx.eventData.replaceCharCnt; - return this._invokeForAll(ctx,(cursorIndex, oneCursor, oneCtx) => OneCursorOp.replacePreviousChar(oneCursor, text, replaceCharCnt, oneCtx)); - + return this._invokeForAll(ctx,(cursorIndex, oneCursor, oneCtx) => OneCursorOp.replacePreviousChar(oneCursor, text, replaceCharCnt, oneCtx), false, false); } private _tab(ctx: IMultipleCursorOperationContext): boolean { @@ -1496,31 +1495,40 @@ export class Cursor extends EventEmitter { } private _scrollUpOrDown(editorScrollArg: editorCommon.EditorScrollArguments, ctx: IMultipleCursorOperationContext): boolean { - let up = editorScrollArg.to === editorCommon.EditorScrollDirection.Up; - let cursor: OneCursor = this.cursors.getAll()[0]; - - if (editorCommon.EditorScrollByUnit.Line === editorScrollArg.by) { - let range = up ? cursor.getRangeToRevealModelLinesBeforeViewPortTop(editorScrollArg.value) : cursor.getRangeToRevealModelLinesAfterViewPortBottom(editorScrollArg.value); - this.emitCursorRevealRange(range, null, up ? editorCommon.VerticalRevealType.Top : editorCommon.VerticalRevealType.Bottom, false, true); + if (this._scrollByReveal(editorScrollArg, ctx)) { return true; } - - let noOfLines = 1; + let up = editorScrollArg.to === editorCommon.EditorScrollDirection.Up; + let cursor: OneCursor = this.cursors.getAll()[0]; + let noOfLines = editorScrollArg.value || 1; switch (editorScrollArg.by) { - case editorCommon.EditorScrollByUnit.WrappedLine: - noOfLines = editorScrollArg.value; - break; case editorCommon.EditorScrollByUnit.Page: - noOfLines = cursor.getPageSize() * editorScrollArg.value; + noOfLines = cursor.getPageSize() * noOfLines; break; case editorCommon.EditorScrollByUnit.HalfPage: - noOfLines = Math.round(cursor.getPageSize() / 2) * editorScrollArg.value; + noOfLines = Math.round(cursor.getPageSize() / 2) * noOfLines; break; } this.emitCursorScrollRequest((up ? -1 : 1) * noOfLines, !!editorScrollArg.revealCursor); return true; } + private _scrollByReveal(editorScrollArg: editorCommon.EditorScrollArguments, ctx: IMultipleCursorOperationContext): boolean { + let up = editorScrollArg.to === editorCommon.EditorScrollDirection.Up; + let cursor: OneCursor = this.cursors.getAll()[0]; + if (editorCommon.EditorScrollByUnit.Line !== editorScrollArg.by) { + // Scroll by reveal is done only when unit is line. + return false; + } + if (!up && cursor.isLastLineVisibleInViewPort()) { + // Scroll by reveal is not done if last line is visible and scrolling down. + return false; + } + let range = up ? cursor.getRangeToRevealModelLinesBeforeViewPortTop(editorScrollArg.value) : cursor.getRangeToRevealModelLinesAfterViewPortBottom(editorScrollArg.value); + this.emitCursorRevealRange(range, null, up ? editorCommon.VerticalRevealType.Top : editorCommon.VerticalRevealType.Bottom, false, true); + return true; + } + private _scrollUp(isPaged: boolean, ctx: IMultipleCursorOperationContext): boolean { ctx.eventData = { to: editorCommon.EditorScrollDirection.Up, value: 1 }; ctx.eventData.by = isPaged ? editorCommon.EditorScrollByUnit.Page : editorCommon.EditorScrollByUnit.WrappedLine; diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index 82606589ccc..c84d9d336b6 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -599,12 +599,15 @@ export class OneCursor { } // -- view + public isLastLineVisibleInViewPort(): boolean { + return this.viewModelHelper.viewModel.getLineCount() <= this.getCompletelyVisibleViewLinesRangeInViewport().getEndPosition().lineNumber; + } public getCompletelyVisibleViewLinesRangeInViewport(): Range { return this.viewModelHelper.getCurrentCompletelyVisibleViewLinesRangeInViewport(); } public getRevealViewLinesRangeInViewport(): Range { let visibleRange = this.getCompletelyVisibleViewLinesRangeInViewport().cloneRange(); - if (visibleRange.endLineNumber > visibleRange.startLineNumber) { + if (!this.isLastLineVisibleInViewPort() && visibleRange.endLineNumber > visibleRange.startLineNumber) { visibleRange.endLineNumber = visibleRange.endLineNumber - 1; visibleRange.endColumn = this.viewModelHelper.viewModel.getLineLastNonWhitespaceColumn(visibleRange.endLineNumber); } @@ -1367,7 +1370,7 @@ export class OneCursorOp { return false; } - return this._enter(cursor, false, ctx); + return this._enter(cursor, false, ctx, cursor.getPosition(), cursor.getSelection()); } public static lineInsertBefore(cursor:OneCursor, ctx: IOneCursorOperationContext): boolean { @@ -1391,19 +1394,13 @@ export class OneCursorOp { } public static lineBreakInsert(cursor:OneCursor, ctx: IOneCursorOperationContext): boolean { - return this._enter(cursor, true, ctx); + return this._enter(cursor, true, ctx, cursor.getPosition(), cursor.getSelection()); } - private static _enter(cursor:OneCursor, keepPosition: boolean, ctx: IOneCursorOperationContext, position?: Position, range?: Range): boolean { - if (typeof position === 'undefined') { - position = cursor.getPosition(); - } - if (typeof range === 'undefined') { - range = cursor.getSelection(); - } + private static _enter(cursor:OneCursor, keepPosition: boolean, ctx: IOneCursorOperationContext, position: Position, range: Range): boolean { ctx.shouldPushStackElementBefore = true; - let r = LanguageConfigurationRegistry.getEnterActionAtPosition(cursor.model, position.lineNumber, position.column); + let r = LanguageConfigurationRegistry.getEnterActionAtPosition(cursor.model, range.startLineNumber, range.startColumn); let enterAction = r.enterAction; let indentation = r.indentation; @@ -1530,22 +1527,16 @@ export class OneCursorOp { return false; } - let selectionContainsOnlyWhitespace = true, - lineNumber:number, - startIndex:number, - endIndex:number, - charIndex:number, - charCode:number, - lineText:string, - _tab = '\t'.charCodeAt(0), - _space = ' '.charCodeAt(0); + let selectionContainsOnlyWhitespace = true; + let _tab = '\t'.charCodeAt(0); + let _space = ' '.charCodeAt(0); - for (lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) { - lineText = cursor.model.getLineContent(lineNumber); - startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0); - endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length); - for (charIndex = startIndex; charIndex < endIndex; charIndex++) { - charCode = lineText.charCodeAt(charIndex); + for (let lineNumber = selection.startLineNumber; lineNumber <= selection.endLineNumber; lineNumber++) { + let lineText = cursor.model.getLineContent(lineNumber); + let startIndex = (lineNumber === selection.startLineNumber ? selection.startColumn - 1 : 0); + let endIndex = (lineNumber === selection.endLineNumber ? selection.endColumn - 1 : lineText.length); + for (let charIndex = startIndex; charIndex < endIndex; charIndex++) { + let charCode = lineText.charCodeAt(charIndex); if (charCode !== _tab && charCode !== _space) { selectionContainsOnlyWhitespace = false; diff --git a/src/vs/editor/common/controller/textAreaHandler.ts b/src/vs/editor/common/controller/textAreaHandler.ts index ee8ad3804c0..90c7dcdb48f 100644 --- a/src/vs/editor/common/controller/textAreaHandler.ts +++ b/src/vs/editor/common/controller/textAreaHandler.ts @@ -117,11 +117,8 @@ export class TextAreaHandler extends Disposable { this.textareaIsShownAtCursor = true; // In IE we cannot set .value when handling 'compositionstart' because the entire composition will get canceled. - let shouldEmptyTextArea = true; - if (shouldEmptyTextArea) { - if (!this.Browser.isEdgeOrIE) { - this.setTextAreaState('compositionstart', this.textAreaState.toEmpty()); - } + if (!this.Browser.isEdgeOrIE) { + this.setTextAreaState('compositionstart', this.textAreaState.toEmpty()); } this._onCompositionStart.fire({ diff --git a/src/vs/editor/common/core/modeTransition.ts b/src/vs/editor/common/core/modeTransition.ts index 77cfa4b45b6..5c54003fd8a 100644 --- a/src/vs/editor/common/core/modeTransition.ts +++ b/src/vs/editor/common/core/modeTransition.ts @@ -4,32 +4,20 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IMode, IModeTransition} from 'vs/editor/common/modes'; import {Arrays} from 'vs/editor/common/core/arrays'; export class ModeTransition { _modeTransitionBrand: void; public startIndex:number; - public mode:IMode; public modeId: string; - constructor(startIndex:number, mode:IMode) { + constructor(startIndex:number, modeId:string) { this.startIndex = startIndex|0; - this.mode = mode; - this.modeId = mode.getId(); + this.modeId = modeId; } public static findIndexInSegmentsArray(arr:ModeTransition[], desiredIndex: number): number { return Arrays.findIndexInSegmentsArray(arr, desiredIndex); } - - public static create(modeTransitions:IModeTransition[]): ModeTransition[] { - let result:ModeTransition[] = []; - for (let i = 0, len = modeTransitions.length; i < len; i++) { - let modeTransition = modeTransitions[i]; - result.push(new ModeTransition(modeTransition.startIndex, modeTransition.mode)); - } - return result; - } } diff --git a/src/vs/editor/common/model/lineToken.ts b/src/vs/editor/common/core/token.ts similarity index 74% rename from src/vs/editor/common/model/lineToken.ts rename to src/vs/editor/common/core/token.ts index f93127305dc..dcedd242027 100644 --- a/src/vs/editor/common/model/lineToken.ts +++ b/src/vs/editor/common/core/token.ts @@ -6,11 +6,8 @@ import {Arrays} from 'vs/editor/common/core/arrays'; -/** - * A token on a line. - */ -export class LineToken { - public _lineTokenBrand: void; +export class Token { + _tokenBrand: void; public startIndex:number; public type:string; @@ -20,18 +17,22 @@ export class LineToken { this.type = type; } - public equals(other:LineToken): boolean { + public toString(): string { + return '(' + this.startIndex + ', ' + this.type + ')'; + } + + public equals(other:Token): boolean { return ( this.startIndex === other.startIndex && this.type === other.type ); } - public static findIndexInSegmentsArray(arr:LineToken[], desiredIndex: number): number { + public static findIndexInSegmentsArray(arr:Token[], desiredIndex: number): number { return Arrays.findIndexInSegmentsArray(arr, desiredIndex); } - public static equalsArray(a:LineToken[], b:LineToken[]): boolean { + public static equalsArray(a:Token[], b:Token[]): boolean { let aLen = a.length; let bLen = b.length; if (aLen !== bLen) { @@ -44,4 +45,4 @@ export class LineToken { } return true; } -} \ No newline at end of file +} diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index ad41a4c615d..e373c4d0178 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -4,13 +4,13 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {IEventEmitter, BulkListenerCallback} from 'vs/base/common/eventEmitter'; +import {BulkListenerCallback} from 'vs/base/common/eventEmitter'; import {MarkedString} from 'vs/base/common/htmlContent'; import * as types from 'vs/base/common/types'; import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; -import {ServicesAccessor, IInstantiationService, IConstructorSignature1, IConstructorSignature2} from 'vs/platform/instantiation/common/instantiation'; -import {ILineContext, IMode, IToken} from 'vs/editor/common/modes'; +import {ServicesAccessor, IConstructorSignature1, IConstructorSignature2} from 'vs/platform/instantiation/common/instantiation'; +import {ILineContext, IMode} from 'vs/editor/common/modes'; import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; import {ScrollbarVisibility} from 'vs/base/common/scrollable'; import {IDisposable} from 'vs/base/common/lifecycle'; @@ -18,6 +18,7 @@ import {Position} from 'vs/editor/common/core/position'; import {Range} from 'vs/editor/common/core/range'; import {Selection} from 'vs/editor/common/core/selection'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; import {IndentRange} from 'vs/editor/common/model/indentRanges'; import {ICommandHandlerDescription} from 'vs/platform/commands/common/commands'; import {ContextKeyExpr, RawContextKey} from 'vs/platform/contextkey/common/contextkey'; @@ -113,7 +114,7 @@ export interface IEditorScrollbarOptions { horizontal?:string; /** * Cast horizontal and vertical shadows when the content is scrolled. - * Defaults to false. + * Defaults to true. */ useShadows?:boolean; /** @@ -309,6 +310,12 @@ export interface IEditorOptions { * Defaults to 300. */ wrappingColumn?:number; + /** + * Control the alternate style of viewport wrapping. + * When set to true viewport wrapping is used only when the window width is less than the number of columns specified in the wrappingColumn property. Has no effect if wrappingColumn is not a positive number. + * Defaults to false. + */ + wordWrap?:boolean; /** * Control indentation of wrapped lines. Can be: 'none', 'same' or 'indent'. * Defaults to 'none'. @@ -434,9 +441,14 @@ export interface IEditorOptions { renderControlCharacters?: boolean; /** * Enable rendering of indent guides. - * Defaults to true. + * Defaults to false. */ renderIndentGuides?: boolean; + /** + * Enable rendering of current line highlight. + * Defaults to true. + */ + renderLineHighlight?: boolean; /** * Inserting and deleting whitespace follows tab stops. */ @@ -635,6 +647,7 @@ export class InternalEditorViewOptions { renderWhitespace: boolean; renderControlCharacters: boolean; renderIndentGuides: boolean; + renderLineHighlight: boolean; scrollbar:InternalEditorScrollbarOptions; /** @@ -662,6 +675,7 @@ export class InternalEditorViewOptions { renderWhitespace: boolean; renderControlCharacters: boolean; renderIndentGuides: boolean; + renderLineHighlight: boolean; scrollbar:InternalEditorScrollbarOptions; }) { this.theme = String(source.theme); @@ -685,6 +699,7 @@ export class InternalEditorViewOptions { this.renderWhitespace = Boolean(source.renderWhitespace); this.renderControlCharacters = Boolean(source.renderControlCharacters); this.renderIndentGuides = Boolean(source.renderIndentGuides); + this.renderLineHighlight = Boolean(source.renderLineHighlight); this.scrollbar = source.scrollbar.clone(); } @@ -742,6 +757,7 @@ export class InternalEditorViewOptions { && this.renderWhitespace === other.renderWhitespace && this.renderControlCharacters === other.renderControlCharacters && this.renderIndentGuides === other.renderIndentGuides + && this.renderLineHighlight === other.renderLineHighlight && this.scrollbar.equals(other.scrollbar) ); } @@ -772,6 +788,7 @@ export class InternalEditorViewOptions { renderWhitespace: this.renderWhitespace !== newOpts.renderWhitespace, renderControlCharacters: this.renderControlCharacters !== newOpts.renderControlCharacters, renderIndentGuides: this.renderIndentGuides !== newOpts.renderIndentGuides, + renderLineHighlight: this.renderLineHighlight !== newOpts.renderLineHighlight, scrollbar: (!this.scrollbar.equals(newOpts.scrollbar)), }; } @@ -806,6 +823,7 @@ export interface IViewConfigurationChangedEvent { renderWhitespace: boolean; renderControlCharacters: boolean; renderIndentGuides: boolean; + renderLineHighlight: boolean; scrollbar: boolean; } @@ -1236,7 +1254,7 @@ export interface IWordRange { * @internal */ export interface ITokenInfo { - token: IToken; + token: Token; lineNumber: number; startColumn: number; endColumn: number; @@ -1709,6 +1727,49 @@ export interface ITextModel { * @internal */ isTooLargeForHavingARichMode(): boolean; + + /** + * Search the model. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchOnlyEditableRange Limit the searching to only search inside the editable range of the model. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @param limitResultCount Limit the number of results + * @return The ranges where the matches are. It is empty if not matches have been found. + */ + findMatches(searchString:string, searchOnlyEditableRange:boolean, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount?:number): Range[]; + /** + * Search the model. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchScope Limit the searching to only search inside this range. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @param limitResultCount Limit the number of results + * @return The ranges where the matches are. It is empty if no matches have been found. + */ + findMatches(searchString:string, searchScope:IRange, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount?:number): Range[]; + /** + * Search the model for the next match. Loops to the beginning of the model if needed. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchStart Start the searching at the specified position. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @return The range where the next match is. It is null if no next match has been found. + */ + findNextMatch(searchString:string, searchStart:IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range; + /** + * Search the model for the previous match. Loops to the end of the model if needed. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchStart Start the searching at the specified position. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @return The range where the previous match is. It is null if no previous match has been found. + */ + findPreviousMatch(searchString:string, searchStart:IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range; } export interface IReadOnlyModel extends ITextModel { @@ -1789,6 +1850,11 @@ export interface ITokenizedModel extends ITextModel { */ getMode(): IMode; + /** + * Get the language associated with this model. + */ + getModeId(): string; + /** * Set the current language mode associated with the model. */ @@ -2192,49 +2258,6 @@ export interface IModel extends IReadOnlyModel, IEditableTextModel, ITextModelWi */ dispose(): void; - /** - * Search the model. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchOnlyEditableRange Limit the searching to only search inside the editable range of the model. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @param limitResultCount Limit the number of results - * @return The ranges where the matches are. It is empty if not matches have been found. - */ - findMatches(searchString:string, searchOnlyEditableRange:boolean, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount?:number): Range[]; - /** - * Search the model. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchScope Limit the searching to only search inside this range. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @param limitResultCount Limit the number of results - * @return The ranges where the matches are. It is empty if no matches have been found. - */ - findMatches(searchString:string, searchScope:IRange, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount?:number): Range[]; - /** - * Search the model for the next match. Loops to the beginning of the model if needed. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchStart Start the searching at the specified position. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @return The range where the next match is. It is null if no next match has been found. - */ - findNextMatch(searchString:string, searchStart:IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range; - /** - * Search the model for the previous match. Loops to the end of the model if needed. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchStart Start the searching at the specified position. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @return The range where the previous match is. It is null if no previous match has been found. - */ - findPreviousMatch(searchString:string, searchStart:IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range; - /** * @internal */ @@ -2260,24 +2283,6 @@ export interface IRangeWithText { range:IRange; } -/** - * @internal - */ -export interface IMirrorModel extends IEventEmitter, ITokenizedModel { - uri: URI; - - getOffsetFromPosition(position:IPosition): number; - getPositionFromOffset(offset:number): Position; - getOffsetAndLengthFromRange(range:IRange): {offset:number; length:number;}; - getRangeFromOffsetAndLength(offset:number, length:number): Range; - getLineStart(lineNumber:number): number; - - getAllWordsWithRange(): IRangeWithText[]; - getAllUniqueWords(skipWordOnce?:string): string[]; - - getModeId(): string; -} - /** * An event describing that the current mode associated with a model has changed. */ @@ -3492,17 +3497,6 @@ export type IEditorActionContributionCtor = IConstructorSignature2; -/** - * An editor contribution descriptor that will be used to construct editor contributions - * @internal - */ -export interface ICommonEditorContributionDescriptor { - /** - * Create an instance of the contribution - */ - createInstance(instantiationService:IInstantiationService, editor:ICommonCodeEditor): IEditorContribution; -} - export interface IEditorAction { id: string; label: string; diff --git a/src/vs/editor/common/editorCommonExtensions.ts b/src/vs/editor/common/editorCommonExtensions.ts index e73a75aacf4..8a5b6a08688 100644 --- a/src/vs/editor/common/editorCommonExtensions.ts +++ b/src/vs/editor/common/editorCommonExtensions.ts @@ -7,7 +7,7 @@ import {illegalArgument} from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; -import {ServicesAccessor, IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation'; import {CommandsRegistry} from 'vs/platform/commands/common/commands'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {Registry} from 'vs/platform/platform'; @@ -95,6 +95,10 @@ export function editorAction(ctor:{ new(): EditorAction; }): void { CommonEditorRegistry.registerEditorAction(new ctor()); } +export function commonEditorContribution(ctor:editorCommon.ICommonEditorContributionCtor): void { + EditorContributionRegistry.INSTANCE.registerEditorContribution(ctor); +} + export module CommonEditorRegistry { // --- Editor Actions @@ -108,10 +112,7 @@ export module CommonEditorRegistry { // --- Editor Contributions - export function registerEditorContribution(ctor:editorCommon.ICommonEditorContributionCtor): void { - EditorContributionRegistry.INSTANCE.registerEditorContribution(ctor); - } - export function getEditorContributions(): editorCommon.ICommonEditorContributionDescriptor[] { + export function getEditorContributions(): editorCommon.ICommonEditorContributionCtor[] { return EditorContributionRegistry.INSTANCE.getEditorContributions(); } @@ -149,20 +150,8 @@ export module CommonEditorRegistry { } } -class SimpleEditorContributionDescriptor implements editorCommon.ICommonEditorContributionDescriptor { - private _ctor:editorCommon.ICommonEditorContributionCtor; - - constructor(ctor:editorCommon.ICommonEditorContributionCtor) { - this._ctor = ctor; - } - - public createInstance(instantiationService: IInstantiationService, editor:editorCommon.ICommonCodeEditor): editorCommon.IEditorContribution { - return instantiationService.createInstance(this._ctor, editor); - } -} - // Editor extension points -var Extensions = { +const Extensions = { EditorCommonContributions: 'editor.commonContributions' }; @@ -170,7 +159,7 @@ class EditorContributionRegistry { public static INSTANCE = new EditorContributionRegistry(); - private editorContributions: editorCommon.ICommonEditorContributionDescriptor[]; + private editorContributions: editorCommon.ICommonEditorContributionCtor[]; private editorActions: EditorAction[]; constructor() { @@ -179,7 +168,7 @@ class EditorContributionRegistry { } public registerEditorContribution(ctor:editorCommon.ICommonEditorContributionCtor): void { - this.editorContributions.push(new SimpleEditorContributionDescriptor(ctor)); + this.editorContributions.push(ctor); } public registerEditorAction(action:EditorAction) { @@ -194,7 +183,7 @@ class EditorContributionRegistry { this.editorActions.push(action); } - public getEditorContributions(): editorCommon.ICommonEditorContributionDescriptor[] { + public getEditorContributions(): editorCommon.ICommonEditorContributionCtor[] { return this.editorContributions.slice(0); } diff --git a/src/vs/editor/common/model/mirrorModel.ts b/src/vs/editor/common/model/compatMirrorModel.ts similarity index 52% rename from src/vs/editor/common/model/mirrorModel.ts rename to src/vs/editor/common/model/compatMirrorModel.ts index 2b1b7977ad6..d55fe353dc1 100644 --- a/src/vs/editor/common/model/mirrorModel.ts +++ b/src/vs/editor/common/model/compatMirrorModel.ts @@ -8,43 +8,27 @@ import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ModelLine} from 'vs/editor/common/model/modelLine'; -import {TextModel} from 'vs/editor/common/model/textModel'; import {TextModelWithTokens} from 'vs/editor/common/model/textModelWithTokens'; import {IMode} from 'vs/editor/common/modes'; -import {Range} from 'vs/editor/common/core/range'; -import {Position} from 'vs/editor/common/core/position'; +import {ICompatMirrorModel} from 'vs/editor/common/services/resourceService'; -export interface IMirrorModelEvents { +export interface ICompatMirrorModelEvents { contentChanged: editorCommon.IModelContentChangedEvent[]; } const NO_TAB_SIZE = 0; -export class AbstractMirrorModel extends TextModelWithTokens implements editorCommon.IMirrorModel { +export class CompatMirrorModel extends TextModelWithTokens implements ICompatMirrorModel { - _associatedResource:URI; + protected _associatedResource:URI; - constructor(allowedEventTypes:string[], versionId:number, value:editorCommon.IRawText, mode:IMode|TPromise, associatedResource?:URI) { - super(allowedEventTypes.concat([editorCommon.EventType.ModelDispose]), value, mode); + constructor(versionId:number, value:editorCommon.IRawText, mode:IMode|TPromise, associatedResource?:URI) { + super(['changed', editorCommon.EventType.ModelDispose], value, mode); this._setVersionId(versionId); this._associatedResource = associatedResource; } - public getModeId(): string { - return this.getMode().getId(); - } - - public _constructLines(rawText:editorCommon.IRawText):void { - super._constructLines(rawText); - // Force EOL to be \n - this._EOL = '\n'; - } - - public destroy(): void { - this.dispose(); - } - public dispose(): void { this.emit(editorCommon.EventType.ModelDispose); super.dispose(); @@ -54,116 +38,13 @@ export class AbstractMirrorModel extends TextModelWithTokens implements editorCo return this._associatedResource; } - public getRangeFromOffsetAndLength(offset:number, length:number): Range { - let startPosition = this.getPositionAt(offset); - let endPosition = this.getPositionAt(offset + length); - return new Range( - startPosition.lineNumber, - startPosition.column, - endPosition.lineNumber, - endPosition.column - ); + protected _constructLines(rawText:editorCommon.IRawText):void { + super._constructLines(rawText); + // Force EOL to be \n + this._EOL = '\n'; } - public getOffsetAndLengthFromRange(range:editorCommon.IRange):{offset:number; length:number;} { - let startOffset = this.getOffsetAt(new Position(range.startLineNumber, range.startColumn)); - let endOffset = this.getOffsetAt(new Position(range.endLineNumber, range.endColumn)); - return { - offset: startOffset, - length: endOffset - startOffset - }; - } - - public getPositionFromOffset(offset:number): Position { - return this.getPositionAt(offset); - } - - public getOffsetFromPosition(position:editorCommon.IPosition): number { - return this.getOffsetAt(position); - } - - public getLineStart(lineNumber:number): number { - if (lineNumber < 1) { - lineNumber = 1; - } - if (lineNumber > this.getLineCount()) { - lineNumber = this.getLineCount(); - } - return this.getOffsetAt(new Position(lineNumber, 1)); - } - - public getAllWordsWithRange(): editorCommon.IRangeWithText[] { - if (this._lines.length > 10000) { - // This is a very heavy method, unavailable for very heavy models - return []; - } - - var result:editorCommon.IRangeWithText[] = [], - i:number; - - var toTextRange = function (info: editorCommon.IWordRange) { - var s = line.text.substring(info.start, info.end); - var r = { startLineNumber: i + 1, startColumn: info.start + 1, endLineNumber: i + 1, endColumn: info.end + 1 }; - result.push({ text: s, range: r}); - }; - - for(i = 0; i < this._lines.length; i++) { - var line = this._lines[i]; - this.wordenize(line.text).forEach(toTextRange); - } - - return result; - } - - public getAllWords(): string[] { - var result:string[] = []; - this._lines.forEach((line) => { - this.wordenize(line.text).forEach((info) => { - result.push(line.text.substring(info.start, info.end)); - }); - }); - return result; - } - - public getAllUniqueWords(skipWordOnce?:string) : string[] { - var foundSkipWord = false; - var uniqueWords = {}; - return this.getAllWords().filter((word) => { - if (skipWordOnce && !foundSkipWord && skipWordOnce === word) { - foundSkipWord = true; - return false; - } else if (uniqueWords[word]) { - return false; - } else { - uniqueWords[word] = true; - return true; - } - }); - } - -// // TODO@Joh, TODO@Alex - remove these and make sure the super-things work - private wordenize(content:string): editorCommon.IWordRange[] { - var result:editorCommon.IWordRange[] = []; - var match:RegExpExecArray; - var wordsRegexp = this._getWordDefinition(); - while (match = wordsRegexp.exec(content)) { - result.push({ start: match.index, end: match.index + match[0].length }); - } - return result; - } -} - -export function createTestMirrorModelFromString(value:string, mode:IMode = null, associatedResource?:URI): MirrorModel { - return new MirrorModel(0, TextModel.toRawText(value, TextModel.DEFAULT_CREATION_OPTIONS), mode, associatedResource); -} - -export class MirrorModel extends AbstractMirrorModel implements editorCommon.IMirrorModel { - - constructor(versionId:number, value:editorCommon.IRawText, mode:IMode|TPromise, associatedResource?:URI) { - super(['changed'], versionId, value, mode, associatedResource); - } - - public onEvents(events:IMirrorModelEvents) : void { + public onEvents(events:ICompatMirrorModelEvents) : void { let changed = false; for (let i = 0, len = events.contentChanged.length; i < len; i++) { let contentChangedEvent = events.contentChanged[i]; diff --git a/src/vs/editor/common/model/editableTextModel.ts b/src/vs/editor/common/model/editableTextModel.ts index 720b8f3e241..c474407629e 100644 --- a/src/vs/editor/common/model/editableTextModel.ts +++ b/src/vs/editor/common/model/editableTextModel.ts @@ -70,7 +70,7 @@ export class EditableTextModel extends TextModelWithDecorations implements edito super.dispose(); } - _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { + protected _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { super._resetValue(e, newValue); // Destroy my edit history and settings diff --git a/src/vs/editor/common/model/model.ts b/src/vs/editor/common/model/model.ts index 3e79546d394..93c8e9f14a6 100644 --- a/src/vs/editor/common/model/model.ts +++ b/src/vs/editor/common/model/model.ts @@ -105,10 +105,6 @@ export class Model extends EditableTextModel implements IModel { // console.log('ALIVE MODELS: ' + Object.keys(aliveModels).join('\n')); } - public getModeId(): string { - return this.getMode().getId(); - } - public destroy(): void { this.dispose(); } diff --git a/src/vs/editor/common/model/modelLine.ts b/src/vs/editor/common/model/modelLine.ts index b1aa3b132cc..5c8db2c7509 100644 --- a/src/vs/editor/common/model/modelLine.ts +++ b/src/vs/editor/common/model/modelLine.ts @@ -6,10 +6,10 @@ import * as strings from 'vs/base/common/strings'; import {ILineTokens, IReadOnlyLineMarker} from 'vs/editor/common/editorCommon'; -import {IMode, IState} from 'vs/editor/common/modes'; +import {IState} from 'vs/editor/common/modes'; import {TokensBinaryEncoding, TokensInflatorMap} from 'vs/editor/common/model/tokensBinaryEncoding'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; -import {LineToken} from 'vs/editor/common/model/lineToken'; +import {Token} from 'vs/editor/common/core/token'; import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; const START_INDEX_MASK = TokensBinaryEncoding.START_INDEX_MASK; @@ -160,6 +160,12 @@ export class ModelLine { // --- BEGIN STATE + public resetTokenizationState(): void { + this._state = null; + this._modeTransitions = null; + this._lineTokens = null; + } + public setState(state: IState): void { this._state = state; } @@ -172,11 +178,11 @@ export class ModelLine { // --- BEGIN MODE TRANSITIONS - public getModeTransitions(topLevelMode:IMode): ModeTransition[] { + public getModeTransitions(topLevelModeId:string): ModeTransition[] { if (this._modeTransitions) { return this._modeTransitions; } else { - return [new ModeTransition(0, topLevelMode)]; + return [new ModeTransition(0, topLevelModeId)]; } } @@ -184,9 +190,9 @@ export class ModelLine { // --- BEGIN TOKENS - public setTokens(map: TokensInflatorMap, tokens: LineToken[], topLevelMode:IMode, modeTransitions:ModeTransition[]): void { + public setTokens(map: TokensInflatorMap, tokens: Token[], topLevelModeId:string, modeTransitions:ModeTransition[]): void { this._lineTokens = toLineTokensFromInflated(map, tokens, this._text.length); - this._modeTransitions = toModeTransitions(topLevelMode, modeTransitions); + this._modeTransitions = toModeTransitions(topLevelModeId, modeTransitions); } private _setLineTokensFromDeflated(tokens:number[]): void { @@ -691,7 +697,7 @@ export class ModelLine { } } -function toLineTokensFromInflated(map:TokensInflatorMap, tokens:LineToken[], textLength:number): number[] { +function toLineTokensFromInflated(map:TokensInflatorMap, tokens:Token[], textLength:number): number[] { if (textLength === 0) { return null; } @@ -868,11 +874,11 @@ export class DefaultLineTokens implements ILineTokens { } -function toModeTransitions(topLevelMode:IMode, modeTransitions:ModeTransition[]): ModeTransition[] { +function toModeTransitions(topLevelModeId:string, modeTransitions:ModeTransition[]): ModeTransition[] { if (!modeTransitions || modeTransitions.length === 0) { return null; - } else if (modeTransitions.length === 1 && modeTransitions[0].startIndex === 0 && modeTransitions[0].mode === topLevelMode) { + } else if (modeTransitions.length === 1 && modeTransitions[0].startIndex === 0 && modeTransitions[0].modeId === topLevelModeId) { return null; } diff --git a/src/vs/editor/common/model/textModel.ts b/src/vs/editor/common/model/textModel.ts index 284a46c9020..ffcc98a04db 100644 --- a/src/vs/editor/common/model/textModel.ts +++ b/src/vs/editor/common/model/textModel.ts @@ -18,11 +18,6 @@ import {IndentRange, computeRanges} from 'vs/editor/common/model/indentRanges'; const LIMIT_FIND_COUNT = 999; export const LONG_LINE_BOUNDARY = 1000; -export interface IParsedSearchRequest { - regex: RegExp; - isMultiline: boolean; -} - export class TextModel extends OrderGuaranteeEventEmitter implements editorCommon.ITextModel { private static MODEL_SYNC_LIMIT = 5 * 1024 * 1024; // 5 MB private static MODEL_TOKENIZATION_LIMIT = 20 * 1024 * 1024; // 20 MB @@ -218,16 +213,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return new Position(out.index + 1, Math.min(out.remainder + 1, lineLength + 1)); } - _increaseVersionId(): void { + protected _increaseVersionId(): void { this._setVersionId(this._versionId + 1); } - _setVersionId(newVersionId:number): void { + protected _setVersionId(newVersionId:number): void { this._versionId = newVersionId; this._alternativeVersionId = this._versionId; } - _overwriteAlternativeVersionId(newAlternativeVersionId:number): void { + protected _overwriteAlternativeVersionId(newAlternativeVersionId:number): void { this._alternativeVersionId = newAlternativeVersionId; } @@ -245,7 +240,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo super.dispose(); } - _createContentChangedFlushEvent(): editorCommon.IModelContentChangedFlushEvent { + protected _createContentChangedFlushEvent(): editorCommon.IModelContentChangedFlushEvent { return { changeType: editorCommon.EventType.ModelRawContentChangedFlush, detail: null, @@ -271,7 +266,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo } } - _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { + protected _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { this._constructLines(newValue); this._increaseVersionId(); @@ -641,7 +636,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return new Range(1, 1, lineCount, this.getLineMaxColumn(lineCount)); } - _emitModelContentChangedFlushEvent(e:editorCommon.IModelContentChangedFlushEvent): void { + protected _emitModelContentChangedFlushEvent(e:editorCommon.IModelContentChangedFlushEvent): void { if (!this._isDisposing) { this.emit(editorCommon.EventType.ModelRawContentChanged, e); } @@ -705,7 +700,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo }; } - _constructLines(rawText:editorCommon.IRawText): void { + protected _constructLines(rawText:editorCommon.IRawText): void { const tabSize = rawText.options.tabSize; let rawLines = rawText.lines; let modelLines: ModelLine[] = []; @@ -764,15 +759,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return false; } - public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): IParsedSearchRequest { + public static parseSearchRequest(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean): RegExp { if (searchString === '') { return null; } // Try to create a RegExp out of the params - var regex:RegExp = null; + var regex: RegExp = null; + var multiline = isRegex && TextModel._isMultiline(searchString); try { - regex = strings.createRegExp(searchString, isRegex, matchCase, wholeWord, true); + regex = strings.createRegExp(searchString, isRegex, {matchCase, wholeWord, multiline, global: true}); } catch (err) { return null; } @@ -781,15 +777,12 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return null; } - return { - regex: regex, - isMultiline: isRegex && TextModel._isMultiline(searchString) - }; + return regex; } public findMatches(searchString:string, rawSearchScope:any, isRegex:boolean, matchCase:boolean, wholeWord:boolean, limitResultCount:number = LIMIT_FIND_COUNT): Range[] { - let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); - if (!r) { + let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); + if (!regex) { return []; } @@ -800,10 +793,10 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo searchRange = this.getFullModelRange(); } - if (r.isMultiline) { - return this._doFindMatchesMultiline(searchRange, r.regex, limitResultCount); + if (regex.multiline) { + return this._doFindMatchesMultiline(searchRange, regex, limitResultCount); } - return this._doFindMatchesLineByLine(searchRange, r.regex, limitResultCount); + return this._doFindMatchesLineByLine(searchRange, regex, limitResultCount); } private _doFindMatchesMultiline(searchRange:Range, searchRegex:RegExp, limitResultCount:number): Range[] { @@ -871,23 +864,24 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo } public findNextMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range { - let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); - if (!r) { + let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); + if (!regex) { return null; } let searchStart = this.validatePosition(rawSearchStart); - if (r.isMultiline) { - return this._doFindNextMatchMultiline(searchStart, r.regex); + if (regex.multiline) { + return this._doFindNextMatchMultiline(searchStart, regex); } - return this._doFindNextMatchLineByLine(searchStart, r.regex); + return this._doFindNextMatchLineByLine(searchStart, regex); } - private _doFindNextMatchMultiline(searchStart:Position, searchRegex:RegExp): Range { - let deltaOffset = this.getOffsetAt(searchStart); - let text = this.getValueInRange(new Range(searchStart.lineNumber, searchStart.column, this.getLineCount(), this.getLineMaxColumn(this.getLineCount()))); - + private _doFindNextMatchMultiline(searchStart: Position, searchRegex: RegExp): Range { + let searchTextStart: editorCommon.IPosition = { lineNumber: searchStart.lineNumber, column: 1 }; + let deltaOffset = this.getOffsetAt(searchTextStart); + let text = this.getValueInRange(new Range(searchTextStart.lineNumber, searchTextStart.column, this.getLineCount(), this.getLineMaxColumn(this.getLineCount()))); + searchRegex.lastIndex = searchStart.column - 1; let m = searchRegex.exec(text); if (m) { let startOffset = deltaOffset + m.index; @@ -912,8 +906,8 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo let r: Range; // Look in first line - text = this._lines[startLineNumber - 1].text.substring(searchStart.column - 1); - r = this._findFirstMatchInLine(searchRegex, text, startLineNumber, searchStart.column - 1); + text = this._lines[startLineNumber - 1].text; + r = this._findFirstMatchInLine(searchRegex, text, startLineNumber, searchStart.column); if (r) { return r; } @@ -921,7 +915,7 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo for (let i = 1; i <= lineCount; i++) { let lineIndex = (startLineNumber + i - 1) % lineCount; text = this._lines[lineIndex].text; - r = this._findFirstMatchInLine(searchRegex, text, lineIndex + 1, 0); + r = this._findFirstMatchInLine(searchRegex, text, lineIndex + 1, 1); if (r) { return r; } @@ -931,16 +925,16 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo } public findPreviousMatch(searchString:string, rawSearchStart:editorCommon.IPosition, isRegex:boolean, matchCase:boolean, wholeWord:boolean): Range { - let r = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); - if (!r) { + let regex = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); + if (!regex) { return null; } let searchStart = this.validatePosition(rawSearchStart); - if (r.isMultiline) { - return this._doFindPreviousMatchMultiline(searchStart, r.regex); + if (regex.multiline) { + return this._doFindPreviousMatchMultiline(searchStart, regex); } - return this._doFindPreviousMatchLineByLine(searchStart, r.regex); + return this._doFindPreviousMatchLineByLine(searchStart, regex); } private _doFindPreviousMatchMultiline(searchStart:Position, searchRegex:RegExp): Range { @@ -982,12 +976,11 @@ export class TextModel extends OrderGuaranteeEventEmitter implements editorCommo return null; } - private _findFirstMatchInLine(searchRegex:RegExp, text:string, lineNumber:number, deltaOffset:number): Range { - var m = searchRegex.exec(text); - if (!m) { - return null; - } - return new Range(lineNumber, m.index + 1 + deltaOffset, lineNumber, m.index + 1 + m[0].length + deltaOffset); + private _findFirstMatchInLine(searchRegex: RegExp, text: string, lineNumber: number, fromColumn: number): Range { + // Set regex to search from column + searchRegex.lastIndex = fromColumn - 1; + var m: RegExpExecArray = searchRegex.exec(text); + return m ? new Range(lineNumber, m.index + 1, lineNumber, m.index + 1 + m[0].length) : null; } private _findLastMatchInLine(searchRegex:RegExp, text:string, lineNumber:number): Range { diff --git a/src/vs/editor/common/model/textModelWithDecorations.ts b/src/vs/editor/common/model/textModelWithDecorations.ts index 8071ddff458..b008f2f3588 100644 --- a/src/vs/editor/common/model/textModelWithDecorations.ts +++ b/src/vs/editor/common/model/textModelWithDecorations.ts @@ -113,7 +113,7 @@ export class TextModelWithDecorations extends TextModelWithTrackedRanges impleme super.dispose(); } - _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { + protected _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { super._resetValue(e, newValue); // Destroy all my decorations @@ -289,7 +289,7 @@ export class TextModelWithDecorations extends TextModelWithTrackedRanges impleme return result; } - _withDeferredEvents(callback:(deferredEventsBuilder:DeferredEventsBuilder)=>any): any { + protected _withDeferredEvents(callback:(deferredEventsBuilder:DeferredEventsBuilder)=>any): any { return this.deferredEmit(() => { var createDeferredEvents = this._currentDeferredEvents ? false : true; if (createDeferredEvents) { diff --git a/src/vs/editor/common/model/textModelWithMarkers.ts b/src/vs/editor/common/model/textModelWithMarkers.ts index de35431466f..427b4d465f9 100644 --- a/src/vs/editor/common/model/textModelWithMarkers.ts +++ b/src/vs/editor/common/model/textModelWithMarkers.ts @@ -62,7 +62,7 @@ export class TextModelWithMarkers extends TextModelWithTokens implements ITextMo super.dispose(); } - _resetValue(e:IModelContentChangedFlushEvent, newValue:IRawText): void { + protected _resetValue(e:IModelContentChangedFlushEvent, newValue:IRawText): void { super._resetValue(e, newValue); // Destroy all my markers @@ -195,7 +195,7 @@ export class TextModelWithMarkers extends TextModelWithTokens implements ITextMo } } - _getMarkersInMap(markersMap:{[markerId:string]:boolean;}): ILineMarker[] { + protected _getMarkersInMap(markersMap:{[markerId:string]:boolean;}): ILineMarker[] { let result: ILineMarker[] = []; let keys = Object.keys(markersMap); for (let i = 0, len = keys.length; i < len; i++) { diff --git a/src/vs/editor/common/model/textModelWithTokens.ts b/src/vs/editor/common/model/textModelWithTokens.ts index 7fcc0447432..bef45d17dff 100644 --- a/src/vs/editor/common/model/textModelWithTokens.ts +++ b/src/vs/editor/common/model/textModelWithTokens.ts @@ -17,15 +17,15 @@ import {ModelLine} from 'vs/editor/common/model/modelLine'; import {TextModel} from 'vs/editor/common/model/textModel'; import {WordHelper} from 'vs/editor/common/model/textModelWithTokensHelpers'; import {TokenIterator} from 'vs/editor/common/model/tokenIterator'; -import {ILineContext, ILineTokens, IToken, IModeTransition, IMode, IState} from 'vs/editor/common/modes'; +import {ILineContext, ILineTokens, IMode, IState} from 'vs/editor/common/modes'; import {NullMode, NullState, nullTokenize} from 'vs/editor/common/modes/nullMode'; import {ignoreBracketsInToken} from 'vs/editor/common/modes/supports'; import {BracketsUtils} from 'vs/editor/common/modes/supports/richEditBrackets'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; -import {LineToken} from 'vs/editor/common/model/lineToken'; import {TokensInflatorMap} from 'vs/editor/common/model/tokensBinaryEncoding'; import {Position} from 'vs/editor/common/core/position'; import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; +import {Token} from 'vs/editor/common/core/token'; class ModeToModelBinder implements IDisposable { @@ -92,7 +92,7 @@ export class FullModelRetokenizer implements IRetokenizeRequest { public isFulfilled: boolean; - _model:TextModelWithTokens; + protected _model:TextModelWithTokens; private _retokenizePromise:TPromise; private _isDisposed: boolean; @@ -131,8 +131,8 @@ class LineContext implements ILineContext { private _text:string; private _lineTokens:editorCommon.ILineTokens; - constructor (topLevelMode:IMode, line:ModelLine, map:TokensInflatorMap) { - this.modeTransitions = line.getModeTransitions(topLevelMode); + constructor (topLevelModeId:string, line:ModelLine, map:TokensInflatorMap) { + this.modeTransitions = line.getModeTransitions(topLevelModeId); this._text = line.text; this._lineTokens = line.getTokens(map); } @@ -149,20 +149,10 @@ class LineContext implements ILineContext { return this._lineTokens.getTokenStartIndex(tokenIndex); } - public getTokenEndIndex(tokenIndex:number): number { - return this._lineTokens.getTokenEndIndex(tokenIndex, this._text.length); - } - public getTokenType(tokenIndex:number): string { return this._lineTokens.getTokenType(tokenIndex); } - public getTokenText(tokenIndex:number): string { - var startIndex = this._lineTokens.getTokenStartIndex(tokenIndex); - var endIndex = this._lineTokens.getTokenEndIndex(tokenIndex, this._text.length); - return this._text.substring(startIndex, endIndex); - } - public findIndexOfOffset(offset:number): number { return this._lineTokens.findIndexOfOffset(offset); } @@ -312,17 +302,17 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } } - _createRetokenizer(retokenizePromise:TPromise, lineNumber:number): IRetokenizeRequest { + protected _createRetokenizer(retokenizePromise:TPromise, lineNumber:number): IRetokenizeRequest { return new FullModelRetokenizer(retokenizePromise, this); } - _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { + protected _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { super._resetValue(e, newValue); // Cancel tokenization, clear all tokens and begin tokenizing this._resetTokenizationState(); } - _resetMode(e:editorCommon.IModelModeChangedEvent, newMode:IMode): void { + protected _resetMode(e:editorCommon.IModelModeChangedEvent, newMode:IMode): void { // Cancel tokenization, clear all tokens and begin tokenizing this._mode = newMode; this._resetModeListener(newMode); @@ -349,24 +339,14 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } } - _resetTokenizationState(): void { + protected _resetTokenizationState(): void { this._retokenizers = dispose(this._retokenizers); this._scheduleRetokenizeNow.cancel(); this._clearTimers(); for (var i = 0; i < this._lines.length; i++) { - this._lines[i].setState(null); + this._lines[i].resetTokenizationState(); } - this._initializeTokenizationState(); - } - private _clearTimers(): void { - if (this._revalidateTokensTimeout !== -1) { - clearTimeout(this._revalidateTokensTimeout); - this._revalidateTokensTimeout = -1; - } - } - - private _initializeTokenizationState(): void { // Initialize tokenization states var initialState:IState = null; if (this._mode.tokenizationSupport) { @@ -389,6 +369,13 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._beginBackgroundTokenization(); } + private _clearTimers(): void { + if (this._revalidateTokensTimeout !== -1) { + clearTimeout(this._revalidateTokensTimeout); + this._revalidateTokensTimeout = -1; + } + } + public getLineTokens(lineNumber:number, inaccurateTokensAcceptable:boolean = false): editorCommon.ILineTokens { if (lineNumber < 1 || lineNumber > this.getLineCount()) { throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); @@ -407,10 +394,10 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke this._updateTokensUntilLine(lineNumber, true); - return new LineContext(this._mode, this._lines[lineNumber - 1], this._tokensInflatorMap); + return new LineContext(this.getModeId(), this._lines[lineNumber - 1], this._tokensInflatorMap); } - _getInternalTokens(lineNumber:number): editorCommon.ILineTokens { + protected _getInternalTokens(lineNumber:number): editorCommon.ILineTokens { this._updateTokensUntilLine(lineNumber, true); return this._lines[lineNumber - 1].getTokens(this._tokensInflatorMap); } @@ -419,6 +406,10 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke return this._mode; } + public getModeId(): string { + return this.getMode().getId(); + } + public setMode(newModeOrPromise:IMode|TPromise): void { if (!newModeOrPromise) { // There's nothing to do @@ -463,7 +454,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } } - _invalidateLine(lineIndex:number): void { + protected _invalidateLine(lineIndex:number): void { this._lines[lineIndex].isInvalid = true; if (lineIndex < this._invalidLineStartIndex) { if (this._invalidLineStartIndex < this._lines.length) { @@ -474,38 +465,6 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } } - private static _toLineTokens(tokens:IToken[]): LineToken[] { - if (!tokens || tokens.length === 0) { - return []; - } - if (tokens[0] instanceof LineToken) { - return tokens; - } - let result:LineToken[] = []; - for (let i = 0, len = tokens.length; i < len; i++) { - result[i] = new LineToken(tokens[i].startIndex, tokens[i].type); - } - return result; - } - - private static _toModeTransitions(modeTransitions:IModeTransition[]): ModeTransition[] { - if (!modeTransitions || modeTransitions.length === 0) { - return []; - } - if (modeTransitions[0] instanceof ModeTransition) { - return modeTransitions; - } - let result:ModeTransition[] = []; - for (let i = 0, len = modeTransitions.length; i < len; i++) { - result[i] = new ModeTransition(modeTransitions[i].startIndex, modeTransitions[i].mode); - } - return result; - } - - private _updateLineTokens(lineIndex:number, map:TokensInflatorMap, topLevelMode:IMode, r:ILineTokens): void { - this._lines[lineIndex].setTokens(map, TextModelWithTokens._toLineTokens(r.tokens), topLevelMode, TextModelWithTokens._toModeTransitions(r.modeTransitions)); - } - private _beginBackgroundTokenization(): void { if (this._shouldAutoTokenize() && this._revalidateTokensTimeout === -1) { this._revalidateTokensTimeout = setTimeout(() => { @@ -604,7 +563,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke throw new Error('Illegal value ' + lineNumber + ' for `lineNumber`'); } this._updateTokensUntilLine(lineNumber, true); - return this._lines[lineNumber - 1].getModeTransitions(this._mode); + return this._lines[lineNumber - 1].getModeTransitions(this.getModeId()); } private _updateTokensUntilLine(lineNumber:number, emitEvents:boolean): void { @@ -640,10 +599,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke if (r && r.actualStopOffset < text.length) { // Treat the rest of the line (if above limit) as one default token - r.tokens.push({ - startIndex: r.actualStopOffset, - type: '' - }); + r.tokens.push(new Token(r.actualStopOffset, '')); // Use as end state the starting state r.endState = this._lines[lineIndex].getState(); @@ -651,19 +607,16 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } if (!r) { - r = nullTokenize(this._mode, text, this._lines[lineIndex].getState()); + r = nullTokenize(this.getModeId(), text, this._lines[lineIndex].getState()); } if (!r.modeTransitions) { r.modeTransitions = []; } if (r.modeTransitions.length === 0) { // Make sure there is at least the transition to the top-most mode - r.modeTransitions.push({ - startIndex: 0, - mode: this._mode - }); + r.modeTransitions.push(new ModeTransition(0, this.getModeId())); } - this._updateLineTokens(lineIndex, this._tokensInflatorMap, this._mode, r); + this._lines[lineIndex].setTokens(this._tokensInflatorMap, r.tokens, this.getModeId(), r.modeTransitions); if (this._lines[lineIndex].isInvalid) { this._lines[lineIndex].isInvalid = false; @@ -732,8 +685,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke return this._invalidLineStartIndex > lineNumber - 1; } - _getWordDefinition(): RegExp { - return WordHelper.massageWordDefinitionOf(this._mode); + protected _getWordDefinition(): RegExp { + return WordHelper.massageWordDefinitionOf(this.getModeId()); } public getWordAtPosition(position:editorCommon.IPosition): editorCommon.IWordAtPosition { @@ -763,9 +716,11 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke return result; } - public findMatchingBracketUp(bracket:string, _position:editorCommon.IPosition): Range { + public findMatchingBracketUp(_bracket:string, _position:editorCommon.IPosition): Range { + let bracket = _bracket.toLowerCase(); let position = this.validatePosition(_position); - let modeTransitions = this._lines[position.lineNumber - 1].getModeTransitions(this._mode); + + let modeTransitions = this._lines[position.lineNumber - 1].getModeTransitions(this.getModeId()); let currentModeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, position.column - 1); let currentMode = modeTransitions[currentModeIndex]; let currentModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(currentMode.modeId); @@ -795,7 +750,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke let currentTokenIndex = lineTokens.findIndexOfOffset(position.column - 1); let currentTokenStart = lineTokens.getTokenStartIndex(currentTokenIndex); - let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this._mode); + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this.getModeId()); let currentModeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, position.column - 1); let currentMode = modeTransitions[currentModeIndex]; let currentModeBrackets = LanguageConfigurationRegistry.getBracketsSupport(currentMode.modeId); @@ -826,6 +781,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke // check that we didn't hit a bracket too far away from position if (foundBracket && foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + foundBracketText = foundBracketText.toLowerCase(); + let r = this._matchFoundBracket(foundBracket, prevModeBrackets.textIsBracket[foundBracketText], prevModeBrackets.textIsOpenBracket[foundBracketText]); // check that we can actually match this bracket @@ -859,6 +816,8 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke // check that we didn't hit a bracket too far away from position if (foundBracket.startColumn <= position.column && position.column <= foundBracket.endColumn) { let foundBracketText = lineText.substring(foundBracket.startColumn - 1, foundBracket.endColumn - 1); + foundBracketText = foundBracketText.toLowerCase(); + let r = this._matchFoundBracket(foundBracket, currentModeBrackets.textIsBracket[foundBracketText], currentModeBrackets.textIsOpenBracket[foundBracketText]); // check that we can actually match this bracket @@ -901,7 +860,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke for (let lineNumber = position.lineNumber; lineNumber >= 1; lineNumber--) { let lineTokens = this._lines[lineNumber - 1].getTokens(this._tokensInflatorMap); let lineText = this._lines[lineNumber - 1].text; - let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this._mode); + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this.getModeId()); let currentModeIndex = modeTransitions.length - 1; let currentModeStart = modeTransitions[currentModeIndex].startIndex; let currentModeId = modeTransitions[currentModeIndex].modeId; @@ -936,6 +895,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + hitText = hitText.toLowerCase(); if (hitText === bracket.open) { count++; @@ -968,7 +928,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke for (let lineNumber = position.lineNumber, lineCount = this.getLineCount(); lineNumber <= lineCount; lineNumber++) { let lineTokens = this._lines[lineNumber - 1].getTokens(this._tokensInflatorMap); let lineText = this._lines[lineNumber - 1].text; - let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this._mode); + let modeTransitions = this._lines[lineNumber - 1].getModeTransitions(this.getModeId()); let currentModeIndex = 0; let nextModeStart = (currentModeIndex + 1 < modeTransitions.length ? modeTransitions[currentModeIndex + 1].startIndex : lineText.length + 1); let currentModeId = modeTransitions[currentModeIndex].modeId; @@ -1002,6 +962,7 @@ export class TextModelWithTokens extends TextModel implements editorCommon.IToke } let hitText = lineText.substring(r.startColumn - 1, r.endColumn - 1); + hitText = hitText.toLowerCase(); if (hitText === bracket.open) { count++; diff --git a/src/vs/editor/common/model/textModelWithTokensHelpers.ts b/src/vs/editor/common/model/textModelWithTokensHelpers.ts index 39864e4b19c..575e207de2d 100644 --- a/src/vs/editor/common/model/textModelWithTokensHelpers.ts +++ b/src/vs/editor/common/model/textModelWithTokensHelpers.ts @@ -5,7 +5,6 @@ 'use strict'; import {IPosition, IWordAtPosition} from 'vs/editor/common/editorCommon'; -import {IMode, IModeTransition} from 'vs/editor/common/modes'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; import {getWordAtText, ensureValidWordDefinition} from 'vs/editor/common/model/wordHelper'; @@ -16,32 +15,28 @@ export interface ITextSource { getLineContent(lineNumber:number): string; - getMode(): IMode; + getModeId(): string; _getLineModeTransitions(lineNumber:number): ModeTransition[]; } -export interface INonWordTokenMap { - [key:string]:boolean; -} - export class WordHelper { - private static _safeGetWordDefinition(mode:IMode): RegExp { - return LanguageConfigurationRegistry.getWordDefinition(mode.getId()); + private static _safeGetWordDefinition(modeId:string): RegExp { + return LanguageConfigurationRegistry.getWordDefinition(modeId); } - public static massageWordDefinitionOf(mode:IMode): RegExp { - return ensureValidWordDefinition(WordHelper._safeGetWordDefinition(mode)); + public static massageWordDefinitionOf(modeId:string): RegExp { + return ensureValidWordDefinition(WordHelper._safeGetWordDefinition(modeId)); } - private static _getWordAtColumn(txt:string, column:number, modeIndex: number, modeTransitions:IModeTransition[]): IWordAtPosition { - var modeStartIndex = modeTransitions[modeIndex].startIndex, - modeEndIndex = (modeIndex + 1 < modeTransitions.length ? modeTransitions[modeIndex + 1].startIndex : txt.length), - mode = modeTransitions[modeIndex].mode; + private static _getWordAtColumn(txt:string, column:number, modeIndex: number, modeTransitions:ModeTransition[]): IWordAtPosition { + let modeStartIndex = modeTransitions[modeIndex].startIndex; + let modeEndIndex = (modeIndex + 1 < modeTransitions.length ? modeTransitions[modeIndex + 1].startIndex : txt.length); + let modeId = modeTransitions[modeIndex].modeId; return getWordAtText( - column, WordHelper.massageWordDefinitionOf(mode), + column, WordHelper.massageWordDefinitionOf(modeId), txt.substring(modeStartIndex, modeEndIndex), modeStartIndex ); } @@ -49,14 +44,14 @@ export class WordHelper { public static getWordAtPosition(textSource:ITextSource, position:IPosition): IWordAtPosition { if (!textSource._lineIsTokenized(position.lineNumber)) { - return getWordAtText(position.column, WordHelper.massageWordDefinitionOf(textSource.getMode()), textSource.getLineContent(position.lineNumber), 0); + return getWordAtText(position.column, WordHelper.massageWordDefinitionOf(textSource.getModeId()), textSource.getLineContent(position.lineNumber), 0); } - var result: IWordAtPosition = null; - var txt = textSource.getLineContent(position.lineNumber), - modeTransitions = textSource._getLineModeTransitions(position.lineNumber), - columnIndex = position.column - 1, - modeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, columnIndex); + let result: IWordAtPosition = null; + let txt = textSource.getLineContent(position.lineNumber); + let modeTransitions = textSource._getLineModeTransitions(position.lineNumber); + let columnIndex = position.column - 1; + let modeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, columnIndex); result = WordHelper._getWordAtColumn(txt, position.column, modeIndex, modeTransitions); diff --git a/src/vs/editor/common/model/textModelWithTrackedRanges.ts b/src/vs/editor/common/model/textModelWithTrackedRanges.ts index d234f8e2549..0320a75678f 100644 --- a/src/vs/editor/common/model/textModelWithTrackedRanges.ts +++ b/src/vs/editor/common/model/textModelWithTrackedRanges.ts @@ -85,7 +85,7 @@ export class TextModelWithTrackedRanges extends TextModelWithMarkers implements this._multiLineTrackedRanges = {}; } - _createRetokenizer(retokenizePromise:TPromise, lineNumber:number): IRetokenizeRequest { + protected _createRetokenizer(retokenizePromise:TPromise, lineNumber:number): IRetokenizeRequest { return new TrackedRangeModelRetokenizer(retokenizePromise, lineNumber, this); } @@ -96,7 +96,7 @@ export class TextModelWithTrackedRanges extends TextModelWithMarkers implements super.dispose(); } - _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { + protected _resetValue(e:editorCommon.IModelContentChangedFlushEvent, newValue:editorCommon.IRawText): void { super._resetValue(e, newValue); // Destroy all my tracked ranges @@ -346,7 +346,7 @@ export class TextModelWithTrackedRanges extends TextModelWithMarkers implements return result; } - _onChangedMarkers(changedMarkers:ILineMarker[]): editorCommon.IChangedTrackedRanges { + protected _onChangedMarkers(changedMarkers:ILineMarker[]): editorCommon.IChangedTrackedRanges { var changedRanges:editorCommon.IChangedTrackedRanges = {}, changedRange:editorCommon.IRange, range:ITrackedRange, diff --git a/src/vs/editor/common/model/tokenIterator.ts b/src/vs/editor/common/model/tokenIterator.ts index 156526bb8be..70a31d59485 100644 --- a/src/vs/editor/common/model/tokenIterator.ts +++ b/src/vs/editor/common/model/tokenIterator.ts @@ -5,6 +5,7 @@ 'use strict'; import * as editorCommon from 'vs/editor/common/editorCommon'; +import {Token} from 'vs/editor/common/core/token'; export class TokenIterator implements editorCommon.ITokenIterator { @@ -101,10 +102,7 @@ export class TokenIterator implements editorCommon.ITokenIterator { let endIndex = this._currentLineTokens.getTokenEndIndex(this._currentTokenIndex, this._model.getLineContent(this._currentLineNumber).length); return { - token: { - startIndex: startIndex, - type: type - }, + token: new Token(startIndex, type), lineNumber: this._currentLineNumber, startColumn: startIndex + 1, endColumn: endIndex + 1 diff --git a/src/vs/editor/common/model/tokensBinaryEncoding.ts b/src/vs/editor/common/model/tokensBinaryEncoding.ts index a329514d822..6e8b55a4105 100644 --- a/src/vs/editor/common/model/tokensBinaryEncoding.ts +++ b/src/vs/editor/common/model/tokensBinaryEncoding.ts @@ -7,7 +7,7 @@ import {onUnexpectedError} from 'vs/base/common/errors'; import * as strings from 'vs/base/common/strings'; import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; -import {LineToken} from 'vs/editor/common/model/lineToken'; +import {Token} from 'vs/editor/common/core/token'; const START_INDEX_MASK = 0xffffffff; const TYPE_MASK = 0xffff; @@ -40,7 +40,7 @@ export class TokensBinaryEncoding { public static START_INDEX_OFFSET = START_INDEX_OFFSET; public static TYPE_OFFSET = TYPE_OFFSET; - public static deflateArr(map:TokensInflatorMap, tokens:LineToken[]): number[] { + public static deflateArr(map:TokensInflatorMap, tokens:Token[]): number[] { if (tokens.length === 0) { return DEFLATED_TOKENS_EMPTY_TEXT; } @@ -52,7 +52,7 @@ export class TokensBinaryEncoding { len:number, deflatedToken:number, deflated:number, - token:LineToken, + token:Token, inflateMap = map._inflate, deflateMap = map._deflate, prevStartIndex:number = -1, diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index b14485643e9..95c17b3f2f1 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -11,6 +11,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IFilter} from 'vs/base/common/filters'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; import LanguageFeatureRegistry from 'vs/editor/common/modes/languageFeatureRegistry'; import {CancellationToken} from 'vs/base/common/cancellation'; import {Position} from 'vs/editor/common/core/position'; @@ -175,8 +176,6 @@ export interface ILineContext { getTokenCount(): number; getTokenStartIndex(tokenIndex:number): number; getTokenType(tokenIndex:number): string; - getTokenText(tokenIndex:number): string; - getTokenEndIndex(tokenIndex:number): number; findIndexOfOffset(offset:number): number; } @@ -211,31 +210,14 @@ export interface IMode { tokenizationSupport?: ITokenizationSupport; } -/** - * Interface used for tokenization - * @internal - */ -export interface IToken { - startIndex:number; - type:string; -} - -/** - * @internal - */ -export interface IModeTransition { - startIndex: number; - mode: IMode; -} - /** * @internal */ export interface ILineTokens { - tokens: IToken[]; + tokens: Token[]; actualStopOffset: number; endState: IState; - modeTransitions: IModeTransition[]; + modeTransitions: ModeTransition[]; retokenize?:TPromise; } diff --git a/src/vs/editor/common/modes/editorModeContext.ts b/src/vs/editor/common/modes/editorModeContext.ts index 608d126cd21..915d3b705b7 100644 --- a/src/vs/editor/common/modes/editorModeContext.ts +++ b/src/vs/editor/common/modes/editorModeContext.ts @@ -7,13 +7,14 @@ import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IContextKey, IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import * as modes from 'vs/editor/common/modes'; -import {ICommonCodeEditor, ModeContextKeys} from 'vs/editor/common/editorCommon'; +import {ICommonCodeEditor, ModeContextKeys, EditorContextKeys} from 'vs/editor/common/editorCommon'; export class EditorModeContext { private _disposables: IDisposable[] = []; private _editor: ICommonCodeEditor; + private _langId: IContextKey; private _hasCompletionItemProvider: IContextKey; private _hasCodeActionsProvider: IContextKey; private _hasCodeLensProvider: IContextKey; @@ -32,6 +33,7 @@ export class EditorModeContext { ) { this._editor = editor; + this._langId = EditorContextKeys.LanguageId.bindTo(contextKeyService); this._hasCompletionItemProvider = ModeContextKeys.hasCompletionItemProvider.bindTo(contextKeyService); this._hasCodeActionsProvider = ModeContextKeys.hasCodeActionsProvider.bindTo(contextKeyService); this._hasCodeLensProvider = ModeContextKeys.hasCodeLensProvider.bindTo(contextKeyService); @@ -70,6 +72,7 @@ export class EditorModeContext { } reset() { + this._langId.reset(); this._hasCompletionItemProvider.reset(); this._hasCodeActionsProvider.reset(); this._hasCodeLensProvider.reset(); @@ -89,6 +92,7 @@ export class EditorModeContext { this.reset(); return; } + this._langId.set(model.getModeId()); this._hasCompletionItemProvider.set(modes.SuggestRegistry.has(model)); this._hasCodeActionsProvider.set(modes.CodeActionProviderRegistry.has(model)); this._hasCodeLensProvider.set(modes.CodeLensProviderRegistry.has(model)); diff --git a/src/vs/editor/common/modes/nullMode.ts b/src/vs/editor/common/modes/nullMode.ts index 4ed0c80e131..8408dd23eb2 100644 --- a/src/vs/editor/common/modes/nullMode.ts +++ b/src/vs/editor/common/modes/nullMode.ts @@ -4,28 +4,30 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import * as modes from 'vs/editor/common/modes'; +import {IMode, IState, IStream, ITokenizationResult, ILineTokens} from 'vs/editor/common/modes'; +import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; -export class NullState implements modes.IState { +export class NullState implements IState { - private mode: modes.IMode; - private stateData: modes.IState; + private mode: IMode; + private stateData: IState; - constructor(mode: modes.IMode, stateData: modes.IState) { + constructor(mode: IMode, stateData: IState) { this.mode = mode; this.stateData = stateData; } - public clone(): modes.IState { - var stateDataClone:modes.IState = (this.stateData ? this.stateData.clone() : null); + public clone(): IState { + let stateDataClone:IState = (this.stateData ? this.stateData.clone() : null); return new NullState(this.mode, stateDataClone); } - public equals(other:modes.IState): boolean { + public equals(other:IState): boolean { if (this.mode !== other.getMode()) { return false; } - var otherStateData = other.getStateData(); + let otherStateData = other.getStateData(); if (!this.stateData && !otherStateData) { return true; } @@ -35,28 +37,28 @@ export class NullState implements modes.IState { return false; } - public getMode(): modes.IMode { + public getMode(): IMode { return this.mode; } - public tokenize(stream:modes.IStream):modes.ITokenizationResult { + public tokenize(stream:IStream):ITokenizationResult { stream.advanceToEOS(); return { type:'' }; } - public getStateData(): modes.IState { + public getStateData(): IState { return this.stateData; } - public setStateData(stateData:modes.IState):void { + public setStateData(stateData:IState):void { this.stateData = stateData; } } -export class NullMode implements modes.IMode { +export class NullMode implements IMode { - public static ID = 'vs.editor.modes.nullMode'; + public static ID = 'vs.editor.nullMode'; constructor() { } @@ -65,25 +67,15 @@ export class NullMode implements modes.IMode { return NullMode.ID; } - public toSimplifiedMode(): modes.IMode { + public toSimplifiedMode(): IMode { return this; } } -export function nullTokenize(mode: modes.IMode, buffer:string, state: modes.IState, deltaOffset:number = 0, stopAtOffset?:number): modes.ILineTokens { - var tokens:modes.IToken[] = [ - { - startIndex: deltaOffset, - type: '' - } - ]; +export function nullTokenize(modeId: string, buffer:string, state: IState, deltaOffset:number = 0, stopAtOffset?:number): ILineTokens { + let tokens:Token[] = [new Token(deltaOffset, '')]; - var modeTransitions:modes.IModeTransition[] = [ - { - startIndex: deltaOffset, - mode: mode - } - ]; + let modeTransitions:ModeTransition[] = [new ModeTransition(deltaOffset, modeId)]; return { tokens: tokens, diff --git a/src/vs/editor/common/modes/snippetsRegistry.ts b/src/vs/editor/common/modes/snippetsRegistry.ts index ac7da674f0c..3cdc0b5a732 100644 --- a/src/vs/editor/common/modes/snippetsRegistry.ts +++ b/src/vs/editor/common/modes/snippetsRegistry.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import {localize} from 'vs/nls'; import * as strings from 'vs/base/common/strings'; import {IReadOnlyModel, IPosition} from 'vs/editor/common/editorCommon'; import {ISuggestion} from 'vs/editor/common/modes'; @@ -28,16 +29,22 @@ export interface ISnippetsRegistry { /** * Get all snippet completions for the given position */ - getSnippetCompletions(model: IReadOnlyModel, position: IPosition, result: ISuggestion[]): void; + getSnippetCompletions(model: IReadOnlyModel, position: IPosition): ISuggestion[]; } export interface ISnippet { + name: string; + owner: string; prefix: string; description: string; codeSnippet: string; } +interface ISnippetSuggestion extends ISuggestion { + disambiguateLabel: string; +} + class SnippetsRegistry implements ISnippetsRegistry { private _snippets: { [modeId: string]: { [owner: string]: ISnippet[] } } = Object.create(null); @@ -62,14 +69,17 @@ class SnippetsRegistry implements ISnippetsRegistry { } } - public getSnippetCompletions(model: IReadOnlyModel, position: IPosition, result: ISuggestion[]): void { - let modeId = model.getModeId(); + public getSnippetCompletions(model: IReadOnlyModel, position: IPosition): ISuggestion[] { + const modeId = model.getModeId(); if (!this._snippets[modeId]) { return; } - let word = model.getWordAtPosition(position); - let currentWord = word ? word.word.substring(0, position.column - word.startColumn).toLowerCase() : ''; - let currentFullWord = getNonWhitespacePrefix(model, position).toLowerCase(); + + const result: ISnippetSuggestion[] = []; + + const word = model.getWordAtPosition(position); + const currentWord = word ? word.word.substring(0, position.column - word.startColumn).toLowerCase() : ''; + const currentFullWord = getNonWhitespacePrefix(model, position).toLowerCase(); this.visitSnippets(modeId, s => { let overwriteBefore: number; @@ -77,7 +87,7 @@ class SnippetsRegistry implements ISnippetsRegistry { // if there's no prefix, only show snippets at the beginning of the line, or after a whitespace overwriteBefore = 0; } else { - let label = s.prefix.toLowerCase(); + const label = s.prefix.toLowerCase(); // force that the current word or full word matches with the snippet prefix if (currentWord.length > 0 && strings.startsWith(label, currentWord)) { overwriteBefore = currentWord.length; @@ -87,17 +97,39 @@ class SnippetsRegistry implements ISnippetsRegistry { return true; } } + + // store in result result.push({ type: 'snippet', label: s.prefix, + get disambiguateLabel() { return localize('snippetSuggest.longLabel', "{0}, {1}", s.prefix, s.name); }, + detail: s.owner, documentation: s.description, insertText: s.codeSnippet, noAutoAccept: true, isTMSnippet: true, overwriteBefore }); + return true; }); + + // dismbiguate suggestions with same labels + let lastSuggestion: ISnippetSuggestion; + for (const suggestion of result.sort(SnippetsRegistry._compareSuggestionsByLabel)) { + if (lastSuggestion && lastSuggestion.label === suggestion.label) { + // use the disambiguateLabel instead of the actual label + lastSuggestion.label = lastSuggestion.disambiguateLabel; + suggestion.label = suggestion.disambiguateLabel; + } + lastSuggestion = suggestion; + } + + return result; + } + + private static _compareSuggestionsByLabel(a: ISuggestion, b: ISuggestion): number{ + return strings.compare(a.label, b.label); } } diff --git a/src/vs/editor/common/modes/supports.ts b/src/vs/editor/common/modes/supports.ts index 0729035dda8..4b3e0559774 100644 --- a/src/vs/editor/common/modes/supports.ts +++ b/src/vs/editor/common/modes/supports.ts @@ -7,22 +7,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import * as modes from 'vs/editor/common/modes'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; - -export class Token implements modes.IToken { - _tokenBrand: void; - - public startIndex:number; - public type:string; - - constructor(startIndex:number, type:string) { - this.startIndex = startIndex; - this.type = type; - } - - public toString(): string { - return '(' + this.startIndex + ', ' + this.type + ')'; - } -} +import {Token} from 'vs/editor/common/core/token'; export class LineTokens implements modes.ILineTokens { _lineTokensBrand: void; @@ -43,18 +28,18 @@ export class LineTokens implements modes.ILineTokens { } export function handleEvent(context:modes.ILineContext, offset:number, runner:(modeId:string, newContext:modes.ILineContext, offset:number)=>T):T { - var modeTransitions = context.modeTransitions; + let modeTransitions = context.modeTransitions; if (modeTransitions.length === 1) { return runner(modeTransitions[0].modeId, context, offset); } - var modeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, offset); - var nestedMode = modeTransitions[modeIndex].mode; - var modeStartIndex = modeTransitions[modeIndex].startIndex; + let modeIndex = ModeTransition.findIndexInSegmentsArray(modeTransitions, offset); + let nestedModeId = modeTransitions[modeIndex].modeId; + let modeStartIndex = modeTransitions[modeIndex].startIndex; - var firstTokenInModeIndex = context.findIndexOfOffset(modeStartIndex); - var nextCharacterAfterModeIndex = -1; - var nextTokenAfterMode = -1; + let firstTokenInModeIndex = context.findIndexOfOffset(modeStartIndex); + let nextCharacterAfterModeIndex = -1; + let nextTokenAfterMode = -1; if (modeIndex + 1 < modeTransitions.length) { nextTokenAfterMode = context.findIndexOfOffset(modeTransitions[modeIndex + 1].startIndex); nextCharacterAfterModeIndex = context.getTokenStartIndex(nextTokenAfterMode); @@ -63,9 +48,9 @@ export function handleEvent(context:modes.ILineContext, offset:number, runner nextCharacterAfterModeIndex = context.getLineContent().length; } - var firstTokenCharacterOffset = context.getTokenStartIndex(firstTokenInModeIndex); - var newCtx = new FilteredLineContext(context, nestedMode, firstTokenInModeIndex, nextTokenAfterMode, firstTokenCharacterOffset, nextCharacterAfterModeIndex); - return runner(nestedMode.getId(), newCtx, offset - firstTokenCharacterOffset); + let firstTokenCharacterOffset = context.getTokenStartIndex(firstTokenInModeIndex); + let newCtx = new FilteredLineContext(context, nestedModeId, firstTokenInModeIndex, nextTokenAfterMode, firstTokenCharacterOffset, nextCharacterAfterModeIndex); + return runner(nestedModeId, newCtx, offset - firstTokenCharacterOffset); } export class FilteredLineContext implements modes.ILineContext { @@ -78,11 +63,11 @@ export class FilteredLineContext implements modes.ILineContext { private _firstTokenCharacterOffset:number; private _nextCharacterAfterModeIndex:number; - constructor(actual:modes.ILineContext, mode:modes.IMode, + constructor(actual:modes.ILineContext, modeId:string, firstTokenInModeIndex:number, nextTokenAfterMode:number, firstTokenCharacterOffset:number, nextCharacterAfterModeIndex:number) { - this.modeTransitions = [new ModeTransition(0, mode)]; + this.modeTransitions = [new ModeTransition(0, modeId)]; this._actual = actual; this._firstTokenInModeIndex = firstTokenInModeIndex; this._nextTokenAfterMode = nextTokenAfterMode; @@ -107,17 +92,9 @@ export class FilteredLineContext implements modes.ILineContext { return this._actual.getTokenStartIndex(tokenIndex + this._firstTokenInModeIndex) - this._firstTokenCharacterOffset; } - public getTokenEndIndex(tokenIndex:number): number { - return this._actual.getTokenEndIndex(tokenIndex + this._firstTokenInModeIndex) - this._firstTokenCharacterOffset; - } - public getTokenType(tokenIndex:number): string { return this._actual.getTokenType(tokenIndex + this._firstTokenInModeIndex); } - - public getTokenText(tokenIndex:number): string { - return this._actual.getTokenText(tokenIndex + this._firstTokenInModeIndex); - } } const IGNORE_IN_TOKENS = /\b(comment|string|regex)\b/; diff --git a/src/vs/editor/common/modes/supports/electricCharacter.ts b/src/vs/editor/common/modes/supports/electricCharacter.ts index 1f9dadfb2cb..fbb9ce8c14b 100644 --- a/src/vs/editor/common/modes/supports/electricCharacter.ts +++ b/src/vs/editor/common/modes/supports/electricCharacter.ts @@ -143,6 +143,8 @@ export class Brackets { let r = BracketsUtils.findPrevBracketInToken(reversedBracketRegex, 1, lineText, tokenStart, tokenEnd); if (r) { let text = lineText.substring(r.startColumn - 1, r.endColumn - 1); + text = text.toLowerCase(); + let isOpen = this._richEditBrackets.textIsOpenBracket[text]; if (!isOpen) { return { diff --git a/src/vs/editor/common/modes/supports/richEditBrackets.ts b/src/vs/editor/common/modes/supports/richEditBrackets.ts index cfe3e9c713a..1c671c8069b 100644 --- a/src/vs/editor/common/modes/supports/richEditBrackets.ts +++ b/src/vs/editor/common/modes/supports/richEditBrackets.ts @@ -40,10 +40,10 @@ export class RichEditBrackets implements IRichEditBrackets { this.textIsOpenBracket = {}; this.maxBracketLength = 0; this.brackets.forEach((b) => { - this.textIsBracket[b.open] = b; - this.textIsBracket[b.close] = b; - this.textIsOpenBracket[b.open] = true; - this.textIsOpenBracket[b.close] = false; + this.textIsBracket[b.open.toLowerCase()] = b; + this.textIsBracket[b.close.toLowerCase()] = b; + this.textIsOpenBracket[b.open.toLowerCase()] = true; + this.textIsOpenBracket[b.close.toLowerCase()] = false; this.maxBracketLength = Math.max(this.maxBracketLength, b.open.length); this.maxBracketLength = Math.max(this.maxBracketLength, b.close.length); }); @@ -101,7 +101,7 @@ var getReversedRegexForBrackets = once( function createOrRegex(pieces:string[]): RegExp { let regexStr = `(${pieces.map(strings.escapeRegExpCharacters).join(')|(')})`; - return strings.createRegExp(regexStr, true, false, false, false); + return strings.createRegExp(regexStr, true); } function toReversedString(str:string): string { diff --git a/src/vs/editor/common/modes/supports/tokenizationSupport.ts b/src/vs/editor/common/modes/supports/tokenizationSupport.ts index dc48f5564fd..46ab89fffab 100644 --- a/src/vs/editor/common/modes/supports/tokenizationSupport.ts +++ b/src/vs/editor/common/modes/supports/tokenizationSupport.ts @@ -9,7 +9,8 @@ import {TPromise} from 'vs/base/common/winjs.base'; import * as modes from 'vs/editor/common/modes'; import {LineStream} from 'vs/editor/common/modes/lineStream'; import {NullMode, NullState, nullTokenize} from 'vs/editor/common/modes/nullMode'; -import {Token} from 'vs/editor/common/modes/supports'; +import {Token} from 'vs/editor/common/core/token'; +import {ModeTransition} from 'vs/editor/common/core/modeTransition'; export interface ILeavingNestedModeData { /** @@ -76,10 +77,12 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa public supportsNestedModes:boolean; private _mode:modes.IMode; + private _modeId:string; private _embeddedModesListeners: { [modeId:string]: IDisposable; }; constructor(mode:modes.IMode, customization:ITokenizationCustomization, supportsNestedModes:boolean) { this._mode = mode; + this._modeId = this._mode.getId(); this.customization = customization; this.supportsNestedModes = supportsNestedModes; this._embeddedModesListeners = {}; @@ -98,7 +101,7 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa } public dispose() : void { - for (var listener in this._embeddedModesListeners) { + for (let listener in this._embeddedModesListeners) { this._embeddedModesListeners[listener].dispose(); delete this._embeddedModesListeners[listener]; } @@ -120,44 +123,44 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa * Precondition is: nestedModeState.getMode() !== this * This means we are in a nested mode when parsing starts on this line. */ - private _nestedTokenize(buffer:string, nestedModeState:modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:modes.IToken[], prependModeTransitions:modes.IModeTransition[]):modes.ILineTokens { - var myStateBeforeNestedMode = nestedModeState.getStateData(); - var leavingNestedModeData = this.getLeavingNestedModeData(buffer, myStateBeforeNestedMode); + private _nestedTokenize(buffer:string, nestedModeState:modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Token[], prependModeTransitions:ModeTransition[]):modes.ILineTokens { + let myStateBeforeNestedMode = nestedModeState.getStateData(); + let leavingNestedModeData = this.getLeavingNestedModeData(buffer, myStateBeforeNestedMode); // Be sure to give every embedded mode the // opportunity to leave nested mode. // i.e. Don't go straight to the most nested mode - var stepOnceNestedState = nestedModeState; + let stepOnceNestedState = nestedModeState; while (stepOnceNestedState.getStateData() && stepOnceNestedState.getStateData().getMode() !== this._mode) { stepOnceNestedState = stepOnceNestedState.getStateData(); } - var nestedMode = stepOnceNestedState.getMode(); + let nestedMode = stepOnceNestedState.getMode(); if (!leavingNestedModeData) { // tokenization will not leave nested mode - var result:modes.ILineTokens; + let result:modes.ILineTokens; if (nestedMode.tokenizationSupport) { result = nestedMode.tokenizationSupport.tokenize(buffer, nestedModeState, deltaOffset, stopAtOffset); } else { // The nested mode doesn't have tokenization support, // unfortunatelly this means we have to fake it - result = nullTokenize(nestedMode, buffer, nestedModeState, deltaOffset); + result = nullTokenize(nestedMode.getId(), buffer, nestedModeState, deltaOffset); } result.tokens = prependTokens.concat(result.tokens); result.modeTransitions = prependModeTransitions.concat(result.modeTransitions); return result; } - var nestedModeBuffer = leavingNestedModeData.nestedModeBuffer; + let nestedModeBuffer = leavingNestedModeData.nestedModeBuffer; if (nestedModeBuffer.length > 0) { // Tokenize with the nested mode - var nestedModeLineTokens:modes.ILineTokens; + let nestedModeLineTokens:modes.ILineTokens; if (nestedMode.tokenizationSupport) { nestedModeLineTokens = nestedMode.tokenizationSupport.tokenize(nestedModeBuffer, nestedModeState, deltaOffset, stopAtOffset); } else { // The nested mode doesn't have tokenization support, // unfortunatelly this means we have to fake it - nestedModeLineTokens = nullTokenize(nestedMode, nestedModeBuffer, nestedModeState, deltaOffset); + nestedModeLineTokens = nullTokenize(nestedMode.getId(), nestedModeBuffer, nestedModeState, deltaOffset); } // Save last state of nested mode @@ -168,8 +171,8 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa prependModeTransitions = prependModeTransitions.concat(nestedModeLineTokens.modeTransitions); } - var bufferAfterNestedMode = leavingNestedModeData.bufferAfterNestedMode; - var myStateAfterNestedMode = leavingNestedModeData.stateAfterNestedMode; + let bufferAfterNestedMode = leavingNestedModeData.bufferAfterNestedMode; + let myStateAfterNestedMode = leavingNestedModeData.stateAfterNestedMode; myStateAfterNestedMode.setStateData(myStateBeforeNestedMode.getStateData()); this.onReturningFromNestedMode(myStateAfterNestedMode, nestedModeState); @@ -180,22 +183,19 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa * Precondition is: state.getMode() === this * This means we are in the current mode when parsing starts on this line. */ - private _myTokenize(buffer:string, myState:modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:modes.IToken[], prependModeTransitions:modes.IModeTransition[]):modes.ILineTokens { - var lineStream = new LineStream(buffer); - var tokenResult:modes.ITokenizationResult, beforeTokenizeStreamPos:number; - var previousType:string = null; - var retokenize:TPromise = null; + private _myTokenize(buffer:string, myState:modes.IState, deltaOffset:number, stopAtOffset:number, prependTokens:Token[], prependModeTransitions:ModeTransition[]):modes.ILineTokens { + let lineStream = new LineStream(buffer); + let tokenResult:modes.ITokenizationResult, beforeTokenizeStreamPos:number; + let previousType:string = null; + let retokenize:TPromise = null; myState = myState.clone(); - if (prependModeTransitions.length <= 0 || prependModeTransitions[prependModeTransitions.length-1].mode !== this._mode) { + if (prependModeTransitions.length <= 0 || prependModeTransitions[prependModeTransitions.length-1].modeId !== this._modeId) { // Avoid transitioning to the same mode (this can happen in case of empty embedded modes) - prependModeTransitions.push({ - startIndex: deltaOffset, - mode: this._mode - }); + prependModeTransitions.push(new ModeTransition(deltaOffset,this._modeId)); } - var maxPos = Math.min(stopAtOffset - deltaOffset, buffer.length); + let maxPos = Math.min(stopAtOffset - deltaOffset, buffer.length); while (lineStream.pos() < maxPos) { beforeTokenizeStreamPos = lineStream.pos(); @@ -212,7 +212,7 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa myState = tokenResult.nextState; } if (lineStream.pos() <= beforeTokenizeStreamPos) { - throw new Error('Stream did not advance while tokenizing. Mode id is ' + this._mode.getId() + ' (stuck at token type: "' + tokenResult.type + '", prepend tokens: "' + (prependTokens.map(t => t.type).join(',')) + '").'); + throw new Error('Stream did not advance while tokenizing. Mode id is ' + this._modeId + ' (stuck at token type: "' + tokenResult.type + '", prepend tokens: "' + (prependTokens.map(t => t.type).join(',')) + '").'); } } while (!tokenResult.type && tokenResult.type !== ''); @@ -223,14 +223,14 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa previousType = tokenResult.type; if (this.supportsNestedModes && this.enterNestedMode(myState)) { - var currentEmbeddedLevels = this._getEmbeddedLevel(myState); + let currentEmbeddedLevels = this._getEmbeddedLevel(myState); if (currentEmbeddedLevels < TokenizationSupport.MAX_EMBEDDED_LEVELS) { - var nestedModeState = this.getNestedModeInitialState(myState); + let nestedModeState = this.getNestedModeInitialState(myState); // Re-emit tokenizationSupport change events from all modes that I ever embedded - var embeddedMode = nestedModeState.state.getMode(); + let embeddedMode = nestedModeState.state.getMode(); if (typeof embeddedMode.addSupportChangedListener === 'function' && !this._embeddedModesListeners.hasOwnProperty(embeddedMode.getId())) { - var emitting = false; + let emitting = false; this._embeddedModesListeners[embeddedMode.getId()] = embeddedMode.addSupportChangedListener((e) => { if (emitting) { return; @@ -248,8 +248,8 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa if (!lineStream.eos()) { // There is content from the embedded mode - var restOfBuffer = buffer.substr(lineStream.pos()); - var result = this._nestedTokenize(restOfBuffer, nestedModeState.state, deltaOffset + lineStream.pos(), stopAtOffset, prependTokens, prependModeTransitions); + let restOfBuffer = buffer.substr(lineStream.pos()); + let result = this._nestedTokenize(restOfBuffer, nestedModeState.state, deltaOffset + lineStream.pos(), stopAtOffset, prependTokens, prependModeTransitions); result.retokenize = result.retokenize || nestedModeState.missingModePromise; return result; } else { @@ -271,7 +271,7 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa } private _getEmbeddedLevel(state:modes.IState): number { - var result = -1; + let result = -1; while(state) { result++; state = state.getStateData(); @@ -295,7 +295,7 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa } private static _validatedNestedMode(input:IEnteringNestedModeData): IEnteringNestedModeData { - var mode: modes.IMode = new NullMode(), + let mode: modes.IMode = new NullMode(), missingModePromise: TPromise = null; if (input && input.mode) { @@ -313,9 +313,9 @@ export class TokenizationSupport implements modes.ITokenizationSupport, IDisposa private getNestedModeInitialState(state:modes.IState): { state:modes.IState; missingModePromise:TPromise; } { if (this.defaults.getNestedModeInitialState) { - var nestedMode = TokenizationSupport._validatedNestedMode(this.getNestedMode(state)); - var missingModePromise = nestedMode.missingModePromise; - var nestedModeState: modes.IState; + let nestedMode = TokenizationSupport._validatedNestedMode(this.getNestedMode(state)); + let missingModePromise = nestedMode.missingModePromise; + let nestedModeState: modes.IState; if (nestedMode.mode.tokenizationSupport) { nestedModeState = nestedMode.mode.tokenizationSupport.getInitialState(); diff --git a/src/vs/editor/common/services/compatWorkerServiceMain.ts b/src/vs/editor/common/services/compatWorkerServiceMain.ts index cd27ec84904..97bbbaf0e65 100644 --- a/src/vs/editor/common/services/compatWorkerServiceMain.ts +++ b/src/vs/editor/common/services/compatWorkerServiceMain.ts @@ -27,7 +27,7 @@ export class MainThreadCompatWorkerService implements ICompatWorkerService { constructor( @IModelService modelService: IModelService ) { - this._workerFactory = new DefaultWorkerFactory(true); + this._workerFactory = new DefaultWorkerFactory('compatWorker', true); this._worker = null; this._workerCreatedPromise = new TPromise((c, e, p) => { this._triggerWorkersCreatedPromise = c; diff --git a/src/vs/editor/common/services/compatWorkerServiceWorker.ts b/src/vs/editor/common/services/compatWorkerServiceWorker.ts index 4a6b65b6b82..c683dd99bb4 100644 --- a/src/vs/editor/common/services/compatWorkerServiceWorker.ts +++ b/src/vs/editor/common/services/compatWorkerServiceWorker.ts @@ -8,7 +8,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {ICompatWorkerService, ICompatMode, IRawModelData} from 'vs/editor/common/services/compatWorkerService'; import {IResourceService} from 'vs/editor/common/services/resourceService'; import {ILanguageExtensionPoint, IModeService} from 'vs/editor/common/services/modeService'; -import {IMirrorModelEvents, MirrorModel} from 'vs/editor/common/model/mirrorModel'; +import {ICompatMirrorModelEvents, CompatMirrorModel} from 'vs/editor/common/model/compatMirrorModel'; import {onUnexpectedError} from 'vs/base/common/errors'; import URI from 'vs/base/common/uri'; import {ILegacyLanguageDefinition, ModesRegistry} from 'vs/editor/common/modes/modesRegistry'; @@ -63,7 +63,7 @@ export class CompatWorkerServiceWorker implements ICompatWorkerService { private _acceptNewModel(data: IRawModelData): TPromise { // Create & insert the mirror model eagerly in the resource service - let mirrorModel = new MirrorModel(data.versionId, data.value, null, data.url); + let mirrorModel = new CompatMirrorModel(data.versionId, data.value, null, data.url); this.resourceService.insert(mirrorModel.uri, mirrorModel); // Block worker execution until the mode is instantiated @@ -82,13 +82,13 @@ export class CompatWorkerServiceWorker implements ICompatWorkerService { } private _acceptDidDisposeModel(uri: URI): void { - let model = this.resourceService.get(uri); + let model = this.resourceService.get(uri); this.resourceService.remove(uri); model.dispose(); } - private _acceptModelEvents(uri: URI, events: IMirrorModelEvents): void { - let model = this.resourceService.get(uri); + private _acceptModelEvents(uri: URI, events: ICompatMirrorModelEvents): void { + let model = this.resourceService.get(uri); try { model.onEvents(events); } catch (err) { diff --git a/src/vs/editor/common/services/editorWorkerServiceImpl.ts b/src/vs/editor/common/services/editorWorkerServiceImpl.ts index 98fd75e07a7..e823cf81a05 100644 --- a/src/vs/editor/common/services/editorWorkerServiceImpl.ts +++ b/src/vs/editor/common/services/editorWorkerServiceImpl.ts @@ -106,7 +106,7 @@ class WorkerManager extends Disposable { public withWorker(): TPromise { this._lastWorkerUsedTime = (new Date()).getTime(); if (!this._editorWorkerClient) { - this._editorWorkerClient = new EditorWorkerClient(this._modelService); + this._editorWorkerClient = new EditorWorkerClient(this._modelService, 'editorWorkerService'); } return TPromise.as(this._editorWorkerClient); } @@ -253,10 +253,10 @@ export class EditorWorkerClient extends Disposable { private _workerFactory: DefaultWorkerFactory; private _modelManager: EditorModelManager; - constructor(modelService: IModelService) { + constructor(modelService: IModelService, label:string) { super(); this._modelService = modelService; - this._workerFactory = new DefaultWorkerFactory(/*do not use iframe*/false); + this._workerFactory = new DefaultWorkerFactory(label, /*do not use iframe*/false); this._worker = null; this._modelManager = null; } @@ -322,7 +322,7 @@ export class EditorWorkerClient extends Disposable { if (!model) { return null; } - let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); + let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getModeId()); let wordDef = wordDefRegExp.source; let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); return proxy.textualSuggest(resource.toString(), position, wordDef, wordDefFlags); @@ -335,7 +335,7 @@ export class EditorWorkerClient extends Disposable { if (!model) { return null; } - let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getMode()); + let wordDefRegExp = WordHelper.massageWordDefinitionOf(model.getModeId()); let wordDef = wordDefRegExp.source; let wordDefFlags = (wordDefRegExp.global ? 'g' : '') + (wordDefRegExp.ignoreCase ? 'i' : '') + (wordDefRegExp.multiline ? 'm' : ''); return proxy.navigateValueSet(resource.toString(), range, up, wordDef, wordDefFlags); diff --git a/src/vs/editor/common/services/modeServiceImpl.ts b/src/vs/editor/common/services/modeServiceImpl.ts index afe87ce987d..30fc407781f 100644 --- a/src/vs/editor/common/services/modeServiceImpl.ts +++ b/src/vs/editor/common/services/modeServiceImpl.ts @@ -23,7 +23,8 @@ import {LanguagesRegistry} from 'vs/editor/common/services/languagesRegistry'; import {ILanguageExtensionPoint, IValidLanguageExtensionPoint, IModeLookupResult, IModeService} from 'vs/editor/common/services/modeService'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {AbstractState} from 'vs/editor/common/modes/abstractState'; -import {Token} from 'vs/editor/common/modes/supports'; +import {Token} from 'vs/editor/common/core/token'; +import {ModeTransition} from 'vs/editor/common/core/modeTransition'; let languagesExtPoint = ExtensionsRegistry.registerExtensionPoint('languages', { description: nls.localize('vscode.extension.contributes.languages', 'Contributes language declarations.'), @@ -454,7 +455,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { public tokenize(line:string, state:modes.IState, offsetDelta: number = 0, stopAtOffset?: number): modes.ILineTokens { if (state instanceof TokenizationState2Adapter) { let actualResult = this._actual.tokenize(line, state.actual); - let tokens: modes.IToken[] = []; + let tokens: Token[] = []; actualResult.tokens.forEach((t) => { if (typeof t.scopes === 'string') { tokens.push(new Token(t.startIndex + offsetDelta, t.scopes)); @@ -468,7 +469,7 @@ export class TokenizationSupport2Adapter implements modes.ITokenizationSupport { tokens: tokens, actualStopOffset: offsetDelta + line.length, endState: new TokenizationState2Adapter(state.getMode(), actualResult.endState, state.getStateData()), - modeTransitions: [{ startIndex: offsetDelta, mode: state.getMode() }], + modeTransitions: [new ModeTransition(offsetDelta, state.getMode().getId())], }; } throw new Error('Unexpected state to tokenize with!'); @@ -547,7 +548,7 @@ export class MainThreadModeServiceImpl extends ModeServiceImpl { Object.keys(configuration.files.associations).forEach(pattern => { const langId = configuration.files.associations[pattern]; const mimetype = this.getMimeForMode(langId) || `text/x-${langId}`; - + mime.registerTextMime({ mime: mimetype, filepattern: pattern, userConfigured: true }); }); } diff --git a/src/vs/editor/common/services/modelServiceImpl.ts b/src/vs/editor/common/services/modelServiceImpl.ts index 74a1aafdd76..ca99a8a8246 100644 --- a/src/vs/editor/common/services/modelServiceImpl.ts +++ b/src/vs/editor/common/services/modelServiceImpl.ts @@ -8,7 +8,7 @@ import * as nls from 'vs/nls'; import network = require('vs/base/common/network'); import Event, {Emitter} from 'vs/base/common/event'; import {EmitterEvent} from 'vs/base/common/eventEmitter'; -import {MarkedString, textAsCodeBlock} from 'vs/base/common/htmlContent'; +import {MarkedString, textToMarkedString} from 'vs/base/common/htmlContent'; import {IDisposable} from 'vs/base/common/lifecycle'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; @@ -147,7 +147,7 @@ class ModelMarkerHandler { if (source) { message = nls.localize('sourceAndDiagMessage', "[{0}] {1}", source, message); } - hoverMessage = [textAsCodeBlock(message)]; + hoverMessage = [textToMarkedString(message)]; } return { diff --git a/src/vs/editor/common/services/resourceService.ts b/src/vs/editor/common/services/resourceService.ts index f130872e7d4..305b960858d 100644 --- a/src/vs/editor/common/services/resourceService.ts +++ b/src/vs/editor/common/services/resourceService.ts @@ -6,7 +6,10 @@ import URI from 'vs/base/common/uri'; import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; -import {IMirrorModel} from 'vs/editor/common/editorCommon'; +import {ITokenizedModel} from 'vs/editor/common/editorCommon'; + +export interface ICompatMirrorModel extends ITokenizedModel { +} // Resource Service @@ -15,7 +18,7 @@ export let IResourceService = createDecorator('resourceService export interface IResourceService { _serviceBrand: any; - insert(url: URI, element: IMirrorModel): void; - get(url: URI): IMirrorModel; + insert(url: URI, element: ICompatMirrorModel): void; + get(url: URI): ICompatMirrorModel; remove(url: URI): void; } diff --git a/src/vs/editor/common/services/resourceServiceImpl.ts b/src/vs/editor/common/services/resourceServiceImpl.ts index c81fd117af0..cd924f4ed3c 100644 --- a/src/vs/editor/common/services/resourceServiceImpl.ts +++ b/src/vs/editor/common/services/resourceServiceImpl.ts @@ -5,22 +5,21 @@ 'use strict'; import URI from 'vs/base/common/uri'; -import {IMirrorModel} from 'vs/editor/common/editorCommon'; -import {IResourceService} from 'vs/editor/common/services/resourceService'; +import {IResourceService, ICompatMirrorModel} from 'vs/editor/common/services/resourceService'; -class MirrorModelMap { +class CompatMirrorModelMap { - private _data: {[key:string]:IMirrorModel}; + private _data: {[key:string]:ICompatMirrorModel}; constructor() { this._data = {}; } - public set(key:string, data:IMirrorModel): void { + public set(key:string, data:ICompatMirrorModel): void { this._data[key] = data; } - public get(key:string): IMirrorModel { + public get(key:string): ICompatMirrorModel { return this._data[key] || null; } @@ -36,10 +35,10 @@ class MirrorModelMap { export class ResourceService implements IResourceService { public _serviceBrand: any; - private _map:MirrorModelMap; + private _map:CompatMirrorModelMap; constructor() { - this._map = new MirrorModelMap(); + this._map = new CompatMirrorModelMap(); } private static _anonymousModelId(input:string): string { @@ -63,7 +62,7 @@ export class ResourceService implements IResourceService { return r; } - public insert(uri:URI, element:IMirrorModel): void { + public insert(uri:URI, element:ICompatMirrorModel): void { let key = uri.toString(); if (this._map.contains(key)) { @@ -73,7 +72,7 @@ export class ResourceService implements IResourceService { this._map.set(key, element); } - public get(uri:URI):IMirrorModel { + public get(uri:URI):ICompatMirrorModel { let key = uri.toString(); return this._map.get(key); diff --git a/src/vs/editor/common/services/webWorker.ts b/src/vs/editor/common/services/webWorker.ts new file mode 100644 index 00000000000..e8a849cca41 --- /dev/null +++ b/src/vs/editor/common/services/webWorker.ts @@ -0,0 +1,106 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {ShallowCancelThenPromise} from 'vs/base/common/async'; +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {EditorWorkerClient} from 'vs/editor/common/services/editorWorkerServiceImpl'; + +/** + * Create a new web worker that has model syncing capabilities built in. + * Specify an AMD module to load that will `create` an object that will be proxied. + */ +export function createWebWorker(modelService: IModelService, opts:IWebWorkerOptions): MonacoWebWorker { + return new MonacoWebWorkerImpl(modelService, opts); +} + +/** + * A web worker that can provide a proxy to an arbitrary file. + */ +export interface MonacoWebWorker { + /** + * Terminate the web worker, thus invalidating the returned proxy. + */ + dispose(): void; + /** + * Get a proxy to the arbitrary loaded code. + */ + getProxy(): TPromise; + /** + * Synchronize (send) the models at `resources` to the web worker, + * making them available in the monaco.worker.getMirrorModels(). + */ + withSyncedResources(resources: URI[]): TPromise; +} + +export interface IWebWorkerOptions { + /** + * The AMD moduleId to load. + * It should export a function `create` that should return the exported proxy. + */ + moduleId: string; + /** + * The data to send over when calling create on the module. + */ + createData?: any; + /** + * A label to be used to identify the web worker for debugging purposes. + */ + label?: string; +} + +class MonacoWebWorkerImpl extends EditorWorkerClient implements MonacoWebWorker { + + private _foreignModuleId: string; + private _foreignModuleCreateData: any; + private _foreignProxy: TPromise; + + constructor(modelService: IModelService, opts:IWebWorkerOptions) { + super(modelService, opts.label); + this._foreignModuleId = opts.moduleId; + this._foreignModuleCreateData = opts.createData || null; + this._foreignProxy = null; + } + + private _getForeignProxy(): TPromise { + if (!this._foreignProxy) { + this._foreignProxy = new ShallowCancelThenPromise(this._getProxy().then((proxy) => { + return proxy.loadForeignModule(this._foreignModuleId, this._foreignModuleCreateData).then((foreignMethods) => { + this._foreignModuleId = null; + this._foreignModuleCreateData = null; + + let proxyMethodRequest = (method:string, args:any[]): TPromise => { + return proxy.fmr(method, args); + }; + + let createProxyMethod = (method:string, proxyMethodRequest:(method:string, args:any[])=>TPromise): Function => { + return function () { + let args = Array.prototype.slice.call(arguments, 0); + return proxyMethodRequest(method, args); + }; + }; + + let foreignProxy = {}; + for (let i = 0; i < foreignMethods.length; i++) { + foreignProxy[foreignMethods[i]] = createProxyMethod(foreignMethods[i], proxyMethodRequest); + } + + return foreignProxy; + }); + })); + } + return this._foreignProxy; + } + + public getProxy(): TPromise { + return this._getForeignProxy(); + } + + public withSyncedResources(resources: URI[]): TPromise { + return this._withSyncedResources(resources).then(_ => this.getProxy()); + } +} diff --git a/src/vs/editor/common/viewLayout/verticalObjects.ts b/src/vs/editor/common/viewLayout/verticalObjects.ts index c7cd9823a80..50beeed165a 100644 --- a/src/vs/editor/common/viewLayout/verticalObjects.ts +++ b/src/vs/editor/common/viewLayout/verticalObjects.ts @@ -302,7 +302,7 @@ export class VerticalObjects { } } - if (currentVerticalOffset > verticalOffset2) { + if (currentVerticalOffset >= verticalOffset2) { // We have covered the entire viewport area, time to stop endLineNumber = lineNumber; break; diff --git a/src/vs/editor/common/viewModel/viewModelDecorations.ts b/src/vs/editor/common/viewModel/viewModelDecorations.ts index 2e958243839..6c4cee767bb 100644 --- a/src/vs/editor/common/viewModel/viewModelDecorations.ts +++ b/src/vs/editor/common/viewModel/viewModelDecorations.ts @@ -254,7 +254,7 @@ export class ViewModelDecorations implements IDisposable { intersectedStartLineNumber = Math.max(startLineNumber, r.startLineNumber); intersectedEndLineNumber = Math.min(endLineNumber, r.endLineNumber); for (j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++) { - inlineDecorations[j - startLineNumber].push(inlineDecoration); + insert(inlineDecoration, inlineDecorations[j - startLineNumber]); } } if (d.options.beforeContentClassName && r.startLineNumber >= startLineNumber) { @@ -263,7 +263,7 @@ export class ViewModelDecorations implements IDisposable { new Range(r.startLineNumber, r.startColumn, r.startLineNumber, r.startColumn + 1), d.options.beforeContentClassName ); - inlineDecorations[r.startLineNumber - startLineNumber].push(inlineDecoration); + insert(inlineDecoration, inlineDecorations[r.startLineNumber - startLineNumber]); } if (d.options.afterContentClassName && r.endLineNumber <= endLineNumber) { if (r.endColumn > 1) { @@ -271,7 +271,7 @@ export class ViewModelDecorations implements IDisposable { new Range(r.endLineNumber, r.endColumn - 1, r.endLineNumber, r.endColumn), d.options.afterContentClassName ); - inlineDecorations[r.endLineNumber - startLineNumber].push(inlineDecoration); + insert(inlineDecoration, inlineDecorations[r.endLineNumber - startLineNumber]); } } } @@ -283,6 +283,23 @@ export class ViewModelDecorations implements IDisposable { } } +// insert sorted by startColumn. All decorations are already sorted but this is necessary +// as the startColumn of 'afterContent'-InlineDecoration is different from the decoration startColumn. +function insert(decoration: InlineDecoration, decorations: InlineDecoration[]) { + let startColumn = decoration.range.startColumn; + let last = decorations.length - 1; + let idx = last; + while (idx >= 0 && decorations[idx].range.startColumn > startColumn) { + idx--; + } + if (idx === last) { + decorations.push(decoration); + } else { + decorations.splice(idx + 1, 0, decoration); + } + +} + function hasInlineChanges(decoration:editorCommon.IModelDecoration) : boolean { let options = decoration.options; return !!(options.inlineClassName || options.beforeContentClassName || options.afterContentClassName); diff --git a/src/vs/editor/contrib/accessibility/browser/accessibility.ts b/src/vs/editor/contrib/accessibility/browser/accessibility.ts index d7204efb6cb..46b8577221b 100644 --- a/src/vs/editor/contrib/accessibility/browser/accessibility.ts +++ b/src/vs/editor/contrib/accessibility/browser/accessibility.ts @@ -22,12 +22,13 @@ import {GlobalScreenReaderNVDA} from 'vs/editor/common/config/commonEditorConfig import {ICommonCodeEditor, IEditorContribution, EditorContextKeys} from 'vs/editor/common/editorCommon'; import {editorAction, CommonEditorRegistry, EditorAction, EditorCommand, Command} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor, IOverlayWidget, IOverlayWidgetPosition} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {ToggleTabFocusModeAction} from 'vs/editor/contrib/toggleTabFocusMode/common/toggleTabFocusMode'; const CONTEXT_ACCESSIBILITY_WIDGET_VISIBLE = new RawContextKey('accessibilityHelpWidgetVisible', false); const TOGGLE_EXPERIMENTAL_SCREEN_READER_SUPPORT_COMMAND_ID = 'toggleExperimentalScreenReaderSupport'; +@editorContribution class AccessibilityHelpController extends Disposable implements IEditorContribution { private static ID = 'editor.contrib.accessibilityHelpController'; @@ -212,12 +213,12 @@ class ShowAccessibilityHelpAction extends EditorAction { public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): void { let controller = AccessibilityHelpController.get(editor); - controller.show(); + if (controller) { + controller.show(); + } } } -EditorBrowserRegistry.registerEditorContribution(AccessibilityHelpController); - const AccessibilityHelpCommand = EditorCommand.bindToContribution(AccessibilityHelpController.get); CommonEditorRegistry.registerEditorCommand(new AccessibilityHelpCommand({ diff --git a/src/vs/editor/contrib/codelens/browser/codelens.ts b/src/vs/editor/contrib/codelens/browser/codelens.ts index 4f01cea8943..c43f561d31d 100644 --- a/src/vs/editor/contrib/codelens/browser/codelens.ts +++ b/src/vs/editor/contrib/codelens/browser/codelens.ts @@ -20,7 +20,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import {CodeLensProviderRegistry, ICodeLensSymbol, Command} from 'vs/editor/common/modes'; import {IModelService} from 'vs/editor/common/services/modelService'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {ICodeLensData, getCodeLensData} from '../common/codelens'; @@ -339,6 +339,7 @@ class CodeLens { } } +@editorContribution export class CodeLensContribution implements editorCommon.IEditorContribution { private static ID: string = 'css.editor.codeLens'; @@ -519,7 +520,7 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { for (let symbol of symbols) { let line = symbol.symbol.range.startLineNumber; - if (line < 1 || line >= maxLineNumber) { + if (line < 1 || line > maxLineNumber) { // invalid code lens continue; } else if (lastGroup && lastGroup[lastGroup.length - 1].symbol.range.startLineNumber === line) { @@ -626,5 +627,3 @@ export class CodeLensContribution implements editorCommon.IEditorContribution { }); } } - -EditorBrowserRegistry.registerEditorContribution(CodeLensContribution); diff --git a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts index 39592578d6a..df3e48c3ef7 100644 --- a/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts +++ b/src/vs/editor/contrib/contextmenu/browser/contextmenu.ts @@ -16,17 +16,18 @@ import {IContextMenuService, IContextViewService} from 'vs/platform/contextview/ import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import {IMenuService, IMenu, MenuId} from 'vs/platform/actions/common/actions'; -import {ICommonCodeEditor, IEditorContribution, MouseTargetType, EditorContextKeys} from 'vs/editor/common/editorCommon'; +import {ICommonCodeEditor, IEditorContribution, MouseTargetType, EditorContextKeys, IScrollEvent} from 'vs/editor/common/editorCommon'; import {editorAction, ServicesAccessor, EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor, IEditorMouseEvent} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; -interface IPosition { +export interface IPosition { x: number; y: number; } -class ContextMenuController implements IEditorContribution { +@editorContribution +export class ContextMenuController implements IEditorContribution { private static ID = 'editor.contrib.contextmenu'; @@ -53,6 +54,11 @@ class ContextMenuController implements IEditorContribution { this._toDispose.push(this._contextMenu); this._toDispose.push(this._editor.onContextMenu((e: IEditorMouseEvent) => this._onContextMenu(e))); + this._toDispose.push(this._editor.onDidScrollChange((e: IScrollEvent) => { + if (this._contextMenuIsBeingShownCount > 0) { + this._contextViewService.hideContextView(); + } + })); this._toDispose.push(this._editor.onKeyDown((e: IKeyboardEvent) => { if (e.keyCode === KeyCode.ContextMenu) { // Chrome is funny like that @@ -158,6 +164,7 @@ class ContextMenuController implements IEditorContribution { } // Show menu + this._contextMenuIsBeingShownCount++; this._contextMenuService.showContextMenu({ getAnchor: () => menuPosition, @@ -231,9 +238,7 @@ class ShowContextMenu extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): void { - var contribution = ContextMenuController.get(editor); + let contribution = ContextMenuController.get(editor); contribution.showContextMenu(); } } - -EditorBrowserRegistry.registerEditorContribution(ContextMenuController); diff --git a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts index 54868e0fefd..71ce96aa6d3 100644 --- a/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts +++ b/src/vs/editor/contrib/defineKeybinding/browser/defineKeybinding.ts @@ -22,7 +22,7 @@ import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {editorAction, ServicesAccessor, EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {CodeSnippet} from 'vs/editor/contrib/snippet/common/snippet'; import {SnippetController} from 'vs/editor/contrib/snippet/common/snippetController'; import {SmartSnippetInserter} from 'vs/editor/contrib/defineKeybinding/common/smartSnippetInserter'; @@ -36,6 +36,7 @@ const NLS_KB_LAYOUT_ERROR_MESSAGE = nls.localize('defineKeybinding.kbLayoutError const INTERESTING_FILE = /keybindings\.json$/; +@editorContribution export class DefineKeybindingController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.defineKeybinding'; @@ -469,8 +470,10 @@ export class DefineKeybindingAction extends EditorAction { if (!isInterestingEditorModel(editor)) { return; } - var controller = DefineKeybindingController.get(editor); - controller.launch(); + let controller = DefineKeybindingController.get(editor); + if (controller) { + controller.launch(); + } } } @@ -486,5 +489,3 @@ function isInterestingEditorModel(editor:editorCommon.ICommonCodeEditor): boolea let url = model.uri.toString(); return INTERESTING_FILE.test(url); } - -EditorBrowserRegistry.registerEditorContribution(DefineKeybindingController); diff --git a/src/vs/editor/contrib/find/browser/find.ts b/src/vs/editor/contrib/find/browser/find.ts index 9ec31b782b7..6924b1dc3cf 100644 --- a/src/vs/editor/contrib/find/browser/find.ts +++ b/src/vs/editor/contrib/find/browser/find.ts @@ -8,10 +8,11 @@ import {IContextViewService} from 'vs/platform/contextview/browser/contextView'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {FindWidget, IFindController} from 'vs/editor/contrib/find/browser/findWidget'; -import {CommonFindController, FindStartFocusAction, IFindStartOptions, SelectionHighlighter} from 'vs/editor/contrib/find/common/findController'; +import {CommonFindController, FindStartFocusAction, IFindStartOptions} from 'vs/editor/contrib/find/common/findController'; +@editorContribution class FindController extends CommonFindController implements IFindController { private _widget: FindWidget; @@ -37,6 +38,3 @@ class FindController extends CommonFindController implements IFindController { } } } - -EditorBrowserRegistry.registerEditorContribution(FindController); -EditorBrowserRegistry.registerEditorContribution(SelectionHighlighter); diff --git a/src/vs/editor/contrib/find/browser/findWidget.css b/src/vs/editor/contrib/find/browser/findWidget.css index 3865e84c359..f80e0f456cc 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.css +++ b/src/vs/editor/contrib/find/browser/findWidget.css @@ -115,7 +115,6 @@ display: inline-block; margin: 0 1px 0 3px; padding: 2px 2px 0 2px; - min-width: 69px; height: 25px; vertical-align: middle; box-sizing: border-box; diff --git a/src/vs/editor/contrib/find/browser/findWidget.ts b/src/vs/editor/contrib/find/browser/findWidget.ts index 11f46a02584..74044d8acc5 100644 --- a/src/vs/editor/contrib/find/browser/findWidget.ts +++ b/src/vs/editor/contrib/find/browser/findWidget.ts @@ -43,6 +43,8 @@ const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "O const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}"); const NLS_NO_RESULTS = nls.localize('label.noResults', "No results"); +let MAX_MATCHES_COUNT_WIDTH = 69; + export class FindWidget extends Widget implements IOverlayWidget { private static ID = 'editor.contrib.findWidget'; @@ -200,6 +202,7 @@ export class FindWidget extends Widget implements IOverlayWidget { } private _updateMatchesCount(): void { + this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px'; if (this._state.matchesCount >= MATCHES_LIMIT) { this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE; } else { @@ -226,6 +229,8 @@ export class FindWidget extends Widget implements IOverlayWidget { label = NLS_NO_RESULTS; } this._matchesCount.appendChild(document.createTextNode(label)); + + MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth); } // ----- actions diff --git a/src/vs/editor/contrib/find/common/findController.ts b/src/vs/editor/contrib/find/common/findController.ts index 39cb266c1c2..65b3cb7fcb4 100644 --- a/src/vs/editor/contrib/find/common/findController.ts +++ b/src/vs/editor/contrib/find/common/findController.ts @@ -12,7 +12,7 @@ import {Range} from 'vs/editor/common/core/range'; import {Selection} from 'vs/editor/common/core/selection'; import * as strings from 'vs/base/common/strings'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {editorAction, commonEditorContribution, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {FIND_IDS, FindModelBoundToEditorModel} from 'vs/editor/contrib/find/common/findModel'; import {FindReplaceState, FindReplaceStateChangedEvent, INewFindReplaceState} from 'vs/editor/contrib/find/common/findState'; import {DocumentHighlightProviderRegistry} from 'vs/editor/common/modes'; @@ -255,19 +255,21 @@ export class StartFindAction extends EditorAction { public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { let controller = CommonFindController.get(editor); - controller.start({ - forceRevealReplace: false, - seedSearchStringFromSelection: true, - shouldFocus: FindStartFocusAction.FocusFindInput, - shouldAnimate: true - }); + if (controller) { + controller.start({ + forceRevealReplace: false, + seedSearchStringFromSelection: true, + shouldFocus: FindStartFocusAction.FocusFindInput, + shouldAnimate: true + }); + } } } export abstract class MatchFindAction extends EditorAction { public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { let controller = CommonFindController.get(editor); - if (!this._run(controller)) { + if (controller && !this._run(controller)) { controller.start({ forceRevealReplace: false, seedSearchStringFromSelection: (controller.getState().searchString.length === 0), @@ -328,6 +330,9 @@ export class PreviousMatchFindAction extends MatchFindAction { export abstract class SelectionMatchFindAction extends EditorAction { public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { let controller = CommonFindController.get(editor); + if (!controller) { + return; + } let selectionSearchString = controller.getSelectionSearchString(); if (selectionSearchString) { controller.setSearchString(selectionSearchString); @@ -411,12 +416,14 @@ export class StartFindReplaceAction extends EditorAction { } let controller = CommonFindController.get(editor); - controller.start({ - forceRevealReplace: true, - seedSearchStringFromSelection: true, - shouldFocus: FindStartFocusAction.FocusReplaceInput, - shouldAnimate: true - }); + if (controller) { + controller.start({ + forceRevealReplace: true, + seedSearchStringFromSelection: true, + shouldFocus: FindStartFocusAction.FocusReplaceInput, + shouldAnimate: true + }); + } } } @@ -430,6 +437,9 @@ export interface IMultiCursorFindResult { function multiCursorFind(editor:editorCommon.ICommonCodeEditor, changeFindSearchString:boolean): IMultiCursorFindResult { let controller = CommonFindController.get(editor); + if (!controller) { + return null; + } let state = controller.getState(); let searchText: string; let currentMatch: Selection; @@ -694,6 +704,7 @@ export class CompatChangeAll extends AbstractSelectHighlightsAction { } } +@commonEditorContribution export class SelectionHighlighter extends Disposable implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.selectionHighlighter'; diff --git a/src/vs/editor/contrib/find/common/findDecorations.ts b/src/vs/editor/contrib/find/common/findDecorations.ts index 364ea1f3c56..d1e853f2d2d 100644 --- a/src/vs/editor/contrib/find/common/findDecorations.ts +++ b/src/vs/editor/contrib/find/common/findDecorations.ts @@ -14,7 +14,7 @@ export class FindDecorations implements IDisposable { private _editor:editorCommon.ICommonCodeEditor; private _decorations:string[]; private _findScopeDecorationId:string; - private _lineHighlightDecorationId:string; + private _rangeHighlightDecorationId:string; private _highlightedDecorationId:string; private _startPosition:Position; @@ -22,7 +22,7 @@ export class FindDecorations implements IDisposable { this._editor = editor; this._decorations = []; this._findScopeDecorationId = null; - this._lineHighlightDecorationId = null; + this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; this._startPosition = this._editor.getPosition(); } @@ -33,7 +33,7 @@ export class FindDecorations implements IDisposable { this._editor = null; this._decorations = []; this._findScopeDecorationId = null; - this._lineHighlightDecorationId = null; + this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; this._startPosition = null; } @@ -41,7 +41,7 @@ export class FindDecorations implements IDisposable { public reset(): void { this._decorations = []; this._findScopeDecorationId = null; - this._lineHighlightDecorationId = null; + this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; } @@ -99,13 +99,13 @@ export class FindDecorations implements IDisposable { this._highlightedDecorationId = newCurrentDecorationId; changeAccessor.changeDecorationOptions(this._highlightedDecorationId, FindDecorations.createFindMatchDecorationOptions(true)); } - if (this._lineHighlightDecorationId !== null) { - changeAccessor.removeDecoration(this._lineHighlightDecorationId); - this._lineHighlightDecorationId = null; + if (this._rangeHighlightDecorationId !== null) { + changeAccessor.removeDecoration(this._rangeHighlightDecorationId); + this._rangeHighlightDecorationId = null; } if (newCurrentDecorationId !== null) { let rng = this._editor.getModel().getDecorationRange(newCurrentDecorationId); - this._lineHighlightDecorationId = changeAccessor.addDecoration(rng, FindDecorations.createLineHighlightDecoration()); + this._rangeHighlightDecorationId = changeAccessor.addDecoration(rng, FindDecorations.createRangeHighlightDecoration()); } }); } @@ -134,7 +134,7 @@ export class FindDecorations implements IDisposable { this._findScopeDecorationId = null; } this._decorations = tmpDecorations; - this._lineHighlightDecorationId = null; + this._rangeHighlightDecorationId = null; this._highlightedDecorationId = null; } @@ -144,8 +144,8 @@ export class FindDecorations implements IDisposable { if (this._findScopeDecorationId) { result.push(this._findScopeDecorationId); } - if (this._lineHighlightDecorationId) { - result.push(this._lineHighlightDecorationId); + if (this._rangeHighlightDecorationId) { + result.push(this._rangeHighlightDecorationId); } return result; } @@ -162,10 +162,10 @@ export class FindDecorations implements IDisposable { }; } - private static createLineHighlightDecoration(): editorCommon.IModelDecorationOptions { + private static createRangeHighlightDecoration(): editorCommon.IModelDecorationOptions { return { stickiness: editorCommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - className: 'lineHighlight', + className: 'rangeHighlight', isWholeLine: true }; } diff --git a/src/vs/editor/contrib/find/common/findModel.ts b/src/vs/editor/contrib/find/common/findModel.ts index f9d17383023..c27d94d6fa1 100644 --- a/src/vs/editor/contrib/find/common/findModel.ts +++ b/src/vs/editor/contrib/find/common/findModel.ts @@ -11,6 +11,7 @@ import {ReplaceCommand} from 'vs/editor/common/commands/replaceCommand'; import {Position} from 'vs/editor/common/core/position'; import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; +import {TextModel} from 'vs/editor/common/model/textModel'; import {FindDecorations} from './findDecorations'; import {FindReplaceState, FindReplaceStateChangedEvent} from './findState'; import {ReplaceAllCommand} from './replaceAllCommand'; @@ -315,9 +316,31 @@ export class FindModelBoundToEditorModel { this._moveToNextMatch(this._editor.getSelection().getEndPosition()); } - private getReplaceString(matchedString:string): string { - let replacePattern= new ReplacePattern(this._state.replaceString, {pattern: this._state.searchString, isRegExp: this._state.isRegex, isCaseSensitive: this._state.matchCase, isWordMatch: this._state.wholeWord}); - return replacePattern.getReplaceString(matchedString); + private getReplaceString(matchRange: Range): string { + if (this._state.isRegex) { + let regExp = TextModel.parseSearchRequest(this._state.searchString, this._state.isRegex, this._state.matchCase, this._state.wholeWord); + let replacePattern = new ReplacePattern(this._state.replaceString, true, regExp); + let model = this._editor.getModel(); + let matchedString = model.getValueInRange(matchRange); + let replacedString = replacePattern.getReplaceString(matchedString); + // If matched string is not matching then regex pattern has a lookahead expression + if (replacedString === null) { + replacedString = replacePattern.getReplaceString(this._getTextToMatch(matchRange, regExp)); + } + return replacedString; + } + return this._state.replaceString; + } + + private _getTextToMatch(matchRange: Range, regExp: RegExp): string { + let model = this._editor.getModel(); + // If regex is multiline, then return the text from starting of the matching range till end of the model. + if (regExp.multiline) { + let lineCount = model.getLineCount(); + return model.getValueInRange(new Range(matchRange.startLineNumber, matchRange.startColumn, lineCount, model.getLineMaxColumn(lineCount))); + } + // If regex is not multiline, then return the text from starting of the matching range till end of the line. + return model.getValueInRange(new Range(matchRange.startLineNumber, matchRange.startColumn, matchRange.endLineNumber, model.getLineMaxColumn(matchRange.endLineNumber))); } public replace(): void { @@ -326,12 +349,11 @@ export class FindModelBoundToEditorModel { } let selection = this._editor.getSelection(); - let selectionText = this._editor.getModel().getValueInRange(selection); let nextMatch = this._getNextMatch(selection.getStartPosition()); if (nextMatch) { if (selection.equalsRange(nextMatch)) { // selection sits on a find match => replace it! - let replaceString = this.getReplaceString(selectionText); + let replaceString = this.getReplaceString(selection); let command = new ReplaceCommand(selection, replaceString); @@ -356,7 +378,6 @@ export class FindModelBoundToEditorModel { return; } - let model = this._editor.getModel(); let findScope = this._decorations.getFindScope(); // Get all the ranges (even more than the highlighted ones) @@ -364,7 +385,7 @@ export class FindModelBoundToEditorModel { let replaceStrings:string[] = []; for (let i = 0, len = ranges.length; i < len; i++) { - replaceStrings.push(this.getReplaceString(model.getValueInRange(ranges[i]))); + replaceStrings.push(this.getReplaceString(ranges[i])); } let command = new ReplaceAllCommand(this._editor.getSelection(), ranges, replaceStrings); diff --git a/src/vs/editor/contrib/find/test/common/findModel.test.ts b/src/vs/editor/contrib/find/test/common/findModel.test.ts index b8ccb3928e8..9d36ef7553c 100644 --- a/src/vs/editor/contrib/find/test/common/findModel.test.ts +++ b/src/vs/editor/contrib/find/test/common/findModel.test.ts @@ -1792,4 +1792,136 @@ suite('FindModel', () => { findModel.dispose(); findState.dispose(); }); + + findTest('replace when search string has look ahed regex and replace string has capturing groups', (editor, cursor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'hel(lo)(?=\\sworld)', replaceString: 'hi$1', isRegex:true }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [8, 14, 8, 19] + ] + ); + + findModel.replace(); + + assertFindState( + editor, + [6, 14, 6, 19], + [6, 14, 6, 19], + [ + [6, 14, 6, 19], + [7, 14, 7, 19], + [8, 14, 8, 19] + ] + ); + assert.equal(editor.getModel().getLineContent(6), ' cout << "hello world, Hello!" << endl;'); + + findModel.replace(); + assertFindState( + editor, + [7, 14, 7, 19], + [7, 14, 7, 19], + [ + [7, 14, 7, 19], + [8, 14, 8, 19] + ] + ); + assert.equal(editor.getModel().getLineContent(6), ' cout << "hilo world, Hello!" << endl;'); + + findModel.replace(); + assertFindState( + editor, + [8, 14, 8, 19], + [8, 14, 8, 19], + [ + [8, 14, 8, 19] + ] + ); + assert.equal(editor.getModel().getLineContent(7), ' cout << "hilo world again" << endl;'); + + findModel.replace(); + assertFindState( + editor, + [8, 18, 8, 18], + null, + [ ] + ); + assert.equal(editor.getModel().getLineContent(8), ' cout << "hilo world again" << endl;'); + + findModel.dispose(); + findState.dispose(); + }); + + findTest('replaceAll when search string has look ahed regex and replace string has capturing groups', (editor, cursor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'wo(rl)d(?=.*;$)', replaceString: 'gi$1', isRegex:true }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ + [6, 20, 6, 25], + [7, 20, 7, 25], + [8, 20, 8, 25], + [9, 19, 9, 24] + ] + ); + + findModel.replaceAll(); + + assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); + assert.equal(editor.getModel().getLineContent(7), ' cout << "hello girl again" << endl;'); + assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;'); + assert.equal(editor.getModel().getLineContent(9), ' cout << "hellogirl again" << endl;'); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ ] + ); + + findModel.dispose(); + findState.dispose(); + }); + + findTest('replaceAll when search string is multiline and has look ahed regex and replace string has capturing groups', (editor, cursor) => { + let findState = new FindReplaceState(); + findState.change({ searchString: 'wo(rl)d(.*;\\n)(?=.*hello)', replaceString: 'gi$1$2', isRegex:true, matchCase:true }, false); + let findModel = new FindModelBoundToEditorModel(editor, findState); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ + [6, 20, 7, 1], + [8, 20, 9, 1] + ] + ); + + findModel.replaceAll(); + + assert.equal(editor.getModel().getLineContent(6), ' cout << "hello girl, Hello!" << endl;'); + assert.equal(editor.getModel().getLineContent(8), ' cout << "Hello girl again" << endl;'); + + assertFindState( + editor, + [1, 1, 1, 1], + null, + [ ] + ); + + findModel.dispose(); + findState.dispose(); + }); }); diff --git a/src/vs/editor/contrib/folding/browser/folding.ts b/src/vs/editor/contrib/folding/browser/folding.ts index fd121d5e701..cb2ece74832 100644 --- a/src/vs/editor/contrib/folding/browser/folding.ts +++ b/src/vs/editor/contrib/folding/browser/folding.ts @@ -16,13 +16,14 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import {Range} from 'vs/editor/common/core/range'; import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor, IEditorMouseEvent} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {CollapsibleRegion, getCollapsibleRegionsToFoldAtLine, getCollapsibleRegionsToUnfoldAtLine, doesLineBelongsToCollapsibleRegion, IFoldingRange} from 'vs/editor/contrib/folding/common/foldingModel'; import {computeRanges, limitByIndent} from 'vs/editor/contrib/folding/common/indentFoldStrategy'; import {Selection} from 'vs/editor/common/core/selection'; import EditorContextKeys = editorCommon.EditorContextKeys; +@editorContribution export class FoldingController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.folding'; @@ -241,22 +242,27 @@ export class FoldingController implements editorCommon.IEditorContribution { return; } let hasChanges = false; - let position = this.editor.getPosition(); - let lineNumber = position.lineNumber; + let selections = this.editor.getSelections(); + this.editor.changeDecorations(changeAccessor => { return this.decorations.forEach(dec => { if (dec.isCollapsed) { let decRange = dec.getDecorationRange(model); - // reveal if cursor in in one of the collapsed line (not the first) - if (decRange && decRange.startLineNumber < lineNumber && lineNumber <= decRange.endLineNumber) { - dec.setCollapsed(false, changeAccessor); - hasChanges = true; + if (decRange) { + for (let selection of selections) { + // reveal if cursor in in one of the collapsed line (not the first) + if (decRange.startLineNumber < selection.selectionStartLineNumber && selection.selectionStartLineNumber <= decRange.endLineNumber) { + dec.setCollapsed(false, changeAccessor); + hasChanges = true; + break; + } + } } } }); }); if (hasChanges) { - this.updateHiddenAreas(lineNumber); + this.updateHiddenAreas(this.editor.getPosition().lineNumber); } } @@ -501,8 +507,11 @@ abstract class FoldingAction extends EditorAction { abstract invoke(foldingController: FoldingController, editor:editorCommon.ICommonCodeEditor, args:T): void; public runEditorCommand(accessor:ServicesAccessor, editor: editorCommon.ICommonCodeEditor, args: T): void | TPromise { - this.reportTelemetry(accessor); let foldingController = FoldingController.get(editor); + if (!foldingController) { + return; + } + this.reportTelemetry(accessor); this.invoke(foldingController, editor, args); } @@ -701,8 +710,6 @@ class FoldLevelAction extends FoldingAction { } } -EditorBrowserRegistry.registerEditorContribution(FoldingController); - CommonEditorRegistry.registerEditorAction( new FoldLevelAction({ id: FoldLevelAction.ID(1), diff --git a/src/vs/editor/contrib/format/common/formatActions.ts b/src/vs/editor/contrib/format/common/formatActions.ts index 00e17df4632..ac483187a8e 100644 --- a/src/vs/editor/contrib/format/common/formatActions.ts +++ b/src/vs/editor/contrib/format/common/formatActions.ts @@ -11,7 +11,7 @@ import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ContextKeyExpr} from 'vs/platform/contextkey/common/contextkey'; -import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {editorAction, ServicesAccessor, EditorAction, commonEditorContribution} from 'vs/editor/common/editorCommonExtensions'; import {OnTypeFormattingEditProviderRegistry} from 'vs/editor/common/modes'; import {getOnTypeFormattingEdits, getDocumentFormattingEdits, getDocumentRangeFormattingEdits} from '../common/format'; import {EditOperationsCommand} from './formatCommand'; @@ -20,6 +20,7 @@ import {Selection} from 'vs/editor/common/core/selection'; import ModeContextKeys = editorCommon.ModeContextKeys; import EditorContextKeys = editorCommon.EditorContextKeys; +@commonEditorContribution class FormatOnType implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.autoFormat'; @@ -203,6 +204,3 @@ export class FormatAction extends EditorAction { editor.executeCommand(this.id, command); } } - -// register action -CommonEditorRegistry.registerEditorContribution(FormatOnType); diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index 895923a27a6..9817cc4957c 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -9,7 +9,7 @@ import 'vs/css!./goToDeclaration'; import * as nls from 'vs/nls'; import {Throttler} from 'vs/base/common/async'; import {onUnexpectedError} from 'vs/base/common/errors'; -import {MarkedString, textAsCodeBlock} from 'vs/base/common/htmlContent'; +import {MarkedString, textToMarkedString} from 'vs/base/common/htmlContent'; import {KeyCode, KeyMod} from 'vs/base/common/keyCodes'; import * as platform from 'vs/base/common/platform'; import Severity from 'vs/base/common/severity'; @@ -24,7 +24,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import {editorAction, IActionOptions, ServicesAccessor, EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {Location, DefinitionProviderRegistry} from 'vs/editor/common/modes'; import {ICodeEditor, IEditorMouseEvent, IMouseTarget} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {getDeclarationsAtPosition} from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; import {ReferencesController} from 'vs/editor/contrib/referenceSearch/browser/referencesController'; import {ReferencesModel} from 'vs/editor/contrib/referenceSearch/browser/referencesModel'; @@ -129,16 +129,18 @@ export class DefinitionAction extends EditorAction { } private _openInPeek(editorService:IEditorService, target: editorCommon.ICommonCodeEditor, model: ReferencesModel) { - let controller = ReferencesController.getController(target); - controller.toggleWidget(target.getSelection(), TPromise.as(model), { - getMetaTitle: (model) => { - return model.references.length > 1 && nls.localize('meta.title', " – {0} definitions", model.references.length); - }, - onGoto: (reference) => { - controller.closeWidget(); - return this._openReference(editorService, reference, false); - } - }); + let controller = ReferencesController.get(target); + if (controller) { + controller.toggleWidget(target.getSelection(), TPromise.as(model), { + getMetaTitle: (model) => { + return model.references.length > 1 && nls.localize('meta.title', " – {0} definitions", model.references.length); + }, + onGoto: (reference) => { + controller.closeWidget(); + return this._openReference(editorService, reference, false); + } + }); + } } } @@ -211,6 +213,7 @@ export class PeekDefinitionAction extends DefinitionAction { // --- Editor Contribution to goto definition using the mouse and a modifier key +@editorContribution class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.gotodefinitionwithmouse'; @@ -385,7 +388,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC value: text }; } else { - hoverMessage = textAsCodeBlock(text); + hoverMessage = textToMarkedString(text); } } @@ -487,5 +490,3 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC this.toUnhook = dispose(this.toUnhook); } } - -EditorBrowserRegistry.registerEditorContribution(GotoDefinitionWithMouseEditorContribution); diff --git a/src/vs/editor/contrib/gotoError/browser/gotoError.ts b/src/vs/editor/contrib/gotoError/browser/gotoError.ts index e016f6ce7fd..5d808c6b507 100644 --- a/src/vs/editor/contrib/gotoError/browser/gotoError.ts +++ b/src/vs/editor/contrib/gotoError/browser/gotoError.ts @@ -25,7 +25,7 @@ import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {editorAction, ServicesAccessor, IActionOptions, EditorAction, EditorCommand, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {ZoneWidget} from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import {getCodeActions, IQuickFix2} from 'vs/editor/contrib/quickFix/common/quickFix'; @@ -393,7 +393,12 @@ class MarkerNavigationWidget extends ZoneWidget { this._fixesWidget .update(newQuickFixes) - .then(() => this.show(this.position, this.computeRequiredHeight())); + .then(() => { + const selections = this.editor.getSelections(); + super.show(this.position, this.computeRequiredHeight()); + this.editor.setSelections(selections); + this.editor.focus(); + }); } private computeRequiredHeight() { @@ -433,7 +438,12 @@ class MarkerNavigationAction extends EditorAction { public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { const telemetryService = accessor.get(ITelemetryService); - let model = MarkerController.get(editor).getOrCreateModel(); + let controller = MarkerController.get(editor); + if (!controller) { + return; + } + + let model = controller.getOrCreateModel(); telemetryService.publicLog('zoneWidgetShown', { mode: 'go to error' }); if (model) { if (this._isNext) { @@ -446,6 +456,7 @@ class MarkerNavigationAction extends EditorAction { } } +@editorContribution class MarkerController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.markerController'; @@ -572,5 +583,3 @@ CommonEditorRegistry.registerEditorCommand(new MarkerCommand({ secondary: [KeyMod.Shift | KeyCode.Escape] } })); - -EditorBrowserRegistry.registerEditorContribution(MarkerController); \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/browser/hover.css b/src/vs/editor/contrib/hover/browser/hover.css index 142b345e803..1e830aac851 100644 --- a/src/vs/editor/contrib/hover/browser/hover.css +++ b/src/vs/editor/contrib/hover/browser/hover.css @@ -4,36 +4,73 @@ *--------------------------------------------------------------------------------------------*/ .monaco-editor-hover { - padding: 0 3px 0 3px; - border: 1px solid #CCC; - position: absolute; - margin-top: -1px; cursor: default; + position: absolute; + overflow: hidden; + background-color: #F3F3F3; + border: 1px solid #CCC; z-index: 50; - - -webkit-animation-duration: 0.15s; - -webkit-animation-name: fadeIn; - -moz-animation-duration: 0.15s; - -moz-animation-name: fadeIn; - -ms-animation-duration: 0.15s; - -ms-animation-name: fadeIn; - animation-duration: 0.15s; - animation-name: fadeIn; - -webkit-user-select: text; -ms-user-select: text; -khtml-user-select: text; -moz-user-select: text; -o-user-select: text; user-select: text; - - overflow: hidden; - - white-space: pre-wrap; + box-sizing: initial; + animation: fadein 100ms linear; + animation: fadein 100ms ease-in; + line-height: 1.5em; } -.monaco-editor.vs-dark .monaco-editor-hover { border-color: #555; } -.monaco-editor.vs-dark .monaco-editor-hover a { color: #1C5DAF; } +.monaco-editor-hover.hidden { + display: none; +} -.monaco-editor .hoverHighlight { background: rgba(173, 214, 255, 0.15); } -.monaco-editor.vs-dark .hoverHighlight { background: rgba(38, 79, 120, 0.25); } +.monaco-editor-hover .monaco-editor-hover-content { + max-width: 438px; +} + +.monaco-editor-hover .hover-row { + padding: 4px 5px; +} + +.monaco-editor-hover .hover-row:not(:first-child):not(:empty) { + border-top: 1px solid rgba(204, 204, 204, 0.5); +} + +.monaco-editor-hover p, +.monaco-editor-hover ul { + margin: 8px 0; +} + +.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 { + list-style-position: inside; + -webkit-padding-start: 6px; +} + +.monaco-editor-hover code { + background-color: rgba(132, 132, 132, 0.14); + border-radius: 3px; + padding: 0 0.4em; +} + +.monaco-editor .hoverHighlight { + background: rgba(173, 214, 255, 0.15); +} + +.monaco-editor.vs-dark .monaco-editor-hover { background-color: #2D2D30; border-color: #555; } +.monaco-editor.vs-dark .monaco-editor-hover a { color: #1C5DAF; } +.monaco-editor.vs-dark .monaco-editor-hover .hover-row:not(:first-child):not(:empty) { border-color: rgba(85,85,85,0.5); } +.monaco-editor.vs-dark .hoverHighlight { background: rgba(38, 79, 120, 0.25); } + +.monaco-editor.hc-black .monaco-editor-hover { background-color: #0C141F; } \ No newline at end of file diff --git a/src/vs/editor/contrib/hover/browser/hover.ts b/src/vs/editor/contrib/hover/browser/hover.ts index f4ca8b9a3ef..f6b0be75137 100644 --- a/src/vs/editor/contrib/hover/browser/hover.ts +++ b/src/vs/editor/contrib/hover/browser/hover.ts @@ -16,13 +16,14 @@ import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {editorAction, ServicesAccessor, EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor, IEditorMouseEvent} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {ModesContentHoverWidget} from './modesContentHover'; import {ModesGlyphHoverWidget} from './modesGlyphHover'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import EditorContextKeys = editorCommon.EditorContextKeys; +@editorContribution class ModesHoverController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.hover'; @@ -59,7 +60,7 @@ class ModesHoverController implements editorCommon.IEditorContribution { })); this._contentWidget = new ModesContentHoverWidget(editor, openerService, modeService); - this._glyphWidget = new ModesGlyphHoverWidget(editor); + this._glyphWidget = new ModesGlyphHoverWidget(editor, openerService, modeService); } } @@ -160,10 +161,12 @@ class ShowHoverAction extends EditorAction { } public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { + let controller = ModesHoverController.get(editor); + if (!controller) { + return; + } const position = editor.getPosition(); const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); - ModesHoverController.get(editor).showContentHover(range, true); + controller.showContentHover(range, true); } } - -EditorBrowserRegistry.registerEditorContribution(ModesHoverController); diff --git a/src/vs/editor/contrib/hover/browser/hoverWidgets.ts b/src/vs/editor/contrib/hover/browser/hoverWidgets.ts index 579a41d4290..06a3bab592f 100644 --- a/src/vs/editor/contrib/hover/browser/hoverWidgets.ts +++ b/src/vs/editor/contrib/hover/browser/hoverWidgets.ts @@ -6,25 +6,38 @@ import {CommonKeybindings} from 'vs/base/common/keyCodes'; import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent'; -import {StyleMutator} from 'vs/base/browser/styleMutator'; +import {toggleClass} from 'vs/base/browser/dom'; import {Position} from 'vs/editor/common/core/position'; import {IPosition, IConfigurationChangedEvent} from 'vs/editor/common/editorCommon'; import * as editorBrowser from 'vs/editor/browser/editorBrowser'; import {Widget} from 'vs/base/browser/ui/widget'; +import {DomScrollableElement} from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; export class ContentHoverWidget extends Widget implements editorBrowser.IContentWidget { private _id: string; protected _editor: editorBrowser.ICodeEditor; - protected _isVisible: boolean; + private _isVisible: boolean; private _containerDomNode: HTMLElement; - protected _domNode: HTMLElement; + private _domNode: HTMLElement; protected _showAtPosition: Position; private _stoleFocus: boolean; + private scrollbar: DomScrollableElement; + private disposables: IDisposable[] = []; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; + protected get isVisible(): boolean { + return this._isVisible; + } + + protected set isVisible(value: boolean) { + this._isVisible = value; + toggleClass(this._containerDomNode, 'hidden', !this._isVisible); + } + constructor(id: string, editor: editorBrowser.ICodeEditor) { super(); this._id = id; @@ -32,25 +45,31 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent this._isVisible = false; this._containerDomNode = document.createElement('div'); - this._containerDomNode.className = 'monaco-editor-hover monaco-editor-background'; + this._containerDomNode.className = 'monaco-editor-hover hidden'; + this._containerDomNode.tabIndex = 0; this._domNode = document.createElement('div'); - this._domNode.style.display = 'inline-block'; - this._containerDomNode.appendChild(this._domNode); - this._containerDomNode.tabIndex = 0; + this._domNode.className = 'monaco-editor-hover-content'; + + this.scrollbar = new DomScrollableElement(this._domNode, { canUseTranslate3d: false }); + this.disposables.push(this.scrollbar); + this._containerDomNode.appendChild(this.scrollbar.getDomNode()); + this.onkeydown(this._containerDomNode, (e: IKeyboardEvent) => { if (e.equals(CommonKeybindings.ESCAPE)) { this.hide(); } }); - this._editor.applyFontInfo(this._domNode); this._register(this._editor.onDidChangeConfiguration((e:IConfigurationChangedEvent) => { if (e.fontInfo) { - this._editor.applyFontInfo(this._domNode); + this.updateFont(); } })); + this._editor.onDidLayoutChange(e => this.updateMaxHeight()); + + this.updateMaxHeight(); this._editor.addContentWidget(this); this._showAtPosition = null; } @@ -64,22 +83,9 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent } public showAt(position:IPosition, focus: boolean): void { - // Position has changed this._showAtPosition = new Position(position.lineNumber, position.column); - this._isVisible = true; - var editorMaxWidth = Math.min(800, parseInt(this._containerDomNode.style.maxWidth, 10)); - - // When scrolled horizontally, the div does not want to occupy entire visible area. - StyleMutator.setWidth(this._containerDomNode, editorMaxWidth); - StyleMutator.setHeight(this._containerDomNode, 0); - StyleMutator.setLeft(this._containerDomNode, 0); - - var renderedWidth = Math.min(editorMaxWidth, this._domNode.clientWidth + 5); - var renderedHeight = this._domNode.clientHeight + 1; - - StyleMutator.setWidth(this._containerDomNode, renderedWidth); - StyleMutator.setHeight(this._containerDomNode, renderedHeight); + this.isVisible = true; this._editor.layoutContentWidget(this); // Simply force a synchronous render on the editor @@ -92,10 +98,12 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent } public hide(): void { - if (!this._isVisible) { + if (!this.isVisible) { return; } - this._isVisible = false; + + this.isVisible = false; + this._editor.layoutContentWidget(this); if (this._stoleFocus) { this._editor.focus(); @@ -103,7 +111,7 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent } public getPosition():editorBrowser.IContentWidgetPosition { - if (this._isVisible) { + if (this.isVisible) { return { position: this._showAtPosition, preference: [ @@ -117,16 +125,39 @@ export class ContentHoverWidget extends Widget implements editorBrowser.IContent public dispose(): void { this._editor.removeContentWidget(this); + this.disposables = dispose(this.disposables); super.dispose(); } + + private updateFont(): void { + const codeTags: HTMLPhraseElement[] = Array.prototype.slice.call(this._domNode.getElementsByTagName('code')); + const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code')); + + [...codeTags, ...codeClasses].forEach(node => this._editor.applyFontInfo(node)); + } + + protected updateContents(node: Node): void { + this._domNode.textContent = ''; + this._domNode.appendChild(node); + this.updateFont(); + + this._editor.layoutContentWidget(this); + this.scrollbar.scanDomNode(); + } + + private updateMaxHeight(): void { + const height = Math.max(this._editor.getLayoutInfo().height / 4, 250); + + this._domNode.style.maxHeight = `${ height }px`; + } } export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWidget { private _id: string; protected _editor: editorBrowser.ICodeEditor; - protected _isVisible: boolean; - protected _domNode: HTMLElement; + private _isVisible: boolean; + private _domNode: HTMLElement; protected _showAtLineNumber: number; constructor(id: string, editor: editorBrowser.ICodeEditor) { @@ -136,23 +167,30 @@ export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWi this._isVisible = false; this._domNode = document.createElement('div'); - this._domNode.className = 'monaco-editor-hover monaco-editor-background'; - this._domNode.style.display = 'none'; + this._domNode.className = 'monaco-editor-hover hidden'; this._domNode.setAttribute('aria-hidden', 'true'); this._domNode.setAttribute('role', 'presentation'); this._showAtLineNumber = -1; - this._editor.applyFontInfo(this._domNode); this._register(this._editor.onDidChangeConfiguration((e:IConfigurationChangedEvent) => { if (e.fontInfo) { - this._editor.applyFontInfo(this._domNode); + this.updateFont(); } })); this._editor.addOverlayWidget(this); } + protected get isVisible(): boolean { + return this._isVisible; + } + + protected set isVisible(value: boolean) { + this._isVisible = value; + toggleClass(this._domNode, 'hidden', !this._isVisible); + } + public getId(): string { return this._id; } @@ -164,25 +202,26 @@ export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWi public showAt(lineNumber: number): void { this._showAtLineNumber = lineNumber; - if (!this._isVisible) { - this._isVisible = true; - this._domNode.style.display = 'block'; + if (!this.isVisible) { + this.isVisible = true; } - var editorLayout = this._editor.getLayoutInfo(); - var topForLineNumber = this._editor.getTopForLineNumber(this._showAtLineNumber); - var editorScrollTop = this._editor.getScrollTop(); + const editorLayout = this._editor.getLayoutInfo(); + const topForLineNumber = this._editor.getTopForLineNumber(this._showAtLineNumber); + const editorScrollTop = this._editor.getScrollTop(); + const lineHeight = this._editor.getConfiguration().lineHeight; + const nodeHeight = this._domNode.clientHeight; + const top = topForLineNumber - editorScrollTop - ((nodeHeight - lineHeight) / 2); - this._domNode.style.left = (editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth) + 'px'; - this._domNode.style.top = (topForLineNumber - editorScrollTop) + 'px'; + this._domNode.style.left = `${ editorLayout.glyphMarginLeft + editorLayout.glyphMarginWidth }px`; + this._domNode.style.top = `${ Math.max(Math.round(top), 0) }px`; } public hide(): void { - if (!this._isVisible) { + if (!this.isVisible) { return; } - this._isVisible = false; - this._domNode.style.display = 'none'; + this.isVisible = false; } public getPosition():editorBrowser.IOverlayWidgetPosition { @@ -193,4 +232,17 @@ export class GlyphHoverWidget extends Widget implements editorBrowser.IOverlayWi this._editor.removeOverlayWidget(this); super.dispose(); } + + private updateFont(): void { + const codeTags: HTMLPhraseElement[] = Array.prototype.slice.call(this._domNode.getElementsByTagName('code')); + const codeClasses: HTMLElement[] = Array.prototype.slice.call(this._domNode.getElementsByClassName('code')); + + [...codeTags, ...codeClasses].forEach(node => this._editor.applyFontInfo(node)); + } + + protected updateContents(node: Node): void { + this._domNode.textContent = ''; + this._domNode.appendChild(node); + this.updateFont(); + } } diff --git a/src/vs/editor/contrib/hover/browser/modesContentHover.ts b/src/vs/editor/contrib/hover/browser/modesContentHover.ts index 734b56205f1..ee34fbc1c66 100644 --- a/src/vs/editor/contrib/hover/browser/modesContentHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesContentHover.ts @@ -7,6 +7,7 @@ import 'vs/css!vs/base/browser/ui/progressbar/progressbar'; import * as nls from 'vs/nls'; import URI from 'vs/base/common/uri'; +import {$} from 'vs/base/browser/dom'; import {TPromise} from 'vs/base/common/winjs.base'; import {renderMarkedString} from 'vs/base/browser/htmlContentRenderer'; import {IOpenerService, NullOpenerService} from 'vs/platform/opener/common/opener'; @@ -20,7 +21,7 @@ import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; import {getHover} from '../common/hover'; import {HoverOperation, IHoverComputer} from './hoverOperation'; import {ContentHoverWidget} from './hoverWidgets'; -import {textAsCodeBlock, MarkedString} from 'vs/base/common/htmlContent'; +import {textToMarkedString, MarkedString} from 'vs/base/common/htmlContent'; class ModesContentComputer implements IHoverComputer { @@ -112,7 +113,7 @@ class ModesContentComputer implements IHoverComputer { private _getLoadingMessage(): Hover { return { range: this._range, - contents: [textAsCodeBlock(nls.localize('modesContentHover.loading', "Loading..."))] + contents: [textToMarkedString(nls.localize('modesContentHover.loading', "Loading..."))] }; } } @@ -156,7 +157,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { if (this._isChangingDecorations) { return; } - if (this._isVisible) { + if (this.isVisible) { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); @@ -175,7 +176,7 @@ export class ModesContentHoverWidget extends ContentHoverWidget { this._hoverOperation.cancel(); - if (this._isVisible) { + if (this.isVisible) { // The range might have changed, but the hover is visible // Instead of hiding it completely, filter out messages that are still in the new range and // kick off a new computation @@ -238,40 +239,35 @@ export class ModesContentHoverWidget extends ContentHoverWidget { renderColumn = Math.min(renderColumn, msg.range.startColumn); highlightRange = Range.plusRange(highlightRange, msg.range); - var row:HTMLElement = document.createElement('div'); - var container = row; + msg.contents + .filter(contents => !!contents) + .forEach(contents => { + const renderedContents = renderMarkedString(contents, { + actionCallback: (content) => { + this._openerService.open(URI.parse(content)); + }, + codeBlockRenderer: (modeId, value): string | TPromise => { + const mode = this._modeService.getMode(modeId || this._editor.getModel().getModeId()); + const getMode = mode => mode ? TPromise.as(mode) : this._modeService.getOrCreateMode(modeId); - if (msg.contents && msg.contents.length > 0) { - container.appendChild(renderMarkedString(msg.contents, { - actionCallback: (content) => { - this._openerService.open(URI.parse(content)); - }, - codeBlockRenderer: (modeId, value): string | TPromise => { - - let mode = this._modeService.getMode(modeId); - if (mode) { - return tokenizeToString(value, mode); + return getMode(mode) + .then(null, err => null) + .then(mode => `
${ tokenizeToString(value, mode) }
`); } + }); - return this._modeService.getOrCreateMode(modeId).then( - mode => tokenizeToString(value, mode), - err => tokenizeToString(value, null)); - } - })); - } - - fragment.appendChild(row); + fragment.appendChild($('div.hover-row', null, renderedContents)); + }); }); - this._domNode.textContent = ''; - this._domNode.appendChild(fragment); - // show this.showAt({ lineNumber: renderRange.startLineNumber, column: renderColumn }, this._shouldFocus); + this.updateContents(fragment); + this._isChangingDecorations = true; this._highlightDecorations = this._editor.deltaDecorations(this._highlightDecorations, [{ range: highlightRange, diff --git a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts index 1e25ca5b47c..44e4ab30fd1 100644 --- a/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts +++ b/src/vs/editor/contrib/hover/browser/modesGlyphHover.ts @@ -8,6 +8,13 @@ import {IModelDecoration, IRange} from 'vs/editor/common/editorCommon'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; import {HoverOperation, IHoverComputer} from './hoverOperation'; import {GlyphHoverWidget} from './hoverWidgets'; +import {$} from 'vs/base/browser/dom'; +import {renderMarkedString} from 'vs/base/browser/htmlContentRenderer'; +import {IOpenerService, NullOpenerService} from 'vs/platform/opener/common/opener'; +import URI from 'vs/base/common/uri'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {IModeService} from 'vs/editor/common/services/modeService'; +import {tokenizeToString} from 'vs/editor/common/modes/textToHtmlTokenizer'; export interface IHoverMessage { value?: string; @@ -77,9 +84,11 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { private _computer: MarginComputer; private _hoverOperation: HoverOperation; - constructor(editor: ICodeEditor) { + constructor(editor: ICodeEditor, private openerService: IOpenerService, private modeService: IModeService) { super(ModesGlyphHoverWidget.ID, editor); + this.openerService = openerService || NullOpenerService; + this._lastLineNumber = -1; this._computer = new MarginComputer(this._editor); @@ -99,7 +108,7 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { } public onModelDecorationsChanged(): void { - if (this._isVisible) { + if (this.isVisible) { // The decorations have changed and the hover is visible, // we need to recompute the displayed text this._hoverOperation.cancel(); @@ -141,29 +150,25 @@ export class ModesGlyphHoverWidget extends GlyphHoverWidget { private _renderMessages(lineNumber: number, messages: IHoverMessage[]): void { - var fragment = document.createDocumentFragment(); + const fragment = document.createDocumentFragment(); messages.forEach((msg) => { + const renderedContents = renderMarkedString(msg.value, { + actionCallback: content => this.openerService.open(URI.parse(content)), + codeBlockRenderer: (modeId, value): string | TPromise => { + const mode = this.modeService.getMode(modeId || this._editor.getModel().getModeId()); + const getMode = mode => mode ? TPromise.as(mode) : this.modeService.getOrCreateMode(modeId); - var row:HTMLElement = document.createElement('div'); - var span:HTMLElement = null; + return getMode(mode) + .then(null, err => null) + .then(mode => `
${ tokenizeToString(value, mode) }
`); + } + }); - if (msg.className) { - span = document.createElement('span'); - span.textContent = msg.value; - span.className = msg.className; - row.appendChild(span); - } else { - row.textContent = msg.value; - } - - fragment.appendChild(row); + fragment.appendChild($('div.hover-row', null, renderedContents)); }); - this._domNode.textContent = ''; - this._domNode.appendChild(fragment); - - // show + this.updateContents(fragment); this.showAt(lineNumber); } } \ No newline at end of file diff --git a/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.ts b/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.ts index 4aa2689f3fa..d93b4c800ce 100644 --- a/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.ts +++ b/src/vs/editor/contrib/iPadShowKeyboard/browser/iPadShowKeyboard.ts @@ -11,8 +11,9 @@ import * as browser from 'vs/base/browser/browser'; import * as dom from 'vs/base/browser/dom'; import {IEditorContribution} from 'vs/editor/common/editorCommon'; import {ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, OverlayWidgetPositionPreference} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; +@editorContribution export class IPadShowKeyboard implements IEditorContribution { private static ID = 'editor.contrib.iPadShowKeyboard'; @@ -105,5 +106,3 @@ class ShowKeyboardWidget implements IOverlayWidget { }; } } - -EditorBrowserRegistry.registerEditorContribution(IPadShowKeyboard); \ No newline at end of file diff --git a/src/vs/editor/contrib/inPlaceReplace/common/inPlaceReplace.ts b/src/vs/editor/contrib/inPlaceReplace/common/inPlaceReplace.ts index 52c85261fac..4191488a799 100644 --- a/src/vs/editor/contrib/inPlaceReplace/common/inPlaceReplace.ts +++ b/src/vs/editor/contrib/inPlaceReplace/common/inPlaceReplace.ts @@ -9,11 +9,12 @@ import {KeyCode, KeyMod} from 'vs/base/common/keyCodes'; import {TPromise} from 'vs/base/common/winjs.base'; import {Range} from 'vs/editor/common/core/range'; import {EditorContextKeys, IEditorContribution, CodeEditorStateFlag, ICommonCodeEditor, IModelDecorationsChangeAccessor} from 'vs/editor/common/editorCommon'; -import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {editorAction, ServicesAccessor, EditorAction, commonEditorContribution} from 'vs/editor/common/editorCommonExtensions'; import {IInplaceReplaceSupportResult} from 'vs/editor/common/modes'; import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService'; import {InPlaceReplaceCommand} from './inPlaceReplaceCommand'; +@commonEditorContribution class InPlaceReplaceController implements IEditorContribution { private static ID = 'editor.contrib.inPlaceReplaceController'; @@ -136,7 +137,11 @@ class InPlaceReplaceUp extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): TPromise { - return InPlaceReplaceController.get(editor).run(this.id, true); + let controller = InPlaceReplaceController.get(editor); + if (!controller) { + return; + } + return controller.run(this.id, true); } } @@ -157,8 +162,10 @@ class InPlaceReplaceDown extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): TPromise { - return InPlaceReplaceController.get(editor).run(this.id, false); + let controller = InPlaceReplaceController.get(editor); + if (!controller) { + return; + } + return controller.run(this.id, false); } } - -CommonEditorRegistry.registerEditorContribution(InPlaceReplaceController); diff --git a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts index cf636c0ef3c..f4a13a91cea 100644 --- a/src/vs/editor/contrib/linesOperations/common/linesOperations.ts +++ b/src/vs/editor/contrib/linesOperations/common/linesOperations.ts @@ -157,11 +157,7 @@ class SortLinesAscendingAction extends AbstractSortLinesAction { id: 'editor.action.sortLinesAscending', label: nls.localize('lines.sortAscending', "Sort Lines Ascending"), alias: 'Sort Lines Ascending', - precondition: EditorContextKeys.Writable, - kbOpts: { - kbExpr: EditorContextKeys.TextFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_2 - } + precondition: EditorContextKeys.Writable }); } } @@ -173,11 +169,7 @@ class SortLinesDescendingAction extends AbstractSortLinesAction { id: 'editor.action.sortLinesDescending', label: nls.localize('lines.sortDescending', "Sort Lines Descending"), alias: 'Sort Lines Descending', - precondition: EditorContextKeys.Writable, - kbOpts: { - kbExpr: EditorContextKeys.TextFocus, - primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_3 - } + precondition: EditorContextKeys.Writable }); } } diff --git a/src/vs/editor/contrib/links/browser/links.ts b/src/vs/editor/contrib/links/browser/links.ts index 4f02b2ee51c..569f2f5c0b3 100644 --- a/src/vs/editor/contrib/links/browser/links.ts +++ b/src/vs/editor/contrib/links/browser/links.ts @@ -22,7 +22,7 @@ import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerServic import {IEditorMouseEvent, ICodeEditor} from 'vs/editor/browser/editorBrowser'; import {getLinks, Link} from 'vs/editor/contrib/links/common/links'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; class LinkOccurence { @@ -71,6 +71,7 @@ class LinkOccurence { } } +@editorContribution class LinkDetector implements editorCommon.IEditorContribution { private static ID: string = 'editor.linkDetector'; @@ -330,11 +331,13 @@ class OpenLinkAction extends EditorAction { public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { let linkDetector = LinkDetector.get(editor); + if (!linkDetector) { + return; + } + let link = linkDetector.getLinkOccurence(editor.getPosition()); if (link) { linkDetector.openLinkOccurence(link, false); } } } - -EditorBrowserRegistry.registerEditorContribution(LinkDetector); diff --git a/src/vs/editor/contrib/multicursor/browser/menuPreventer.ts b/src/vs/editor/contrib/multicursor/browser/menuPreventer.ts index 1deb1d191f6..9b9607726d4 100644 --- a/src/vs/editor/contrib/multicursor/browser/menuPreventer.ts +++ b/src/vs/editor/contrib/multicursor/browser/menuPreventer.ts @@ -8,11 +8,12 @@ import {KeyMod} from 'vs/base/common/keyCodes'; import {Disposable} from 'vs/base/common/lifecycle'; import {IEditorContribution} from 'vs/editor/common/editorCommon'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; /** * Prevents the top-level menu from showing up when doing Alt + Click in the editor */ +@editorContribution export class MenuPreventer extends Disposable implements IEditorContribution { private static ID = 'editor.contrib.menuPreventer'; @@ -61,5 +62,3 @@ export class MenuPreventer extends Disposable implements IEditorContribution { return MenuPreventer.ID; } } - -EditorBrowserRegistry.registerEditorContribution(MenuPreventer); diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css index b41ff1005c1..016bd170142 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.css +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.css @@ -9,7 +9,7 @@ z-index: 10; } -.monaco-editor.mac .suggest-widget { +.monaco-editor.mac .parameter-hints-widget { font-size: 11px; } diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts index b874446ef5c..348ea0fa010 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHints.ts @@ -12,10 +12,11 @@ import { ICommonCodeEditor, IEditorContribution, EditorContextKeys, ModeContextK import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorBrowserRegistry } from 'vs/editor/browser/editorBrowserExtensions'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { ParameterHintsWidget } from './parameterHintsWidget'; import { Context } from '../common/parameterHints'; +@editorContribution class ParameterHintsController implements IEditorContribution { private static ID = 'editor.controller.parameterHints'; @@ -74,7 +75,10 @@ export class TriggerParameterHintsAction extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): void { - ParameterHintsController.get(editor).trigger(); + let controller = ParameterHintsController.get(editor); + if (controller) { + controller.trigger(); + } } } @@ -82,8 +86,6 @@ const weight = CommonEditorRegistry.commandWeight(75); const ParameterHintsCommand = EditorCommand.bindToContribution(ParameterHintsController.get); -EditorBrowserRegistry.registerEditorContribution(ParameterHintsController); - CommonEditorRegistry.registerEditorCommand(new ParameterHintsCommand({ id: 'closeParameterHints', precondition: Context.Visible, diff --git a/src/vs/editor/contrib/quickFix/browser/lightBulpWidget.ts b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts similarity index 90% rename from src/vs/editor/contrib/quickFix/browser/lightBulpWidget.ts rename to src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts index fdc61c32c49..f0ea1ab6504 100644 --- a/src/vs/editor/contrib/quickFix/browser/lightBulpWidget.ts +++ b/src/vs/editor/contrib/quickFix/browser/lightBulbWidget.ts @@ -10,14 +10,14 @@ import {IPosition} from 'vs/editor/common/editorCommon'; import {Position} from 'vs/editor/common/core/position'; import {ContentWidgetPositionPreference, ICodeEditor, IContentWidget, IContentWidgetPosition} from 'vs/editor/browser/editorBrowser'; -export class LightBulpWidget implements IContentWidget, IDisposable { +export class LightBulbWidget implements IContentWidget, IDisposable { private editor: ICodeEditor; private position: IPosition; private domNode: HTMLElement; private visible: boolean; private onclick: (pos: IPosition) => void; - private toDispose:IDisposable[]; + private toDispose: IDisposable[]; // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; @@ -35,7 +35,7 @@ export class LightBulpWidget implements IContentWidget, IDisposable { } public getId(): string { - return '__lightBulpWidget'; + return '__lightBulbWidget'; } public getDomNode(): HTMLElement { @@ -43,8 +43,8 @@ export class LightBulpWidget implements IContentWidget, IDisposable { this.domNode = document.createElement('div'); this.domNode.style.width = '20px'; this.domNode.style.height = '20px'; - this.domNode.className = 'lightbulp-glyph'; - this.toDispose.push(dom.addDisposableListener(this.domNode, 'click',(e) => { + this.domNode.className = 'lightbulb-glyph'; + this.toDispose.push(dom.addDisposableListener(this.domNode, 'mousedown', (e) => { this.editor.focus(); this.onclick(this.position); })); @@ -58,7 +58,7 @@ export class LightBulpWidget implements IContentWidget, IDisposable { : null; } - public show(where:IPosition): void { + public show(where: IPosition): void { if (this.visible && Position.equals(this.position, where)) { return; } @@ -78,4 +78,3 @@ export class LightBulpWidget implements IContentWidget, IDisposable { this.editor.layoutContentWidget(this); } } - diff --git a/src/vs/editor/contrib/quickFix/browser/lightbulp-dark.svg b/src/vs/editor/contrib/quickFix/browser/lightbulb-dark.svg similarity index 100% rename from src/vs/editor/contrib/quickFix/browser/lightbulp-dark.svg rename to src/vs/editor/contrib/quickFix/browser/lightbulb-dark.svg diff --git a/src/vs/editor/contrib/quickFix/browser/lightbulp.svg b/src/vs/editor/contrib/quickFix/browser/lightbulb.svg similarity index 100% rename from src/vs/editor/contrib/quickFix/browser/lightbulp.svg rename to src/vs/editor/contrib/quickFix/browser/lightbulb.svg diff --git a/src/vs/editor/contrib/quickFix/browser/quickFix.css b/src/vs/editor/contrib/quickFix/browser/quickFix.css index 171dea79007..63fcc65286e 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFix.css +++ b/src/vs/editor/contrib/quickFix/browser/quickFix.css @@ -5,24 +5,24 @@ /* light bulp */ -.monaco-editor .lightbulp-glyph { +.monaco-editor .lightbulb-glyph { display: flex; align-items: center; justify-content: center; } -.monaco-editor .lightbulp-glyph:hover { +.monaco-editor .lightbulb-glyph:hover { cursor: pointer; } -.monaco-editor.vs .lightbulp-glyph { - background: url('lightbulp.svg') center center no-repeat; +.monaco-editor.vs .lightbulb-glyph { + background: url('lightbulb.svg') center center no-repeat; height: 16px; width: 16px; } -.monaco-editor.vs-dark .lightbulp-glyph, .monaco-editor.hc-black .lightbulp-glyph { - background: url('lightbulp-dark.svg') center center no-repeat; +.monaco-editor.vs-dark .lightbulb-glyph, .monaco-editor.hc-black .lightbulb-glyph { + background: url('lightbulb-dark.svg') center center no-repeat; height: 16px; width: 16px; } diff --git a/src/vs/editor/contrib/quickFix/browser/quickFix.ts b/src/vs/editor/contrib/quickFix/browser/quickFix.ts index facd2ec35bc..028b9d8dc84 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFix.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFix.ts @@ -16,21 +16,22 @@ import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {ICommonCodeEditor, EditorContextKeys, ModeContextKeys, IEditorContribution, IRange} from 'vs/editor/common/editorCommon'; import {editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {IQuickFix2} from '../common/quickFix'; import {QuickFixModel} from './quickFixModel'; import {QuickFixSelectionWidget} from './quickFixSelectionWidget'; +@editorContribution export class QuickFixController implements IEditorContribution { private static ID = 'editor.contrib.quickFixController'; - public static get(editor:ICommonCodeEditor): QuickFixController { + public static get(editor: ICommonCodeEditor): QuickFixController { return editor.getContribution(QuickFixController.ID); } - private editor:ICodeEditor; - private model:QuickFixModel; + private editor: ICodeEditor; + private model: QuickFixModel; private suggestWidget: QuickFixSelectionWidget; private quickFixWidgetVisible: IContextKey; @@ -46,9 +47,9 @@ export class QuickFixController implements IEditorContribution { this.model = new QuickFixModel(this.editor, this._markerService, this.onAccept.bind(this)); this.quickFixWidgetVisible = CONTEXT_QUICK_FIX_WIDGET_VISIBLE.bindTo(this._contextKeyService); - this.suggestWidget = new QuickFixSelectionWidget(this.editor, telemetryService,() => { + this.suggestWidget = new QuickFixSelectionWidget(this.editor, telemetryService, () => { this.quickFixWidgetVisible.set(true); - },() => { + }, () => { this.quickFixWidgetVisible.reset(); }); this.suggestWidget.setModel(this.model); @@ -130,8 +131,11 @@ export class QuickFixAction extends EditorAction { }); } - public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): void { - QuickFixController.get(editor).run(); + public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void { + let controller = QuickFixController.get(editor); + if (controller) { + controller.run(); + } } } @@ -204,5 +208,3 @@ CommonEditorRegistry.registerEditorCommand(new QuickFixCommand({ primary: KeyCode.PageUp } })); - -EditorBrowserRegistry.registerEditorContribution(QuickFixController); \ No newline at end of file diff --git a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts b/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts index 9d24ee30e99..0a104c3addf 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFixModel.ts @@ -14,11 +14,11 @@ import URI from 'vs/base/common/uri'; import {TPromise} from 'vs/base/common/winjs.base'; import {IMarker, IMarkerService} from 'vs/platform/markers/common/markers'; import {Range} from 'vs/editor/common/core/range'; -import {ICursorPositionChangedEvent, IPosition, IRange} from 'vs/editor/common/editorCommon'; +import {IPosition, IRange} from 'vs/editor/common/editorCommon'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; import {CodeActionProviderRegistry} from 'vs/editor/common/modes'; import {IQuickFix2, getCodeActions} from '../common/quickFix'; -import {LightBulpWidget} from './lightBulpWidget'; +import {LightBulbWidget} from './lightBulbWidget'; enum QuickFixSuggestState { NOT_ACTIVE = 0, @@ -29,20 +29,15 @@ enum QuickFixSuggestState { export class QuickFixModel extends EventEmitter { private editor: ICodeEditor; - private onAccept: (fix: IQuickFix2, range:IRange) => void; + private onAccept: (fix: IQuickFix2, range: IRange) => void; private markers: IMarker[]; private lastMarker: IMarker; - private lightBulpPosition: IPosition; - private toDispose: IDisposable[]; - private toLocalDispose: IDisposable[]; - private lightBulpDecoration: string[]; + private lightBulbPosition: IPosition; + private toDispose: IDisposable[] = []; + private toLocalDispose: IDisposable[] = []; - private autoSuggestDelay: number; - private enableAutoQuckFix: boolean; - - private triggerAutoSuggestPromise:TPromise; - private state:QuickFixSuggestState; + private state: QuickFixSuggestState; private quickFixRequestPromiseRange: IRange; private quickFixRequestPromise: TPromise; @@ -51,9 +46,9 @@ export class QuickFixModel extends EventEmitter { private updateScheduler: RunOnceScheduler; - private lightBulp: LightBulpWidget; + private lightBulb: LightBulbWidget; - constructor(editor: ICodeEditor, markerService: IMarkerService, onAccept: (fix: IQuickFix2, marker:IMarker) => void) { + constructor(editor: ICodeEditor, markerService: IMarkerService, onAccept: (fix: IQuickFix2, marker: IMarker) => void) { super(/*[ 'cancel', 'loading', @@ -65,18 +60,7 @@ export class QuickFixModel extends EventEmitter { this.markerService = markerService; this.onAccept = onAccept; - this.quickFixRequestPromise = null; - this.lightBulpDecoration = []; - this.toDispose = []; - this.toLocalDispose = []; - - this.lightBulp = new LightBulpWidget(editor, (pos) => { this.onLightBulpClicked(pos); }); - - this.enableAutoQuckFix = false; // turn off for now - this.autoSuggestDelay = this.editor.getConfiguration().contribInfo.quickSuggestionsDelay; - if (isNaN(this.autoSuggestDelay) || (!this.autoSuggestDelay && this.autoSuggestDelay !== 0) || this.autoSuggestDelay > 2000 || this.autoSuggestDelay < 0) { - this.autoSuggestDelay = 300; - } + this.lightBulb = new LightBulbWidget(editor, (pos) => { this.onLightBulbClicked(pos); }); this.toDispose.push(this.editor.onDidChangeModel(() => this.onModelChanged())); this.toDispose.push(this.editor.onDidChangeModelMode(() => this.onModelChanged())); @@ -87,7 +71,7 @@ export class QuickFixModel extends EventEmitter { this.cancelDialog(); this.localDispose(); this.lastMarker = null; - this.lightBulpPosition = null; + this.lightBulbPosition = null; this.markers = null; this.updateScheduler = null; @@ -97,17 +81,15 @@ export class QuickFixModel extends EventEmitter { } this.markerService.onMarkerChanged(this.onMarkerChanged, this, this.toLocalDispose); - - this.toLocalDispose.push(this.editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { - this.onCursorPositionChanged(); - })); + this.toLocalDispose.push(this.editor.onDidChangeCursorPosition(e => this.onCursorPositionChanged())); + this.toLocalDispose.push(this.editor.onDidBlurEditorText(() => this.setDecoration(null))); } - private onLightBulpClicked(pos: IPosition) : void { + private onLightBulbClicked(pos: IPosition): void { this.triggerDialog(true, pos); } - private isSimilarMarker(marker1: IMarker, marker2: IMarker) : boolean { + private isSimilarMarker(marker1: IMarker, marker2: IMarker): boolean { if (marker1) { return marker2 && marker1.owner === marker2.owner && marker1.code === marker2.code; } @@ -137,49 +119,51 @@ export class QuickFixModel extends EventEmitter { } private setDecoration(pos: IPosition): void { - this.lightBulpPosition = pos; + this.lightBulbPosition = pos; this.updateDecoration(); } - private updateDecoration() : void { - if (this.lightBulpPosition && this.state === QuickFixSuggestState.NOT_ACTIVE) { - this.lightBulp.show(this.lightBulpPosition); + private updateDecoration(): void { + if (this.lightBulbPosition && this.state === QuickFixSuggestState.NOT_ACTIVE) { + this.lightBulb.show(this.lightBulbPosition); } else { - this.lightBulp.hide(); + this.lightBulb.hide(); } } private onCursorPositionChanged(): void { - if (this.triggerAutoSuggestPromise) { - this.triggerAutoSuggestPromise.cancel(); - this.triggerAutoSuggestPromise = null; - } this.cancelDialog(); if (!this.updateScheduler) { this.updateScheduler = new RunOnceScheduler(() => { - var marker = this.lastMarker; - var pos = this.editor.getPosition(); + + const pos = this.editor.getPosition(); + let marker = this.lastMarker; if (marker && Range.containsPosition(marker, pos)) { // still on the same marker - if (this.lightBulpPosition) { + if (this.lightBulbPosition) { this.setDecoration(pos); } return; } - this.lastMarker = marker = this.findMarker(pos, false); - if (!marker) { - // remove lightbulp + if (!this.editor.isFocused()) { + // remove lightbulb when editor lost focus this.setDecoration(null); return; } - var $tRequest = timer.start(timer.Topic.EDITOR, 'quickfix/lighbulp'); - var computeFixesPromise = this.computeFixes(marker); + this.lastMarker = marker = this.findMarker(pos, false); + if (!marker) { + // remove lightbulb + this.setDecoration(null); + return; + } + + const $tRequest = timer.start(timer.Topic.EDITOR, 'quickfix/lighbulp'); + const computeFixesPromise = this.computeFixes(marker); computeFixesPromise.done((fixes) => { this.setDecoration(!arrays.isFalsyOrEmpty(fixes) ? pos : null); - this.triggerAutoSuggest(marker); $tRequest.stop(); }, (error) => { onUnexpectedError(error); @@ -259,7 +243,7 @@ export class QuickFixModel extends EventEmitter { return result; } - public cancelDialog(silent:boolean = false):boolean { + public cancelDialog(silent: boolean = false): boolean { if (this.state !== QuickFixSuggestState.NOT_ACTIVE) { this.state = QuickFixSuggestState.NOT_ACTIVE; if (!silent) { @@ -271,23 +255,11 @@ export class QuickFixModel extends EventEmitter { return false; } - private isAutoSuggest():boolean { + private isAutoSuggest(): boolean { return this.state === QuickFixSuggestState.AUTO_TRIGGER; } - private triggerAutoSuggest(marker: IMarker): void { - if (this.enableAutoQuckFix && this.state === QuickFixSuggestState.NOT_ACTIVE) { - this.triggerAutoSuggestPromise = TPromise.timeout(this.autoSuggestDelay); - this.triggerAutoSuggestPromise.then(() => { - this.triggerAutoSuggestPromise = null; - if (marker === this.lastMarker) { - this.triggerDialog(true, this.editor.getPosition()); - } - }); - } - } - - public triggerDialog(auto:boolean, pos: IPosition):void { + public triggerDialog(auto: boolean, pos: IPosition): void { // Cancel previous requests, change state & update UI this.cancelDialog(false); @@ -334,14 +306,16 @@ export class QuickFixModel extends EventEmitter { }); } - public accept(quickFix:IQuickFix2, range: IRange):boolean { + public accept(quickFix: IQuickFix2, range: IRange): boolean { this.cancelDialog(); if (!quickFix) { return false; } - - this.onAccept(quickFix, range); - + try { + this.onAccept(quickFix, range); + } catch (e) { + onUnexpectedError(e); + } return true; } diff --git a/src/vs/editor/contrib/quickFix/browser/quickFixSelectionWidget.ts b/src/vs/editor/contrib/quickFix/browser/quickFixSelectionWidget.ts index 76a6d89b38a..a66b5aca3d1 100644 --- a/src/vs/editor/contrib/quickFix/browser/quickFixSelectionWidget.ts +++ b/src/vs/editor/contrib/quickFix/browser/quickFixSelectionWidget.ts @@ -29,11 +29,11 @@ const $ = dom.$; function isQuickFix(quickfix: any): quickfix is IQuickFix2 { return quickfix && typeof (quickfix).command === 'object' - && typeof ( quickfix).command.title === 'string'; + && typeof (quickfix).command.title === 'string'; } function getAriaAlertLabel(item: IQuickFix2): string { - return nls.localize('ariaCurrentFix',"{0}, quick fix suggestion", item.command.title); + return nls.localize('ariaCurrentFix', "{0}, quick fix suggestion", item.command.title); } // To be used as a tree element when we want to show a message @@ -59,7 +59,7 @@ class DataSource implements IDataSource { this.root = null; } - private isRoot(element: any) : boolean { + private isRoot(element: any): boolean { if (element instanceof MessageRoot) { return true; } else if (element instanceof Message) { @@ -79,8 +79,8 @@ class DataSource implements IDataSource { return 'message' + element.message; } else if (Array.isArray(element)) { return 'root'; - } else if(isQuickFix(element)) { - return ( element).id; + } else if (isQuickFix(element)) { + return (element).id; } else { throw illegalArgument('element'); } @@ -112,7 +112,7 @@ class DataSource implements IDataSource { class Controller extends DefaultController { - /* protected */ public onLeftClick(tree:ITree, element:any, event:IMouseEvent):boolean { + /* protected */ public onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean { event.preventDefault(); event.stopPropagation(); @@ -129,12 +129,12 @@ export class AccessibilityProvider implements IAccessibilityProvider { public getAriaLabel(tree: ITree, element: any): string { if (isQuickFix(element)) { - return getAriaAlertLabel( element); + return getAriaAlertLabel(element); } } } -function getHeight(tree:ITree, element:any): number { +function getHeight(tree: ITree, element: any): number { // var fix = element; // if (!(element instanceof Message) && !!fix.documentation && tree.isFocused(fix)) { @@ -146,7 +146,7 @@ function getHeight(tree:ITree, element:any): number { class Renderer implements IRenderer { - public getHeight(tree:ITree, element:any):number { + public getHeight(tree: ITree, element: any): number { return getHeight(tree, element); } @@ -176,7 +176,7 @@ class Renderer implements IRenderer { return; } - var quickFix = element; + var quickFix = element; templateData.main.textContent = quickFix.command.title; templateData.documentationLabel.textContent = /*quickFix.documentation ||*/ ''; } @@ -225,7 +225,7 @@ export class QuickFixSelectionWidget implements IContentWidget { // Editor.IContentWidget.allowEditorOverflow public allowEditorOverflow = true; - constructor(editor: ICodeEditor, telemetryService:ITelemetryService, onShown: () => void, onHidden: () => void) { + constructor(editor: ICodeEditor, telemetryService: ITelemetryService, onShown: () => void, onHidden: () => void) { this.editor = editor; this._onShown = onShown; this._onHidden = onHidden; @@ -252,14 +252,14 @@ export class QuickFixSelectionWidget implements IContentWidget { controller: new Controller(), accessibilityProvider: new AccessibilityProvider() }, { - twistiePixels: 0, - alwaysFocused: true, - verticalScrollMode: ScrollbarVisibility.Visible, - useShadows: false, - ariaLabel: nls.localize('treeAriaLabel', "Quick Fix") - }); + twistiePixels: 0, + alwaysFocused: true, + verticalScrollMode: ScrollbarVisibility.Visible, + useShadows: false, + ariaLabel: nls.localize('treeAriaLabel', "Quick Fix") + }); - this.listenersToRemove.push(this.tree.addListener2('selection', (e:ISelectionEvent) => { + this.listenersToRemove.push(this.tree.addListener2('selection', (e: ISelectionEvent) => { if (e.selection && e.selection.length > 0) { var element = e.selection[0]; if (isQuickFix(element) && !(element instanceof MessageRoot) && !(element instanceof Message)) { @@ -278,7 +278,7 @@ export class QuickFixSelectionWidget implements IContentWidget { var oldFocus: any = null; - this.listenersToRemove.push(this.tree.addListener2('focus', (e:IFocusEvent) => { + this.listenersToRemove.push(this.tree.addListener2('focus', (e: IFocusEvent) => { var focus = e.focus; var payload = e.payload; @@ -323,7 +323,7 @@ export class QuickFixSelectionWidget implements IContentWidget { } private _lastAriaAlertLabel: string; - private _ariaAlert(newAriaAlertLabel:string): void { + private _ariaAlert(newAriaAlertLabel: string): void { if (this._lastAriaAlertLabel === newAriaAlertLabel) { return; } @@ -333,12 +333,12 @@ export class QuickFixSelectionWidget implements IContentWidget { } } - public setModel(newModel: QuickFixModel) : void { + public setModel(newModel: QuickFixModel): void { this.releaseModel(); this.model = newModel; - var timer : timer.ITimerEvent = null, - loadingHandle:number; + var timer: timer.ITimerEvent = null, + loadingHandle: number; this.modelListenersToRemove.push(this.model.addListener2('loading', (e: any) => { if (!this.isActive) { @@ -363,10 +363,10 @@ export class QuickFixSelectionWidget implements IContentWidget { } })); - this.modelListenersToRemove.push(this.model.addListener2('suggest',(e: { fixes: IQuickFix2[]; range: IRange; auto:boolean; }) => { + this.modelListenersToRemove.push(this.model.addListener2('suggest', (e: { fixes: IQuickFix2[]; range: IRange; auto: boolean; }) => { this.isLoading = false; - if(typeof loadingHandle !== 'undefined') { + if (typeof loadingHandle !== 'undefined') { clearTimeout(loadingHandle); loadingHandle = void 0; } @@ -374,7 +374,7 @@ export class QuickFixSelectionWidget implements IContentWidget { var fixes = e.fixes; var bestFixIndex = -1; - var bestFix:IQuickFix2 = fixes[0]; + var bestFix: IQuickFix2 = fixes[0]; var bestScore = -1; for (var i = 0, len = fixes.length; i < len; i++) { @@ -398,18 +398,18 @@ export class QuickFixSelectionWidget implements IContentWidget { this.telemetryData.suggestionCount = fixes.length; this.telemetryData.suggestedIndex = bestFixIndex; - if(timer) { - timer.data = { reason: 'results'}; + if (timer) { + timer.data = { reason: 'results' }; timer.stop(); timer = null; } })); - this.modelListenersToRemove.push(this.model.addListener2('empty', (e: { auto:boolean; }) => { + this.modelListenersToRemove.push(this.model.addListener2('empty', (e: { auto: boolean; }) => { var wasLoading = this.isLoading; this.isLoading = false; - if(typeof loadingHandle !== 'undefined') { + if (typeof loadingHandle !== 'undefined') { clearTimeout(loadingHandle); loadingHandle = void 0; } @@ -429,17 +429,17 @@ export class QuickFixSelectionWidget implements IContentWidget { dom.addClass(this.domnode, 'empty'); } - if(timer) { - timer.data = { reason: 'empty'}; + if (timer) { + timer.data = { reason: 'empty' }; timer.stop(); timer = null; } })); - this.modelListenersToRemove.push(this.model.addListener2('cancel', (e:any) => { + this.modelListenersToRemove.push(this.model.addListener2('cancel', (e: any) => { this.isLoading = false; - if(typeof loadingHandle !== 'undefined') { + if (typeof loadingHandle !== 'undefined') { clearTimeout(loadingHandle); loadingHandle = void 0; } @@ -515,7 +515,7 @@ export class QuickFixSelectionWidget implements IContentWidget { return false; } - public acceptSelectedSuggestion() : boolean { + public acceptSelectedSuggestion(): boolean { if (this.isLoading) { return !this.isAuto; } @@ -531,12 +531,12 @@ export class QuickFixSelectionWidget implements IContentWidget { return false; } - private releaseModel() : void { + private releaseModel(): void { this.modelListenersToRemove = dispose(this.modelListenersToRemove); this.model = null; } - public show() : void { + public show(): void { this.isActive = true; this.tree.layout(); this.editor.layoutContentWidget(this); @@ -544,14 +544,14 @@ export class QuickFixSelectionWidget implements IContentWidget { this._onShown(); } - public hide() : void { + public hide(): void { this._onHidden(); this.isActive = false; this.editor.layoutContentWidget(this); } - public getPosition():IContentWidgetPosition { + public getPosition(): IContentWidgetPosition { if (this.isActive) { return { position: this.editor.getPosition(), @@ -561,15 +561,15 @@ export class QuickFixSelectionWidget implements IContentWidget { return null; } - public getDomNode() : HTMLElement { + public getDomNode(): HTMLElement { return this.domnode; } - public getId() : string { + public getId(): string { return QuickFixSelectionWidget.ID; } - private submitTelemetryData() : void { + private submitTelemetryData(): void { this.telemetryService.publicLog('QuickFixSelectionWidget', this.telemetryData); this.telemetryData = Object.create(null); } @@ -581,7 +581,7 @@ export class QuickFixSelectionWidget implements IContentWidget { if (input === QuickFixSelectionWidget.LOADING_MESSAGE || input === QuickFixSelectionWidget.NO_SUGGESTIONS_MESSAGE) { height = 19; } else { - var fixes = input; + var fixes = input; height = Math.min(fixes.length - 1, 11) * 19; var focus = this.tree.getFocus(); @@ -593,7 +593,7 @@ export class QuickFixSelectionWidget implements IContentWidget { this.editor.layoutContentWidget(this); } - public destroy() : void { + public destroy(): void { this.releaseModel(); this.domnode = null; diff --git a/src/vs/editor/contrib/quickOpen/browser/editorQuickOpen.ts b/src/vs/editor/contrib/quickOpen/browser/editorQuickOpen.ts index 9ff25a95df5..09889763253 100644 --- a/src/vs/editor/contrib/quickOpen/browser/editorQuickOpen.ts +++ b/src/vs/editor/contrib/quickOpen/browser/editorQuickOpen.ts @@ -8,7 +8,7 @@ import {QuickOpenModel} from 'vs/base/parts/quickopen/browser/quickOpenModel'; import {IAutoFocus} from 'vs/base/parts/quickopen/common/quickOpen'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {QuickOpenEditorWidget} from './quickOpenEditorWidget'; import {Selection} from 'vs/editor/common/core/selection'; import {IActionOptions, EditorAction} from 'vs/editor/common/editorCommonExtensions'; @@ -19,6 +19,7 @@ export interface IQuickOpenControllerOpts { getAutoFocus(searchValue:string):IAutoFocus; } +@editorContribution export class QuickOpenController implements editorCommon.IEditorContribution { private static ID = 'editor.controller.quickOpenController'; @@ -29,7 +30,7 @@ export class QuickOpenController implements editorCommon.IEditorContribution { private editor:ICodeEditor; private widget:QuickOpenEditorWidget; - private lineHighlightDecorationId:string; + private rangeHighlightDecorationId:string; private lastKnownEditorSelection:Selection; constructor(editor:ICodeEditor) { @@ -93,31 +94,31 @@ export class QuickOpenController implements editorCommon.IEditorContribution { public decorateLine(range:editorCommon.IRange, editor:ICodeEditor):void { editor.changeDecorations((changeAccessor:editorCommon.IModelDecorationsChangeAccessor)=>{ var oldDecorations: string[] = []; - if (this.lineHighlightDecorationId) { - oldDecorations.push(this.lineHighlightDecorationId); - this.lineHighlightDecorationId = null; + if (this.rangeHighlightDecorationId) { + oldDecorations.push(this.rangeHighlightDecorationId); + this.rangeHighlightDecorationId = null; } var newDecorations: editorCommon.IModelDeltaDecoration[] = [ { range: range, options: { - className: 'lineHighlight', + className: 'rangeHighlight', isWholeLine: true } } ]; var decorations = changeAccessor.deltaDecorations(oldDecorations, newDecorations); - this.lineHighlightDecorationId = decorations[0]; + this.rangeHighlightDecorationId = decorations[0]; }); } public clearDecorations():void { - if (this.lineHighlightDecorationId) { + if (this.rangeHighlightDecorationId) { this.editor.changeDecorations((changeAccessor:editorCommon.IModelDecorationsChangeAccessor)=>{ - changeAccessor.deltaDecorations([this.lineHighlightDecorationId], []); - this.lineHighlightDecorationId = null; + changeAccessor.deltaDecorations([this.rangeHighlightDecorationId], []); + this.rangeHighlightDecorationId = null; }); } } @@ -164,5 +165,3 @@ export interface IDecorator { decorateLine(range:editorCommon.IRange, editor:editorCommon.IEditor):void; clearDecorations():void; } - -EditorBrowserRegistry.registerEditorContribution(QuickOpenController); diff --git a/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts b/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts index bf8f97a3e81..76a9086aa8e 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referenceSearch.ts @@ -16,7 +16,7 @@ import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegi import {Position} from 'vs/editor/common/core/position'; import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {editorAction, ServicesAccessor, EditorAction, CommonEditorRegistry, commonEditorContribution} from 'vs/editor/common/editorCommonExtensions'; import {Location} from 'vs/editor/common/modes'; import {IPeekViewService, PeekContext, getOuterEditor} from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget'; import {provideReferences} from '../common/referenceSearch'; @@ -32,6 +32,7 @@ const defaultReferenceSearchOptions: RequestOptions = { } }; +@commonEditorContribution export class ReferenceController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.referenceController'; @@ -75,10 +76,13 @@ export class ReferenceAction extends EditorAction { } public run(accessor:ServicesAccessor, editor:editorCommon.ICommonCodeEditor): void { + let controller = ReferencesController.get(editor); + if (!controller) { + return; + } let range = editor.getSelection(); let model = editor.getModel(); let references = provideReferences(model, range.getStartPosition()).then(references => new ReferencesModel(references)); - let controller = ReferencesController.getController(editor); controller.toggleWidget(range, references, defaultReferenceSearchOptions); } } @@ -99,8 +103,12 @@ let findReferencesCommand: ICommandHandler = (accessor:ServicesAccessor, resourc return; } + let controller = ReferencesController.get(control); + if (!controller) { + return; + } + let references = provideReferences(control.getModel(), Position.lift(position)).then(references => new ReferencesModel(references)); - let controller = ReferencesController.getController(control); let range = new Range(position.lineNumber, position.column, position.lineNumber, position.column); return TPromise.as(controller.toggleWidget(range, references, defaultReferenceSearchOptions)); }); @@ -118,7 +126,10 @@ let showReferencesCommand: ICommandHandler = (accessor:ServicesAccessor, resourc return; } - let controller = ReferencesController.getController(control); + let controller = ReferencesController.get(control); + if (!controller) { + return; + } return TPromise.as(controller.toggleWidget( new Range(position.lineNumber, position.column, position.lineNumber, position.column), @@ -129,9 +140,7 @@ let showReferencesCommand: ICommandHandler = (accessor:ServicesAccessor, resourc -// register action - -CommonEditorRegistry.registerEditorContribution(ReferenceController); +// register commands CommandsRegistry.registerCommand('editor.action.findReferences', findReferencesCommand); @@ -149,10 +158,16 @@ CommandsRegistry.registerCommand('editor.action.showReferences', { function closeActiveReferenceSearch(accessor, args) { var outerEditor = getOuterEditor(accessor, args); - if (outerEditor) { - var controller = ReferencesController.getController(outerEditor); - controller.closeWidget(); + if (!outerEditor) { + return; } + + let controller = ReferencesController.get(outerEditor); + if (!controller) { + return; + } + + controller.closeWidget(); } KeybindingsRegistry.registerCommandAndKeybindingRule({ diff --git a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts index 6d916657b6c..703dd277d5c 100644 --- a/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts +++ b/src/vs/editor/contrib/referenceSearch/browser/referencesController.ts @@ -19,7 +19,7 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IStorageService} from 'vs/platform/storage/common/storage'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {IPeekViewService} from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget'; import {ReferencesModel, OneReference} from './referencesModel'; import {ReferenceWidget, LayoutData} from './referencesWidget'; @@ -32,6 +32,7 @@ export interface RequestOptions { onGoto?: (reference: OneReference) => TPromise; } +@editorContribution export class ReferencesController implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.referencesController'; @@ -45,7 +46,7 @@ export class ReferencesController implements editorCommon.IEditorContribution { private _referenceSearchVisible: IContextKey; - public static getController(editor:editorCommon.ICommonCodeEditor): ReferencesController { + public static get(editor:editorCommon.ICommonCodeEditor): ReferencesController { return editor.getContribution(ReferencesController.ID); } @@ -238,6 +239,3 @@ export class ReferencesController implements editorCommon.IEditorContribution { } } } - - -EditorBrowserRegistry.registerEditorContribution(ReferencesController); \ No newline at end of file diff --git a/src/vs/editor/contrib/rename/browser/rename.ts b/src/vs/editor/contrib/rename/browser/rename.ts index 666f5ef4ec5..c49745a1037 100644 --- a/src/vs/editor/contrib/rename/browser/rename.ts +++ b/src/vs/editor/contrib/rename/browser/rename.ts @@ -16,7 +16,7 @@ import {RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr} from 'vs import {IMessageService} from 'vs/platform/message/common/message'; import {IProgressService} from 'vs/platform/progress/common/progress'; import {editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {IRange, ICommonCodeEditor, EditorContextKeys, ModeContextKeys, IEditorContribution} from 'vs/editor/common/editorCommon'; import {BulkEdit, createBulkEdit} from 'vs/editor/common/services/bulkEdit'; import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; @@ -27,6 +27,7 @@ import RenameInputField from './renameInputField'; const CONTEXT_RENAME_INPUT_VISIBLE = new RawContextKey('renameInputVisible', false); +@editorContribution class RenameController implements IEditorContribution { private static ID = 'editor.contrib.renameController'; @@ -166,12 +167,13 @@ export class RenameAction extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): TPromise { - return RenameController.get(editor).run(); + let controller = RenameController.get(editor); + if (controller) { + return controller.run(); + } } } -EditorBrowserRegistry.registerEditorContribution(RenameController); - const RenameCommand = EditorCommand.bindToContribution(RenameController.get); CommonEditorRegistry.registerEditorCommand(new RenameCommand({ diff --git a/src/vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard.ts b/src/vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard.ts index 69b7d6f156c..1a6e9f8db57 100644 --- a/src/vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard.ts +++ b/src/vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard.ts @@ -10,12 +10,13 @@ import * as platform from 'vs/base/common/platform'; import {ICodeEditor, IEditorMouseEvent} from 'vs/editor/browser/editorBrowser'; import {Disposable} from 'vs/base/common/lifecycle'; import {EndOfLinePreference, IEditorContribution, ICursorSelectionChangedEvent, IConfigurationChangedEvent} from 'vs/editor/common/editorCommon'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; import {RunOnceScheduler} from 'vs/base/common/async'; import {Range} from 'vs/editor/common/core/range'; -class SelectionClipboard extends Disposable implements IEditorContribution { +@editorContribution +export class SelectionClipboard extends Disposable implements IEditorContribution { private static ID = 'editor.contrib.selectionClipboard'; @@ -98,5 +99,3 @@ class SelectionClipboard extends Disposable implements IEditorContribution { super.dispose(); } } - -EditorBrowserRegistry.registerEditorContribution(SelectionClipboard); diff --git a/src/vs/editor/contrib/smartSelect/common/smartSelect.ts b/src/vs/editor/contrib/smartSelect/common/smartSelect.ts index 605b35373b1..aaa9309795c 100644 --- a/src/vs/editor/contrib/smartSelect/common/smartSelect.ts +++ b/src/vs/editor/contrib/smartSelect/common/smartSelect.ts @@ -11,7 +11,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {Range} from 'vs/editor/common/core/range'; import {ICommonCodeEditor, ICursorPositionChangedEvent, EditorContextKeys, IEditorContribution} from 'vs/editor/common/editorCommon'; -import {editorAction, ServicesAccessor, IActionOptions, EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {editorAction, ServicesAccessor, IActionOptions, EditorAction, commonEditorContribution} from 'vs/editor/common/editorCommonExtensions'; import {TokenSelectionSupport, ILogicalSelectionEntry} from './tokenSelectionSupport'; // --- selection state machine @@ -37,6 +37,7 @@ var ignoreSelection = false; // -- action implementation +@commonEditorContribution class SmartSelectController implements IEditorContribution { private static ID = 'editor.contrib.smartSelectController'; @@ -152,7 +153,10 @@ abstract class AbstractSmartSelect extends EditorAction { } public run(accessor:ServicesAccessor, editor:ICommonCodeEditor): TPromise { - return SmartSelectController.get(editor).run(this._forward); + let controller = SmartSelectController.get(editor); + if (controller) { + return controller.run(this._forward); + } } } @@ -189,6 +193,3 @@ class ShrinkSelectionAction extends AbstractSmartSelect { }); } } - -// register actions -CommonEditorRegistry.registerEditorContribution(SmartSelectController); diff --git a/src/vs/editor/contrib/smartSelect/common/tokenTree.ts b/src/vs/editor/contrib/smartSelect/common/tokenTree.ts index ce33648dccf..8bd16bef5df 100644 --- a/src/vs/editor/contrib/smartSelect/common/tokenTree.ts +++ b/src/vs/editor/contrib/smartSelect/common/tokenTree.ts @@ -187,6 +187,8 @@ class TokenScanner { let bracketIsOpen: boolean = false; if (nextBracket) { let bracketText = this._currentLineText.substring(nextBracket.startColumn - 1, nextBracket.endColumn - 1); + bracketText = bracketText.toLowerCase(); + bracketData = this._currentModeBrackets.textIsBracket[bracketText]; bracketIsOpen = this._currentModeBrackets.textIsOpenBracket[bracketText]; } diff --git a/src/vs/editor/contrib/snippet/common/snippetController.ts b/src/vs/editor/contrib/snippet/common/snippetController.ts index d54746a198b..d03ae43a262 100644 --- a/src/vs/editor/contrib/snippet/common/snippetController.ts +++ b/src/vs/editor/contrib/snippet/common/snippetController.ts @@ -12,7 +12,7 @@ import {EditOperation} from 'vs/editor/common/core/editOperation'; import {Range} from 'vs/editor/common/core/range'; import {Selection} from 'vs/editor/common/core/selection'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {CommonEditorRegistry, EditorCommand} from 'vs/editor/common/editorCommonExtensions'; +import {CommonEditorRegistry, commonEditorContribution, EditorCommand} from 'vs/editor/common/editorCommonExtensions'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {ICodeSnippet, CodeSnippet} from './snippet'; @@ -349,6 +349,7 @@ interface IPreparedSnippet { adaptedSnippet: ICodeSnippet; } +@commonEditorContribution export class SnippetController { private static ID = 'editor.contrib.snippetController'; @@ -594,7 +595,6 @@ export var CONTEXT_SNIPPET_MODE = new RawContextKey('inSnippetMode', fa const SnippetCommand = EditorCommand.bindToContribution(SnippetController.get); -CommonEditorRegistry.registerEditorContribution(SnippetController); CommonEditorRegistry.registerEditorCommand(new SnippetCommand({ id: 'jumpToNextSnippetPlaceholder', precondition: CONTEXT_SNIPPET_MODE, diff --git a/src/vs/editor/contrib/suggest/browser/suggest.css b/src/vs/editor/contrib/suggest/browser/suggest.css index f539aaf0a8c..4a3c5e1e51d 100644 --- a/src/vs/editor/contrib/suggest/browser/suggest.css +++ b/src/vs/editor/contrib/suggest/browser/suggest.css @@ -9,6 +9,7 @@ font-size: 12px; border: 1px solid rgb(200, 200, 200); z-index: 40; + width: 438px; } .monaco-editor .suggest-widget.visible { diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index aaf69c96b67..0d11aed88ed 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -15,7 +15,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICommonCodeEditor, IEditorContribution, EditorContextKeys, ModeContextKeys } from 'vs/editor/common/editorCommon'; import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { EditorBrowserRegistry } from 'vs/editor/browser/editorBrowserExtensions'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { EditOperation } from 'vs/editor/common/core/editOperation'; import { CodeSnippet } from 'vs/editor/contrib/snippet/common/snippet'; import { SnippetController } from 'vs/editor/contrib/snippet/common/snippetController'; @@ -24,6 +24,7 @@ import { SuggestModel } from '../common/suggestModel'; import { ICompletionItem } from '../common/completionModel'; import { SuggestWidget } from './suggestWidget'; +@editorContribution export class SuggestController implements IEditorContribution { private static ID: string = 'editor.contrib.suggestController'; @@ -281,5 +282,3 @@ CommonEditorRegistry.registerEditorCommand(new SuggestCommand({ mac: { primary: KeyMod.WinCtrl | KeyCode.Space } } })); - -EditorBrowserRegistry.registerEditorContribution(SuggestController); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index 692c0967fc3..b127651d0c7 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -284,7 +284,6 @@ class SuggestionDetails { export class SuggestWidget implements IContentWidget, IDisposable { private static ID: string = 'editor.widget.suggestWidget'; - static WIDTH: number = 438; static LOADING_MESSAGE: string = nls.localize('suggestWidget.loading', "Loading..."); static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions."); @@ -324,10 +323,7 @@ export class SuggestWidget implements IContentWidget, IDisposable { this.isAuto = false; this.focusedItem = null; - this.element = $('.editor-widget.suggest-widget.monaco-editor-background'); - this.element.style.width = SuggestWidget.WIDTH + 'px'; - this.element.style.top = '0'; - this.element.style.left = '0'; + this.element = $('.editor-widget.suggest-widget'); if (!this.editor.getConfiguration().contribInfo.iconsInSuggestions) { addClass(this.element, 'no-icons'); diff --git a/src/vs/editor/contrib/suggest/browser/tabCompletion.ts b/src/vs/editor/contrib/suggest/browser/tabCompletion.ts index 33ef3383374..a399cb91ef5 100644 --- a/src/vs/editor/contrib/suggest/browser/tabCompletion.ts +++ b/src/vs/editor/contrib/suggest/browser/tabCompletion.ts @@ -13,7 +13,7 @@ import {Registry} from 'vs/platform/platform'; import {endsWith} from 'vs/base/common/strings'; import {IDisposable} from 'vs/base/common/lifecycle'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {CommonEditorRegistry, EditorCommand} from 'vs/editor/common/editorCommonExtensions'; +import {CommonEditorRegistry, commonEditorContribution, EditorCommand} from 'vs/editor/common/editorCommonExtensions'; import {CodeSnippet} from 'vs/editor/contrib/snippet/common/snippet'; import {SnippetController} from 'vs/editor/contrib/snippet/common/snippetController'; @@ -21,7 +21,8 @@ import EditorContextKeys = editorCommon.EditorContextKeys; let snippetsRegistry = Registry.as(Extensions.Snippets); -class TabCompletionController implements editorCommon.IEditorContribution { +@commonEditorContribution +export class TabCompletionController implements editorCommon.IEditorContribution { private static ID = 'editor.tabCompletionController'; static ContextKey = new RawContextKey('hasSnippetCompletions', undefined); @@ -79,8 +80,6 @@ class TabCompletionController implements editorCommon.IEditorContribution { } } -CommonEditorRegistry.registerEditorContribution(TabCompletionController); - const TabCompletionCommand = EditorCommand.bindToContribution(TabCompletionController.get); CommonEditorRegistry.registerEditorCommand(new TabCompletionCommand({ diff --git a/src/vs/editor/contrib/suggest/common/completionModel.ts b/src/vs/editor/contrib/suggest/common/completionModel.ts index abb4ee7bdf5..0e3ddaaf89d 100644 --- a/src/vs/editor/contrib/suggest/common/completionModel.ts +++ b/src/vs/editor/contrib/suggest/common/completionModel.ts @@ -138,7 +138,13 @@ export class CompletionModel { // no match on label nor codeSnippet -> check on filterText if(!match && typeof suggestion.filterText === 'string') { - match = !isFalsyOrEmpty(filter(word, suggestion.filterText)); + if (!isFalsyOrEmpty(filter(word, suggestion.filterText))) { + match = true; + + // try to compute highlights by stripping none-word + // characters from the end of the string + item.highlights = filter(word.replace(/^\W+|\W+$/, ''), suggestion.label); + } } if (!match) { @@ -149,7 +155,7 @@ export class CompletionModel { // compute score against word const wordLowerCase = word.toLowerCase(); - const score = CompletionModel._score(suggestion.label, word, wordLowerCase); + const score = CompletionModel._score(suggestion.insertText, word, wordLowerCase); if (score > topScore) { topScore = score; this._topScoreIdx = this._filteredItems.length - 1; diff --git a/src/vs/editor/contrib/suggest/common/suggest.ts b/src/vs/editor/contrib/suggest/common/suggest.ts index 7250978e7b3..1fe0e23f114 100644 --- a/src/vs/editor/contrib/suggest/common/suggest.ts +++ b/src/vs/editor/contrib/suggest/common/suggest.ts @@ -43,10 +43,10 @@ export const snippetSuggestSupport: ISuggestSupport = { triggerCharacters: [], provideCompletionItems(model: IReadOnlyModel, position: Position): ISuggestResult { - // currentWord is irrelevant, all suggestion use overwriteBefore - const result: ISuggestResult = { suggestions: [], currentWord: '' }; - Registry.as(Extensions.Snippets).getSnippetCompletions(model, position, result.suggestions); - return result; + const suggestions = Registry.as(Extensions.Snippets).getSnippetCompletions(model, position); + if (suggestions) { + return { suggestions, currentWord: '' }; + } } }; diff --git a/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts index 07c816c75ce..723e6f3f36e 100644 --- a/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/common/wordHighlighter.ts @@ -9,7 +9,7 @@ import {onUnexpectedError} from 'vs/base/common/errors'; import {TPromise} from 'vs/base/common/winjs.base'; import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {CommonEditorRegistry, commonEditorContribution} from 'vs/editor/common/editorCommonExtensions'; import {DocumentHighlight, DocumentHighlightKind, DocumentHighlightProviderRegistry} from 'vs/editor/common/modes'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {Position} from 'vs/editor/common/core/position'; @@ -265,12 +265,13 @@ class WordHighlighter { this._decorationIds = this.editor.deltaDecorations(this._decorationIds, decorations); } - public destroy(): void { + public dispose(): void { this._stopAll(); this.toUnhook = dispose(this.toUnhook); } } +@commonEditorContribution class WordHighlighterContribution implements editorCommon.IEditorContribution { private static ID = 'editor.contrib.wordHighlighter'; @@ -286,9 +287,6 @@ class WordHighlighterContribution implements editorCommon.IEditorContribution { } public dispose(): void { - this.wordHighligher.destroy(); + this.wordHighligher.dispose(); } } - -CommonEditorRegistry.registerEditorContribution(WordHighlighterContribution); - diff --git a/src/vs/editor/node/textMate/TMSnippets.ts b/src/vs/editor/node/textMate/TMSnippets.ts index 8f3545ff845..cad80f56637 100644 --- a/src/vs/editor/node/textMate/TMSnippets.ts +++ b/src/vs/editor/node/textMate/TMSnippets.ts @@ -49,13 +49,13 @@ export class MainProcessTextMateSnippet { for (let i = 0; i < extensions.length; i++) { let tmSnippets = extensions[i].value; for (let j = 0; j < tmSnippets.length; j++) { - this._withSnippetContribution(extensions[i].description.extensionFolderPath, tmSnippets[j], extensions[i].collector); + this._withSnippetContribution(extensions[i].description.name, extensions[i].description.extensionFolderPath, tmSnippets[j], extensions[i].collector); } } }); } - private _withSnippetContribution(extensionFolderPath: string, snippet: ISnippetsExtensionPoint, collector: IExtensionMessageCollector): void { + private _withSnippetContribution(extensionName: string, extensionFolderPath: string, snippet: ISnippetsExtensionPoint, collector: IExtensionMessageCollector): void { if (!snippet.language || (typeof snippet.language !== 'string')) { collector.error(nls.localize('invalid.language', "Unknown language in `contributes.{0}.language`. Provided value: {1}", snippetsExtensionPoint.name, String(snippet.language))); return; @@ -75,7 +75,7 @@ export class MainProcessTextMateSnippet { if (mode.getId() !== modeId) { return; } - readAndRegisterSnippets(modeId, normalizedAbsolutePath); + readAndRegisterSnippets(modeId, normalizedAbsolutePath, extensionName); disposable.dispose(); }); } @@ -83,20 +83,20 @@ export class MainProcessTextMateSnippet { let snippetsRegistry = platform.Registry.as(Extensions.Snippets); -export function readAndRegisterSnippets(modeId: string, filePath: string): TPromise { +export function readAndRegisterSnippets(modeId: string, filePath: string, ownerName: string): TPromise { return readFile(filePath).then(fileContents => { - let snippets = parseSnippetFile(fileContents.toString()); + let snippets = parseSnippetFile(fileContents.toString(), ownerName); snippetsRegistry.registerSnippets(modeId, snippets, filePath); }); } -function parseSnippetFile(snippetFileContent: string): ISnippet[] { +function parseSnippetFile(snippetFileContent: string, owner: string): ISnippet[] { let snippetsObj = parse(snippetFileContent); let topLevelProperties = Object.keys(snippetsObj); let result: ISnippet[] = []; - let processSnippet = (snippet: any, description: string) => { + let processSnippet = (snippet: any, name: string) => { let prefix = snippet['prefix']; let bodyStringOrArray = snippet['body']; @@ -106,8 +106,10 @@ function parseSnippetFile(snippetFileContent: string): ISnippet[] { if (typeof prefix === 'string' && typeof bodyStringOrArray === 'string') { result.push({ + name, + owner, prefix, - description: snippet['description'] || description, + description: snippet['description'] || name, codeSnippet: bodyStringOrArray }); } diff --git a/src/vs/editor/node/textMate/TMSyntax.ts b/src/vs/editor/node/textMate/TMSyntax.ts index 9cb52579c01..3c58e4c4768 100644 --- a/src/vs/editor/node/textMate/TMSyntax.ts +++ b/src/vs/editor/node/textMate/TMSyntax.ts @@ -10,10 +10,11 @@ import * as paths from 'vs/base/common/paths'; import {IExtensionMessageCollector, ExtensionsRegistry} from 'vs/platform/extensions/common/extensionsRegistry'; import {ILineTokens, IMode, ITokenizationSupport} from 'vs/editor/common/modes'; import {TMState} from 'vs/editor/common/modes/TMState'; -import {LineTokens, Token} from 'vs/editor/common/modes/supports'; +import {LineTokens} from 'vs/editor/common/modes/supports'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IGrammar, Registry, StackElement} from 'vscode-textmate'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; export interface ITMSyntaxExtensionPoint { language: string; @@ -247,11 +248,11 @@ class Tokenizer { public tokenize(line: string, state: TMState, offsetDelta: number = 0, stopAtOffset?: number): ILineTokens { // Do not attempt to tokenize if a line has over 20k - // or if the rule stack contains more than 30 rules (indicator of broken grammar that forgets to pop rules) - if (line.length >= 20000 || depth(state.getRuleStack()) > 30) { + // or if the rule stack contains more than 100 rules (indicator of broken grammar that forgets to pop rules) + if (line.length >= 20000 || depth(state.getRuleStack()) > 100) { return new LineTokens( [new Token(offsetDelta, '')], - [new ModeTransition(offsetDelta, state.getMode())], + [new ModeTransition(offsetDelta, state.getMode().getId())], offsetDelta, state ); @@ -278,7 +279,7 @@ class Tokenizer { return new LineTokens( tokens, - [new ModeTransition(offsetDelta, freshState.getMode())], + [new ModeTransition(offsetDelta, freshState.getMode().getId())], offsetDelta + line.length, freshState ); diff --git a/src/vs/editor/test/browser/standalone/simpleServices.test.ts b/src/vs/editor/test/browser/standalone/simpleServices.test.ts new file mode 100644 index 00000000000..27303a890e2 --- /dev/null +++ b/src/vs/editor/test/browser/standalone/simpleServices.test.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {ContextKeyService} from 'vs/platform/contextkey/browser/contextKeyService'; +import {SimpleConfigurationService, SimpleMessageService, SimpleExtensionService, StandaloneKeybindingService, StandaloneCommandService} from 'vs/editor/browser/standalone/simpleServices'; +import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; +import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; +import {KeyCode} from 'vs/base/common/keyCodes'; +import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent'; + +suite('StandaloneKeybindingService', () => { + + class TestStandaloneKeybindingService extends StandaloneKeybindingService { + public dispatch(e: IKeyboardEvent): void { + super._dispatch(e); + } + } + + test('issue Microsoft/monaco-editor#167', () => { + + let serviceCollection = new ServiceCollection(); + const instantiationService = new InstantiationService(serviceCollection, true); + + let configurationService = new SimpleConfigurationService(); + + let contextKeyService = new ContextKeyService(configurationService); + + let extensionService = new SimpleExtensionService(); + + let commandService = new StandaloneCommandService(instantiationService, extensionService); + + let messageService = new SimpleMessageService(); + + let domElement = document.createElement('div'); + + let keybindingService = new TestStandaloneKeybindingService(contextKeyService, commandService, messageService, domElement); + + let commandInvoked = false; + keybindingService.addDynamicKeybinding(KeyCode.F9, () => { + commandInvoked = true; + }, null); + + keybindingService.dispatch({ + asKeybinding: () => KeyCode.F9, + preventDefault: () => {} + }); + + assert.ok(commandInvoked, 'command invoked'); + }); +}); diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts index 85e96fdfd98..6e0a26aff1b 100644 --- a/src/vs/editor/test/common/controller/cursor.test.ts +++ b/src/vs/editor/test/common/controller/cursor.test.ts @@ -11,15 +11,15 @@ import {Position} from 'vs/editor/common/core/position'; import {Range} from 'vs/editor/common/core/range'; import {Selection} from 'vs/editor/common/core/selection'; import { - EndOfLinePreference, EventType, Handler, IPosition, ISelection, IEditorOptions, + EndOfLinePreference, EventType, Handler, IEditorOptions, DefaultEndOfLine, ITextModelCreationOptions, ICommand, - ITokenizedModel, IEditOperationBuilder, ICursorStateComputerData + ITokenizedModel, IEditOperationBuilder, ICursorStateComputerData, + ICursorPositionChangedEvent, ICursorSelectionChangedEvent } from 'vs/editor/common/editorCommon'; import {Model} from 'vs/editor/common/model/model'; import {IMode, IndentAction} from 'vs/editor/common/modes'; import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; -import {BracketMode} from 'vs/editor/test/common/testModes'; import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; import {viewModelHelper} from 'vs/editor/test/common/editorTestUtils'; @@ -115,36 +115,15 @@ function deleteWordEndRight(cursor: Cursor) { cursorCommand(cursor, H.DeleteWordEndRight); } -function positionEqual(position:IPosition, lineNumber: number, column: number) { - assert.deepEqual({ - lineNumber: position.lineNumber, - column: position.column - }, { - lineNumber: lineNumber, - column: column - }, 'position equal'); -} - -function selectionEqual(selection:ISelection, posLineNumber: number, posColumn: number, selLineNumber: number, selColumn: number) { - assert.deepEqual({ - selectionStartLineNumber: selection.selectionStartLineNumber, - selectionStartColumn: selection.selectionStartColumn, - positionLineNumber: selection.positionLineNumber, - positionColumn: selection.positionColumn - }, { - selectionStartLineNumber: selLineNumber, - selectionStartColumn: selColumn, - positionLineNumber: posLineNumber, - positionColumn: posColumn - }, 'selection equal'); -} - -function cursorEqual(cursor: Cursor, posLineNumber: number, posColumn: number, selLineNumber: number = posLineNumber, selColumn: number = posColumn) { - positionEqual(cursor.getPosition(), posLineNumber, posColumn); - selectionEqual(cursor.getSelection(), posLineNumber, posColumn, selLineNumber, selColumn); -} - -function cursorEquals(cursor: Cursor, selections: Selection[]): void { +function assertCursor(cursor: Cursor, what: Position|Selection|Selection[]): void { + let selections:Selection[]; + if (what instanceof Position) { + selections = [new Selection(what.lineNumber, what.column, what.lineNumber, what.column)]; + } else if (what instanceof Selection) { + selections = [what]; + } else { + selections = what; + } let actual = cursor.getSelections().map(s => s.toString()); let expected = selections.map(s => s.toString()); @@ -182,90 +161,90 @@ suite('Editor Controller - Cursor', () => { }); test('cursor initialized', () => { - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); // --------- absolute move test('no move', () => { moveTo(thisCursor, 1, 1); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move', () => { moveTo(thisCursor, 1, 2); - cursorEqual(thisCursor, 1, 2); + assertCursor(thisCursor, new Position(1, 2)); }); test('move in selection mode', () => { moveTo(thisCursor, 1, 2, true); - cursorEqual(thisCursor, 1, 2, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, 2)); }); test('move beyond line end', () => { moveTo(thisCursor, 1, 25); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); test('move empty line', () => { moveTo(thisCursor, 4, 20); - cursorEqual(thisCursor, 4, 1); + assertCursor(thisCursor, new Position(4, 1)); }); test('move one char line', () => { moveTo(thisCursor, 5, 20); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); }); test('selection down', () => { moveTo(thisCursor, 2, 1, true); - cursorEqual(thisCursor, 2, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, 1)); }); test('move and then select', () => { moveTo(thisCursor, 2, 3); - cursorEqual(thisCursor, 2, 3); + assertCursor(thisCursor, new Position(2, 3)); moveTo(thisCursor, 2, 15, true); - cursorEqual(thisCursor, 2, 15, 2, 3); + assertCursor(thisCursor, new Selection(2, 3, 2, 15)); moveTo(thisCursor, 1, 2, true); - cursorEqual(thisCursor, 1, 2, 2, 3); + assertCursor(thisCursor, new Selection(2, 3, 1, 2)); }); // --------- move left test('move left on top left position', () => { moveLeft(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move left', () => { moveTo(thisCursor, 1, 3); - cursorEqual(thisCursor, 1, 3); + assertCursor(thisCursor, new Position(1, 3)); moveLeft(thisCursor); - cursorEqual(thisCursor, 1, 2); + assertCursor(thisCursor, new Position(1, 2)); }); test('move left with surrogate pair', () => { moveTo(thisCursor, 3, 17); - cursorEqual(thisCursor, 3, 17); + assertCursor(thisCursor, new Position(3, 17)); moveLeft(thisCursor); - cursorEqual(thisCursor, 3, 15); + assertCursor(thisCursor, new Position(3, 15)); }); test('move left goes to previous row', () => { moveTo(thisCursor, 2, 1); - cursorEqual(thisCursor, 2, 1); + assertCursor(thisCursor, new Position(2, 1)); moveLeft(thisCursor); - cursorEqual(thisCursor, 1, 21); + assertCursor(thisCursor, new Position(1, 21)); }); test('move left selection', () => { moveTo(thisCursor, 2, 1); - cursorEqual(thisCursor, 2, 1); + assertCursor(thisCursor, new Position(2, 1)); moveLeft(thisCursor, true); - cursorEqual(thisCursor, 1, 21, 2, 1); + assertCursor(thisCursor, new Selection(2, 1, 1, 21)); }); // --------- move word left @@ -301,46 +280,46 @@ suite('Editor Controller - Cursor', () => { test('move word left selection', () => { moveTo(thisCursor, 5, 2); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); moveWordLeft(thisCursor, true); - cursorEqual(thisCursor, 5, 1, 5, 2); + assertCursor(thisCursor, new Selection(5, 2, 5, 1)); }); // --------- move right test('move right on bottom right position', () => { moveTo(thisCursor, 5, 2); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); moveRight(thisCursor); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); }); test('move right', () => { moveTo(thisCursor, 1, 3); - cursorEqual(thisCursor, 1, 3); + assertCursor(thisCursor, new Position(1, 3)); moveRight(thisCursor); - cursorEqual(thisCursor, 1, 4); + assertCursor(thisCursor, new Position(1, 4)); }); test('move right with surrogate pair', () => { moveTo(thisCursor, 3, 15); - cursorEqual(thisCursor, 3, 15); + assertCursor(thisCursor, new Position(3, 15)); moveRight(thisCursor); - cursorEqual(thisCursor, 3, 17); + assertCursor(thisCursor, new Position(3, 17)); }); test('move right goes to next row', () => { moveTo(thisCursor, 1, 21); - cursorEqual(thisCursor, 1, 21); + assertCursor(thisCursor, new Position(1, 21)); moveRight(thisCursor); - cursorEqual(thisCursor, 2, 1); + assertCursor(thisCursor, new Position(2, 1)); }); test('move right selection', () => { moveTo(thisCursor, 1, 21); - cursorEqual(thisCursor, 1, 21); + assertCursor(thisCursor, new Position(1, 21)); moveRight(thisCursor, true); - cursorEqual(thisCursor, 2, 1, 1, 21); + assertCursor(thisCursor, new Selection(1, 21, 2, 1)); }); // --------- move word right @@ -374,233 +353,233 @@ suite('Editor Controller - Cursor', () => { test('move word right selection', () => { moveTo(thisCursor, 1, 1); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); moveWordRight(thisCursor, true); - cursorEqual(thisCursor, 1, 8, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, 8)); }); // --------- move down test('move down', () => { moveDown(thisCursor, 1); - cursorEqual(thisCursor, 2, 1); + assertCursor(thisCursor, new Position(2, 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 3, 1); + assertCursor(thisCursor, new Position(3, 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 4, 1); + assertCursor(thisCursor, new Position(4, 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 5, 1); + assertCursor(thisCursor, new Position(5, 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); }); test('move down with selection', () => { moveDown(thisCursor, 1, true); - cursorEqual(thisCursor, 2, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, 1)); moveDown(thisCursor, 1, true); - cursorEqual(thisCursor, 3, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 3, 1)); moveDown(thisCursor, 1, true); - cursorEqual(thisCursor, 4, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 4, 1)); moveDown(thisCursor, 1, true); - cursorEqual(thisCursor, 5, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 5, 1)); moveDown(thisCursor, 1, true); - cursorEqual(thisCursor, 5, 2, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 5, 2)); }); test('move down with tabs', () => { moveTo(thisCursor, 1, 5); - cursorEqual(thisCursor, 1, 5); + assertCursor(thisCursor, new Position(1, 5)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 2, 2); + assertCursor(thisCursor, new Position(2, 2)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 3, 5); + assertCursor(thisCursor, new Position(3, 5)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 4, 1); + assertCursor(thisCursor, new Position(4, 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); }); // --------- move up test('move up', () => { moveTo(thisCursor, 3, 5); - cursorEqual(thisCursor, 3, 5); + assertCursor(thisCursor, new Position(3, 5)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 2, 2); + assertCursor(thisCursor, new Position(2, 2)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 1, 5); + assertCursor(thisCursor, new Position(1, 5)); }); test('move up with selection', () => { moveTo(thisCursor, 3, 5); - cursorEqual(thisCursor, 3, 5); + assertCursor(thisCursor, new Position(3, 5)); moveUp(thisCursor, 1, true); - cursorEqual(thisCursor, 2, 2, 3, 5); + assertCursor(thisCursor, new Selection(3, 5, 2, 2)); moveUp(thisCursor, 1, true); - cursorEqual(thisCursor, 1, 5, 3, 5); + assertCursor(thisCursor, new Selection(3, 5, 1, 5)); }); test('move up and down with tabs', () => { moveTo(thisCursor, 1, 5); - cursorEqual(thisCursor, 1, 5); + assertCursor(thisCursor, new Position(1, 5)); moveDown(thisCursor, 4); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 4, 1); + assertCursor(thisCursor, new Position(4, 1)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 3, 5); + assertCursor(thisCursor, new Position(3, 5)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 2, 2); + assertCursor(thisCursor, new Position(2, 2)); moveUp(thisCursor, 1); - cursorEqual(thisCursor, 1, 5); + assertCursor(thisCursor, new Position(1, 5)); }); test('move up and down with end of lines starting from a long one', () => { moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); + assertCursor(thisCursor, new Position(1, LINE1.length - 1)); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 2, LINE2.length + 1); + assertCursor(thisCursor, new Position(2, LINE2.length + 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 3, LINE3.length + 1); + assertCursor(thisCursor, new Position(3, LINE3.length + 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 4, LINE4.length + 1); + assertCursor(thisCursor, new Position(4, LINE4.length + 1)); moveDown(thisCursor, 1); - cursorEqual(thisCursor, 5, LINE5.length + 1); + assertCursor(thisCursor, new Position(5, LINE5.length + 1)); moveUp(thisCursor, 4); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); // --------- move to beginning of line test('move to beginning of line', () => { moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 6); + assertCursor(thisCursor, new Position(1, 6)); moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move to beginning of line from within line', () => { moveTo(thisCursor, 1, 8); moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 6); + assertCursor(thisCursor, new Position(1, 6)); moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move to beginning of line from whitespace at beginning of line', () => { moveTo(thisCursor, 1, 2); moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); moveToBeginningOfLine(thisCursor); - cursorEqual(thisCursor, 1, 6); + assertCursor(thisCursor, new Position(1, 6)); }); test('move to beginning of line from within line selection', () => { moveTo(thisCursor, 1, 8); moveToBeginningOfLine(thisCursor, true); - cursorEqual(thisCursor, 1, 6, 1, 8); + assertCursor(thisCursor, new Selection(1, 8, 1, 6)); moveToBeginningOfLine(thisCursor, true); - cursorEqual(thisCursor, 1, 1, 1, 8); + assertCursor(thisCursor, new Selection(1, 8, 1, 1)); }); // --------- move to end of line test('move to end of line', () => { moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); + assertCursor(thisCursor, new Position(1, LINE1.length - 1)); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); test('move to end of line from within line', () => { moveTo(thisCursor, 1, 6); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); + assertCursor(thisCursor, new Position(1, LINE1.length - 1)); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); }); test('move to end of line from whitespace at end of line', () => { moveTo(thisCursor, 1, 20); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); + assertCursor(thisCursor, new Position(1, LINE1.length + 1)); moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); + assertCursor(thisCursor, new Position(1, LINE1.length - 1)); }); test('move to end of line from within line selection', () => { moveTo(thisCursor, 1, 6); moveToEndOfLine(thisCursor, true); - cursorEqual(thisCursor, 1, LINE1.length - 1, 1, 6); + assertCursor(thisCursor, new Selection(1, 6, 1, LINE1.length - 1)); moveToEndOfLine(thisCursor, true); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 6); + assertCursor(thisCursor, new Selection(1, 6, 1, LINE1.length + 1)); }); // --------- move to beginning of buffer test('move to beginning of buffer', () => { moveToBeginningOfBuffer(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move to beginning of buffer from within first line', () => { moveTo(thisCursor, 1, 3); moveToBeginningOfBuffer(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move to beginning of buffer from within another line', () => { moveTo(thisCursor, 3, 3); moveToBeginningOfBuffer(thisCursor); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('move to beginning of buffer from within first line selection', () => { moveTo(thisCursor, 1, 3); moveToBeginningOfBuffer(thisCursor, true); - cursorEqual(thisCursor, 1, 1, 1, 3); + assertCursor(thisCursor, new Selection(1, 3, 1, 1)); }); test('move to beginning of buffer from within another line selection', () => { moveTo(thisCursor, 3, 3); moveToBeginningOfBuffer(thisCursor, true); - cursorEqual(thisCursor, 1, 1, 3, 3); + assertCursor(thisCursor, new Selection(3, 3, 1, 1)); }); // --------- move to end of buffer test('move to end of buffer', () => { moveToEndOfBuffer(thisCursor); - cursorEqual(thisCursor, 5, LINE5.length + 1); + assertCursor(thisCursor, new Position(5, LINE5.length + 1)); }); test('move to end of buffer from within last line', () => { moveTo(thisCursor, 5, 1); moveToEndOfBuffer(thisCursor); - cursorEqual(thisCursor, 5, LINE5.length + 1); + assertCursor(thisCursor, new Position(5, LINE5.length + 1)); }); test('move to end of buffer from within another line', () => { moveTo(thisCursor, 3, 3); moveToEndOfBuffer(thisCursor); - cursorEqual(thisCursor, 5, LINE5.length + 1); + assertCursor(thisCursor, new Position(5, LINE5.length + 1)); }); test('move to end of buffer from within last line selection', () => { moveTo(thisCursor, 5, 1); moveToEndOfBuffer(thisCursor, true); - cursorEqual(thisCursor, 5, LINE5.length + 1, 5, 1); + assertCursor(thisCursor, new Selection(5, 1, 5, LINE5.length + 1)); }); test('move to end of buffer from within another line selection', () => { moveTo(thisCursor, 3, 3); moveToEndOfBuffer(thisCursor, true); - cursorEqual(thisCursor, 5, LINE5.length + 1, 3, 3); + assertCursor(thisCursor, new Selection(3, 3, 5, LINE5.length + 1)); }); // --------- delete word left/right @@ -611,35 +590,35 @@ suite('Editor Controller - Cursor', () => { moveRight(thisCursor, true); deleteWordLeft(thisCursor); assert.equal(thisModel.getLineContent(3), ' Thd Line💩'); - cursorEqual(thisCursor, 3, 7); + assertCursor(thisCursor, new Position(3, 7)); }); test('delete word left for caret at beginning of document', () => { moveTo(thisCursor, 1, 1); deleteWordLeft(thisCursor); assert.equal(thisModel.getLineContent(1), ' \tMy First Line\t '); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); }); test('delete word left for caret at end of whitespace', () => { moveTo(thisCursor, 3, 11); deleteWordLeft(thisCursor); assert.equal(thisModel.getLineContent(3), ' Line💩'); - cursorEqual(thisCursor, 3, 5); + assertCursor(thisCursor, new Position(3, 5)); }); test('delete word left for caret just behind a word', () => { moveTo(thisCursor, 2, 11); deleteWordLeft(thisCursor); assert.equal(thisModel.getLineContent(2), '\tMy Line'); - cursorEqual(thisCursor, 2, 5); + assertCursor(thisCursor, new Position(2, 5)); }); test('delete word left for caret inside of a word', () => { moveTo(thisCursor, 1, 12); deleteWordLeft(thisCursor); assert.equal(thisModel.getLineContent(1), ' \tMy st Line\t '); - cursorEqual(thisCursor, 1, 9); + assertCursor(thisCursor, new Position(1, 9)); }); test('delete word right for non-empty selection', () => { @@ -648,42 +627,42 @@ suite('Editor Controller - Cursor', () => { moveRight(thisCursor, true); deleteWordRight(thisCursor); assert.equal(thisModel.getLineContent(3), ' Thd Line💩'); - cursorEqual(thisCursor, 3, 7); + assertCursor(thisCursor, new Position(3, 7)); }); test('delete word right for caret at end of document', () => { moveTo(thisCursor, 5, 3); deleteWordRight(thisCursor); assert.equal(thisModel.getLineContent(5), '1'); - cursorEqual(thisCursor, 5, 2); + assertCursor(thisCursor, new Position(5, 2)); }); test('delete word right for caret at beggining of whitespace', () => { moveTo(thisCursor, 3, 1); deleteWordRight(thisCursor); assert.equal(thisModel.getLineContent(3), 'Third Line💩'); - cursorEqual(thisCursor, 3, 1); + assertCursor(thisCursor, new Position(3, 1)); }); test('delete word right for caret just before a word', () => { moveTo(thisCursor, 2, 5); deleteWordRight(thisCursor); assert.equal(thisModel.getLineContent(2), '\tMy Line'); - cursorEqual(thisCursor, 2, 5); + assertCursor(thisCursor, new Position(2, 5)); }); test('delete word right for caret inside of a word', () => { moveTo(thisCursor, 1, 11); deleteWordRight(thisCursor); assert.equal(thisModel.getLineContent(1), ' \tMy Fi Line\t '); - cursorEqual(thisCursor, 1, 11); + assertCursor(thisCursor, new Position(1, 11)); }); // --------- misc test('select all', () => { cursorCommand(thisCursor, H.SelectAll); - cursorEqual(thisCursor, 5, LINE5.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 5, LINE5.length + 1)); }); test('expandLineSelection', () => { @@ -692,37 +671,37 @@ suite('Editor Controller - Cursor', () => { // let LINE1 = ' \tMy First Line\t '; moveTo(thisCursor, 1, 1); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); moveTo(thisCursor, 1, 2); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); moveTo(thisCursor, 1, 5); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); moveTo(thisCursor, 1, 19); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); moveTo(thisCursor, 1, 20); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); moveTo(thisCursor, 1, 21); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 1, LINE1.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 1, LINE1.length + 1)); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 2, LINE2.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, LINE2.length + 1)); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 3, LINE3.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 3, LINE3.length + 1)); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 4, LINE4.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 4, LINE4.length + 1)); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 5, LINE5.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 5, LINE5.length + 1)); cursorCommand(thisCursor, H.ExpandLineSelection); - cursorEqual(thisCursor, 5, LINE5.length + 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 5, LINE5.length + 1)); }); // --------- eventing @@ -739,13 +718,13 @@ suite('Editor Controller - Cursor', () => { test('move eventing', () => { let events = 0; - thisCursor.addListener2(EventType.CursorPositionChanged, (e) => { + thisCursor.addListener2(EventType.CursorPositionChanged, (e:ICursorPositionChangedEvent) => { events++; - positionEqual(e.position, 1, 2); + assert.deepEqual(e.position, new Position(1, 2)); }); - thisCursor.addListener2(EventType.CursorSelectionChanged, (e) => { + thisCursor.addListener2(EventType.CursorSelectionChanged, (e:ICursorSelectionChangedEvent) => { events++; - selectionEqual(e.selection, 1, 2, 1, 2); + assert.deepEqual(e.selection, new Selection(1, 2, 1, 2)); }); moveTo(thisCursor, 1, 2); assert.equal(events, 2, 'receives 2 events'); @@ -753,13 +732,13 @@ suite('Editor Controller - Cursor', () => { test('move in selection mode eventing', () => { let events = 0; - thisCursor.addListener2(EventType.CursorPositionChanged, (e) => { + thisCursor.addListener2(EventType.CursorPositionChanged, (e:ICursorPositionChangedEvent) => { events++; - positionEqual(e.position, 1, 2); + assert.deepEqual(e.position, new Position(1, 2)); }); - thisCursor.addListener2(EventType.CursorSelectionChanged, (e) => { + thisCursor.addListener2(EventType.CursorSelectionChanged, (e:ICursorSelectionChangedEvent) => { events++; - selectionEqual(e.selection, 1, 2, 1, 1); + assert.deepEqual(e.selection, new Selection(1, 1, 1, 2)); }); moveTo(thisCursor, 1, 2, true); assert.equal(events, 2, 'receives 2 events'); @@ -769,15 +748,15 @@ suite('Editor Controller - Cursor', () => { test('saveState & restoreState', () => { moveTo(thisCursor, 2, 1, true); - cursorEqual(thisCursor, 2, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, 1)); let savedState = JSON.stringify(thisCursor.saveState()); moveTo(thisCursor, 1, 1, false); - cursorEqual(thisCursor, 1, 1); + assertCursor(thisCursor, new Position(1, 1)); thisCursor.restoreState(JSON.parse(savedState)); - cursorEqual(thisCursor, 2, 1, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, 1)); }); // --------- updating cursor @@ -786,7 +765,7 @@ suite('Editor Controller - Cursor', () => { moveTo(thisCursor, 2, 16, true); thisModel.applyEdits([EditOperation.delete(new Range(2, 1, 2, 2))]); - cursorEqual(thisCursor, 2, 15, 1, 1); + assertCursor(thisCursor, new Selection(1, 1, 2, 15)); }); test('column select 1', () => { @@ -800,7 +779,7 @@ suite('Editor Controller - Cursor', () => { let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 7, false); - cursorEqual(cursor, 1, 7); + assertCursor(cursor, new Position(1, 7)); cursorCommand(cursor, H.ColumnSelect, { position: new Position(4, 4), @@ -815,7 +794,7 @@ suite('Editor Controller - Cursor', () => { new Selection(4, 4, 4, 4), ]; - cursorEquals(cursor, expectedSelections); + assertCursor(cursor, expectedSelections); cursor.dispose(); model.dispose(); @@ -834,7 +813,7 @@ suite('Editor Controller - Cursor', () => { let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 4, false); - cursorEqual(cursor, 1, 4); + assertCursor(cursor, new Position(1, 4)); cursorCommand(cursor, H.ColumnSelect, { position: new Position(4, 1), @@ -842,7 +821,7 @@ suite('Editor Controller - Cursor', () => { mouseColumn: 1 }); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 1), new Selection(2, 4, 2, 1), new Selection(3, 4, 3, 1), @@ -866,21 +845,21 @@ suite('Editor Controller - Cursor', () => { let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 4, false); - cursorEqual(cursor, 1, 4); + assertCursor(cursor, new Position(1, 4)); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 5) ]); cursorCommand(cursor, H.CursorColumnSelectDown); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 5), new Selection(2, 4, 2, 5) ]); cursorCommand(cursor, H.CursorColumnSelectDown); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 5), new Selection(2, 4, 2, 5), new Selection(3, 4, 3, 5), @@ -890,7 +869,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectDown); cursorCommand(cursor, H.CursorColumnSelectDown); cursorCommand(cursor, H.CursorColumnSelectDown); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 5), new Selection(2, 4, 2, 5), new Selection(3, 4, 3, 5), @@ -901,7 +880,7 @@ suite('Editor Controller - Cursor', () => { ]); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 6), new Selection(2, 4, 2, 6), new Selection(3, 4, 3, 6), @@ -922,7 +901,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 16), new Selection(2, 4, 2, 16), new Selection(3, 4, 3, 16), @@ -943,7 +922,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 26), new Selection(2, 4, 2, 26), new Selection(3, 4, 3, 26), @@ -956,7 +935,7 @@ suite('Editor Controller - Cursor', () => { // 2 times => reaching the ending of lines 1 and 2 cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 28), @@ -971,7 +950,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -984,7 +963,7 @@ suite('Editor Controller - Cursor', () => { // 2 times => reaching the ending of line 4 cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -996,7 +975,7 @@ suite('Editor Controller - Cursor', () => { // 1 time => reaching the ending of line 7 cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -1010,7 +989,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -1022,7 +1001,7 @@ suite('Editor Controller - Cursor', () => { // cannot go anywhere anymore cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -1037,7 +1016,7 @@ suite('Editor Controller - Cursor', () => { cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); cursorCommand(cursor, H.CursorColumnSelectRight); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -1049,7 +1028,7 @@ suite('Editor Controller - Cursor', () => { // can easily go back cursorCommand(cursor, H.CursorColumnSelectLeft); - cursorEquals(cursor, [ + assertCursor(cursor, [ new Selection(1, 4, 1, 28), new Selection(2, 4, 2, 28), new Selection(3, 4, 3, 32), @@ -1149,6 +1128,19 @@ suite('Editor Controller - Regression tests', () => { }); test('issue #183: jump to matching bracket position', () => { + class BracketMode extends MockMode { + constructor() { + super(); + LanguageConfigurationRegistry.register(this.getId(), { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ] + }); + } + } + usingCursor({ text: [ 'var x = (3 + (5-7));' @@ -1161,13 +1153,13 @@ suite('Editor Controller - Regression tests', () => { moveTo(cursor, 1, 20); cursorCommand(cursor, H.JumpToBracket, null, 'keyboard'); - cursorEqual(cursor, 1, 10); + assertCursor(cursor, new Position(1, 10)); cursorCommand(cursor, H.JumpToBracket, null, 'keyboard'); - cursorEqual(cursor, 1, 20); + assertCursor(cursor, new Position(1, 20)); cursorCommand(cursor, H.JumpToBracket, null, 'keyboard'); - cursorEqual(cursor, 1, 10); + assertCursor(cursor, new Position(1, 10)); }); }); @@ -1191,7 +1183,7 @@ suite('Editor Controller - Regression tests', () => { mode: new OnEnterMode(IndentAction.Indent), }, (model, cursor) => { moveTo(cursor, 4, 1, false); - cursorEqual(cursor, 4, 1, 4, 1); + assertCursor(cursor, new Selection(4, 1, 4, 1)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(4), '\t\t'); @@ -1218,7 +1210,7 @@ suite('Editor Controller - Regression tests', () => { mode: new OnEnterMode(IndentAction.Indent), }, (model, cursor) => { moveTo(cursor, 4, 2, false); - cursorEqual(cursor, 4, 2, 4, 2); + assertCursor(cursor, new Selection(4, 2, 4, 2)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(4), '\t\t\t'); @@ -1246,7 +1238,7 @@ suite('Editor Controller - Regression tests', () => { mode: new OnEnterMode(IndentAction.Indent), }, (model, cursor) => { moveTo(cursor, 4, 1, false); - cursorEqual(cursor, 4, 1, 4, 1); + assertCursor(cursor, new Selection(4, 1, 4, 1)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(4), '\t\t\t'); @@ -1273,7 +1265,7 @@ suite('Editor Controller - Regression tests', () => { mode: new OnEnterMode(IndentAction.Indent), }, (model, cursor) => { moveTo(cursor, 4, 3, false); - cursorEqual(cursor, 4, 3, 4, 3); + assertCursor(cursor, new Selection(4, 3, 4, 3)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(4), '\t\t\t\t'); @@ -1300,7 +1292,7 @@ suite('Editor Controller - Regression tests', () => { mode: new OnEnterMode(IndentAction.Indent), }, (model, cursor) => { moveTo(cursor, 4, 4, false); - cursorEqual(cursor, 4, 4, 4, 4); + assertCursor(cursor, new Selection(4, 4, 4, 4)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(4), '\t\t\t\t\t'); @@ -1321,12 +1313,12 @@ suite('Editor Controller - Regression tests', () => { }, }, (model, cursor) => { moveTo(cursor, 1, 2, false); - cursorEqual(cursor, 1, 2, 1, 2); + assertCursor(cursor, new Selection(1, 2, 1, 2)); cursorCommand(cursor, H.Indent, null, 'keyboard'); assert.equal(model.getLineContent(1), '\tfunction baz() {'); - cursorEqual(cursor, 1, 3, 1, 3); + assertCursor(cursor, new Selection(1, 3, 1, 3)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(1), '\tf\tunction baz() {'); }); @@ -1341,11 +1333,11 @@ suite('Editor Controller - Regression tests', () => { modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true } }, (model, cursor) => { moveTo(cursor, 1, 6, false); - cursorEqual(cursor, 1, 6, 1, 6); + assertCursor(cursor, new Selection(1, 6, 1, 6)); cursorCommand(cursor, H.Outdent, null, 'keyboard'); assert.equal(model.getLineContent(1), ' function baz() {'); - cursorEqual(cursor, 1, 5, 1, 5); + assertCursor(cursor, new Selection(1, 5, 1, 5)); }); }); @@ -1357,11 +1349,11 @@ suite('Editor Controller - Regression tests', () => { modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true } }, (model, cursor) => { moveTo(cursor, 1, 7, false); - cursorEqual(cursor, 1, 7, 1, 7); + assertCursor(cursor, new Selection(1, 7, 1, 7)); cursorCommand(cursor, H.Outdent, null, 'keyboard'); assert.equal(model.getLineContent(1), ' '); - cursorEqual(cursor, 1, 5, 1, 5); + assertCursor(cursor, new Selection(1, 5, 1, 5)); }); }); @@ -1385,11 +1377,11 @@ suite('Editor Controller - Regression tests', () => { }, }, (model, cursor) => { moveTo(cursor, 7, 1, false); - cursorEqual(cursor, 7, 1, 7, 1); + assertCursor(cursor, new Selection(7, 1, 7, 1)); cursorCommand(cursor, H.Tab, null, 'keyboard'); assert.equal(model.getLineContent(7), '\t'); - cursorEqual(cursor, 7, 2, 7, 2); + assertCursor(cursor, new Selection(7, 2, 7, 2)); }); }); @@ -1403,7 +1395,7 @@ suite('Editor Controller - Regression tests', () => { let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 2, 1, false); - cursorEqual(cursor, 2, 1, 2, 1); + assertCursor(cursor, new Selection(2, 1, 2, 1)); cursorCommand(cursor, H.Cut, null, 'keyboard'); assert.equal(model.getLineCount(), 1); @@ -1421,7 +1413,7 @@ suite('Editor Controller - Regression tests', () => { cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 2, 1, false); - cursorEqual(cursor, 2, 1, 2, 1); + assertCursor(cursor, new Selection(2, 1, 2, 1)); cursorCommand(cursor, H.Cut, null, 'keyboard'); assert.equal(model.getLineCount(), 1); @@ -1445,13 +1437,13 @@ suite('Editor Controller - Regression tests', () => { }, (model, cursor) => { moveTo(cursor, 1, 3, false); moveTo(cursor, 1, 5, true); - cursorEqual(cursor, 1, 5, 1, 3); + assertCursor(cursor, new Selection(1, 3, 1, 5)); cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard'); - cursorEqual(cursor, 1, 6, 1, 4); + assertCursor(cursor, new Selection(1, 4, 1, 6)); cursorCommand(cursor, H.Type, { text: '(' }, 'keyboard'); - cursorEqual(cursor, 1, 7, 1, 5); + assertCursor(cursor, new Selection(1, 5, 1, 7)); }); }); @@ -1467,10 +1459,10 @@ suite('Editor Controller - Regression tests', () => { }, (model, cursor) => { moveTo(cursor, 3, 2, false); moveTo(cursor, 1, 14, true); - cursorEqual(cursor, 1, 14, 3, 2); + assertCursor(cursor, new Selection(3, 2, 1, 14)); cursorCommand(cursor, H.DeleteLeft); - cursorEqual(cursor, 1, 14, 1, 14); + assertCursor(cursor, new Selection(1, 14, 1, 14)); assert.equal(model.getLineCount(), 1); assert.equal(model.getLineContent(1), 'function baz(;'); }); @@ -2026,6 +2018,89 @@ suite('Editor Controller - Regression tests', () => { deleteWordLeft(cursor); assert.equal(model.getLineContent(1), 'A line with text. And another one', '001'); }); }); + + test('issue Microsoft/monaco-editor#108 part 1/2: Auto indentation on Enter with selection is half broken', () => { + usingCursor({ + text: [ + 'function baz() {', + '\tvar x = 1;', + '\t\t\t\t\t\t\treturn x;', + '}' + ], + modelOpts: { + defaultEOL: DefaultEndOfLine.LF, + detectIndentation: false, + insertSpaces: false, + tabSize: 4, + trimAutoWhitespace: true + }, + mode: new OnEnterMode(IndentAction.None), + }, (model, cursor) => { + moveTo(cursor, 3, 8, false); + moveTo(cursor, 2, 12, true); + assertCursor(cursor, new Selection(3, 8, 2, 12)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.equal(model.getLineContent(3), '\treturn x;'); + assertCursor(cursor, new Position(3, 2)); + }); + }); + + test('issue Microsoft/monaco-editor#108 part 2/2: Auto indentation on Enter with selection is half broken', () => { + usingCursor({ + text: [ + 'function baz() {', + '\tvar x = 1;', + '\t\t\t\t\t\t\treturn x;', + '}' + ], + modelOpts: { + defaultEOL: DefaultEndOfLine.LF, + detectIndentation: false, + insertSpaces: false, + tabSize: 4, + trimAutoWhitespace: true + }, + mode: new OnEnterMode(IndentAction.None), + }, (model, cursor) => { + moveTo(cursor, 2, 12, false); + moveTo(cursor, 3, 8, true); + assertCursor(cursor, new Selection(2, 12, 3, 8)); + + cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); + assert.equal(model.getLineContent(3), '\treturn x;'); + assertCursor(cursor, new Position(3, 2)); + }); + }); + + test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => { + usingCursor({ + text: [ + ] + }, (model, cursor) => { + assertCursor(cursor, new Position(1, 1)); + + // Typing sennsei in Japanese - Hiragana + cursorCommand(cursor, H.Type, { text: 's'}, 'keyboard'); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せ', replaceCharCnt: 1}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せn', replaceCharCnt: 1}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せん', replaceCharCnt: 2}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんs', replaceCharCnt: 2}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせ', replaceCharCnt: 3}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせ', replaceCharCnt: 3}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせい', replaceCharCnt: 3}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせい', replaceCharCnt: 4}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせい', replaceCharCnt: 4}); + cursorCommand(cursor, H.ReplacePreviousChar, {text: 'せんせい', replaceCharCnt: 4}); + + assert.equal(model.getLineContent(1), 'せんせい'); + assertCursor(cursor, new Position(1, 5)); + + cursorCommand(cursor, H.Undo); + assert.equal(model.getLineContent(1), ''); + assertCursor(cursor, new Position(1, 1)); + }); + }); }); suite('Editor Controller - Cursor Configuration', () => { @@ -2124,7 +2199,7 @@ suite('Editor Controller - Cursor Configuration', () => { modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true } }, (model, cursor) => { moveTo(cursor, 1, 7, false); - cursorEqual(cursor, 1, 7, 1, 7); + assertCursor(cursor, new Selection(1, 7, 1, 7)); cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); @@ -2140,7 +2215,7 @@ suite('Editor Controller - Cursor Configuration', () => { modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true } }, (model, cursor) => { moveTo(cursor, 1, 7, false); - cursorEqual(cursor, 1, 7, 1, 7); + assertCursor(cursor, new Selection(1, 7, 1, 7)); cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thello\r\n '); @@ -2156,7 +2231,7 @@ suite('Editor Controller - Cursor Configuration', () => { modelOpts: { insertSpaces: true, tabSize: 4, detectIndentation: false, defaultEOL: DefaultEndOfLine.LF, trimAutoWhitespace: true } }, (model, cursor) => { moveTo(cursor, 1, 7, false); - cursorEqual(cursor, 1, 7, 1, 7); + assertCursor(cursor, new Selection(1, 7, 1, 7)); cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); assert.equal(model.getValue(EndOfLinePreference.CRLF), '\thell(\r\n \r\n )'); @@ -2173,7 +2248,7 @@ suite('Editor Controller - Cursor Configuration', () => { ], }, (model, cursor) => { moveTo(cursor, lineNumber, column, false); - cursorEqual(cursor, lineNumber, column, lineNumber, column); + assertCursor(cursor, new Position(lineNumber, column)); cursorCommand(cursor, H.LineInsertBefore, null, 'keyboard'); callback(model, cursor); @@ -2181,7 +2256,7 @@ suite('Editor Controller - Cursor Configuration', () => { }; testInsertLineBefore(1, 3, (model, cursor) => { - cursorEqual(cursor, 1, 1, 1, 1); + assertCursor(cursor, new Selection(1, 1, 1, 1)); assert.equal(model.getLineContent(1), ''); assert.equal(model.getLineContent(2), 'First line'); assert.equal(model.getLineContent(3), 'Second line'); @@ -2189,7 +2264,7 @@ suite('Editor Controller - Cursor Configuration', () => { }); testInsertLineBefore(2, 3, (model, cursor) => { - cursorEqual(cursor, 2, 1, 2, 1); + assertCursor(cursor, new Selection(2, 1, 2, 1)); assert.equal(model.getLineContent(1), 'First line'); assert.equal(model.getLineContent(2), ''); assert.equal(model.getLineContent(3), 'Second line'); @@ -2197,7 +2272,7 @@ suite('Editor Controller - Cursor Configuration', () => { }); testInsertLineBefore(3, 3, (model, cursor) => { - cursorEqual(cursor, 3, 1, 3, 1); + assertCursor(cursor, new Selection(3, 1, 3, 1)); assert.equal(model.getLineContent(1), 'First line'); assert.equal(model.getLineContent(2), 'Second line'); assert.equal(model.getLineContent(3), ''); @@ -2215,7 +2290,7 @@ suite('Editor Controller - Cursor Configuration', () => { ], }, (model, cursor) => { moveTo(cursor, lineNumber, column, false); - cursorEqual(cursor, lineNumber, column, lineNumber, column); + assertCursor(cursor, new Position(lineNumber, column)); cursorCommand(cursor, H.LineInsertAfter, null, 'keyboard'); callback(model, cursor); @@ -2223,7 +2298,7 @@ suite('Editor Controller - Cursor Configuration', () => { }; testInsertLineAfter(1, 3, (model, cursor) => { - cursorEqual(cursor, 2, 1, 2, 1); + assertCursor(cursor, new Selection(2, 1, 2, 1)); assert.equal(model.getLineContent(1), 'First line'); assert.equal(model.getLineContent(2), ''); assert.equal(model.getLineContent(3), 'Second line'); @@ -2231,7 +2306,7 @@ suite('Editor Controller - Cursor Configuration', () => { }); testInsertLineAfter(2, 3, (model, cursor) => { - cursorEqual(cursor, 3, 1, 3, 1); + assertCursor(cursor, new Selection(3, 1, 3, 1)); assert.equal(model.getLineContent(1), 'First line'); assert.equal(model.getLineContent(2), 'Second line'); assert.equal(model.getLineContent(3), ''); @@ -2239,7 +2314,7 @@ suite('Editor Controller - Cursor Configuration', () => { }); testInsertLineAfter(3, 3, (model, cursor) => { - cursorEqual(cursor, 4, 1, 4, 1); + assertCursor(cursor, new Selection(4, 1, 4, 1)); assert.equal(model.getLineContent(1), 'First line'); assert.equal(model.getLineContent(2), 'Second line'); assert.equal(model.getLineContent(3), 'Third line'); @@ -2441,7 +2516,7 @@ suite('Editor Controller - Cursor Configuration', () => { cursorCommand(cursor, H.Type, { text: '\n' }, 'keyboard'); assert.equal(model.getLineContent(1), ' '); assert.equal(model.getLineContent(2), ' '); - assert.equal(model.getLineContent(3), ''); + assert.equal(model.getLineContent(3), ' '); assert.equal(model.getLineContent(4), ''); assert.equal(model.getLineContent(5), ''); }); diff --git a/src/vs/editor/test/common/model/compatMirrorModel.test.ts b/src/vs/editor/test/common/model/compatMirrorModel.test.ts new file mode 100644 index 00000000000..8ec13ce4bcc --- /dev/null +++ b/src/vs/editor/test/common/model/compatMirrorModel.test.ts @@ -0,0 +1,254 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import {ICompatMirrorModelEvents, CompatMirrorModel} from 'vs/editor/common/model/compatMirrorModel'; +import {Position} from 'vs/editor/common/core/position'; +import {MirrorModel as SimpleMirrorModel} from 'vs/editor/common/services/editorSimpleWorker'; +import {DEFAULT_WORD_REGEXP} from 'vs/editor/common/model/wordHelper'; +import {TextModel} from 'vs/editor/common/model/textModel'; + +function createTestMirrorModelFromString(value:string): CompatMirrorModel { + return new CompatMirrorModel(0, TextModel.toRawText(value, TextModel.DEFAULT_CREATION_OPTIONS), null); +} + +function contentChangedFlushEvent(detail: editorCommon.IRawText): editorCommon.IModelContentChangedFlushEvent { + return { + changeType: editorCommon.EventType.ModelRawContentChangedFlush, + isRedoing: false, + isUndoing: false, + versionId: 0, + detail: detail + }; +} + +function contentChangedLinesDeletedEvent(fromLineNumber: number, toLineNumber: number): editorCommon.IModelContentChangedLinesDeletedEvent { + return { + changeType: editorCommon.EventType.ModelRawContentChangedLinesDeleted, + isRedoing: false, + isUndoing: false, + versionId: 0, + fromLineNumber: fromLineNumber, + toLineNumber: toLineNumber + }; +} + +function contentChangedLinesInsertedEvent(fromLineNumber: number, toLineNumber: number, detail: string): editorCommon.IModelContentChangedLinesInsertedEvent { + return { + changeType: editorCommon.EventType.ModelRawContentChangedLinesInserted, + isRedoing: false, + isUndoing: false, + versionId: 0, + fromLineNumber: fromLineNumber, + toLineNumber: toLineNumber, + detail: detail + }; +} + +function contentChangedLineChanged(lineNumber: number, detail: string): editorCommon.IModelContentChangedLineChangedEvent { + return { + changeType: editorCommon.EventType.ModelRawContentChangedLineChanged, + isRedoing: false, + isUndoing: false, + versionId: 0, + lineNumber: lineNumber, + detail: detail + }; +} + +function mirrorModelEvents(contentChanged:editorCommon.IModelContentChangedEvent[]): ICompatMirrorModelEvents { + return { + contentChanged: contentChanged + }; +} + +suite('Editor Model - MirrorModel', () => { + + var mirrorModel:CompatMirrorModel; + + setup(() => { + mirrorModel = createTestMirrorModelFromString('line1\nline2\nline3\nline4'); + }); + + teardown(() => { + mirrorModel.dispose(); + mirrorModel = null; + }); + + test('get line start ', () => { + assert.equal(mirrorModel.getOffsetAt(new Position(1, 1)), 0); + assert.equal(mirrorModel.getOffsetAt(new Position(2, 1)), 6); + assert.equal(mirrorModel.getOffsetAt(new Position(3, 1)), 12); + assert.equal(mirrorModel.getOffsetAt(new Position(4, 1)), 18); + assert.equal(mirrorModel.getOffsetAt(new Position(1000, 1)), mirrorModel.getOffsetAt(new Position(mirrorModel.getLineCount(), mirrorModel.getLineMaxColumn(mirrorModel.getLineCount())))); + }); + + test('get line start /flush event/', () => { + assert.equal(mirrorModel.getOffsetAt(new Position(2, 1)), 6); + assert.equal(mirrorModel.getOffsetAt(new Position(3, 1)), 12); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({ + length: -1, + lines: [ + 'foo', + 'bar' + ], + BOM: '', + EOL: '\n', + options: { + tabSize: 4, + insertSpaces: true, + trimAutoWhitespace: true, + defaultEOL: editorCommon.DefaultEndOfLine.LF + } + })])); + + assert.equal(mirrorModel.getOffsetAt(new Position(1, 1)), 0); + assert.equal(mirrorModel.getOffsetAt(new Position(2, 1)), 4); + }); + + test('get offset', () => { + assert.equal(mirrorModel.getOffsetAt({lineNumber: 1, column: 1}), 0); + assert.equal(mirrorModel.getOffsetAt({lineNumber: 1, column: 3}), 2); + assert.equal(mirrorModel.getOffsetAt({lineNumber: 2, column: 1}), 6); + assert.equal(mirrorModel.getOffsetAt({lineNumber: 4, column: 6}), 23); + assert.equal(mirrorModel.getOffsetAt({lineNumber: 4, column: 7}), 23); + }); + + test('get position from offset', () => { + assert.deepEqual(mirrorModel.getPositionAt(0), {lineNumber: 1, column: 1}); + assert.deepEqual(mirrorModel.getPositionAt(2), {lineNumber: 1, column: 3}); + assert.deepEqual(mirrorModel.getPositionAt(6), {lineNumber: 2, column: 1}); + assert.deepEqual(mirrorModel.getPositionAt(23), {lineNumber: 4, column: 6}); + assert.deepEqual(mirrorModel.getPositionAt(24), {lineNumber: 4, column: 6}); + }); + + test('get (all/unique) words', () => { + let model = new SimpleMirrorModel(null, [ 'foo bar foo bar' ], '\n', 1); + let uniqueWords = model.getAllUniqueWords(DEFAULT_WORD_REGEXP); + assert.equal(uniqueWords.length, 2); + assert.equal(uniqueWords[0], 'foo'); + assert.equal(uniqueWords[1], 'bar'); + + model = new SimpleMirrorModel(null, [ 'foo bar', 'foo', 'bar' ], '\n', 1); + uniqueWords = model.getAllUniqueWords(DEFAULT_WORD_REGEXP); + assert.equal(uniqueWords.length, 2); + assert.equal(uniqueWords[0], 'foo'); + assert.equal(uniqueWords[1], 'bar'); + }); + + test('word at/until pos', () => { + var pos = { lineNumber: 1, column: 3 }; + assert.equal(mirrorModel.getWordAtPosition(pos).word, 'line1'); + + var model = createTestMirrorModelFromString('foo bar 1234 :";\''); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 1}).word, 'foo'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 2}).word, 'foo'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 3}).word, 'foo'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 4}).word, 'foo'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 5}).word, 'bar'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 6}).word, 'bar'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 7}).word, 'bar'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 8}).word, 'bar'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 9}).word, '1234'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 10}).word, '1234'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 11}).word, '1234'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 12}).word, '1234'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 13}).word, '1234'); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 14}), null); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 15}), null); + assert.equal(model.getWordAtPosition({lineNumber: 1, column: 16}), null); + + assert.equal(mirrorModel.getWordUntilPosition(pos).word, 'li'); + + }); +}); + +suite('Editor Model - MirrorModel Eventing', () => { + + var mirrorModel:CompatMirrorModel; + + setup(() => { + mirrorModel = createTestMirrorModelFromString('line one\nline two\nline three\nline four'); + }); + + teardown(() => { + mirrorModel.dispose(); + mirrorModel = null; + }); + + test('delete single line', () => { + assert.equal(mirrorModel.getLineContent(3), 'line three'); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(3, 3)])); + + assert.equal(mirrorModel.getLineContent(3), 'line four'); + }); + + test('delete multiple lines', () => { + mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 2)])); + + assert.equal(mirrorModel.getLineContent(1), 'line three'); + assert.equal(mirrorModel.getLineContent(2), 'line four'); + }); + + test('delete all lines', () => { + mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 4)])); + }); + + test('add single lines', () => { + assert.equal(mirrorModel.getLineContent(1), 'line one'); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 1, 'foo bar\nbar foo')])); + + assert.equal(mirrorModel.getLineContent(1), 'foo bar'); + assert.equal(mirrorModel.getLineContent(2), 'line one'); + }); + + + test('add multiple lines', () => { + assert.equal(mirrorModel.getLineContent(1), 'line one'); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 2, 'foo bar\nbar foo')])); + + assert.equal(mirrorModel.getLineContent(1), 'foo bar'); + assert.equal(mirrorModel.getLineContent(2), 'bar foo'); + assert.equal(mirrorModel.getLineContent(3), 'line one'); + }); + + test('change line', () => { + assert.equal(mirrorModel.getLineContent(1), 'line one'); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedLineChanged(1, 'foobar')])); + + assert.equal(mirrorModel.getLineContent(1), 'foobar'); + }); + + test('flush model', () => { + assert.equal(mirrorModel.getLineContent(1), 'line one'); + assert.equal(mirrorModel.getLineContent(2), 'line two'); + + mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({ + length: -1, + lines: [ + 'foo', + 'bar' + ], + BOM: '', + EOL: '\n', + options: { + tabSize: 4, + insertSpaces: true, + trimAutoWhitespace: true, + defaultEOL: editorCommon.DefaultEndOfLine.LF + } + })])); + + assert.equal(mirrorModel.getLineContent(1), 'foo'); + assert.equal(mirrorModel.getLineContent(2), 'bar'); + }); + +}); diff --git a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts index 4f9b0a34da3..bda4765257a 100644 --- a/src/vs/editor/test/common/model/editableTextModelTestUtils.ts +++ b/src/vs/editor/test/common/model/editableTextModelTestUtils.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {EditableTextModel} from 'vs/editor/common/model/editableTextModel'; -import {IMirrorModelEvents, MirrorModel} from 'vs/editor/common/model/mirrorModel'; +import {ICompatMirrorModelEvents, CompatMirrorModel} from 'vs/editor/common/model/compatMirrorModel'; import {MirrorModel2} from 'vs/editor/common/model/mirrorModel2'; import {TextModel} from 'vs/editor/common/model/textModel'; import {Position} from 'vs/editor/common/core/position'; @@ -88,7 +88,7 @@ export function assertSyncedModels(text:string, callback:(model:EditableTextMode assertLineMapping(model, 'model'); } - var mirrorModel1 = new MirrorModel(model.getVersionId(), model.toRawText(), null); + var mirrorModel1 = new CompatMirrorModel(model.getVersionId(), model.toRawText(), null); assertLineMapping(mirrorModel1, 'mirrorModel1'); var mirrorModel1PrevVersionId = model.getVersionId(); @@ -101,7 +101,7 @@ export function assertSyncedModels(text:string, callback:(model:EditableTextMode console.warn('Model version id did not advance between edits (1)'); } mirrorModel1PrevVersionId = versionId; - let mirrorModelEvents:IMirrorModelEvents = { + let mirrorModelEvents:ICompatMirrorModelEvents = { contentChanged: [e] }; mirrorModel1.onEvents(mirrorModelEvents); diff --git a/src/vs/editor/test/common/model/mirrorModel.test.ts b/src/vs/editor/test/common/model/mirrorModel.test.ts deleted file mode 100644 index e6488da2893..00000000000 --- a/src/vs/editor/test/common/model/mirrorModel.test.ts +++ /dev/null @@ -1,353 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as assert from 'assert'; -import * as editorCommon from 'vs/editor/common/editorCommon'; -import {IMirrorModelEvents, MirrorModel, createTestMirrorModelFromString} from 'vs/editor/common/model/mirrorModel'; - -function equalRange(left, right) { - if(left.startLineNumber !== right.startLineNumber) { - assert.ok(false, 'startLineNumber: actual:' + left.startLineNumber + ' expected:' + right.startLineNumber); - } else if(left.endLineNumber !== right.endLineNumber){ - assert.ok(false, 'endLineNumber: actual:' + left.endLineNumber + ' expected:' + right.endLineNumber); - } else if(left.startColumn !== right.startColumn) { - assert.ok(false, 'startColumn: actual:' + left.startColumn + ' expected:' + right.startColumn); - } else if(left.endColumn !== right.endColumn){ - assert.ok(false, 'endColumn: actual:' + left.endColumn + ' expected:' + right.endColumn); - } - assert.ok(true, 'ranges'); -}; - -function contentChangedFlushEvent(detail: editorCommon.IRawText): editorCommon.IModelContentChangedFlushEvent { - return { - changeType: editorCommon.EventType.ModelRawContentChangedFlush, - isRedoing: false, - isUndoing: false, - versionId: 0, - detail: detail - }; -} - -function contentChangedLinesDeletedEvent(fromLineNumber: number, toLineNumber: number): editorCommon.IModelContentChangedLinesDeletedEvent { - return { - changeType: editorCommon.EventType.ModelRawContentChangedLinesDeleted, - isRedoing: false, - isUndoing: false, - versionId: 0, - fromLineNumber: fromLineNumber, - toLineNumber: toLineNumber - }; -} - -function contentChangedLinesInsertedEvent(fromLineNumber: number, toLineNumber: number, detail: string): editorCommon.IModelContentChangedLinesInsertedEvent { - return { - changeType: editorCommon.EventType.ModelRawContentChangedLinesInserted, - isRedoing: false, - isUndoing: false, - versionId: 0, - fromLineNumber: fromLineNumber, - toLineNumber: toLineNumber, - detail: detail - }; -} - -function contentChangedLineChanged(lineNumber: number, detail: string): editorCommon.IModelContentChangedLineChangedEvent { - return { - changeType: editorCommon.EventType.ModelRawContentChangedLineChanged, - isRedoing: false, - isUndoing: false, - versionId: 0, - lineNumber: lineNumber, - detail: detail - }; -} - -function mirrorModelEvents(contentChanged:editorCommon.IModelContentChangedEvent[]): IMirrorModelEvents { - return { - contentChanged: contentChanged - }; -} - -suite('Editor Model - MirrorModel', () => { - - var mirrorModel:MirrorModel; - - setup(() => { - mirrorModel = createTestMirrorModelFromString('line1\nline2\nline3\nline4'); - }); - - teardown(() => { - mirrorModel.dispose(); - mirrorModel = null; - }); - - test('get line start ', () => { - assert.equal(mirrorModel.getLineStart(1), 0); - assert.equal(mirrorModel.getLineStart(2), 6); - assert.equal(mirrorModel.getLineStart(3), 12); - assert.equal(mirrorModel.getLineStart(4), 18); - assert.equal(mirrorModel.getLineStart(1000), mirrorModel.getLineStart(mirrorModel.getLineCount())); - }); - - test('get line start /flush event/', () => { - assert.equal(mirrorModel.getLineStart(2), 6); - assert.equal(mirrorModel.getLineStart(3), 12); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({ - length: -1, - lines: [ - 'foo', - 'bar' - ], - BOM: '', - EOL: '\n', - options: { - tabSize: 4, - insertSpaces: true, - trimAutoWhitespace: true, - defaultEOL: editorCommon.DefaultEndOfLine.LF - } - })])); - - assert.equal(mirrorModel.getLineStart(1), 0); - assert.equal(mirrorModel.getLineStart(2), 4); - }); - - test('get offset', () => { - assert.equal(mirrorModel.getOffsetFromPosition({lineNumber: 1, column: 1}), 0); - assert.equal(mirrorModel.getOffsetFromPosition({lineNumber: 1, column: 3}), 2); - assert.equal(mirrorModel.getOffsetFromPosition({lineNumber: 2, column: 1}), 6); - assert.equal(mirrorModel.getOffsetFromPosition({lineNumber: 4, column: 6}), 23); - assert.equal(mirrorModel.getOffsetFromPosition({lineNumber: 4, column: 7}), 23); - }); - - test('get position from offset', () => { - assert.deepEqual(mirrorModel.getPositionFromOffset(0), {lineNumber: 1, column: 1}); - assert.deepEqual(mirrorModel.getPositionFromOffset(2), {lineNumber: 1, column: 3}); - assert.deepEqual(mirrorModel.getPositionFromOffset(6), {lineNumber: 2, column: 1}); - assert.deepEqual(mirrorModel.getPositionFromOffset(23), {lineNumber: 4, column: 6}); - assert.deepEqual(mirrorModel.getPositionFromOffset(24), {lineNumber: 4, column: 6}); - }); - - test('get (all/unique) words', () => { - var model = createTestMirrorModelFromString('foo bar foo bar'); - var words = model.getAllWords(); - var uniqueWords = model.getAllUniqueWords(); - assert.equal(words.length, 4); - assert.equal(words[0], 'foo'); - assert.equal(words[1], 'bar'); - assert.equal(words[2], 'foo'); - assert.equal(words[3], 'bar'); - assert.equal(uniqueWords.length, 2); - assert.equal(uniqueWords[0], 'foo'); - assert.equal(uniqueWords[1], 'bar'); - - model = createTestMirrorModelFromString('foo bar\nfoo\nbar'); - words = model.getAllWords(); - uniqueWords = model.getAllUniqueWords(); - assert.equal(words.length, 4); - assert.equal(words[0], 'foo'); - assert.equal(words[1], 'bar'); - assert.equal(words[2], 'foo'); - assert.equal(words[3], 'bar'); - assert.equal(uniqueWords.length, 2); - assert.equal(uniqueWords[0], 'foo'); - assert.equal(uniqueWords[1], 'bar'); - }); - - test('word at/until pos', () => { - var pos = { lineNumber: 1, column: 3 }; - assert.equal(mirrorModel.getWordAtPosition(pos).word, 'line1'); - - var model = createTestMirrorModelFromString('foo bar 1234 :";\''); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 1}).word, 'foo'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 2}).word, 'foo'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 3}).word, 'foo'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 4}).word, 'foo'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 5}).word, 'bar'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 6}).word, 'bar'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 7}).word, 'bar'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 8}).word, 'bar'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 9}).word, '1234'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 10}).word, '1234'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 11}).word, '1234'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 12}).word, '1234'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 13}).word, '1234'); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 14}), null); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 15}), null); - assert.equal(model.getWordAtPosition({lineNumber: 1, column: 16}), null); - - assert.equal(mirrorModel.getWordUntilPosition(pos).word, 'li'); - - }); - - test('words with ranges', () => { - - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 4); - assert.equal(wordsWithRanges[0].text, 'line1'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 6 }); - assert.equal(wordsWithRanges[1].text, 'line2'); - equalRange(wordsWithRanges[1].range, { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 6 }); - assert.equal(wordsWithRanges[2].text, 'line3'); - equalRange(wordsWithRanges[2].range, { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 6 }); - assert.equal(wordsWithRanges[3].text, 'line4'); - equalRange(wordsWithRanges[3].range, { startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 6 }); - - var model = createTestMirrorModelFromString('foo bar\nfoo\nbar'); - wordsWithRanges = model.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 4); - assert.equal(wordsWithRanges[0].text, 'foo'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 4 }); - assert.equal(wordsWithRanges[1].text, 'bar'); - equalRange(wordsWithRanges[1].range, { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 8 }); - assert.equal(wordsWithRanges[2].text, 'foo'); - equalRange(wordsWithRanges[2].range, { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 4 }); - assert.equal(wordsWithRanges[3].text, 'bar'); - equalRange(wordsWithRanges[3].range, { startLineNumber: 3, startColumn: 1, endLineNumber: 3, endColumn: 4 }); - }); -}); - -suite('Editor Model - MirrorModel Eventing', () => { - - var mirrorModel:MirrorModel; - - setup(() => { - mirrorModel = createTestMirrorModelFromString('line one\nline two\nline three\nline four'); - }); - - teardown(() => { - mirrorModel.dispose(); - mirrorModel = null; - }); - - test('delete single line', () => { - assert.equal(mirrorModel.getLineContent(3), 'line three'); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(3, 3)])); - - assert.equal(mirrorModel.getLineContent(3), 'line four'); - var words = mirrorModel.getAllWords(); - assert.equal(words.length, 6); - assert.equal(words[0], 'line'); - assert.equal(words[1], 'one'); - assert.equal(words[2], 'line'); - assert.equal(words[3], 'two'); - assert.equal(words[4], 'line'); - assert.equal(words[5], 'four'); - }); - - test('delete multiple lines', () => { - mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 2)])); - - assert.equal(mirrorModel.getLineContent(1), 'line three'); - assert.equal(mirrorModel.getLineContent(2), 'line four'); - var words = mirrorModel.getAllWords(); - assert.equal(words.length, 4); - assert.equal(words[0], 'line'); - assert.equal(words[1], 'three'); - assert.equal(words[2], 'line'); - assert.equal(words[3], 'four'); - - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 4); - assert.equal(wordsWithRanges[0].text, 'line'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 5 }); - assert.equal(wordsWithRanges[1].text, 'three'); - equalRange(wordsWithRanges[1].range, { startLineNumber: 1, startColumn: 6, endLineNumber: 1, endColumn: 11 }); - assert.equal(wordsWithRanges[2].text, 'line'); - equalRange(wordsWithRanges[2].range, { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 5 }); - assert.equal(wordsWithRanges[3].text, 'four'); - equalRange(wordsWithRanges[3].range, { startLineNumber: 2, startColumn: 6, endLineNumber: 2, endColumn: 10 }); - }); - - test('delete all lines', () => { - mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesDeletedEvent(1, 4)])); - - var words = mirrorModel.getAllWords(); - assert.equal(words.length, 0); - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 0); - }); - - test('add single lines', () => { - assert.equal(mirrorModel.getLineContent(1), 'line one'); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 1, 'foo bar\nbar foo')])); - - assert.equal(mirrorModel.getLineContent(1), 'foo bar'); - assert.equal(mirrorModel.getLineContent(2), 'line one'); - - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 10); - assert.equal(wordsWithRanges[0].text, 'foo'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 4 }); - assert.equal(wordsWithRanges[1].text, 'bar'); - equalRange(wordsWithRanges[1].range, { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 8 }); - }); - - - test('add multiple lines', () => { - assert.equal(mirrorModel.getLineContent(1), 'line one'); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedLinesInsertedEvent(1, 2, 'foo bar\nbar foo')])); - - assert.equal(mirrorModel.getLineContent(1), 'foo bar'); - assert.equal(mirrorModel.getLineContent(2), 'bar foo'); - assert.equal(mirrorModel.getLineContent(3), 'line one'); - - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 12); - assert.equal(wordsWithRanges[0].text, 'foo'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 4 }); - assert.equal(wordsWithRanges[1].text, 'bar'); - equalRange(wordsWithRanges[1].range, { startLineNumber: 1, startColumn: 5, endLineNumber: 1, endColumn: 8 }); - assert.equal(wordsWithRanges[2].text, 'bar'); - equalRange(wordsWithRanges[2].range, { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 4 }); - assert.equal(wordsWithRanges[3].text, 'foo'); - equalRange(wordsWithRanges[3].range, { startLineNumber: 2, startColumn: 5, endLineNumber: 2, endColumn: 8 }); - }); - - test('change line', () => { - assert.equal(mirrorModel.getLineContent(1), 'line one'); - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 8); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedLineChanged(1, 'foobar')])); - - assert.equal(mirrorModel.getLineContent(1), 'foobar'); - wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 7); - assert.equal(wordsWithRanges[0].text, 'foobar'); - equalRange(wordsWithRanges[0].range, { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 7 }); - }); - - test('flush model', () => { - assert.equal(mirrorModel.getLineContent(1), 'line one'); - assert.equal(mirrorModel.getLineContent(2), 'line two'); - - mirrorModel.onEvents(mirrorModelEvents([contentChangedFlushEvent({ - length: -1, - lines: [ - 'foo', - 'bar' - ], - BOM: '', - EOL: '\n', - options: { - tabSize: 4, - insertSpaces: true, - trimAutoWhitespace: true, - defaultEOL: editorCommon.DefaultEndOfLine.LF - } - })])); - - assert.equal(mirrorModel.getLineContent(1), 'foo'); - assert.equal(mirrorModel.getLineContent(2), 'bar'); - var wordsWithRanges = mirrorModel.getAllWordsWithRange(); - assert.equal(wordsWithRanges.length, 2); - }); - -}); diff --git a/src/vs/editor/test/common/model/model.brackets.test.ts b/src/vs/editor/test/common/model/model.brackets.test.ts deleted file mode 100644 index e3433c2b514..00000000000 --- a/src/vs/editor/test/common/model/model.brackets.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import * as assert from 'assert'; -import {Range} from 'vs/editor/common/core/range'; -import {IFoundBracket} from 'vs/editor/common/editorCommon'; -import {TextModel} from 'vs/editor/common/model/textModel'; -import {TextModelWithTokens} from 'vs/editor/common/model/textModelWithTokens'; - -suite('TextModelWithTokens', () => { - - function toRelaxedFoundBracket(a:IFoundBracket) { - if (!a) { - return null; - } - return { - range: a.range.toString(), - open: a.open, - close: a.close, - isOpen: a.isOpen - }; - } - - function testBrackets(contents: string[], brackets:string[][]): void { - let charIsBracket: {[char:string]:boolean} = {}; - let charIsOpenBracket: {[char:string]:boolean} = {}; - let openForChar: {[char:string]:string} = {}; - let closeForChar: {[char:string]:string} = {}; - brackets.forEach((b) => { - charIsBracket[b[0]] = true; - charIsBracket[b[1]] = true; - - charIsOpenBracket[b[0]] = true; - charIsOpenBracket[b[1]] = false; - - openForChar[b[0]] = b[0]; - closeForChar[b[0]] = b[1]; - - openForChar[b[1]] = b[0]; - closeForChar[b[1]] = b[1]; - }); - - let expectedBrackets:IFoundBracket[] = []; - for (let lineIndex = 0; lineIndex < contents.length; lineIndex++) { - let lineText = contents[lineIndex]; - - for (let charIndex = 0; charIndex < lineText.length; charIndex++) { - let ch = lineText.charAt(charIndex); - if (charIsBracket[ch]) { - expectedBrackets.push({ - open: openForChar[ch], - close: closeForChar[ch], - isOpen: charIsOpenBracket[ch], - range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2) - }); - } - } - } - - let model = new TextModelWithTokens([], TextModel.toRawText(contents.join('\n'), TextModel.DEFAULT_CREATION_OPTIONS), null); - - // findPrevBracket - { - let expectedBracketIndex = expectedBrackets.length - 1; - let currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; - for (let lineNumber = contents.length; lineNumber >= 1; lineNumber--) { - let lineText = contents[lineNumber - 1]; - - for (let column = lineText.length + 1; column >= 1; column--) { - - if (currentExpectedBracket) { - if (lineNumber === currentExpectedBracket.range.startLineNumber && column < currentExpectedBracket.range.endColumn) { - expectedBracketIndex--; - currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; - } - } - - let actual = model.findPrevBracket({ - lineNumber: lineNumber, - column: column - }); - - assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column); - } - } - } - - // findNextBracket - { - let expectedBracketIndex = 0; - let currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; - for (let lineNumber = 1; lineNumber <= contents.length; lineNumber++) { - let lineText = contents[lineNumber - 1]; - - for (let column = 1; column <= lineText.length + 1; column++) { - - if (currentExpectedBracket) { - if (lineNumber === currentExpectedBracket.range.startLineNumber && column > currentExpectedBracket.range.startColumn) { - expectedBracketIndex++; - currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; - } - } - - let actual = model.findNextBracket({ - lineNumber: lineNumber, - column: column - }); - - assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column); - } - } - } - - model.dispose(); - } - - test('brackets', () => { - testBrackets([ - 'if (a == 3) { return (7 * (a + 5)); }' - ], [ - ['{', '}'], - ['[', ']'], - ['(', ')'] - ]); - }); - -}); diff --git a/src/vs/editor/test/common/model/model.line.test.ts b/src/vs/editor/test/common/model/model.line.test.ts index fd0e2ffac7f..28f4e4b82f4 100644 --- a/src/vs/editor/test/common/model/model.line.test.ts +++ b/src/vs/editor/test/common/model/model.line.test.ts @@ -9,10 +9,9 @@ import {ILineTokens} from 'vs/editor/common/editorCommon'; import {ModelLine, ILineEdit, ILineMarker} from 'vs/editor/common/model/modelLine'; import {LineMarker} from 'vs/editor/common/model/textModelWithMarkers'; import {TokensInflatorMap} from 'vs/editor/common/model/tokensBinaryEncoding'; -import {IToken} from 'vs/editor/common/modes'; -import {LineToken} from 'vs/editor/common/model/lineToken'; +import {Token} from 'vs/editor/common/core/token'; -function assertLineTokens(actual:ILineTokens, expected:IToken[]): void { +function assertLineTokens(actual:ILineTokens, expected:Token[]): void { var inflatedActual = actual.inflate(); assert.deepEqual(inflatedActual, expected, 'Line tokens are equal'); } @@ -263,7 +262,7 @@ suite('Editor Model - modelLine.append text', () => { }); suite('Editor Model - modelLine.applyEdits text & tokens', () => { - function testLineEditTokens(initialText:string, initialTokens: LineToken[], edits:ILineEdit[], expectedText:string, expectedTokens: LineToken[]): void { + function testLineEditTokens(initialText:string, initialTokens: Token[], edits:ILineEdit[], expectedText:string, expectedTokens: Token[]): void { let line = new ModelLine(1, initialText, NO_TAB_SIZE); let map = new TokensInflatorMap(); line.setTokens(map, initialTokens, null, []); @@ -277,25 +276,22 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { test('insertion on empty line', () => { let line = new ModelLine(1, 'some text', NO_TAB_SIZE); let map = new TokensInflatorMap(); - line.setTokens(map, [new LineToken(0, 'bar')], null, []); + line.setTokens(map, [new Token(0, 'bar')], null, []); line.applyEdits({}, [{startColumn:1, endColumn:10, text:'', forceMoveMarkers: false}], NO_TAB_SIZE); line.setTokens(map, [], null, []); line.applyEdits({}, [{startColumn:1, endColumn:1, text:'a', forceMoveMarkers: false}], NO_TAB_SIZE); - assertLineTokens(line.getTokens(map), [{ - startIndex: 0, - type:'' - }]); + assertLineTokens(line.getTokens(map), [new Token(0, '')]); }); test('updates tokens on insertion 1', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -305,9 +301,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'aabcd efgh', [ - new LineToken(0, '1'), - new LineToken(5, '2'), - new LineToken(6, '3') + new Token(0, '1'), + new Token(5, '2'), + new Token(6, '3') ] ); }); @@ -316,9 +312,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'aabcd efgh', [ - new LineToken(0, '1'), - new LineToken(5, '2'), - new LineToken(6, '3') + new Token(0, '1'), + new Token(5, '2'), + new Token(6, '3') ], [{ startColumn: 2, @@ -328,9 +324,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'axabcd efgh', [ - new LineToken(0, '1'), - new LineToken(6, '2'), - new LineToken(7, '3') + new Token(0, '1'), + new Token(6, '2'), + new Token(7, '3') ] ); }); @@ -339,9 +335,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axabcd efgh', [ - new LineToken(0, '1'), - new LineToken(6, '2'), - new LineToken(7, '3') + new Token(0, '1'), + new Token(6, '2'), + new Token(7, '3') ], [{ startColumn: 3, @@ -351,9 +347,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'axstuabcd efgh', [ - new LineToken(0, '1'), - new LineToken(9, '2'), - new LineToken(10, '3') + new Token(0, '1'), + new Token(9, '2'), + new Token(10, '3') ] ); }); @@ -362,9 +358,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd efgh', [ - new LineToken(0, '1'), - new LineToken(9, '2'), - new LineToken(10, '3') + new Token(0, '1'), + new Token(9, '2'), + new Token(10, '3') ], [{ startColumn: 10, @@ -374,9 +370,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t efgh', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(11, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(11, '3') ] ); }); @@ -385,9 +381,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t efgh', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(11, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(11, '3') ], [{ startColumn: 12, @@ -397,9 +393,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t ddefgh', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(13, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(13, '3') ] ); }); @@ -408,9 +404,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t ddefgh', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(13, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(13, '3') ], [{ startColumn: 18, @@ -420,9 +416,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'axstuabcd\t ddefghxyz', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(13, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(13, '3') ] ); }); @@ -431,9 +427,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'axstuabcd\t ddefghxyz', [ - new LineToken(0, '1'), - new LineToken(10, '2'), - new LineToken(13, '3') + new Token(0, '1'), + new Token(10, '2'), + new Token(13, '3') ], [{ startColumn: 1, @@ -443,9 +439,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyz', [ - new LineToken(0, '1'), - new LineToken(11, '2'), - new LineToken(14, '3') + new Token(0, '1'), + new Token(11, '2'), + new Token(14, '3') ] ); }); @@ -454,9 +450,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'xaxstuabcd\t ddefghxyz', [ - new LineToken(0, '1'), - new LineToken(11, '2'), - new LineToken(14, '3') + new Token(0, '1'), + new Token(11, '2'), + new Token(14, '3') ], [{ startColumn: 22, @@ -466,9 +462,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyzx', [ - new LineToken(0, '1'), - new LineToken(11, '2'), - new LineToken(14, '3') + new Token(0, '1'), + new Token(11, '2'), + new Token(14, '3') ] ); }); @@ -477,9 +473,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'xaxstuabcd\t ddefghxyzx', [ - new LineToken(0, '1'), - new LineToken(11, '2'), - new LineToken(14, '3') + new Token(0, '1'), + new Token(11, '2'), + new Token(14, '3') ], [{ startColumn: 2, @@ -489,9 +485,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'xaxstuabcd\t ddefghxyzx', [ - new LineToken(0, '1'), - new LineToken(11, '2'), - new LineToken(14, '3') + new Token(0, '1'), + new Token(11, '2'), + new Token(14, '3') ] ); }); @@ -508,7 +504,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'a', [ - new LineToken(0, '') + new Token(0, '') ] ); }); @@ -517,9 +513,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcdefghij', [ - new LineToken(0, '1'), - new LineToken(3, '2'), - new LineToken(6, '3') + new Token(0, '1'), + new Token(3, '2'), + new Token(6, '3') ], [{ startColumn: 4, @@ -529,8 +525,8 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcghij', [ - new LineToken(0, '1'), - new LineToken(3, '3') + new Token(0, '1'), + new Token(3, '3') ] ); }); @@ -539,9 +535,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcdefghij', [ - new LineToken(0, '1'), - new LineToken(3, '2'), - new LineToken(6, '3') + new Token(0, '1'), + new Token(3, '2'), + new Token(6, '3') ], [{ startColumn: 4, @@ -551,9 +547,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abchellodefghij', [ - new LineToken( 0, '1'), - new LineToken( 8, '2'), - new LineToken(11, '3') + new Token( 0, '1'), + new Token( 8, '2'), + new Token(11, '3') ] ); }); @@ -562,9 +558,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -574,9 +570,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'bcd efgh', [ - new LineToken(0, '1'), - new LineToken(3, '2'), - new LineToken(4, '3') + new Token(0, '1'), + new Token(3, '2'), + new Token(4, '3') ] ); }); @@ -585,9 +581,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 2, @@ -597,9 +593,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'ad efgh', [ - new LineToken(0, '1'), - new LineToken(2, '2'), - new LineToken(3, '3') + new Token(0, '1'), + new Token(2, '2'), + new Token(3, '3') ] ); }); @@ -608,9 +604,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -620,8 +616,8 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], ' efgh', [ - new LineToken(0, '2'), - new LineToken(1, '3') + new Token(0, '2'), + new Token(1, '3') ] ); }); @@ -630,9 +626,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 5, @@ -642,8 +638,8 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcdefgh', [ - new LineToken(0, '1'), - new LineToken(4, '3') + new Token(0, '1'), + new Token(4, '3') ] ); }); @@ -652,9 +648,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 5, @@ -664,8 +660,8 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcdfgh', [ - new LineToken(0, '1'), - new LineToken(4, '3') + new Token(0, '1'), + new Token(4, '3') ] ); }); @@ -674,9 +670,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 5, @@ -686,7 +682,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcd', [ - new LineToken(0, '1') + new Token(0, '1') ] ); }); @@ -695,9 +691,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -714,9 +710,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -726,9 +722,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); @@ -737,9 +733,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 1, @@ -749,9 +745,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'cd efgh', [ - new LineToken(0, '1'), - new LineToken(2, '2'), - new LineToken(3, '3') + new Token(0, '1'), + new Token(2, '2'), + new Token(3, '3') ] ); }); @@ -760,9 +756,9 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], [{ startColumn: 5, @@ -772,7 +768,7 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'abcd', [ - new LineToken(0, '1') + new Token(0, '1') ] ); }); @@ -781,11 +777,11 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'Hello world, ciao', [ - new LineToken(0, 'hello'), - new LineToken(5, ''), - new LineToken(6, 'world'), - new LineToken(11, ''), - new LineToken(13, '') + new Token(0, 'hello'), + new Token(5, ''), + new Token(6, 'world'), + new Token(11, ''), + new Token(13, '') ], [{ startColumn: 1, @@ -795,11 +791,11 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'Hi world, ciao', [ - new LineToken(0, 'hello'), - new LineToken(2, ''), - new LineToken(3, 'world'), - new LineToken(8, '' ), - new LineToken(10, '' ), + new Token(0, 'hello'), + new Token(2, ''), + new Token(3, 'world'), + new Token(8, '' ), + new Token(10, '' ), ] ); }); @@ -808,11 +804,11 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { testLineEditTokens( 'Hello world, ciao', [ - new LineToken(0, 'hello'), - new LineToken(5, ''), - new LineToken(6, 'world'), - new LineToken(11, ''), - new LineToken(13, ''), + new Token(0, 'hello'), + new Token(5, ''), + new Token(6, 'world'), + new Token(11, ''), + new Token(13, ''), ], [{ startColumn: 1, @@ -827,18 +823,18 @@ suite('Editor Model - modelLine.applyEdits text & tokens', () => { }], 'Hi wmy friends, ciao', [ - new LineToken(0, 'hello'), - new LineToken(2, ''), - new LineToken(3, 'world'), - new LineToken(14, ''), - new LineToken(16, ''), + new Token(0, 'hello'), + new Token(2, ''), + new Token(3, 'world'), + new Token(14, ''), + new Token(16, ''), ] ); }); }); suite('Editor Model - modelLine.split text & tokens', () => { - function testLineSplitTokens(initialText:string, initialTokens: LineToken[], splitColumn:number, expectedText1:string, expectedText2:string, expectedTokens: LineToken[]): void { + function testLineSplitTokens(initialText:string, initialTokens: Token[], splitColumn:number, expectedText1:string, expectedText2:string, expectedTokens: Token[]): void { let line = new ModelLine(1, initialText, NO_TAB_SIZE); let map = new TokensInflatorMap(); line.setTokens(map, initialTokens, null, []); @@ -854,9 +850,9 @@ suite('Editor Model - modelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 1, '', @@ -869,17 +865,17 @@ suite('Editor Model - modelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 10, 'abcd efgh', '', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); @@ -888,15 +884,15 @@ suite('Editor Model - modelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 5, 'abcd', ' efgh', [ - new LineToken(0, '1') + new Token(0, '1') ] ); }); @@ -905,23 +901,23 @@ suite('Editor Model - modelLine.split text & tokens', () => { testLineSplitTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 6, 'abcd ', 'efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2') + new Token(0, '1'), + new Token(4, '2') ] ); }); }); suite('Editor Model - modelLine.append text & tokens', () => { - function testLineAppendTokens(aText:string, aTokens: LineToken[], bText:string, bTokens:LineToken[], expectedText:string, expectedTokens:IToken[]): void { + function testLineAppendTokens(aText:string, aTokens: Token[], bText:string, bTokens:Token[], expectedText:string, expectedTokens:Token[]): void { let inflator = new TokensInflatorMap(); let a = new ModelLine(1, aText, NO_TAB_SIZE); @@ -940,17 +936,17 @@ suite('Editor Model - modelLine.append text & tokens', () => { testLineAppendTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], '', [], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); @@ -961,15 +957,15 @@ suite('Editor Model - modelLine.append text & tokens', () => { [], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); @@ -978,24 +974,24 @@ suite('Editor Model - modelLine.append text & tokens', () => { testLineAppendTokens( 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ], 'abcd efgh', [ - new LineToken(0, '4'), - new LineToken(4, '5'), - new LineToken(5, '6') + new Token(0, '4'), + new Token(4, '5'), + new Token(5, '6') ], 'abcd efghabcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3'), - new LineToken(9, '4'), - new LineToken(13, '5'), - new LineToken(14, '6') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3'), + new Token(9, '4'), + new Token(13, '5'), + new Token(14, '6') ] ); }); @@ -1004,18 +1000,18 @@ suite('Editor Model - modelLine.append text & tokens', () => { testLineAppendTokens( 'abcd ', [ - new LineToken(0, '1'), - new LineToken(4, '2') + new Token(0, '1'), + new Token(4, '2') ], 'efgh', [ - new LineToken(0, '3') + new Token(0, '3') ], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); @@ -1024,18 +1020,18 @@ suite('Editor Model - modelLine.append text & tokens', () => { testLineAppendTokens( 'abcd', [ - new LineToken(0, '1'), + new Token(0, '1'), ], ' efgh', [ - new LineToken(0, '2'), - new LineToken(1, '3') + new Token(0, '2'), + new Token(1, '3') ], 'abcd efgh', [ - new LineToken(0, '1'), - new LineToken(4, '2'), - new LineToken(5, '3') + new Token(0, '1'), + new Token(4, '2'), + new Token(5, '3') ] ); }); diff --git a/src/vs/editor/test/common/model/model.test.ts b/src/vs/editor/test/common/model/model.test.ts index 8246f33a038..2a1a42834ec 100644 --- a/src/vs/editor/test/common/model/model.test.ts +++ b/src/vs/editor/test/common/model/model.test.ts @@ -13,24 +13,10 @@ import { IModelContentChangedLinesDeletedEvent, IModelContentChangedLinesInsertedEvent } from 'vs/editor/common/editorCommon'; import {Model} from 'vs/editor/common/model/model'; -import {BracketMode} from 'vs/editor/test/common/testModes'; -import {TextModel, IParsedSearchRequest} from 'vs/editor/common/model/textModel'; +import {TextModel} from 'vs/editor/common/model/textModel'; // --------- utils -function isNotABracket(model:Model, lineNumber:number, column:number) { - var match = model.matchBracket(new Position(lineNumber, column)); - assert.equal(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); -} - -function isBracket(model:Model, lineNumber1:number, column11:number, column12:number, lineNumber2:number, column21:number, column22:number) { - var match = model.matchBracket(new Position(lineNumber1, column11)); - assert.deepEqual(match, [ - new Range(lineNumber1, column11, lineNumber1, column12), - new Range(lineNumber2, column21, lineNumber2, column22) - ], 'is matching brackets at ' + lineNumber1 + ', ' + column11); -} - var LINE1 = 'My First Line'; var LINE2 = '\t\tMy Second Line'; var LINE3 = ' Third Line'; @@ -358,7 +344,7 @@ suite('Editor Model - Model Line Separators', () => { }); teardown(() => { - thisModel.destroy(); + thisModel.dispose(); }); test('model getValue', () => { @@ -378,112 +364,6 @@ suite('Editor Model - Model Line Separators', () => { }); -// --------- bracket matching - -suite('Editor Model - bracket Matching', () => { - - var thisModel: Model; - var bracketMode = new BracketMode(); - - setup(() => { - var text = - 'var bar = {' + '\n' + - 'foo: {' + '\n' + - '}, bar: {hallo: [{' + '\n' + - '}, {' + '\n' + - '}]}}'; - thisModel = Model.createFromString(text, undefined, bracketMode); - }); - - teardown(() => { - thisModel.destroy(); - }); - - test('Model bracket matching 1', () => { - - var brackets = [ - [1, 11, 12, 5, 4, 5], - [1, 12, 11, 5, 4, 5], - [5, 5, 4, 1, 11, 12], - - [2, 6, 7, 3, 1, 2], - [2, 7, 6, 3, 1, 2], - [3, 1, 2, 2, 6, 7], - [3, 2, 1, 2, 6, 7], - - [3, 9, 10, 5, 3, 4], - [3, 10, 9, 5, 3, 4], - [5, 4, 3, 3, 9, 10], - - [3, 17, 18, 5, 2, 3], - [3, 18, 17, 5, 2, 3], - [5, 3, 2, 3, 17, 18], - - [3, 19, 18, 4, 1, 2], - [4, 2, 1, 3, 18, 19], - [4, 1, 2, 3, 18, 19], - - [4, 4, 5, 5, 1, 2], - [4, 5, 4, 5, 1, 2], - [5, 2, 1, 4, 4, 5], - [5, 1, 2, 4, 4, 5] - ]; - var i, len, b, isABracket = {1:{}, 2:{}, 3:{}, 4:{}, 5:{}}; - - for (i = 0, len = brackets.length; i < len; i++) { - b = brackets[i]; - isBracket(thisModel, b[0], b[1], b[2], b[3], b[4], b[5]); - isABracket[b[0]][b[1]] = true; - } - - for (i = 1, len = thisModel.getLineCount(); i <= len; i++) { - var line = thisModel.getLineContent(i), j, lenJ; - for (j = 1, lenJ = line.length + 1; j <= lenJ; j++) { - if (!isABracket[i].hasOwnProperty(j)) { - isNotABracket(thisModel, i, j); - } - } - } - }); -}); - - -suite('Editor Model - bracket Matching 2', () => { - - var thisModel: Model; - var bracketMode = new BracketMode(); - - setup(() => { - var text = - ')]}{[(' + '\n' + - ')]}{[('; - thisModel = Model.createFromString(text, undefined, bracketMode); - }); - - teardown(() => { - thisModel.destroy(); - }); - - test('Model bracket matching', () => { - isNotABracket(thisModel, 1, 1); - isNotABracket(thisModel, 1, 2); - isNotABracket(thisModel, 1, 3); - isBracket(thisModel, 1, 4, 5, 2, 3, 4); - isBracket(thisModel, 1, 5, 4, 2, 3, 4); - isBracket(thisModel, 1, 6, 5, 2, 2, 3); - isBracket(thisModel, 1, 7, 6, 2, 1, 2); - - isBracket(thisModel, 2, 1, 2, 1, 6, 7); - isBracket(thisModel, 2, 2, 1, 1, 6, 7); - isBracket(thisModel, 2, 3, 2, 1, 5, 6); - isBracket(thisModel, 2, 4, 3, 1, 4, 5); - isNotABracket(thisModel, 2, 5); - isNotABracket(thisModel, 2, 6); - isNotABracket(thisModel, 2, 7); - }); -}); - - // --------- Words suite('Editor Model - Words', () => { @@ -496,7 +376,7 @@ suite('Editor Model - Words', () => { }); teardown(() => { - thisModel.destroy(); + thisModel.dispose(); }); test('Get word at position', () => { @@ -719,6 +599,77 @@ suite('Editor Model - Find', () => { ); }); + test('multiline find with line beginning regex', () => { + assertFindMatches( + [ + 'if', + 'else', + '', + 'if', + 'else' + ].join('\n'), + '^if\\nelse', true, false, false, + [ + [1, 1, 2, 5], + [4, 1, 5, 5] + ] + ); + }); + + test('matching empty lines using boundary expression', () => { + assertFindMatches( + [ + 'if', + '', + 'else', + ' ', + 'if', + ' ', + 'else' + ].join('\n'), + '^\\s*$\\n', true, false, false, + [ + [2, 1, 3, 1], + [4, 1, 5, 1], + [6, 1, 7, 1] + ] + ); + }); + + test('matching lines starting with A and ending with B', () => { + assertFindMatches( + [ + 'a if b', + 'a', + 'ab', + 'eb' + ].join('\n'), + '^a.*b$', true, false, false, + [ + [1, 1, 1, 7], + [3, 1, 3, 3] + ] + ); + }); + + test('multiline find with line ending regex', () => { + assertFindMatches( + [ + 'if', + 'else', + '', + 'if', + 'elseif', + 'else' + ].join('\n'), + 'if\\nelse$', true, false, false, + [ + [1, 1, 2, 5], + [5, 5, 6, 5] + ] + ); + }); + test('issue #4836 - ^.*$', () => { assertFindMatches( [ @@ -739,7 +690,97 @@ suite('Editor Model - Find', () => { ); }); - function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:IParsedSearchRequest): void { + test('findNextMatch without regex', () => { + var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let actual = testObject.findNextMatch('line', { lineNumber: 1, column: 1 }, false, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false); + assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString()); + + actual = testObject.findNextMatch('line', {lineNumber: 1, column: 3}, false, false, false); + assert.equal(new Range(1, 6, 1, 10).toString(), actual.toString()); + + actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false); + assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('line', actual.getEndPosition(), false, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + testObject.dispose(); + }); + + test('findNextMatch with beginning boundary regex', () => { + var testObject = new TextModel([], TextModel.toRawText('line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false); + assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false); + assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + testObject.dispose(); + }); + + test('findNextMatch with beginning boundary regex and line has repetitive beginnings', () => { + var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 1 }, true, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false); + assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', { lineNumber: 1, column: 3 }, true, false, false); + assert.equal(new Range(2, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line', actual.getEndPosition(), true, false, false); + assert.equal(new Range(1, 1, 1, 5).toString(), actual.toString()); + + testObject.dispose(); + }); + + test('findNextMatch with beginning boundary multiline regex and line has repetitive beginnings', () => { + var testObject = new TextModel([], TextModel.toRawText('line line one\nline two\nline three\nline four', TextModel.DEFAULT_CREATION_OPTIONS)); + + let actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 1, column: 1 }, true, false, false); + assert.equal(new Range(1, 1, 2, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line.*\\nline', actual.getEndPosition(), true, false, false); + assert.equal(new Range(3, 1, 4, 5).toString(), actual.toString()); + + actual = testObject.findNextMatch('^line.*\\nline', { lineNumber: 2, column: 1 }, true, false, false); + assert.equal(new Range(2, 1, 3, 5).toString(), actual.toString()); + + testObject.dispose(); + }); + + test('findNextMatch with ending boundary regex', () => { + var testObject = new TextModel([], TextModel.toRawText('one line line\ntwo line\nthree', TextModel.DEFAULT_CREATION_OPTIONS)); + + let actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 1 }, true, false, false); + assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + + actual = testObject.findNextMatch('line$', { lineNumber: 1, column: 4 }, true, false, false); + assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + + actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false); + assert.equal(new Range(2, 5, 2, 9).toString(), actual.toString()); + + actual = testObject.findNextMatch('line$', actual.getEndPosition(), true, false, false); + assert.equal(new Range(1, 10, 1, 14).toString(), actual.toString()); + + testObject.dispose(); + }); + + function assertParseSearchResult(searchString:string, isRegex:boolean, matchCase:boolean, wholeWord:boolean, expected:RegExp): void { let actual = TextModel.parseSearchRequest(searchString, isRegex, matchCase, wholeWord); assert.deepEqual(actual, expected); } @@ -751,24 +792,24 @@ suite('Editor Model - Find', () => { }); test('parseSearchRequest non regex', () => { - assertParseSearchResult('foo', false, false, false, { regex: /foo/gi, isMultiline: false }); - assertParseSearchResult('foo', false, false, true, { regex: /\bfoo\b/gi, isMultiline: false }); - assertParseSearchResult('foo', false, true, false, { regex: /foo/g, isMultiline: false }); - assertParseSearchResult('foo', false, true, true, { regex: /\bfoo\b/g, isMultiline: false }); - assertParseSearchResult('foo\\n', false, false, false, { regex: /foo\\n/gi, isMultiline: false }); - assertParseSearchResult('foo\\\\n', false, false, false, { regex: /foo\\\\n/gi, isMultiline: false }); - assertParseSearchResult('foo\\r', false, false, false, { regex: /foo\\r/gi, isMultiline: false }); - assertParseSearchResult('foo\\\\r', false, false, false, { regex: /foo\\\\r/gi, isMultiline: false }); + assertParseSearchResult('foo', false, false, false, /foo/gi); + assertParseSearchResult('foo', false, false, true, /\bfoo\b/gi); + assertParseSearchResult('foo', false, true, false, /foo/g); + assertParseSearchResult('foo', false, true, true, /\bfoo\b/g); + assertParseSearchResult('foo\\n', false, false, false, /foo\\n/gi); + assertParseSearchResult('foo\\\\n', false, false, false, /foo\\\\n/gi); + assertParseSearchResult('foo\\r', false, false, false, /foo\\r/gi); + assertParseSearchResult('foo\\\\r', false, false, false, /foo\\\\r/gi); }); test('parseSearchRequest regex', () => { - assertParseSearchResult('foo', true, false, false, { regex: /foo/gi, isMultiline: false }); - assertParseSearchResult('foo', true, false, true, { regex: /\bfoo\b/gi, isMultiline: false }); - assertParseSearchResult('foo', true, true, false, { regex: /foo/g, isMultiline: false }); - assertParseSearchResult('foo', true, true, true, { regex: /\bfoo\b/g, isMultiline: false }); - assertParseSearchResult('foo\\n', true, false, false, { regex: /foo\n/gi, isMultiline: true }); - assertParseSearchResult('foo\\\\n', true, false, false, { regex: /foo\\n/gi, isMultiline: false }); - assertParseSearchResult('foo\\r', true, false, false, { regex: /foo\r/gi, isMultiline: true }); - assertParseSearchResult('foo\\\\r', true, false, false, { regex: /foo\\r/gi, isMultiline: false }); + assertParseSearchResult('foo', true, false, false, /foo/gi); + assertParseSearchResult('foo', true, false, true, /\bfoo\b/gi); + assertParseSearchResult('foo', true, true, false, /foo/g); + assertParseSearchResult('foo', true, true, true, /\bfoo\b/g); + assertParseSearchResult('foo\\n', true, false, false, /foo\n/gim); + assertParseSearchResult('foo\\\\n', true, false, false, /foo\\n/gi); + assertParseSearchResult('foo\\r', true, false, false, /foo\r/gim); + assertParseSearchResult('foo\\\\r', true, false, false, /foo\\r/gi); }); }); diff --git a/src/vs/editor/test/common/model/textModelWithTokens.test.ts b/src/vs/editor/test/common/model/textModelWithTokens.test.ts new file mode 100644 index 00000000000..ca756915838 --- /dev/null +++ b/src/vs/editor/test/common/model/textModelWithTokens.test.ts @@ -0,0 +1,330 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {Model} from 'vs/editor/common/model/model'; +import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; +import {ITokenizationSupport} from 'vs/editor/common/modes'; +import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; +import {Token} from 'vs/editor/common/core/token'; +import {Range} from 'vs/editor/common/core/range'; +import {Position} from 'vs/editor/common/core/position'; +import {IFoundBracket} from 'vs/editor/common/editorCommon'; +import {TextModel} from 'vs/editor/common/model/textModel'; +import {TextModelWithTokens} from 'vs/editor/common/model/textModelWithTokens'; +import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConfigurationRegistry'; + +suite('TextModelWithTokens', () => { + + function testBrackets(contents: string[], brackets:string[][]): void { + function toRelaxedFoundBracket(a:IFoundBracket) { + if (!a) { + return null; + } + return { + range: a.range.toString(), + open: a.open, + close: a.close, + isOpen: a.isOpen + }; + } + + let charIsBracket: {[char:string]:boolean} = {}; + let charIsOpenBracket: {[char:string]:boolean} = {}; + let openForChar: {[char:string]:string} = {}; + let closeForChar: {[char:string]:string} = {}; + brackets.forEach((b) => { + charIsBracket[b[0]] = true; + charIsBracket[b[1]] = true; + + charIsOpenBracket[b[0]] = true; + charIsOpenBracket[b[1]] = false; + + openForChar[b[0]] = b[0]; + closeForChar[b[0]] = b[1]; + + openForChar[b[1]] = b[0]; + closeForChar[b[1]] = b[1]; + }); + + let expectedBrackets:IFoundBracket[] = []; + for (let lineIndex = 0; lineIndex < contents.length; lineIndex++) { + let lineText = contents[lineIndex]; + + for (let charIndex = 0; charIndex < lineText.length; charIndex++) { + let ch = lineText.charAt(charIndex); + if (charIsBracket[ch]) { + expectedBrackets.push({ + open: openForChar[ch], + close: closeForChar[ch], + isOpen: charIsOpenBracket[ch], + range: new Range(lineIndex + 1, charIndex + 1, lineIndex + 1, charIndex + 2) + }); + } + } + } + + let model = new TextModelWithTokens([], TextModel.toRawText(contents.join('\n'), TextModel.DEFAULT_CREATION_OPTIONS), null); + + // findPrevBracket + { + let expectedBracketIndex = expectedBrackets.length - 1; + let currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; + for (let lineNumber = contents.length; lineNumber >= 1; lineNumber--) { + let lineText = contents[lineNumber - 1]; + + for (let column = lineText.length + 1; column >= 1; column--) { + + if (currentExpectedBracket) { + if (lineNumber === currentExpectedBracket.range.startLineNumber && column < currentExpectedBracket.range.endColumn) { + expectedBracketIndex--; + currentExpectedBracket = expectedBracketIndex >= 0 ? expectedBrackets[expectedBracketIndex] : null; + } + } + + let actual = model.findPrevBracket({ + lineNumber: lineNumber, + column: column + }); + + assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findPrevBracket of ' + lineNumber + ', ' + column); + } + } + } + + // findNextBracket + { + let expectedBracketIndex = 0; + let currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; + for (let lineNumber = 1; lineNumber <= contents.length; lineNumber++) { + let lineText = contents[lineNumber - 1]; + + for (let column = 1; column <= lineText.length + 1; column++) { + + if (currentExpectedBracket) { + if (lineNumber === currentExpectedBracket.range.startLineNumber && column > currentExpectedBracket.range.startColumn) { + expectedBracketIndex++; + currentExpectedBracket = expectedBracketIndex < expectedBrackets.length ? expectedBrackets[expectedBracketIndex] : null; + } + } + + let actual = model.findNextBracket({ + lineNumber: lineNumber, + column: column + }); + + assert.deepEqual(toRelaxedFoundBracket(actual), toRelaxedFoundBracket(currentExpectedBracket), 'findNextBracket of ' + lineNumber + ', ' + column); + } + } + } + + model.dispose(); + } + + test('brackets', () => { + testBrackets([ + 'if (a == 3) { return (7 * (a + 5)); }' + ], [ + ['{', '}'], + ['[', ']'], + ['(', ')'] + ]); + }); + + +}); + +suite('TextModelWithTokens - bracket matching', () => { + + function isNotABracket(model:Model, lineNumber:number, column:number) { + let match = model.matchBracket(new Position(lineNumber, column)); + assert.equal(match, null, 'is not matching brackets at ' + lineNumber + ', ' + column); + } + + function isBracket(model:Model, lineNumber1:number, column11:number, column12:number, lineNumber2:number, column21:number, column22:number) { + let match = model.matchBracket(new Position(lineNumber1, column11)); + assert.deepEqual(match, [ + new Range(lineNumber1, column11, lineNumber1, column12), + new Range(lineNumber2, column21, lineNumber2, column22) + ], 'is matching brackets at ' + lineNumber1 + ', ' + column11); + } + + class BracketMode extends MockMode { + constructor() { + super(); + LanguageConfigurationRegistry.register(this.getId(), { + brackets: [ + ['{', '}'], + ['[', ']'], + ['(', ')'], + ] + }); + } + } + + test('bracket matching 1', () => { + let text = + ')]}{[(' + '\n' + + ')]}{[('; + let model = Model.createFromString(text, undefined, new BracketMode()); + + isNotABracket(model, 1, 1); + isNotABracket(model, 1, 2); + isNotABracket(model, 1, 3); + isBracket(model, 1, 4, 5, 2, 3, 4); + isBracket(model, 1, 5, 4, 2, 3, 4); + isBracket(model, 1, 6, 5, 2, 2, 3); + isBracket(model, 1, 7, 6, 2, 1, 2); + + isBracket(model, 2, 1, 2, 1, 6, 7); + isBracket(model, 2, 2, 1, 1, 6, 7); + isBracket(model, 2, 3, 2, 1, 5, 6); + isBracket(model, 2, 4, 3, 1, 4, 5); + isNotABracket(model, 2, 5); + isNotABracket(model, 2, 6); + isNotABracket(model, 2, 7); + + model.dispose(); + }); + + test('bracket matching 2', () => { + let text = + 'var bar = {' + '\n' + + 'foo: {' + '\n' + + '}, bar: {hallo: [{' + '\n' + + '}, {' + '\n' + + '}]}}'; + let model = Model.createFromString(text, undefined, new BracketMode()); + + let brackets = [ + [1, 11, 12, 5, 4, 5], + [1, 12, 11, 5, 4, 5], + [5, 5, 4, 1, 11, 12], + + [2, 6, 7, 3, 1, 2], + [2, 7, 6, 3, 1, 2], + [3, 1, 2, 2, 6, 7], + [3, 2, 1, 2, 6, 7], + + [3, 9, 10, 5, 3, 4], + [3, 10, 9, 5, 3, 4], + [5, 4, 3, 3, 9, 10], + + [3, 17, 18, 5, 2, 3], + [3, 18, 17, 5, 2, 3], + [5, 3, 2, 3, 17, 18], + + [3, 19, 18, 4, 1, 2], + [4, 2, 1, 3, 18, 19], + [4, 1, 2, 3, 18, 19], + + [4, 4, 5, 5, 1, 2], + [4, 5, 4, 5, 1, 2], + [5, 2, 1, 4, 4, 5], + [5, 1, 2, 4, 4, 5] + ]; + let i, len, b, isABracket = {1:{}, 2:{}, 3:{}, 4:{}, 5:{}}; + + for (i = 0, len = brackets.length; i < len; i++) { + b = brackets[i]; + isBracket(model, b[0], b[1], b[2], b[3], b[4], b[5]); + isABracket[b[0]][b[1]] = true; + } + + for (i = 1, len = model.getLineCount(); i <= len; i++) { + let line = model.getLineContent(i), j, lenJ; + for (j = 1, lenJ = line.length + 1; j <= lenJ; j++) { + if (!isABracket[i].hasOwnProperty(j)) { + isNotABracket(model, i, j); + } + } + } + + model.dispose(); + }); +}); + + +suite('TextModelWithTokens regression tests', () => { + + test('Microsoft/monaco-editor#122: Unhandled Exception: TypeError: Unable to get property \'replace\' of undefined or null reference', () => { + function assertViewLineTokens(model:Model, lineNumber:number, forceTokenization:boolean, expected:ViewLineToken[]): void { + let actual = model.getLineTokens(lineNumber, !forceTokenization).inflate(); + assert.deepEqual(actual, expected); + } + + let _tokenId = 0; + class IndicisiveMode extends MockMode { + public tokenizationSupport:ITokenizationSupport; + + constructor() { + super(); + this.tokenizationSupport = { + getInitialState: () => { + return null; + }, + tokenize: (line, state, offsetDelta, stopAtOffset) => { + let myId = ++_tokenId; + return { + tokens: [new Token(0, 'custom.'+myId)], + actualStopOffset: line.length, + endState: null, + modeTransitions: [], + retokenize: null + }; + } + }; + } + } + let model = Model.createFromString('A model with\ntwo lines'); + + assertViewLineTokens(model, 1, true, [new ViewLineToken(0, '')]); + assertViewLineTokens(model, 2, true, [new ViewLineToken(0, '')]); + + model.setMode(new IndicisiveMode()); + + assertViewLineTokens(model, 1, true, [new ViewLineToken(0, 'custom.1')]); + assertViewLineTokens(model, 2, true, [new ViewLineToken(0, 'custom.2')]); + + model.setMode(new IndicisiveMode()); + + assertViewLineTokens(model, 1, false, [new ViewLineToken(0, '')]); + assertViewLineTokens(model, 2, false, [new ViewLineToken(0, '')]); + + model.dispose(); + }); + + test('Microsoft/monaco-editor#133: Error: Cannot read property \'modeId\' of undefined', () => { + class BracketMode extends MockMode { + constructor() { + super(); + LanguageConfigurationRegistry.register(this.getId(), { + brackets: [ + ['module','end module'], + ['sub','end sub'] + ] + }); + } + } + + let model = Model.createFromString([ + 'Imports System', + 'Imports System.Collections.Generic', + '', + 'Module m1', + '', + '\tSub Main()', + '\tEnd Sub', + '', + 'End Module', + ].join('\n'), undefined, new BracketMode()); + + let actual = model.matchBracket(new Position(4,1)); + assert.deepEqual(actual, [new Range(4,1,4,7), new Range(9,1,9,11)]); + + model.dispose(); + }); +}); \ No newline at end of file diff --git a/src/vs/editor/test/common/modes/tokenization.test.ts b/src/vs/editor/test/common/modes/tokenization.test.ts index c54d4a27a36..1645f2c1782 100644 --- a/src/vs/editor/test/common/modes/tokenization.test.ts +++ b/src/vs/editor/test/common/modes/tokenization.test.ts @@ -13,6 +13,8 @@ import {handleEvent} from 'vs/editor/common/modes/supports'; import {IEnteringNestedModeData, ILeavingNestedModeData, TokenizationSupport} from 'vs/editor/common/modes/supports/tokenizationSupport'; import {createMockLineContext} from 'vs/editor/test/common/modesTestUtils'; import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; +import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; export class State extends AbstractState { @@ -150,7 +152,7 @@ interface ITestToken { startIndex:number; type:string; } -function assertTokens(actual:modes.IToken[], expected:ITestToken[], message?:string) { +function assertTokens(actual:Token[], expected:ITestToken[], message?:string) { assert.equal(actual.length, expected.length, 'Lengths mismatch'); for (var i = 0; i < expected.length; i++) { assert.equal(actual[i].startIndex, expected[i].startIndex, 'startIndex mismatch'); @@ -162,12 +164,12 @@ interface ITestModeTransition { startIndex:number; id:string; } -function assertModeTransitions(actual:modes.IModeTransition[], expected:ITestModeTransition[], message?:string) { +function assertModeTransitions(actual:ModeTransition[], expected:ITestModeTransition[], message?:string) { var massagedActual:ITestModeTransition[] = []; for (var i = 0; i < actual.length; i++) { massagedActual.push({ startIndex: actual[i].startIndex, - id: actual[i].mode.getId() + id: actual[i].modeId }); } assert.deepEqual(massagedActual, expected, message); diff --git a/src/vs/editor/test/common/modesTestUtils.ts b/src/vs/editor/test/common/modesTestUtils.ts index 90ff862fba4..d59f9c6af61 100644 --- a/src/vs/editor/test/common/modesTestUtils.ts +++ b/src/vs/editor/test/common/modesTestUtils.ts @@ -7,6 +7,7 @@ import {Arrays} from 'vs/editor/common/core/arrays'; import * as modes from 'vs/editor/common/modes'; import {ModeTransition} from 'vs/editor/common/core/modeTransition'; +import {Token} from 'vs/editor/common/core/token'; export interface TokenText { text: string; @@ -14,12 +15,12 @@ export interface TokenText { } export function createLineContextFromTokenText(tokens: TokenText[]): modes.ILineContext { - var line = ''; - var processedTokens: modes.IToken[] = []; + let line = ''; + let processedTokens: Token[] = []; - var indexSoFar = 0; - for (var i = 0; i < tokens.length; ++i){ - processedTokens.push({ startIndex: indexSoFar, type: tokens[i].type }); + let indexSoFar = 0; + for (let i = 0; i < tokens.length; ++i){ + processedTokens.push(new Token(indexSoFar, tokens[i].type)); line += tokens[i].text; indexSoFar += tokens[i].text.length; } @@ -28,16 +29,16 @@ export function createLineContextFromTokenText(tokens: TokenText[]): modes.ILine } export function createMockLineContext(line:string, tokens:modes.ILineTokens): modes.ILineContext { - return new TestLineContext(line, tokens.tokens, ModeTransition.create(tokens.modeTransitions)); + return new TestLineContext(line, tokens.tokens, tokens.modeTransitions); } class TestLineContext implements modes.ILineContext { public modeTransitions: ModeTransition[]; private _line:string; - private _tokens: modes.IToken[]; + private _tokens: Token[]; - constructor(line:string, tokens: modes.IToken[], modeTransitions:ModeTransition[]) { + constructor(line:string, tokens: Token[], modeTransitions:ModeTransition[]) { this.modeTransitions = modeTransitions; this._line = line; this._tokens = tokens; @@ -55,13 +56,6 @@ class TestLineContext implements modes.ILineContext { return this._tokens[tokenIndex].startIndex; } - public getTokenEndIndex(tokenIndex:number): number { - if (tokenIndex + 1 < this._tokens.length) { - return this._tokens[tokenIndex + 1].startIndex; - } - return this._line.length; - } - public getTokenType(tokenIndex:number): string { return this._tokens[tokenIndex].type; } @@ -69,10 +63,4 @@ class TestLineContext implements modes.ILineContext { public findIndexOfOffset(offset:number): number { return Arrays.findIndexInSegmentsArray(this._tokens, offset); } - - public getTokenText(tokenIndex:number): string { - var startIndex = this._tokens[tokenIndex].startIndex; - var endIndex = tokenIndex + 1 < this._tokens.length ? this._tokens[tokenIndex + 1].startIndex : this._line.length; - return this._line.substring(startIndex, endIndex); - } } \ No newline at end of file diff --git a/src/vs/editor/test/common/modesUtil.ts b/src/vs/editor/test/common/modesUtil.ts index 5a049816264..0fdcb35dd52 100644 --- a/src/vs/editor/test/common/modesUtil.ts +++ b/src/vs/editor/test/common/modesUtil.ts @@ -9,10 +9,16 @@ import {Model} from 'vs/editor/common/model/model'; import * as modes from 'vs/editor/common/modes'; import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; import {RichEditSupport, LanguageConfiguration} from 'vs/editor/common/modes/languageConfigurationRegistry'; +import {Token} from 'vs/editor/common/core/token'; + +export interface ITestToken { + startIndex: number; + type: string; +} export interface ITestItem { line: string; - tokens: modes.IToken[]; + tokens: ITestToken[]; } export function assertWords(actual:string[], expected:string[], message?:string): void { @@ -93,6 +99,6 @@ function executeTest(tokenizationSupport: modes.ITokenizationSupport, tests:ITes } } -function assertTokens(actual:modes.IToken[], expected:modes.IToken[], message?:string): void { +function assertTokens(actual:Token[], expected:ITestToken[], message?:string): void { assert.deepEqual(actual, expected, message + ': ' + JSON.stringify(actual, null, '\t')); } diff --git a/src/vs/editor/test/common/services/resourceService.test.ts b/src/vs/editor/test/common/services/resourceService.test.ts index 08d9fb172e6..4f7f75053d5 100644 --- a/src/vs/editor/test/common/services/resourceService.test.ts +++ b/src/vs/editor/test/common/services/resourceService.test.ts @@ -6,8 +6,13 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; -import {createTestMirrorModelFromString} from 'vs/editor/common/model/mirrorModel'; +import {CompatMirrorModel} from 'vs/editor/common/model/compatMirrorModel'; import {ResourceService} from 'vs/editor/common/services/resourceServiceImpl'; +import {TextModel} from 'vs/editor/common/model/textModel'; + +function createTestMirrorModelFromString(value:string): CompatMirrorModel { + return new CompatMirrorModel(0, TextModel.toRawText(value, TextModel.DEFAULT_CREATION_OPTIONS), null); +} suite('Editor Services - ResourceService', () => { diff --git a/src/vs/editor/test/common/testModes.ts b/src/vs/editor/test/common/testModes.ts index 0bea638f3be..23e4f6a0a86 100644 --- a/src/vs/editor/test/common/testModes.ts +++ b/src/vs/editor/test/common/testModes.ts @@ -138,20 +138,6 @@ export class ModelMode2 extends MockMode { } } -export class BracketMode extends MockMode { - - constructor() { - super(); - LanguageConfigurationRegistry.register(this.getId(), { - brackets: [ - ['{', '}'], - ['[', ']'], - ['(', ')'], - ] - }); - } -} - export class NState extends AbstractState { private n:number; diff --git a/src/vs/editor/test/common/viewLayout/verticalObjects.test.ts b/src/vs/editor/test/common/viewLayout/verticalObjects.test.ts index cfab5f3aa8a..4b210e96a31 100644 --- a/src/vs/editor/test/common/viewLayout/verticalObjects.test.ts +++ b/src/vs/editor/test/common/viewLayout/verticalObjects.test.ts @@ -307,7 +307,7 @@ suite('Editor ViewLayout - VerticalObjects', () => { assert.equal(verticalObjects.getCenteredLineInViewport(0, 13, 1), 6); assert.equal(verticalObjects.getCenteredLineInViewport(0, 14, 1), 6); assert.equal(verticalObjects.getCenteredLineInViewport(0, 15, 1), 6); - assert.equal(verticalObjects.getCenteredLineInViewport(0, 16, 1), 7); + assert.equal(verticalObjects.getCenteredLineInViewport(0, 16, 1), 6); assert.equal(verticalObjects.getCenteredLineInViewport(0, 17, 1), 7); assert.equal(verticalObjects.getCenteredLineInViewport(0, 18, 1), 7); assert.equal(verticalObjects.getCenteredLineInViewport(0, 19, 1), 7); @@ -376,8 +376,8 @@ suite('Editor ViewLayout - VerticalObjects', () => { // viewport 0->50 var viewportData = verticalObjects.getLinesViewportData(0,50,10); assert.equal(viewportData.startLineNumber, 1); - assert.equal(viewportData.endLineNumber, 6); - assert.deepEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40, 50]); + assert.equal(viewportData.endLineNumber, 5); + assert.deepEqual(viewportData.relativeVerticalOffset, [0, 10, 20, 30, 40]); assert.equal(viewportData.visibleRangesDeltaTop, 0); // viewport 1->51 @@ -432,8 +432,8 @@ suite('Editor ViewLayout - VerticalObjects', () => { // viewport 50->160 viewportData = verticalObjects.getLinesViewportData(50,160,10); assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); + assert.equal(viewportData.endLineNumber, 6); + assert.deepEqual(viewportData.relativeVerticalOffset, [50]); assert.equal(viewportData.visibleRangesDeltaTop, -50); // viewport 51->161 @@ -498,8 +498,8 @@ suite('Editor ViewLayout - VerticalObjects', () => { // viewport 50->160 var viewportData = verticalObjects.getLinesViewportData(50,160,10); assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 7); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); + assert.equal(viewportData.endLineNumber, 6); + assert.deepEqual(viewportData.relativeVerticalOffset, [50]); assert.equal(viewportData.visibleRangesDeltaTop, -50); var whitespaceData = verticalObjects.getWhitespaceViewportData(50,160,10); assert.deepEqual(whitespaceData, [{ @@ -531,8 +531,8 @@ suite('Editor ViewLayout - VerticalObjects', () => { // viewport 50->220 viewportData = verticalObjects.getLinesViewportData(50,220,10); assert.equal(viewportData.startLineNumber, 6); - assert.equal(viewportData.endLineNumber, 8); - assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160, 220]); + assert.equal(viewportData.endLineNumber, 7); + assert.deepEqual(viewportData.relativeVerticalOffset, [50, 160]); assert.equal(viewportData.visibleRangesDeltaTop, -50); // viewport 50->250 diff --git a/src/vs/languages/html/common/html.contribution.ts b/src/vs/languages/html/common/html.contribution.ts index 56c6bb0a681..04bbde21217 100644 --- a/src/vs/languages/html/common/html.contribution.ts +++ b/src/vs/languages/html/common/html.contribution.ts @@ -34,6 +34,7 @@ export interface IHTMLFormatConfiguration { export interface IHTMLConfiguration { format: IHTMLFormatConfiguration; + suggest: {[providerId:string]:boolean}; } configurationRegistry.registerConfiguration({ @@ -82,5 +83,20 @@ configurationRegistry.registerConfiguration({ 'default': 'head, body, /html', 'description': nls.localize('format.extraLiners', "List of tags, comma separated, that should have an extra newline before them. 'null' defaults to \"head, body, /html\"."), }, +'html.suggest.angular1': { + 'type': ['boolean'], + 'default': true, + 'description': nls.localize('suggest.angular1', "Configures if the built-in HTML language support suggests Angular V1 tags and properties."), +}, +'html.suggest.ionic': { + 'type': ['boolean'], + 'default': true, + 'description': nls.localize('suggest.ionic', "Configures if the built-in HTML language support suggests Ionic tags, properties and values."), +}, +'html.suggest.html5': { + 'type': ['boolean'], + 'default': true, + 'description': nls.localize('suggest.html5', "Configures if the built-in HTML language support suggests HTML5 tags, properties and values."), +}, } }); diff --git a/src/vs/languages/html/common/htmlTags.ts b/src/vs/languages/html/common/htmlTags.ts index 23f0b6e8c47..5ed5d206960 100644 --- a/src/vs/languages/html/common/htmlTags.ts +++ b/src/vs/languages/html/common/htmlTags.ts @@ -38,6 +38,7 @@ import strings = require('vs/base/common/strings'); import nls = require('vs/nls'); export interface IHTMLTagProvider { + getId(): string; collectTags(collector: (tag: string, label: string) => void): void; collectAttributes(tag: string, collector: (attribute: string, type: string) => void): void; collectValues(tag: string, attribute: string, collector: (value: string) => void): void; @@ -469,6 +470,7 @@ export function getHTML5TagProvider(): IHTMLTagProvider { }; return { + getId: () => 'html5', collectTags: (collector: (tag: string, label: string) => void) => collectTagsDefault(collector, HTML_TAGS), collectAttributes: (tag: string, collector: (attribute: string, type: string) => void) => { collectAttributesDefault(tag, collector, HTML_TAGS, globalAttributes); @@ -496,6 +498,7 @@ export function getAngularTagProvider(): IHTMLTagProvider { ]; return { + getId: () => 'angular1', collectTags: (collector: (tag: string) => void) => { // no extra tags }, @@ -543,6 +546,7 @@ export function getIonicTagProvider(): IHTMLTagProvider { }; return { + getId: () => 'ionic', collectTags: (collector: (tag: string, label: string) => void) => collectTagsDefault(collector, IONIC_TAGS), collectAttributes: (tag: string, collector: (attribute: string, type: string) => void) => { collectAttributesDefault(tag, collector, IONIC_TAGS, globalAttributes); diff --git a/src/vs/languages/html/common/htmlWorker.ts b/src/vs/languages/html/common/htmlWorker.ts index 1c74011d77b..a8fe6210935 100644 --- a/src/vs/languages/html/common/htmlWorker.ts +++ b/src/vs/languages/html/common/htmlWorker.ts @@ -12,7 +12,7 @@ import network = require('vs/base/common/network'); import editorCommon = require('vs/editor/common/editorCommon'); import modes = require('vs/editor/common/modes'); import strings = require('vs/base/common/strings'); -import {IResourceService} from 'vs/editor/common/services/resourceService'; +import {IResourceService, ICompatMirrorModel} from 'vs/editor/common/services/resourceService'; import {getScanner, IHTMLScanner} from 'vs/languages/html/common/htmlScanner'; import {isTag, DELIM_END, DELIM_START, DELIM_ASSIGN, ATTRIB_NAME, ATTRIB_VALUE} from 'vs/languages/html/common/htmlTokenTypes'; import {isEmptyElement} from 'vs/languages/html/common/htmlEmptyTagsShared'; @@ -35,7 +35,8 @@ export class HTMLWorker { private resourceService:IResourceService; private _modeId: string; private _tagProviders: htmlTags.IHTMLTagProvider[]; - private formatSettings: IHTMLFormatConfiguration; + private _formatSettings: IHTMLFormatConfiguration; + private _providerConfiguration: {[providerId:string]:boolean}; constructor( modeId: string, @@ -49,6 +50,8 @@ export class HTMLWorker { this._tagProviders.push(htmlTags.getHTML5TagProvider()); this.addCustomTagProviders(this._tagProviders); + + this._providerConfiguration = null; } protected addCustomTagProviders(providers: htmlTags.IHTMLTagProvider[]): void { @@ -56,6 +59,13 @@ export class HTMLWorker { providers.push(htmlTags.getIonicTagProvider()); } + private getTagProviders(): htmlTags.IHTMLTagProvider[] { + if (this._modeId !== 'html' || !this._providerConfiguration) { + return this._tagProviders; + } + return this._tagProviders.filter(p => !!this._providerConfiguration[p.getId()]); + } + public provideDocumentRangeFormattingEdits(resource: URI, range: editorCommon.IRange, options: modes.FormattingOptions): winjs.TPromise { return this.formatHTML(resource, range, options); } @@ -86,8 +96,8 @@ export class HTMLWorker { } private getFormatOption(key: string, dflt: any): any { - if (this.formatSettings && this.formatSettings.hasOwnProperty(key)) { - let value = this.formatSettings[key]; + if (this._formatSettings && this._formatSettings.hasOwnProperty(key)) { + let value = this._formatSettings[key]; if (value !== null) { return value; } @@ -107,7 +117,10 @@ export class HTMLWorker { } _doConfigure(options: IHTMLConfiguration): winjs.TPromise { - this.formatSettings = options && options.format; + this._formatSettings = options && options.format; + if (options && options.suggest) { + this._providerConfiguration = options.suggest; + } return winjs.TPromise.as(null); } @@ -172,7 +185,7 @@ export class HTMLWorker { if (scanner.getTokenType() === DELIM_END && scanner.getTokenRange().endColumn === position.column) { let hasClose = collectClosingTagSuggestion(suggestions.currentWord.length + 1); if (!hasClose) { - this._tagProviders.forEach((provider) => { + this.getTagProviders().forEach((provider) => { provider.collectTags((tag, label) => { suggestions.suggestions.push({ label: '/' + tag, @@ -188,7 +201,7 @@ export class HTMLWorker { } else { collectClosingTagSuggestion(suggestions.currentWord.length); - this._tagProviders.forEach((provider) => { + this.getTagProviders().forEach((provider) => { provider.collectTags((tag, label) => { suggestions.suggestions.push({ label: tag, @@ -219,7 +232,7 @@ export class HTMLWorker { } } while (scanner.scanBack()); - this._tagProviders.forEach((provider) => { + this.getTagProviders().forEach((provider) => { provider.collectAttributes(parentTag,(attribute, type) => { let codeSnippet = attribute; if (type !== 'v') { @@ -256,7 +269,7 @@ export class HTMLWorker { } } - this._tagProviders.forEach((provider) => { + this.getTagProviders().forEach((provider) => { provider.collectValues(parentTag, attribute,(value) => { suggestions.suggestions.push({ label: value, @@ -406,7 +419,6 @@ export class HTMLWorker { public provideDocumentHighlights(resource:URI, position:editorCommon.IPosition, strict:boolean = false): winjs.TPromise { let model = this.resourceService.get(resource), wordAtPosition = model.getWordAtPosition(position), - currentWord = (wordAtPosition ? wordAtPosition.word : ''), result:modes.DocumentHighlight[] = []; @@ -425,14 +437,12 @@ export class HTMLWorker { }); } } else { - let words = model.getAllWordsWithRange(), - upperBound = Math.min(1000, words.length); // Limit find occurences to 1000 occurences - - for(let i = 0; i < upperBound; i++) { - if(words[i].text === currentWord) { + if (wordAtPosition) { + let results = model.findMatches(wordAtPosition.word, false, false, true, true); + for (let i = 0, len = results.length; i < len; i++) { result.push({ - range: words[i].range, - kind: modes.DocumentHighlightKind.Read + range: results[i], + kind: modes.DocumentHighlightKind.Read }); } } @@ -510,11 +520,10 @@ export class HTMLWorker { }; } - private _computeHTMLLinks(model: editorCommon.IMirrorModel, workspaceResource:URI): modes.ILink[] { + private _computeHTMLLinks(model: ICompatMirrorModel, modelAbsoluteUrl:URI, workspaceResource:URI): modes.ILink[] { let lineCount = model.getLineCount(), newLinks: modes.ILink[] = [], state: LinkDetectionState = LinkDetectionState.LOOKING_FOR_HREF_OR_SRC, - modelAbsoluteUrl = model.uri, lineNumber: number, lineContent: string, lineContentLength: number, @@ -590,7 +599,7 @@ export class HTMLWorker { public provideLinks(resource: URI, workspaceResource:URI): winjs.TPromise { let model = this.resourceService.get(resource); - return winjs.TPromise.as(this._computeHTMLLinks(model, workspaceResource)); + return winjs.TPromise.as(this._computeHTMLLinks(model, resource, workspaceResource)); } } diff --git a/src/vs/languages/html/test/common/html-worker.test.ts b/src/vs/languages/html/test/common/html-worker.test.ts index 5f805a88bfb..5ea6f5a40be 100644 --- a/src/vs/languages/html/test/common/html-worker.test.ts +++ b/src/vs/languages/html/test/common/html-worker.test.ts @@ -5,7 +5,7 @@ 'use strict'; import assert = require('assert'); -import mm = require('vs/editor/common/model/mirrorModel'); +import mm = require('vs/editor/common/model/compatMirrorModel'); import htmlWorker = require('vs/languages/html/common/htmlWorker'); import URI from 'vs/base/common/uri'; import ResourceService = require('vs/editor/common/services/resourceServiceImpl'); @@ -13,6 +13,11 @@ import Modes = require('vs/editor/common/modes'); import WinJS = require('vs/base/common/winjs.base'); import {HTMLMode} from 'vs/languages/html/common/html'; import {MockModeService} from 'vs/editor/test/common/mocks/mockModeService'; +import {TextModel} from 'vs/editor/common/model/textModel'; + +function createTestMirrorModelFromString(value:string, mode:Modes.IMode, associatedResource:URI): mm.CompatMirrorModel { + return new mm.CompatMirrorModel(0, TextModel.toRawText(value, TextModel.DEFAULT_CREATION_OPTIONS), mode, associatedResource); +} suite('HTML - worker', () => { @@ -29,10 +34,10 @@ suite('HTML - worker', () => { ); })(); - var mockHtmlWorkerEnv = function (url: URI, content: string): { worker: htmlWorker.HTMLWorker; model: mm.MirrorModel; } { + var mockHtmlWorkerEnv = function (url: URI, content: string): { worker: htmlWorker.HTMLWorker; model: mm.CompatMirrorModel; } { var resourceService = new ResourceService.ResourceService(); - var model = mm.createTestMirrorModelFromString(content, mode, url); + var model = createTestMirrorModelFromString(content, mode, url); resourceService.insert(url, model); var worker = new htmlWorker.HTMLWorker(mode.getId(), resourceService); @@ -48,7 +53,7 @@ suite('HTML - worker', () => { var url = URI.parse('test://1'); var env = mockHtmlWorkerEnv(url, content); - var position = env.model.getPositionFromOffset(idx); + var position = env.model.getPositionAt(idx); return env.worker.provideCompletionItems(url, position); }; diff --git a/src/vs/languages/razor/common/razorWorker.ts b/src/vs/languages/razor/common/razorWorker.ts index 615a1484d33..507dadba613 100644 --- a/src/vs/languages/razor/common/razorWorker.ts +++ b/src/vs/languages/razor/common/razorWorker.ts @@ -18,6 +18,7 @@ export function getRazorTagProvider() : htmlTags.IHTMLTagProvider { }; return { + getId: () => 'razor', collectTags: (collector: (tag: string) => void) => { // no extra tags }, diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index c6e5f52ec44..e508dbb7b8c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -740,14 +740,14 @@ declare module monaco.editor { * `domElement` should be empty (not contain other dom nodes). * The editor will read the size of `domElement`. */ - export function create(domElement: HTMLElement, options?: IEditorConstructionOptions, services?: IEditorOverrideServices): IStandaloneCodeEditor; + export function create(domElement: HTMLElement, options?: IEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneCodeEditor; /** * Create a new diff editor under `domElement`. * `domElement` should be empty (not contain other dom nodes). * The editor will read the size of `domElement`. */ - export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorConstructionOptions, services?: IEditorOverrideServices): IStandaloneDiffEditor; + export function createDiffEditor(domElement: HTMLElement, options?: IDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor; export interface IDiffNavigator { canNavigate(): boolean; @@ -808,6 +808,27 @@ declare module monaco.editor { oldLanguage: string; }) => void): IDisposable; + /** + * Create a new web worker that has model syncing capabilities built in. + * Specify an AMD module to load that will `create` an object that will be proxied. + */ + export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker; + + /** + * Colorize the contents of `domNode` using attribute `data-lang`. + */ + export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise; + + /** + * Colorize `text` using language `languageId`. + */ + export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise; + + /** + * Colorize a line in a model. + */ + export function colorizeModelLine(model: IModel, lineNumber: number, tabSize?: number): string; + /** * A web worker that can provide a proxy to an arbitrary file. */ @@ -837,29 +858,12 @@ declare module monaco.editor { * The data to send over when calling create on the module. */ createData?: any; + /** + * A label to be used to identify the web worker for debugging purposes. + */ + label?: string; } - /** - * Create a new web worker that has model syncing capabilities built in. - * Specify an AMD module to load that will `create` an object that will be proxied. - */ - export function createWebWorker(opts: IWebWorkerOptions): MonacoWebWorker; - - /** - * Colorize the contents of `domNode` using attribute `data-lang`. - */ - export function colorizeElement(domNode: HTMLElement, options: IColorizerElementOptions): Promise; - - /** - * Colorize `text` using language `languageId`. - */ - export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise; - - /** - * Colorize a line in a model. - */ - export function colorizeModelLine(model: IModel, lineNumber: number, tabSize?: number): string; - /** * The options to create an editor. */ @@ -968,7 +972,7 @@ declare module monaco.editor { horizontal?: string; /** * Cast horizontal and vertical shadows when the content is scrolled. - * Defaults to false. + * Defaults to true. */ useShadows?: boolean; /** @@ -1164,6 +1168,12 @@ declare module monaco.editor { * Defaults to 300. */ wrappingColumn?: number; + /** + * Control the alternate style of viewport wrapping. + * When set to true viewport wrapping is used only when the window width is less than the number of columns specified in the wrappingColumn property. Has no effect if wrappingColumn is not a positive number. + * Defaults to false. + */ + wordWrap?: boolean; /** * Control indentation of wrapped lines. Can be: 'none', 'same' or 'indent'. * Defaults to 'none'. @@ -1283,9 +1293,14 @@ declare module monaco.editor { renderControlCharacters?: boolean; /** * Enable rendering of indent guides. - * Defaults to true. + * Defaults to false. */ renderIndentGuides?: boolean; + /** + * Enable rendering of current line highlight. + * Defaults to true. + */ + renderLineHighlight?: boolean; /** * Inserting and deleting whitespace follows tab stops. */ @@ -1383,6 +1398,7 @@ declare module monaco.editor { renderWhitespace: boolean; renderControlCharacters: boolean; renderIndentGuides: boolean; + renderLineHighlight: boolean; scrollbar: InternalEditorScrollbarOptions; } @@ -1408,6 +1424,7 @@ declare module monaco.editor { renderWhitespace: boolean; renderControlCharacters: boolean; renderIndentGuides: boolean; + renderLineHighlight: boolean; scrollbar: boolean; } @@ -1935,6 +1952,48 @@ declare module monaco.editor { * Returns iff the model was disposed or not. */ isDisposed(): boolean; + /** + * Search the model. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchOnlyEditableRange Limit the searching to only search inside the editable range of the model. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @param limitResultCount Limit the number of results + * @return The ranges where the matches are. It is empty if not matches have been found. + */ + findMatches(searchString: string, searchOnlyEditableRange: boolean, isRegex: boolean, matchCase: boolean, wholeWord: boolean, limitResultCount?: number): Range[]; + /** + * Search the model. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchScope Limit the searching to only search inside this range. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @param limitResultCount Limit the number of results + * @return The ranges where the matches are. It is empty if no matches have been found. + */ + findMatches(searchString: string, searchScope: IRange, isRegex: boolean, matchCase: boolean, wholeWord: boolean, limitResultCount?: number): Range[]; + /** + * Search the model for the next match. Loops to the beginning of the model if needed. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchStart Start the searching at the specified position. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @return The range where the next match is. It is null if no next match has been found. + */ + findNextMatch(searchString: string, searchStart: IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range; + /** + * Search the model for the previous match. Loops to the end of the model if needed. + * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. + * @param searchStart Start the searching at the specified position. + * @param isRegex Used to indicate that `searchString` is a regular expression. + * @param matchCase Force the matching to match lower/upper case exactly. + * @param wholeWord Force the matching to match entire words only. + * @return The range where the previous match is. It is null if no previous match has been found. + */ + findPreviousMatch(searchString: string, searchStart: IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range; } export interface IReadOnlyModel extends ITextModel { @@ -1970,6 +2029,10 @@ declare module monaco.editor { * Get the current language mode associated with the model. */ getMode(): languages.IMode; + /** + * Get the language associated with this model. + */ + getModeId(): string; /** * Set the current language mode associated with the model. */ @@ -2150,48 +2213,6 @@ declare module monaco.editor { * and make all necessary clean-up to release this object to the GC. */ dispose(): void; - /** - * Search the model. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchOnlyEditableRange Limit the searching to only search inside the editable range of the model. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @param limitResultCount Limit the number of results - * @return The ranges where the matches are. It is empty if not matches have been found. - */ - findMatches(searchString: string, searchOnlyEditableRange: boolean, isRegex: boolean, matchCase: boolean, wholeWord: boolean, limitResultCount?: number): Range[]; - /** - * Search the model. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchScope Limit the searching to only search inside this range. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @param limitResultCount Limit the number of results - * @return The ranges where the matches are. It is empty if no matches have been found. - */ - findMatches(searchString: string, searchScope: IRange, isRegex: boolean, matchCase: boolean, wholeWord: boolean, limitResultCount?: number): Range[]; - /** - * Search the model for the next match. Loops to the beginning of the model if needed. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchStart Start the searching at the specified position. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @return The range where the next match is. It is null if no next match has been found. - */ - findNextMatch(searchString: string, searchStart: IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range; - /** - * Search the model for the previous match. Loops to the end of the model if needed. - * @param searchString The string used to search. If it is a regular expression, set `isRegex` to true. - * @param searchStart Start the searching at the specified position. - * @param isRegex Used to indicate that `searchString` is a regular expression. - * @param matchCase Force the matching to match lower/upper case exactly. - * @param wholeWord Force the matching to match entire words only. - * @return The range where the previous match is. It is null if no previous match has been found. - */ - findPreviousMatch(searchString: string, searchStart: IPosition, isRegex: boolean, matchCase: boolean, wholeWord: boolean): Range; } /** diff --git a/src/vs/platform/commands/common/commandService.ts b/src/vs/platform/commands/common/commandService.ts index b6f0f43df9a..090f0ee8022 100644 --- a/src/vs/platform/commands/common/commandService.ts +++ b/src/vs/platform/commands/common/commandService.ts @@ -6,35 +6,49 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {ICommandService, CommandsRegistry} from 'vs/platform/commands/common/commands'; +import {ICommandService, ICommand, CommandsRegistry} from 'vs/platform/commands/common/commands'; import {IExtensionService} from 'vs/platform/extensions/common/extensions'; export class CommandService implements ICommandService { _serviceBrand: any; + private _extensionHostIsReady: boolean = false; + constructor( @IInstantiationService private _instantiationService: IInstantiationService, @IExtensionService private _extensionService: IExtensionService ) { - // + this._extensionService.onReady().then(value => this._extensionHostIsReady = value); } executeCommand(id: string, ...args: any[]): TPromise { + // we always send an activation event, but + // we don't wait for it when the extension + // host didn't yet start - return this._extensionService.activateByEvent(`onCommand:${id}`).then(_ => { + const activation = this._extensionService.activateByEvent(`onCommand:${id}`); - const command = CommandsRegistry.getCommand(id); - if (!command) { - return TPromise.wrapError(new Error(`command '${id}' not found`)); - } + return this._extensionHostIsReady + ? activation.then(_ => this._tryExecuteCommand(id, args)) + : this._tryExecuteCommand(id, args); + } - try { - const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler].concat(args)); - return TPromise.as(result); - } catch (err) { - return TPromise.wrapError(err); - } - }); + private _tryExecuteCommand(id: string, args: any[]): TPromise { + const command = this._getCommand(id); + if (!command) { + return TPromise.wrapError(new Error(`command '${id}' not found`)); + } + + try { + const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler].concat(args)); + return TPromise.as(result); + } catch (err) { + return TPromise.wrapError(err); + } + } + + protected _getCommand(id:string): ICommand { + return CommandsRegistry.getCommand(id); } } diff --git a/src/vs/platform/commands/common/commands.ts b/src/vs/platform/commands/common/commands.ts index 018ef9bb688..845e208ff8f 100644 --- a/src/vs/platform/commands/common/commands.ts +++ b/src/vs/platform/commands/common/commands.ts @@ -5,6 +5,7 @@ 'use strict'; import {TPromise} from 'vs/base/common/winjs.base'; +import {IDisposable} from 'vs/base/common/lifecycle'; import {TypeConstraint, validateConstraints} from 'vs/base/common/types'; import {ServicesAccessor, createDecorator} from 'vs/platform/instantiation/common/instantiation'; @@ -36,8 +37,8 @@ export interface ICommandHandlerDescription { } export interface ICommandRegistry { - registerCommand(id: string, command: ICommandHandler): void; - registerCommand(id: string, command: ICommand): void; + registerCommand(id: string, command: ICommandHandler): IDisposable; + registerCommand(id: string, command: ICommand): IDisposable; getCommand(id: string): ICommand; getCommands(): ICommandsMap; } @@ -50,19 +51,18 @@ function isCommand(thing: any): thing is ICommand { export const CommandsRegistry: ICommandRegistry = new class implements ICommandRegistry { - private _commands: { [id: string]: ICommand } = Object.create(null); + private _commands: { [id: string]: ICommand | ICommand[] } = Object.create(null); + + registerCommand(id: string, commandOrDesc: ICommandHandler | ICommand): IDisposable { - registerCommand(id: string, commandOrDesc: ICommandHandler | ICommand): void { - // if (this._commands[id] !== void 0) { - // throw new Error(`command already exists: '${id}'`); - // } if (!commandOrDesc) { throw new Error(`invalid command`); } + let command: ICommand; if (!isCommand(commandOrDesc)) { // simple handler - this._commands[id] = { handler: commandOrDesc }; + command = { handler: commandOrDesc }; } else { const {handler, description} = commandOrDesc; @@ -72,7 +72,7 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR for (let arg of description.args) { constraints.push(arg.constraint); } - this._commands[id] = { + command = { description, handler(accessor, ...args: any[]) { validateConstraints(args, constraints); @@ -81,17 +81,56 @@ export const CommandsRegistry: ICommandRegistry = new class implements ICommandR }; } else { // add as simple handler - this._commands[id] = { handler }; + command = { handler }; } } + + // find a place to store the command + const commandOrArray = this._commands[id]; + if (commandOrArray === void 0) { + this._commands[id] = command; + } else if (Array.isArray(commandOrArray)) { + commandOrArray.unshift(command); + } else { + this._commands[id] = [command, commandOrArray]; + } + + return { + dispose: () => { + const commandOrArray = this._commands[id]; + if (Array.isArray(commandOrArray)) { + // remove from array, remove array + // if last element removed + const idx = commandOrArray.indexOf(command); + if (idx >= 0) { + commandOrArray.splice(idx, 1); + if (commandOrArray.length === 0) { + delete this._commands[id]; + } + } + } else if (isCommand(commandOrArray)) { + // remove from map + delete this._commands[id]; + } + } + }; } getCommand(id: string): ICommand { - return this._commands[id]; + const commandOrArray = this._commands[id]; + if (Array.isArray(commandOrArray)) { + return commandOrArray[0]; + } else { + return commandOrArray; + } } getCommands(): ICommandsMap { - return this._commands; + const result: ICommandsMap = Object.create(null); + for (let id in this._commands) { + result[id] = this.getCommand(id); + } + return result; } }; diff --git a/src/vs/platform/commands/test/commandService.test.ts b/src/vs/platform/commands/test/commandService.test.ts new file mode 100644 index 00000000000..7441df0f8a3 --- /dev/null +++ b/src/vs/platform/commands/test/commandService.test.ts @@ -0,0 +1,92 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {IDisposable} from 'vs/base/common/lifecycle'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {CommandsRegistry} from 'vs/platform/commands/common/commands'; +import {CommandService} from 'vs/platform/commands/common/commandService'; +import {IExtensionService} from 'vs/platform/extensions/common/extensions'; +import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; + +class SimpleExtensionService implements IExtensionService { + _serviceBrand: any; + activateByEvent(activationEvent: string): TPromise { + return this.onReady().then(() => { }); + } + onReady(): TPromise { + return TPromise.as(true); + } + getExtensionsStatus() { + return undefined; + } +} + +suite('CommandService', function () { + + let commandRegistration: IDisposable; + + setup(function () { + commandRegistration = CommandsRegistry.registerCommand('foo', function () { }); + }); + + teardown(function () { + commandRegistration.dispose(); + }); + + test('activateOnCommand', function () { + + let lastEvent: string; + + let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + activateByEvent(activationEvent: string): TPromise { + lastEvent = activationEvent; + return super.activateByEvent(activationEvent); + } + }); + + return service.executeCommand('foo').then(() => { + assert.ok(lastEvent, 'onCommand:foo'); + return service.executeCommand('unknownCommandId'); + }).then(() => { + assert.ok(false); + }, () => { + assert.ok(lastEvent, 'onCommand:unknownCommandId'); + }); + }); + + test('fwd activation error', function () { + + let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + activateByEvent(activationEvent: string): TPromise { + return TPromise.wrapError('bad_activate'); + } + }); + + return service.executeCommand('foo').then(() => assert.ok(false), err => { + assert.equal(err, 'bad_activate'); + }); + }); + + test('!onReady, but executeCommand', function () { + + let callCounter = 0; + let reg = CommandsRegistry.registerCommand('bar', () => callCounter += 1); + + let resolve: Function; + let service = new CommandService(new InstantiationService(), new class extends SimpleExtensionService { + onReady() { + return new TPromise(_resolve => { resolve = _resolve; }); + } + }); + + return service.executeCommand('bar').then(() => { + reg.dispose(); + assert.equal(callCounter, 1); + }); + }); + +}); \ No newline at end of file diff --git a/src/vs/platform/commands/test/commands.test.ts b/src/vs/platform/commands/test/commands.test.ts index 9b147930241..cfa375e6e42 100644 --- a/src/vs/platform/commands/test/commands.test.ts +++ b/src/vs/platform/commands/test/commands.test.ts @@ -14,10 +14,41 @@ suite('Command Tests', function () { assert.throws(() => CommandsRegistry.registerCommand('foo', null)); }); - // test('register command - dupe', function () { - // CommandsRegistry.registerCommand('foo', () => { }); - // assert.throws(() => CommandsRegistry.registerCommand('foo', () => { })); - // }); + test('register/dispose', function () { + const command = function () { }; + const reg = CommandsRegistry.registerCommand('foo', command); + assert.ok(CommandsRegistry.getCommand('foo').handler === command); + reg.dispose(); + assert.ok(CommandsRegistry.getCommand('foo') === undefined); + }); + + test('register/register/dispose', function () { + const command1 = function () { }; + const command2 = function () { }; + + // dispose overriding command + let reg1 = CommandsRegistry.registerCommand('foo', command1); + assert.ok(CommandsRegistry.getCommand('foo').handler === command1); + + let reg2 = CommandsRegistry.registerCommand('foo', command2); + assert.ok(CommandsRegistry.getCommand('foo').handler === command2); + reg2.dispose(); + + assert.ok(CommandsRegistry.getCommand('foo').handler === command1); + reg1.dispose(); + assert.ok(CommandsRegistry.getCommand('foo') === void 0); + + // dispose override command first + reg1 = CommandsRegistry.registerCommand('foo', command1); + reg2 = CommandsRegistry.registerCommand('foo', command2); + assert.ok(CommandsRegistry.getCommand('foo').handler === command2); + + reg1.dispose(); + assert.ok(CommandsRegistry.getCommand('foo').handler === command2); + + reg2.dispose(); + assert.ok(CommandsRegistry.getCommand('foo') === void 0); + }); test('command with description', function() { diff --git a/src/vs/platform/configuration/test/node/configurationService.test.ts b/src/vs/platform/configuration/test/node/configurationService.test.ts index 348cc064d14..c84763bbe6c 100644 --- a/src/vs/platform/configuration/test/node/configurationService.test.ts +++ b/src/vs/platform/configuration/test/node/configurationService.test.ts @@ -12,9 +12,8 @@ import fs = require('fs'); import {Registry} from 'vs/platform/platform'; import {ConfigurationService} from 'vs/platform/configuration/node/configurationService'; -import {ParsedArgs} from 'vs/code/node/argv'; +import {ParsedArgs, parseArgs} from 'vs/platform/environment/node/argv'; import {EnvironmentService} from 'vs/platform/environment/node/environmentService'; -import {parseArgs} from 'vs/code/node/argv'; import extfs = require('vs/base/node/extfs'); import uuid = require('vs/base/common/uuid'); import {IConfigurationRegistry, Extensions as ConfigurationExtensions} from 'vs/platform/configuration/common/configurationRegistry'; diff --git a/src/vs/platform/editor/common/editor.ts b/src/vs/platform/editor/common/editor.ts index 3211035bb98..f0456b19084 100644 --- a/src/vs/platform/editor/common/editor.ts +++ b/src/vs/platform/editor/common/editor.ts @@ -12,7 +12,9 @@ import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; export const IEditorService = createDecorator('editorService'); export interface IEditorService { + _serviceBrand: any; + /** * Specific overload to open an instance of IResourceInput. */ @@ -25,6 +27,11 @@ export interface IEditorService { } export interface IEditorModel extends IEventEmitter { + + /** + * Loads the model. + */ + load(): TPromise; } export interface ITextEditorModel extends IEditorModel { @@ -89,6 +96,11 @@ export interface IEditor { * Asks the underlying control to focus. */ focus(): void; + + /** + * Finds out if this editor is visible or not. + */ + isVisible(): boolean; } /** diff --git a/src/vs/code/node/argv.ts b/src/vs/platform/environment/node/argv.ts similarity index 91% rename from src/vs/code/node/argv.ts rename to src/vs/platform/environment/node/argv.ts index a6b95950dd8..d43cbaaa8e7 100644 --- a/src/vs/code/node/argv.ts +++ b/src/vs/platform/environment/node/argv.ts @@ -122,13 +122,11 @@ export function parseArgs(args: string[]): ParsedArgs { return minimist(args, options) as ParsedArgs; } -const executable = 'code' + (os.platform() === 'win32' ? '.exe' : ''); - export const optionsHelp: { [name: string]: string; } = { '-d, --diff': localize('diff', "Open a diff editor. Requires to pass two file paths as arguments."), '--disable-extensions': localize('disableExtensions', "Disable all installed extensions."), '-g, --goto': localize('goto', "Open the file at path at the line and column (add :line[:column] to path)."), - '--locale=': localize('locale', "The locale to use (e.g. en-US or zh-TW)."), + '--locale ': localize('locale', "The locale to use (e.g. en-US or zh-TW)."), '-n, --new-window': localize('newWindow', "Force a new instance of Code."), '-p, --performance': localize('performance', "Start with the 'Developer: Startup Performance' command enabled."), '-r, --reuse-window': localize('reuseWindow', "Force opening a file or folder in the last active window."), @@ -177,13 +175,14 @@ function wrapText(text: string, columns: number) : string[] { return lines; } -export function buildHelpMessage(version: string): string { - let columns = (process.stdout).isTTY ? (process.stdout).columns : 80; - return `Visual Studio Code v${ version } +export function buildHelpMessage(fullName: string, name: string, version: string): string { + const columns = (process.stdout).isTTY ? (process.stdout).columns : 80; + const executable = `${ name }${ os.platform() === 'win32' ? '.exe' : '' }`; + return `${ fullName } ${ version } -Usage: ${ executable } [arguments] [paths...] +${ localize('usage', "Usage") }: ${ executable } [${ localize('options', "options") }] [${ localize('paths', 'paths') }...] -Options: +${ localize('optionsUpperCase', "Options") }: ${formatOptions(optionsHelp, columns)}`; -} +} \ No newline at end of file diff --git a/src/vs/platform/environment/node/environmentService.ts b/src/vs/platform/environment/node/environmentService.ts index 9b07d29edcd..5e2ea603787 100644 --- a/src/vs/platform/environment/node/environmentService.ts +++ b/src/vs/platform/environment/node/environmentService.ts @@ -8,7 +8,7 @@ import * as crypto from 'crypto'; import * as paths from 'vs/base/node/paths'; import * as os from 'os'; import * as path from 'path'; -import {ParsedArgs} from 'vs/code/node/argv'; +import {ParsedArgs} from 'vs/platform/environment/node/argv'; import URI from 'vs/base/common/uri'; import { memoize } from 'vs/base/common/decorators'; import pkg from 'vs/platform/package'; diff --git a/src/vs/platform/environment/test/node/environmentService.test.ts b/src/vs/platform/environment/test/node/environmentService.test.ts index 8f10d2f0bd2..08459276c7d 100644 --- a/src/vs/platform/environment/test/node/environmentService.test.ts +++ b/src/vs/platform/environment/test/node/environmentService.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { parseArgs } from 'vs/code/node/argv'; +import { parseArgs } from 'vs/platform/environment/node/argv'; import { parseExtensionHostPort } from 'vs/platform/environment/node/environmentService'; suite('EnvironmentService', () => { diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 4ca370603ef..9311299f3d0 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -127,6 +127,7 @@ export class ExtensionManagementService implements IExtensionManagementService { }; return this.galleryService.download(extension) + .then(zipPath => validate(zipPath).then(() => zipPath)) .then(zipPath => this.installExtension(zipPath, id, metadata)) .then( local => this._onDidInstallExtension.fire({ id, local }), @@ -254,7 +255,7 @@ export class ExtensionManagementService implements IExtensionManagementService { let result: T = null; return pfs.readFile(this.obsoletePath, 'utf8') .then(null, err => err.code === 'ENOENT' ? TPromise.as('{}') : TPromise.wrapError(err)) - .then<{ [id: string]: boolean }>(raw => JSON.parse(raw)) + .then<{ [id: string]: boolean }>(raw => { try { return JSON.parse(raw); } catch (e) { return {}; }}) .then(obsolete => { result = fn(obsolete); return obsolete; }) .then(obsolete => { if (Object.keys(obsolete).length === 0) { diff --git a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts index 1923b3da74f..f79d4b4220c 100644 --- a/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts +++ b/src/vs/platform/keybinding/browser/keybindingServiceImpl.ts @@ -13,7 +13,7 @@ import Severity from 'vs/base/common/severity'; import {isFalsyOrEmpty} from 'vs/base/common/arrays'; import * as dom from 'vs/base/browser/dom'; import {IKeyboardEvent, StandardKeyboardEvent} from 'vs/base/browser/keyboardEvent'; -import {ICommandService, CommandsRegistry, ICommandHandler, ICommandHandlerDescription} from 'vs/platform/commands/common/commands'; +import {ICommandService, CommandsRegistry, ICommandHandlerDescription} from 'vs/platform/commands/common/commands'; import {KeybindingResolver} from 'vs/platform/keybinding/common/keybindingResolver'; import {IKeybindingItem, IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; @@ -32,7 +32,7 @@ export abstract class KeybindingService implements IKeybindingService { private _currentChordStatusMessage: IDisposable; private _contextKeyService: IContextKeyService; - private _commandService: ICommandService; + protected _commandService: ICommandService; private _statusService: IStatusbarService; private _messageService: IMessageService; @@ -132,11 +132,7 @@ export abstract class KeybindingService implements IKeybindingService { return '// ' + nls.localize('unboundCommands', "Here are other available commands: ") + '\n// - ' + pretty; } - protected _getCommandHandler(commandId: string): ICommandHandler { - return CommandsRegistry.getCommand(commandId).handler; - } - - private _dispatch(e: IKeyboardEvent): void { + protected _dispatch(e: IKeyboardEvent): void { let isModifierKey = (e.keyCode === KeyCode.Ctrl || e.keyCode === KeyCode.Shift || e.keyCode === KeyCode.Alt || e.keyCode === KeyCode.Meta); if (isModifierKey) { return; diff --git a/src/vs/platform/markers/common/problemMatcher.ts b/src/vs/platform/markers/common/problemMatcher.ts index ba1ed57705a..5be9dde216a 100644 --- a/src/vs/platform/markers/common/problemMatcher.ts +++ b/src/vs/platform/markers/common/problemMatcher.ts @@ -11,6 +11,7 @@ import * as Strings from 'vs/base/common/strings'; import * as Assert from 'vs/base/common/assert'; import * as Paths from 'vs/base/common/paths'; import * as Types from 'vs/base/common/types'; +import * as UUID from 'vs/base/common/uuid'; import Severity from 'vs/base/common/severity'; import URI from 'vs/base/common/uri'; @@ -394,7 +395,7 @@ class MultiLineMatcher extends AbstractLineMatcher { let _defaultPatterns: { [name: string]: ProblemPattern | ProblemPattern[]; } = Object.create(null); _defaultPatterns['msCompile'] = { - regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\):\s+(error|warning|info)\s+(\w{1,2}\d+)\s*:\s*(.*)$/, + regexp: /^([^\s].*)\((\d+|\d+,\d+|\d+,\d+,\d+,\d+)\)\s*:\s+(error|warning|info)\s+(\w{1,2}\d+)\s*:\s*(.*)$/, file: 1, location: 2, severity: 3, @@ -664,7 +665,7 @@ export namespace Config { * The owner of the produced VSCode problem. This is typically * the identifier of a VSCode language service if the problems are * to be merged with the one produced by the language service - * or 'external'. Defaults to 'external' if omitted. + * or a generated internal id. Defaults to the generated internal id. */ owner?: string; @@ -772,7 +773,7 @@ export class ProblemMatcherParser extends Parser { private createProblemMatcher(description: Config.ProblemMatcher): ProblemMatcher { let result: ProblemMatcher = null; - let owner = description.owner ? description.owner : 'external'; + let owner = description.owner ? description.owner : UUID.generateUuid(); let applyTo = Types.isString(description.applyTo) ? ApplyToKind.fromString(description.applyTo) : ApplyToKind.allDocuments; if (!applyTo) { applyTo = ApplyToKind.allDocuments; diff --git a/src/vs/platform/request/common/request.ts b/src/vs/platform/request/common/request.ts index dad7a07efb0..9d42bbc4c2a 100644 --- a/src/vs/platform/request/common/request.ts +++ b/src/vs/platform/request/common/request.ts @@ -23,6 +23,7 @@ export interface IHTTPConfiguration { http?: { proxy?: string; proxyStrictSSL?: boolean; + proxyAuthorization?: string; }; } @@ -42,6 +43,11 @@ Registry.as(Extensions.Configuration) type: 'boolean', default: true, description: localize('strictSSL', "Whether the proxy server certificate should be verified against the list of supplied CAs.") + }, + 'http.proxyAuthorization': { + type: ['null', 'string'], + default: null, + description: localize('proxyAuthorization', "The value to send as the 'Proxy-Authorization' header for every network request.") } } }); \ No newline at end of file diff --git a/src/vs/platform/request/node/requestService.ts b/src/vs/platform/request/node/requestService.ts index 7732034e84c..b4dac563fad 100644 --- a/src/vs/platform/request/node/requestService.ts +++ b/src/vs/platform/request/node/requestService.ts @@ -6,6 +6,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable } from 'vs/base/common/lifecycle'; +import { assign } from 'vs/base/common/objects'; import { IRequestOptions, IRequestContext, request } from 'vs/base/node/request'; import { getProxyAgent } from 'vs/base/node/proxy'; import { IRequestService, IHTTPConfiguration } from 'vs/platform/request/common/request'; @@ -21,6 +22,7 @@ export class RequestService implements IRequestService { private proxyUrl: string; private strictSSL: boolean; + private authorization: string; private disposables: IDisposable[] = []; constructor( @@ -37,6 +39,7 @@ export class RequestService implements IRequestService { private configure(config: IHTTPConfiguration) { this.proxyUrl = config.http && config.http.proxy; this.strictSSL = config.http && config.http.proxyStrictSSL; + this.authorization = config.http && config.http.proxyAuthorization; } request(options: IRequestOptions): TPromise { @@ -45,6 +48,10 @@ export class RequestService implements IRequestService { options.agent = getProxyAgent(options.url, { proxyUrl, strictSSL }); } + if (this.authorization) { + options.headers = assign(options.headers || {}, { 'Proxy-Authorization': this.authorization }); + } + return request(options); } } diff --git a/src/vs/platform/search/common/replace.ts b/src/vs/platform/search/common/replace.ts index 3ca69b60ef8..f8a976a3fc7 100644 --- a/src/vs/platform/search/common/replace.ts +++ b/src/vs/platform/search/common/replace.ts @@ -2,7 +2,6 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -'use strict'; import * as strings from 'vs/base/common/strings'; import {IPatternInfo} from 'vs/platform/search/common/search'; @@ -20,15 +19,28 @@ const t_CHAR_CODE = 't'.charCodeAt(0); export class ReplacePattern { private _replacePattern: string; - private _searchRegExp: RegExp; - private _hasParameters: boolean= false; + private _hasParameters: boolean = false; + private _regExp: RegExp; - constructor(private replaceString: string, private searchPatternInfo: IPatternInfo) { - this._replacePattern= replaceString; - if (searchPatternInfo.isRegExp) { - this._searchRegExp= strings.createRegExp(searchPatternInfo.pattern, searchPatternInfo.isRegExp, searchPatternInfo.isCaseSensitive, searchPatternInfo.isWordMatch, true); + constructor(replaceString: string, searchPatternInfo: IPatternInfo) + constructor(replaceString: string, parseParameters: boolean, regEx: RegExp) + constructor(replaceString: string, arg2: any, arg3?: any) { + this._replacePattern = replaceString; + let searchPatternInfo: IPatternInfo; + let parseParameters: boolean; + if (typeof arg2 === 'boolean') { + parseParameters = arg2; + } else { + searchPatternInfo = arg2; + parseParameters = searchPatternInfo.isRegExp; + } + if (parseParameters) { this.parseReplaceString(replaceString); } + this._regExp = arg3 ? arg3 : strings.createRegExp(searchPatternInfo.pattern, searchPatternInfo.isRegExp, { matchCase: searchPatternInfo.isCaseSensitive, wholeWord: searchPatternInfo.isWordMatch, multiline: searchPatternInfo.isMultiline, global: false }); + if (this._regExp.global) { + this._regExp = strings.createRegExp(this._regExp.source, true, { matchCase: !this._regExp.ignoreCase, wholeWord: false, multiline: this._regExp.multiline, global: false }); + } } public get hasParameters(): boolean { @@ -39,11 +51,28 @@ export class ReplacePattern { return this._replacePattern; } - public getReplaceString(matchedString: string): string { - if (this.hasParameters) { - return matchedString.replace(this._searchRegExp, this.pattern); + public get regExp(): RegExp { + return this._regExp; + } + + /** + * Returns the replace string for the first match in the given text. + * If text has no matches then returns null. + */ + public getReplaceString(text: string): string { + this._regExp.lastIndex = 0; + let match = this._regExp.exec(text); + if (match) { + if (this.hasParameters) { + if (match[0] === text) { + return text.replace(this._regExp, this.pattern); + } + let replaceString = text.replace(this._regExp, this.pattern); + return replaceString.substr(match.index, match[0].length - (text.length - replaceString.length)); + } + return this.pattern; } - return this.pattern; + return null; } /** diff --git a/src/vs/platform/search/common/search.ts b/src/vs/platform/search/common/search.ts index 93c01b4d5c2..68d9a942356 100644 --- a/src/vs/platform/search/common/search.ts +++ b/src/vs/platform/search/common/search.ts @@ -49,6 +49,7 @@ export interface IPatternInfo { pattern: string; isRegExp?: boolean; isWordMatch?: boolean; + isMultiline?: boolean; isCaseSensitive?: boolean; } diff --git a/src/vs/platform/search/test/common/replace.test.ts b/src/vs/platform/search/test/common/replace.test.ts index e2b574e91e3..decdf3a378b 100644 --- a/src/vs/platform/search/test/common/replace.test.ts +++ b/src/vs/platform/search/test/common/replace.test.ts @@ -75,7 +75,23 @@ suite('Replace Pattern test', () => { testParse('hello$02', 'hello$&2', true); }); - test('get replace string for a matched string', () => { + test('create pattern by passing regExp', () => { + let expected = /abc/; + let actual= new ReplacePattern('hello', false, expected).regExp; + assert.deepEqual(expected, actual); + + expected = /abc/; + actual= new ReplacePattern('hello', false, /abc/g).regExp; + assert.deepEqual(expected, actual); + + let testObject= new ReplacePattern('hello$0', false, /abc/g); + assert.equal(false, testObject.hasParameters); + + testObject= new ReplacePattern('hello$0', true, /abc/g); + assert.equal(true, testObject.hasParameters); + }); + + test('get replace string if given text is a complete match', () => { let testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true}); let actual= testObject.getReplaceString('bla'); assert.equal('hello', actual); @@ -100,7 +116,7 @@ suite('Replace Pattern test', () => { assert.equal('import * as something from \'fs\';', actual); actual= testObject.getReplaceString('let require(\'fs\')'); - assert.equal('let require(\'fs\')', actual); + assert.equal(null, actual); testObject= new ReplacePattern('import * as $1 from \'$1\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true}); actual= testObject.getReplaceString('let something = require(\'fs\')'); @@ -112,23 +128,90 @@ suite('Replace Pattern test', () => { testObject= new ReplacePattern('import * as $0 from \'$0\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: true}); actual= testObject.getReplaceString('let something = require(\'fs\');'); - assert.equal('import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';;', actual); + assert.equal('import * as let something = require(\'fs\') from \'let something = require(\'fs\')\';', actual); testObject= new ReplacePattern('import * as $1 from \'$2\';', {pattern: 'let\\s+(\\w+)\\s*=\\s*require\\s*\\(\\s*[\'\"]([\\w\.\\-/]+)\\s*[\'\"]\\s*\\)\\s*', isRegExp: false}); actual= testObject.getReplaceString('let fs = require(\'fs\');'); - assert.equal('import * as $1 from \'$2\';', actual); + assert.equal(null, actual); testObject= new ReplacePattern('cat$1', {pattern: 'for(.*)', isRegExp: true}); actual= testObject.getReplaceString('for ()'); assert.equal('cat ()', actual); + }); - // Not maching cases - testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true}); - actual= testObject.getReplaceString('foo'); - assert.equal('hello', actual); + test('get replace string for no matches', () => { + let testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true}); + let actual= testObject.getReplaceString('foo'); + assert.equal(null, actual); testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: false}); actual= testObject.getReplaceString('foo'); + assert.equal(null, actual); + }); + + test('get replace string if match is sub-string of the text', () => { + let testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: true}); + let actual= testObject.getReplaceString('this is a bla text'); assert.equal('hello', actual); + + testObject= new ReplacePattern('hello', {pattern: 'bla', isRegExp: false}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('hello', actual); + + testObject= new ReplacePattern('that', {pattern: 'this(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('that', actual); + + testObject= new ReplacePattern('$1at', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('that', actual); + + testObject= new ReplacePattern('$1e', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('the', actual); + + testObject= new ReplacePattern('$1ere', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('there', actual); + + testObject= new ReplacePattern('$1', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('th', actual); + + testObject= new ReplacePattern('ma$1', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('math', actual); + + testObject= new ReplacePattern('ma$1s', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('maths', actual); + + testObject= new ReplacePattern('ma$1s', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('maths', actual); + + testObject= new ReplacePattern('$0', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('this', actual); + + testObject= new ReplacePattern('$0$1', {pattern: '(th)is(?=.*bla)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('thisth', actual); + + testObject= new ReplacePattern('foo', {pattern: 'bla(?=\\stext$)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('foo', actual); + + testObject= new ReplacePattern('f$1', {pattern: 'b(la)(?=\\stext$)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('fla', actual); + + testObject= new ReplacePattern('f$0', {pattern: 'b(la)(?=\\stext$)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('fbla', actual); + + testObject= new ReplacePattern('$0ah', {pattern: 'b(la)(?=\\stext$)', isRegExp: true}); + actual= testObject.getReplaceString('this is a bla text'); + assert.equal('blaah', actual); }); }); \ No newline at end of file diff --git a/src/vs/test/utils/servicesTestUtils.ts b/src/vs/test/utils/servicesTestUtils.ts index 2b05e311eae..a65f5da97ac 100644 --- a/src/vs/test/utils/servicesTestUtils.ts +++ b/src/vs/test/utils/servicesTestUtils.ts @@ -5,41 +5,44 @@ 'use strict'; +import 'vs/workbench/parts/files/browser/files.contribution'; // load our contribution into the test import {Promise, TPromise} from 'vs/base/common/winjs.base'; import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; -import EventEmitter = require('vs/base/common/eventEmitter'); -import Paths = require('vs/base/common/paths'); +import {EventEmitter} from 'vs/base/common/eventEmitter'; +import * as paths from 'vs/base/common/paths'; import URI from 'vs/base/common/uri'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import Storage = require('vs/workbench/common/storage'); -import {EditorInputEvent, IEditorGroup} from 'vs/workbench/common/editor'; +import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {Storage, InMemoryLocalStorage} from 'vs/workbench/common/storage'; +import {EditorInputEvent, IEditorGroup, ConfirmResult} from 'vs/workbench/common/editor'; import Event, {Emitter} from 'vs/base/common/event'; import Severity from 'vs/base/common/severity'; import {IConfigurationService, getConfigurationValue, IConfigurationValue} from 'vs/platform/configuration/common/configuration'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; -import WorkbenchEditorService = require('vs/workbench/services/editor/common/editorService'); -import QuickOpenService = require('vs/workbench/services/quickopen/common/quickOpenService'); -import PartService = require('vs/workbench/services/part/common/partService'); -import WorkspaceContextService = require('vs/platform/workspace/common/workspace'); -import {IEditorInput, IEditorModel, Position, Direction, IEditor, IResourceInput, ITextEditorModel} from 'vs/platform/editor/common/editor'; +import {IQuickOpenService} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {IPartService} from 'vs/workbench/services/part/common/partService'; +import {IEditorInput, IEditorOptions, IEditorModel, Position, Direction, IEditor, IResourceInput, ITextEditorModel} from 'vs/platform/editor/common/editor'; import {IEventService} from 'vs/platform/event/common/event'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {IUntitledEditorService, UntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {IMessageService, IConfirmation} from 'vs/platform/message/common/message'; -import {IWorkspace} from 'vs/platform/workspace/common/workspace'; +import {IWorkspace, IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {ILifecycleService, ShutdownEvent} from 'vs/platform/lifecycle/common/lifecycle'; import {EditorStacksModel} from 'vs/workbench/common/editor/editorStacksModel'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; import {IEditorGroupService, GroupArrangement} from 'vs/workbench/services/group/common/groupService'; -import {TextFileService} from 'vs/workbench/parts/files/common/textFileServices'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {TextFileService} from 'vs/workbench/parts/files/common/textFileService'; import {IFileService, IResolveContentOptions} from 'vs/platform/files/common/files'; import {IModelService} from 'vs/editor/common/services/modelService'; import {ModelServiceImpl} from 'vs/editor/common/services/modelServiceImpl'; import {IRawTextContent} from 'vs/workbench/parts/files/common/files'; import {RawText} from 'vs/editor/common/model/textModel'; -import {parseArgs} from 'vs/code/node/argv'; +import {parseArgs} from 'vs/platform/environment/node/argv'; import {EnvironmentService} from 'vs/platform/environment/node/environmentService'; +import {IModeService} from 'vs/editor/common/services/modeService'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {IHistoryService} from 'vs/workbench/services/history/common/history'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; export const TestWorkspace: IWorkspace = { resource: URI.file('C:\\testWorkspace'), @@ -49,7 +52,7 @@ export const TestWorkspace: IWorkspace = { export const TestEnvironmentService = new EnvironmentService(parseArgs(process.argv), process.execPath); -export class TestContextService implements WorkspaceContextService.IWorkspaceContextService { +export class TestContextService implements IWorkspaceContextService { public _serviceBrand: any; private workspace: any; @@ -74,53 +77,93 @@ export class TestContextService implements WorkspaceContextService.IWorkspaceCon public isInsideWorkspace(resource: URI): boolean { if (resource && this.workspace) { - return Paths.isEqualOrParent(resource.fsPath, this.workspace.resource.fsPath); + return paths.isEqualOrParent(resource.fsPath, this.workspace.resource.fsPath); } return false; } public toWorkspaceRelativePath(resource: URI): string { - return Paths.makePosixAbsolute(Paths.normalize(resource.fsPath.substr('c:'.length))); + return paths.makePosixAbsolute(paths.normalize(resource.fsPath.substr('c:'.length))); } public toResource(workspaceRelativePath: string): URI { - return URI.file(Paths.join('C:\\', workspaceRelativePath)); + return URI.file(paths.join('C:\\', workspaceRelativePath)); } } -export abstract class TestTextFileService extends TextFileService { +export class TestTextFileService extends TextFileService { + private promptPath: string; + private confirmResult: ConfirmResult; constructor( - @WorkspaceContextService.IWorkspaceContextService contextService: WorkspaceContextService.IWorkspaceContextService, - @IInstantiationService instantiationService: IInstantiationService, + @ILifecycleService lifecycleService: ILifecycleService, + @IWorkspaceContextService contextService: IWorkspaceContextService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService telemetryService: ITelemetryService, - @WorkbenchEditorService.IWorkbenchEditorService editorService: WorkbenchEditorService.IWorkbenchEditorService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, - @IEventService eventService: IEventService, @IFileService fileService: IFileService, - @IModelService modelService: IModelService + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @IInstantiationService instantiationService: IInstantiationService ) { - super(contextService, instantiationService, configurationService, telemetryService, editorService, eventService, fileService, modelService); + super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, editorService, fileService, untitledEditorService, instantiationService); + } + + public setPromptPath(path: string): void { + this.promptPath = path; + } + + public setConfirmResult(result: ConfirmResult): void { + this.confirmResult = result; } public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { return this.fileService.resolveContent(resource, options).then((content) => { const raw = RawText.fromString(content.value, { defaultEOL: 1, detectIndentation: false, insertSpaces: false, tabSize: 4, trimAutoWhitespace: false }); - return { + return { resource: content.resource, - name: content.name, - mtime: content.mtime, - etag: content.etag, - mime: content.mime, - encoding: content.encoding, - value: raw, - valueLogicalHash: null + name: content.name, + mtime: content.mtime, + etag: content.etag, + mime: content.mime, + encoding: content.encoding, + value: raw, + valueLogicalHash: null }; }); } + + public promptForPath(defaultPath?: string): string { + return this.promptPath; + } + + public confirmSave(resources?: URI[]): ConfirmResult { + return this.confirmResult; + } +} + +export function workbenchInstantiationService(): TestInstantiationService { + let instantiationService = new TestInstantiationService(new ServiceCollection([ILifecycleService, new TestLifecycleService()])); + instantiationService.stub(IEventService, new TestEventService()); + instantiationService.stub(IWorkspaceContextService, new TestContextService(TestWorkspace)); + instantiationService.stub(IConfigurationService, new TestConfigurationService()); + instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(IStorageService, new TestStorageService()); + instantiationService.stub(IWorkbenchEditorService, new TestEditorService(function () { })); + instantiationService.stub(IPartService, new TestPartService()); + instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); + instantiationService.stub(IModeService); + instantiationService.stub(IHistoryService, 'getHistory', []); + instantiationService.stub(IModelService, createMockModelService(instantiationService)); + instantiationService.stub(IFileService, TestFileService); + instantiationService.stub(ITelemetryService, NullTelemetryService); + instantiationService.stub(IMessageService, new TestMessageService()); + instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); + instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + + return instantiationService; } export class TestMessageService implements IMessageService { @@ -151,7 +194,7 @@ export class TestMessageService implements IMessageService { } } -export class TestPartService implements PartService.IPartService { +export class TestPartService implements IPartService { public _serviceBrand: any; public layout(): void { } @@ -197,22 +240,23 @@ export class TestPartService implements PartService.IPartService { public setSideBarPosition(position): void { } public addClass(clazz: string): void { } public removeClass(clazz: string): void { } + public getWorkbenchElementId(): string { return ''; } } -export class TestEventService extends EventEmitter.EventEmitter implements IEventService { +export class TestEventService extends EventEmitter implements IEventService { public _serviceBrand: any; } -export class TestStorageService extends EventEmitter.EventEmitter implements IStorageService { +export class TestStorageService extends EventEmitter implements IStorageService { public _serviceBrand: any; - private storage: Storage.Storage; + private storage: Storage; constructor() { super(); let context = new TestContextService(); - this.storage = new Storage.Storage(new Storage.InMemoryLocalStorage(), null, context); + this.storage = new Storage(new InMemoryLocalStorage(), null, context); } store(key: string, value: any, scope: StorageScope = StorageScope.GLOBAL): void { @@ -243,6 +287,12 @@ export class TestStorageService extends EventEmitter.EventEmitter implements ISt export class TestUntitledEditorService implements IUntitledEditorService { public _serviceBrand: any; + private _onDidChangeDirty = new Emitter(); + + public get onDidChangeDirty(): Event { + return this._onDidChangeDirty.event; + } + public get(resource: URI) { return null; } @@ -255,7 +305,7 @@ export class TestUntitledEditorService implements IUntitledEditorService { return []; } - public revertAll(resources?: URI[]): URI[] { + public revertAll(resources?: URI[]): URI[] { return []; } @@ -291,7 +341,8 @@ export class TestEditorGroupService implements IEditorGroupService { let services = new ServiceCollection(); services.set(IStorageService, new TestStorageService()); - services.set(WorkspaceContextService.IWorkspaceContextService, new TestContextService()); + services.set(IConfigurationService, new TestConfigurationService()); + services.set(IWorkspaceContextService, new TestContextService()); const lifecycle = new TestLifecycleService(); services.set(ILifecycleService, lifecycle); @@ -362,12 +413,12 @@ export class TestEditorGroupService implements IEditorGroupService { } } -export class TestEditorService implements WorkbenchEditorService.IWorkbenchEditorService { +export class TestEditorService implements IWorkbenchEditorService { public _serviceBrand: any; - public activeEditorInput; - public activeEditorOptions; - public activeEditorPosition; + public activeEditorInput: IEditorInput; + public activeEditorOptions: IEditorOptions; + public activeEditorPosition: Position; private callback: (method: string) => void; @@ -442,7 +493,7 @@ export class TestEditorService implements WorkbenchEditorService.IWorkbenchEdito } } -export class TestQuickOpenService implements QuickOpenService.IQuickOpenService { +export class TestQuickOpenService implements IQuickOpenService { public _serviceBrand: any; private callback: (prefix: string) => void; @@ -497,7 +548,7 @@ export const TestFileService = { mime: 'text/plain', encoding: 'utf8', mtime: new Date().getTime(), - name: Paths.basename(resource.fsPath) + name: paths.basename(resource.fsPath) }); }, @@ -505,7 +556,7 @@ export const TestFileService = { return TPromise.as({ resource: resource, value: { - on: (event:string, callback:Function): void => { + on: (event: string, callback: Function): void => { if (event === 'data') { callback('Hello Html'); } @@ -518,7 +569,7 @@ export const TestFileService = { mime: 'text/plain', encoding: 'utf8', mtime: new Date().getTime(), - name: Paths.basename(resource.fsPath) + name: paths.basename(resource.fsPath) }); }, @@ -530,13 +581,13 @@ export const TestFileService = { mime: 'text/plain', encoding: 'utf8', mtime: new Date().getTime(), - name: Paths.basename(res.fsPath) + name: paths.basename(res.fsPath) }; }); } }; -export class TestConfigurationService extends EventEmitter.EventEmitter implements IConfigurationService { +export class TestConfigurationService extends EventEmitter implements IConfigurationService { public _serviceBrand: any; private configuration = Object.create(null); @@ -581,6 +632,10 @@ export class TestLifecycleService implements ILifecycleService { this._onShutdown.fire(); } + public fireWillShutdown(event: ShutdownEvent): void { + this._onWillShutdown.fire(event); + } + public get onWillShutdown(): Event { return this._onWillShutdown.event; } diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index 1334fb388cb..e2941bfd093 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -855,7 +855,7 @@ declare namespace vscode { export interface DecorationOptions { /** - * Range to which this decoration is applied. + * Range to which this decoration is applied. The range must not be empty. */ range: Range; @@ -933,10 +933,11 @@ declare namespace vscode { * be used to make edits. Note that the edit-builder is only valid while the * callback executes. * - * @param callback A function which can make edits using an [edit-builder](#TextEditorEdit). + * @param callback A function which can create edits using an [edit-builder](#TextEditorEdit). + * @param options The undo/redo behaviour around this edit. By default, undo stops will be created before and after this edit. * @return A promise that resolves with a value indicating if the edits could be applied. */ - edit(callback: (editBuilder: TextEditorEdit) => void): Thenable; + edit(callback: (editBuilder: TextEditorEdit) => void, options?:{ undoStopBefore: boolean; undoStopAfter: boolean; }): Thenable; /** * Adds a set of decorations to the text editor. If a set of decorations already exists with @@ -1426,14 +1427,14 @@ declare namespace vscode { ignoreFocusOut?: boolean; /** - * An optional function that will be called to valide input and to give a hint + * An optional function that will be called to validate input and to give a hint * to the user. * * @param value The current value of the input box. * @return A human readable string which is presented as diagnostic message. * Return `undefined`, `null`, or the empty string when 'value' is valid. */ - validateInput?: (value: string) => string; + validateInput?(value: string): string; } /** @@ -1842,7 +1843,7 @@ declare namespace vscode { * @return The resolved symbol or a thenable that resolves to that. When no result is returned, * the given `symbol` is used. */ - resolveWorkspaceSymbol?: (symbol: SymbolInformation, token: CancellationToken) => SymbolInformation | Thenable; + resolveWorkspaceSymbol?(symbol: SymbolInformation, token: CancellationToken): SymbolInformation | Thenable; } /** @@ -2444,7 +2445,7 @@ declare namespace vscode { * @param link The link that is to be resolved. * @param token A cancellation token. */ - resolveDocumentLink?: (link: DocumentLink, token: CancellationToken) => DocumentLink | Thenable; + resolveDocumentLink?(link: DocumentLink, token: CancellationToken): DocumentLink | Thenable; } /** @@ -2646,17 +2647,6 @@ declare namespace vscode { */ has(section: string): boolean; - /** - * Update a configuration value. A value can be changed for the current - * [workspace](#workspace.rootPath) only or globally for all instances of the - * editor. The updated configuration values are persisted. - * - * @param section Configuration name, supports _dotted_ names. - * @param value The new value. - * @param global When `true` changes the configuration value for all instances of the editor. - */ - update(section: string, value: any, global: boolean): Thenable; - /** * Readable dictionary that backs this configuration. * @readonly @@ -3012,9 +3002,10 @@ declare namespace vscode { name: string; /** - * Send text to the terminal. + * Send text to the terminal. The text is written to the stdin of the underlying pty process + * (shell) of the terminal. * - * @param text The text to send to the terminal. + * @param text The text to send. * @param addNewLine Whether to add a new line to the text being sent, this is normally * required to run a command in the terminal. The character(s) added are \n or \r\n * depending on the platform. This defaults to `true`. @@ -3022,14 +3013,14 @@ declare namespace vscode { sendText(text: string, addNewLine?: boolean): void; /** - * Reveal this channel in the UI. + * Show the terminal panel and reveal this terminal in the UI. * - * @param preserveFocus When `true` the channel will not take focus. + * @param preserveFocus When `true` the terminal will not take focus. */ - show(preservceFocus?: boolean): void; + show(preserveFocus?: boolean): void; /** - * Hide this channel from the UI. + * Hide the terminal panel if this terminal is currently showing. */ hide(): void; diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index f4c1b144ae0..60c3d773de3 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -17,6 +17,7 @@ import {ExtHostConfiguration} from 'vs/workbench/api/node/extHostConfiguration'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; import {ExtHostWorkspace} from 'vs/workbench/api/node/extHostWorkspace'; import {ExtHostQuickOpen} from 'vs/workbench/api/node/extHostQuickOpen'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; import {ExtHostStatusBar} from 'vs/workbench/api/node/extHostStatusBar'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; import {ExtHostOutputService} from 'vs/workbench/api/node/extHostOutputService'; @@ -99,12 +100,13 @@ export class ExtHostAPIImplementation { // Addressable instances const col = new InstanceCollection(); + const extHostHeapMonitor = col.define(ExtHostContext.ExtHostHeapService).set(new ExtHostHeapService()); const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set(new ExtHostDocuments(threadService)); const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set(new ExtHostEditors(threadService, extHostDocuments)); const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set(new ExtHostCommands(threadService, extHostEditors)); const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration))); const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set(new ExtHostDiagnostics(threadService)); - const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostDiagnostics)); + const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics)); const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set(new ExtHostFileSystemEventService()); const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set(new ExtHostQuickOpen(threadService)); col.define(ExtHostContext.ExtHostExtensionService).set(extensionService); diff --git a/src/vs/workbench/api/node/extHost.contribution.ts b/src/vs/workbench/api/node/extHost.contribution.ts index 639227e8ee1..56753b05010 100644 --- a/src/vs/workbench/api/node/extHost.contribution.ts +++ b/src/vs/workbench/api/node/extHost.contribution.ts @@ -32,6 +32,7 @@ import {MainThreadTerminalService} from './mainThreadTerminalService'; import {MainThreadWorkspace} from './mainThreadWorkspace'; import {MainProcessExtensionService} from './mainThreadExtensionService'; import {MainThreadFileSystemEventService} from './mainThreadFileSystemEventService'; +import {MainThreadHeapService} from './mainThreadHeapService'; // --- other interested parties import {MainProcessTextMateSyntax} from 'vs/editor/node/textMate/TMSyntax'; @@ -87,6 +88,7 @@ export class ExtHostContribution implements IWorkbenchContribution { create(JSONValidationExtensionPoint); create(LanguageConfigurationFileHandler); create(MainThreadFileSystemEventService); + create(MainThreadHeapService); } } diff --git a/src/vs/workbench/api/node/extHost.protocol.ts b/src/vs/workbench/api/node/extHost.protocol.ts index bdf9cd2ff7f..a2031bbce9a 100644 --- a/src/vs/workbench/api/node/extHost.protocol.ts +++ b/src/vs/workbench/api/node/extHost.protocol.ts @@ -31,8 +31,7 @@ import {ConfigurationTarget} from 'vs/workbench/services/configuration/common/co import {IPickOpenEntry, IPickOptions} from 'vs/workbench/services/quickopen/common/quickOpenService'; import {IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; -import {TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent} from './mainThreadEditorsTracker'; -import {EndOfLine} from './extHostTypes'; +import {IApplyEditsOptions, TextEditorRevealType, ITextEditorConfigurationUpdate, IResolvedTextEditorConfiguration, ISelectionChangeEvent} from './mainThreadEditorsTracker'; export interface InstanceSetter { set(instance:T): R; @@ -79,6 +78,7 @@ function ni() { return new Error('Not implemented'); } export abstract class MainThreadCommandsShape { $registerCommand(id: string): TPromise { throw ni(); } + $unregisterCommand(id: string): TPromise { throw ni(); } $executeCommand(id: string, args: any[]): Thenable { throw ni(); } $getCommands(): Thenable { throw ni(); } } @@ -110,7 +110,7 @@ export abstract class MainThreadEditorsShape { $trySetDecorations(id: string, key: string, ranges: editorCommon.IDecorationOptions[]): TPromise { throw ni(); } $tryRevealRange(id: string, range: editorCommon.IRange, revealType: TextEditorRevealType): TPromise { throw ni(); } $trySetSelections(id: string, selections: editorCommon.ISelection[]): TPromise { throw ni(); } - $tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], setEndOfLine:EndOfLine): TPromise { throw ni(); } + $tryApplyEdits(id: string, modelVersionId: number, edits: editorCommon.ISingleEditOperation[], opts:IApplyEditsOptions): TPromise { throw ni(); } } export abstract class MainThreadErrorsShape { @@ -265,6 +265,24 @@ export abstract class ExtHostFileSystemEventServiceShape { $onFileEvent(events: FileSystemEvents) { throw ni(); } } +export interface ObjectIdentifier { + $ident: number; +} + +export namespace ObjectIdentifier { + export function mixin(obj: T, id: number): T & ObjectIdentifier { + Object.defineProperty(obj, '$ident', { value: id, enumerable: true }); + return obj; + } + export function get(obj: any): number { + return obj['$ident']; + } +} + +export abstract class ExtHostHeapServiceShape { + $onGarbageCollection(ids: number[]): void { throw ni(); } +} + export abstract class ExtHostLanguageFeaturesShape { $provideDocumentSymbols(handle: number, resource: URI): TPromise { throw ni(); } $provideCodeLenses(handle: number, resource: URI): TPromise { throw ni(); } @@ -321,6 +339,7 @@ export const ExtHostContext = { ExtHostDocuments: createExtId('ExtHostDocuments', ExtHostDocumentsShape), ExtHostEditors: createExtId('ExtHostEditors', ExtHostEditorsShape), ExtHostFileSystemEventService: createExtId('ExtHostFileSystemEventService', ExtHostFileSystemEventServiceShape), + ExtHostHeapService: createExtId('ExtHostHeapMonitor', ExtHostHeapServiceShape), ExtHostLanguageFeatures: createExtId('ExtHostLanguageFeatures', ExtHostLanguageFeaturesShape), ExtHostQuickOpen: createExtId('ExtHostQuickOpen', ExtHostQuickOpenShape), ExtHostExtensionService: createExtId('ExtHostExtensionService', ExtHostExtensionServiceShape), diff --git a/src/vs/workbench/api/node/extHostApiCommands.ts b/src/vs/workbench/api/node/extHostApiCommands.ts index d42f4720261..62084d1a085 100644 --- a/src/vs/workbench/api/node/extHostApiCommands.ts +++ b/src/vs/workbench/api/node/extHostApiCommands.ts @@ -173,6 +173,9 @@ class ExtHostApiCommands { let href = encodeURI('command:vscode.previewHtml?' + JSON.stringify(someUri)); let html = 'Show Resource....'; \`\`\` + + The body element of the displayed html is dynamically annotated with one of the following css classes in order to + communicate the kind of color theme vscode is currently using: \`vscode-light\`, \`vscode-dark\`, or \`vscode-high-contrast\'. `, args: [ { name: 'uri', description: 'Uri of the resource to preview.', constraint: value => value instanceof URI || typeof value === 'string' }, diff --git a/src/vs/workbench/api/node/extHostCommands.ts b/src/vs/workbench/api/node/extHostCommands.ts index a0622faf796..a330a555fd8 100644 --- a/src/vs/workbench/api/node/extHostCommands.ts +++ b/src/vs/workbench/api/node/extHostCommands.ts @@ -48,7 +48,11 @@ export class ExtHostCommands extends ExtHostCommandsShape { this._commands[id] = { callback, thisArg, description }; this._proxy.$registerCommand(id); - return new extHostTypes.Disposable(() => delete this._commands[id]); + return new extHostTypes.Disposable(() => { + if (delete this._commands[id]) { + this._proxy.$unregisterCommand(id); + } + }); } executeCommand(id: string, ...args: any[]): Thenable { diff --git a/src/vs/workbench/api/node/extHostConfiguration.ts b/src/vs/workbench/api/node/extHostConfiguration.ts index 760a4a7c9f7..835ebf69510 100644 --- a/src/vs/workbench/api/node/extHostConfiguration.ts +++ b/src/vs/workbench/api/node/extHostConfiguration.ts @@ -9,7 +9,7 @@ import {illegalState} from 'vs/base/common/errors'; import Event, {Emitter} from 'vs/base/common/event'; import {WorkspaceConfiguration} from 'vscode'; import {ExtHostConfigurationShape, MainThreadConfigurationShape} from './extHost.protocol'; -import {ConfigurationTarget} from 'vs/workbench/services/configuration/common/configurationEditing'; +// import {ConfigurationTarget} from 'vs/workbench/services/configuration/common/configurationEditing'; export class ExtHostConfiguration extends ExtHostConfigurationShape { @@ -52,11 +52,11 @@ export class ExtHostConfiguration extends ExtHostConfigurationShape { result = defaultValue; } return result; - }, - update: (key: string, value: any, global: boolean) => { - key = section ? `${section}.${key}` : key; - const target = global ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE; - return this._proxy.$updateConfigurationOption(target, key, value); + // }, + // update: (key: string, value: any, global: boolean) => { + // key = section ? `${section}.${key}` : key; + // const target = global ? ConfigurationTarget.USER : ConfigurationTarget.WORKSPACE; + // return this._proxy.$updateConfigurationOption(target, key, value); } }; diff --git a/src/vs/workbench/api/node/extHostDiagnostics.ts b/src/vs/workbench/api/node/extHostDiagnostics.ts index 0fa5eeaa480..38b7850a6c6 100644 --- a/src/vs/workbench/api/node/extHostDiagnostics.ts +++ b/src/vs/workbench/api/node/extHostDiagnostics.ts @@ -8,6 +8,7 @@ import {localize} from 'vs/nls'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {IMarkerData} from 'vs/platform/markers/common/markers'; import URI from 'vs/base/common/uri'; +import {compare} from 'vs/base/common/strings'; import Severity from 'vs/base/common/severity'; import * as vscode from 'vscode'; import {MainContext, MainThreadDiagnosticsShape, ExtHostDiagnosticsShape} from './extHost.protocol'; @@ -72,20 +73,23 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { } else if (Array.isArray(first)) { // update many rows toSync = []; - for (let entry of first) { - let [uri, diagnostics] = entry; - toSync.push(uri); + let lastUri: vscode.Uri; + for (const entry of first.slice(0).sort(DiagnosticCollection._compareTuplesByUri)) { + const [uri, diagnostics] = entry; + if (!lastUri || uri.toString() !== lastUri.toString()) { + if (lastUri && this._data[lastUri.toString()].length === 0) { + delete this._data[lastUri.toString()]; + } + lastUri = uri; + toSync.push(uri); + this._data[uri.toString()] = []; + } + if (!diagnostics) { // [Uri, undefined] means clear this - delete this._data[uri.toString()]; + this._data[uri.toString()].length = 0; } else { - // set or merge diagnostics - let existing = this._data[uri.toString()]; - if (existing) { - existing.push(...diagnostics); - } else { - this._data[uri.toString()] = diagnostics; - } + this._data[uri.toString()].push(...diagnostics); } } } @@ -196,6 +200,10 @@ export class DiagnosticCollection implements vscode.DiagnosticCollection { default: return Severity.Error; } } + + private static _compareTuplesByUri(a: [vscode.Uri, vscode.Diagnostic[]], b: [vscode.Uri, vscode.Diagnostic[]]): number { + return compare(a[0].toString(), b[0].toString()); + } } export class ExtHostDiagnostics extends ExtHostDiagnosticsShape { diff --git a/src/vs/workbench/api/node/extHostEditors.ts b/src/vs/workbench/api/node/extHostEditors.ts index 4e01b6ebd03..8919fce8a60 100644 --- a/src/vs/workbench/api/node/extHostEditors.ts +++ b/src/vs/workbench/api/node/extHostEditors.ts @@ -181,6 +181,8 @@ export interface IEditData { documentVersionId: number; edits: ITextEditOperation[]; setEndOfLine: EndOfLine; + undoStopBefore:boolean; + undoStopAfter:boolean; } export class TextEditorEdit { @@ -188,18 +190,24 @@ export class TextEditorEdit { private _documentVersionId: number; private _collectedEdits: ITextEditOperation[]; private _setEndOfLine: EndOfLine; + private _undoStopBefore: boolean; + private _undoStopAfter: boolean; - constructor(document: vscode.TextDocument) { + constructor(document: vscode.TextDocument, options:{ undoStopBefore: boolean; undoStopAfter: boolean; }) { this._documentVersionId = document.version; this._collectedEdits = []; this._setEndOfLine = 0; + this._undoStopBefore = options.undoStopBefore; + this._undoStopAfter = options.undoStopAfter; } finalize(): IEditData { return { documentVersionId: this._documentVersionId, edits: this._collectedEdits, - setEndOfLine: this._setEndOfLine + setEndOfLine: this._setEndOfLine, + undoStopBefore: this._undoStopBefore, + undoStopAfter: this._undoStopAfter }; } @@ -402,8 +410,8 @@ class ExtHostTextEditor implements vscode.TextEditor { // ---- editing - edit(callback: (edit: TextEditorEdit) => void): Thenable { - let edit = new TextEditorEdit(this._documentData.document); + edit(callback: (edit: TextEditorEdit) => void, options:{ undoStopBefore: boolean; undoStopAfter: boolean; } = { undoStopBefore: true, undoStopAfter: true }): Thenable { + let edit = new TextEditorEdit(this._documentData.document, options); callback(edit); return this._applyEdit(edit); } @@ -420,7 +428,11 @@ class ExtHostTextEditor implements vscode.TextEditor { }; }); - return this._proxy.$tryApplyEdits(this._id, editData.documentVersionId, edits, editData.setEndOfLine); + return this._proxy.$tryApplyEdits(this._id, editData.documentVersionId, edits, { + setEndOfLine: editData.setEndOfLine, + undoStopBefore: editData.undoStopBefore, + undoStopAfter: editData.undoStopAfter + }); } // ---- util diff --git a/src/vs/workbench/api/node/extHostHeapService.ts b/src/vs/workbench/api/node/extHostHeapService.ts new file mode 100644 index 00000000000..47a3d8acdbc --- /dev/null +++ b/src/vs/workbench/api/node/extHostHeapService.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {ExtHostHeapServiceShape} from './extHost.protocol'; + +export class ExtHostHeapService extends ExtHostHeapServiceShape { + + private static _idPool = 0; + + private _data: { [n: number]: any } = Object.create(null); + private _callbacks: { [n: number]: Function } = Object.create(null); + + keep(obj:any, callback?:() => any): number { + const id = ExtHostHeapService._idPool++; + this._data[id] = obj; + if (typeof callback === 'function') { + this._callbacks[id] = callback; + } + return id; + } + + delete(id: number): boolean { + delete this._callbacks[id]; + return this._data[id]; + } + + get(id: number): T { + return this._data[id]; + } + + $onGarbageCollection(ids: number[]): void { + for (const id of ids) { + setTimeout(this._callbacks[id]); + this.delete(id); + } + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 05062364ca2..02b6c218188 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -10,15 +10,16 @@ import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import * as vscode from 'vscode'; import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; -import {Range, Disposable, SignatureHelp, CompletionList} from 'vs/workbench/api/node/extHostTypes'; +import {Range, Disposable, CompletionList, CompletionItem} from 'vs/workbench/api/node/extHostTypes'; import {IPosition, IRange, ISingleEditOperation} from 'vs/editor/common/editorCommon'; import * as modes from 'vs/editor/common/modes'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; import {IWorkspaceSymbolProvider, IWorkspaceSymbol} from 'vs/workbench/parts/search/common/search'; import {asWinJsPromise, ShallowCancelThenPromise} from 'vs/base/common/async'; -import {MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape} from './extHost.protocol'; +import {MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier} from './extHost.protocol'; import {regExpLeadsToEndlessLoop} from 'vs/base/common/strings'; // --- adapter @@ -283,7 +284,7 @@ class QuickFixAdapter { private _diagnostics: ExtHostDiagnostics; private _provider: vscode.CodeActionProvider; - private _cachedCommands: IDisposable[] = []; + private _cachedCommands: IDisposable[][] = []; constructor(documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) { this._documents = documents; @@ -308,7 +309,15 @@ class QuickFixAdapter { } }); - this._cachedCommands = dispose(this._cachedCommands); + // we cache the last 10 commands that might have been + // created during type conversion. when as have more + // than 10 we drop the first three + const cachedCommands: IDisposable[] = []; + if (this._cachedCommands.push(cachedCommands) > 10) { + dispose(...this._cachedCommands.shift()); + dispose(...this._cachedCommands.shift()); + dispose(...this._cachedCommands.shift()); + } return asWinJsPromise(token => this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token)).then(commands => { if (!Array.isArray(commands)) { @@ -316,7 +325,7 @@ class QuickFixAdapter { } return commands.map((command, i) => { return { - command: TypeConverters.Command.from(command, this._cachedCommands), + command: TypeConverters.Command.from(command, cachedCommands), score: i }; }); @@ -488,18 +497,17 @@ class RenameAdapter { } } -interface ISuggestion2 extends modes.ISuggestion { - id: string; -} class SuggestAdapter { private _documents: ExtHostDocuments; + private _heapService: ExtHostHeapService; private _provider: vscode.CompletionItemProvider; - private _cache: { [key: string]: { list: CompletionList; disposables: IDisposable[]; } } = Object.create(null); + private _disposables: { [id: number]: IDisposable[] } = []; - constructor(documents: ExtHostDocuments, provider: vscode.CompletionItemProvider) { + constructor(documents: ExtHostDocuments, heapMonitor: ExtHostHeapService, provider: vscode.CompletionItemProvider) { this._documents = documents; + this._heapService = heapMonitor; this._provider = provider; } @@ -508,12 +516,6 @@ class SuggestAdapter { const doc = this._documents.getDocumentData(resource).document; const pos = TypeConverters.toPosition(position); - const key = resource.toString(); - if (this._cache[key]) { - dispose(this._cache[key].disposables); - delete this._cache[key]; - } - return asWinJsPromise(token => this._provider.provideCompletionItems(doc, pos, token)).then(value => { const result: modes.ISuggestResult = { @@ -525,26 +527,27 @@ class SuggestAdapter { const wordRangeBeforePos = (doc.getWordRangeAtPosition(pos) || new Range(pos, pos)) .with({ end: pos }); - const disposables: IDisposable[] = []; let list: CompletionList; - if (Array.isArray(value)) { - list = new CompletionList(value); - } else if (value instanceof CompletionList) { - list = value; - result.incomplete = list.isIncomplete; - } else if (!value) { + if (!value) { // undefined and null are valid results return; + + } else if (Array.isArray(value)) { + list = new CompletionList(value); + } else { - // warn about everything else - console.warn('INVALID result from completion provider. expected CompletionItem-array or CompletionList but got:', value); - return; + list = value; + result.incomplete = list.isIncomplete; } for (let i = 0; i < list.items.length; i++) { const item = list.items[i]; - const suggestion = TypeConverters.Suggest.from(item, disposables); + const disposables: IDisposable[] = []; + const suggestion = TypeConverters.Suggest.from(item, disposables); + const id = this._heapService.keep(item, () => dispose(this._disposables[id])); + this._disposables[id] = disposables; + ObjectIdentifier.mixin(suggestion, id); if (item.textEdit) { @@ -568,32 +571,27 @@ class SuggestAdapter { suggestion.overwriteAfter = 0; } - // assign identifier to suggestion - suggestion.id = String(i); - // store suggestion result.suggestions.push(suggestion); } - // cache for details call - this._cache[key] = { list, disposables }; - return result; }); } resolveCompletionItem(resource: URI, position: IPosition, suggestion: modes.ISuggestion): TPromise { - if (typeof this._provider.resolveCompletionItem !== 'function' || !this._cache[resource.toString()]) { + + if (typeof this._provider.resolveCompletionItem !== 'function') { return TPromise.as(suggestion); } - const {list, disposables} = this._cache[resource.toString()]; - const item = list.items[Number(( suggestion).id)]; + const id = ObjectIdentifier.get(suggestion); + const item = this._heapService.get(id); if (!item) { return TPromise.as(suggestion); } return asWinJsPromise(token => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => { - return TypeConverters.Suggest.from(resolvedItem || item, disposables); + return TypeConverters.Suggest.from(resolvedItem || item, this._disposables[id]); }); } } @@ -614,7 +612,7 @@ class SignatureHelpAdapter { const pos = TypeConverters.toPosition(position); return asWinJsPromise(token => this._provider.provideSignatureHelp(doc, pos, token)).then(value => { - if (value instanceof SignatureHelp) { + if (value) { return TypeConverters.SignatureHelp.from(value); } }); @@ -664,6 +662,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { private _proxy: MainThreadLanguageFeaturesShape; private _documents: ExtHostDocuments; private _commands: ExtHostCommands; + private _heapMonitor: ExtHostHeapService; private _diagnostics: ExtHostDiagnostics; private _adapter: { [handle: number]: Adapter } = Object.create(null); @@ -671,12 +670,14 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { threadService: IThreadService, documents: ExtHostDocuments, commands: ExtHostCommands, + heapMonitor: ExtHostHeapService, diagnostics: ExtHostDiagnostics ) { super(); this._proxy = threadService.get(MainContext.MainThreadLanguageFeatures); this._documents = documents; this._commands = commands; + this._heapMonitor = heapMonitor; this._diagnostics = diagnostics; } @@ -863,7 +864,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape { registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable { const handle = this._nextHandle(); - this._adapter[handle] = new SuggestAdapter(this._documents, provider); + this._adapter[handle] = new SuggestAdapter(this._documents, this._heapMonitor, provider); this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters); return this._createDisposable(handle); } diff --git a/src/vs/workbench/api/node/extHostTerminalService.ts b/src/vs/workbench/api/node/extHostTerminalService.ts index 43216c0c005..e1dead17d1b 100644 --- a/src/vs/workbench/api/node/extHostTerminalService.ts +++ b/src/vs/workbench/api/node/extHostTerminalService.ts @@ -10,53 +10,67 @@ import {MainContext, MainThreadTerminalServiceShape} from './extHost.protocol'; export class ExtHostTerminal implements vscode.Terminal { - public name: string; + public _name: string; private _id: number; private _proxy: MainThreadTerminalServiceShape; private _disposed: boolean; + private _queuedRequests: ApiRequest[] = []; constructor(proxy: MainThreadTerminalServiceShape, id: number, name?: string) { - this.name = name; + this._name = name; this._proxy = proxy; this._proxy.$createTerminal(name).then((terminalId) => { this._id = terminalId; + this._queuedRequests.forEach((r) => { + r.run(this._proxy, this._id); + }); }); } + public get name(): string { + this._checkDisposed(); + return this._name; + } + public sendText(text: string, addNewLine: boolean = true): void { - this.checkId(); - this.checkDisposed(); - this._proxy.$sendText(this._id, text, addNewLine); + this._checkDisposed(); + let request: ApiRequest = new ApiRequest(this._proxy.$sendText, [text, addNewLine]); + if (!this._id) { + this._queuedRequests.push(request); + return; + } + request.run(this._proxy, this._id); } public show(preserveFocus: boolean): void { - this.checkId(); - this.checkDisposed(); - this._proxy.$show(this._id, preserveFocus); + this._checkDisposed(); + let request: ApiRequest = new ApiRequest(this._proxy.$show, [preserveFocus]); + if (!this._id) { + this._queuedRequests.push(request); + return; + } + request.run(this._proxy, this._id); } public hide(): void { - this.checkId(); - this.checkDisposed(); - this._proxy.$hide(this._id); + this._checkDisposed(); + let request: ApiRequest = new ApiRequest(this._proxy.$hide, []); + if (!this._id) { + this._queuedRequests.push(request); + return; + } + request.run(this._proxy, this._id); } public dispose(): void { - this.checkId(); if (!this._disposed) { this._disposed = true; this._proxy.$dispose(this._id); } } - private checkId() { - if (!this._id) { - throw new Error('Terminal has not been initialized yet'); - } - } - - private checkDisposed() { + private _checkDisposed() { if (this._disposed) { throw new Error('Terminal has already been disposed'); } @@ -75,3 +89,17 @@ export class ExtHostTerminalService { return new ExtHostTerminal(this._proxy, -1, name); } } + +class ApiRequest { + private _callback: (...args: any[]) => void; + private _args: any[]; + + constructor(callback: (...args: any[]) => void, args: any[]) { + this._callback = callback; + this._args = args; + } + + public run(proxy: MainThreadTerminalServiceShape, id: number) { + this._callback.apply(proxy, [id].concat(this._args)); + } +} diff --git a/src/vs/workbench/api/node/extHostTypeConverters.ts b/src/vs/workbench/api/node/extHostTypeConverters.ts index c1c8f396ce5..4751ac893ec 100644 --- a/src/vs/workbench/api/node/extHostTypeConverters.ts +++ b/src/vs/workbench/api/node/extHostTypeConverters.ts @@ -177,10 +177,12 @@ export const TextEdit = { continue; } + const editOffset = document.offsetAt(edit.range.start); + for (let j = 0; j < changes.length; j++) { const {originalStart, originalLength, modifiedStart, modifiedLength} = changes[j]; - const start = fromPosition( document.positionAt(originalStart)); - const end = fromPosition( document.positionAt(originalStart + originalLength)); + const start = fromPosition( document.positionAt(editOffset + originalStart)); + const end = fromPosition( document.positionAt(editOffset + originalStart + originalLength)); result.push({ text: modified.substr(modifiedStart, modifiedLength), diff --git a/src/vs/workbench/api/node/extHostWorkspace.ts b/src/vs/workbench/api/node/extHostWorkspace.ts index 11e5fdeec1b..c7e98adbe7e 100644 --- a/src/vs/workbench/api/node/extHostWorkspace.ts +++ b/src/vs/workbench/api/node/extHostWorkspace.ts @@ -39,7 +39,7 @@ export class ExtHostWorkspace { } if (isEqualOrParent(path, this._workspacePath)) { - return relative(this._workspacePath, path); + return relative(this._workspacePath, path) || path; } return path; diff --git a/src/vs/workbench/api/node/mainThreadCommands.ts b/src/vs/workbench/api/node/mainThreadCommands.ts index 54dd71eec48..edd01819342 100644 --- a/src/vs/workbench/api/node/mainThreadCommands.ts +++ b/src/vs/workbench/api/node/mainThreadCommands.ts @@ -6,11 +6,13 @@ import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; import {ICommandService, CommandsRegistry, ICommandHandlerDescription} from 'vs/platform/commands/common/commands'; +import {IDisposable} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {ExtHostContext, MainThreadCommandsShape, ExtHostCommandsShape} from './extHost.protocol'; export class MainThreadCommands extends MainThreadCommandsShape { + private _disposables: { [id: string]: IDisposable } = Object.create(null); private _proxy: ExtHostCommandsShape; constructor( @@ -21,11 +23,25 @@ export class MainThreadCommands extends MainThreadCommandsShape { this._proxy = this._threadService.get(ExtHostContext.ExtHostCommands); } + dispose() { + for (let id in this._disposables) { + this._disposables[id].dispose(); + } + } + $registerCommand(id: string): TPromise { - CommandsRegistry.registerCommand(id, (accessor, ...args) => this._proxy.$executeContributedCommand(id, ...args)); + this._disposables[id] = CommandsRegistry.registerCommand(id, (accessor, ...args) => this._proxy.$executeContributedCommand(id, ...args)); return undefined; } + $unregisterCommand(id: string): TPromise { + if (this._disposables[id]) { + this._disposables[id].dispose(); + delete this._disposables[id]; + } + return; + } + $executeCommand(id: string, args: any[]): Thenable { return this._commandService.executeCommand(id, ...args); } diff --git a/src/vs/workbench/api/node/mainThreadConfiguration.ts b/src/vs/workbench/api/node/mainThreadConfiguration.ts index acaa75a4124..03089d095a9 100644 --- a/src/vs/workbench/api/node/mainThreadConfiguration.ts +++ b/src/vs/workbench/api/node/mainThreadConfiguration.ts @@ -7,7 +7,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {IWorkbenchConfigurationService} from 'vs/workbench/services/configuration/common/configuration'; +import {IWorkspaceConfigurationService} from 'vs/workbench/services/configuration/common/configuration'; import {IConfigurationEditingService, ConfigurationTarget} from 'vs/workbench/services/configuration/common/configurationEditing'; import {MainThreadConfigurationShape, ExtHostContext} from './extHost.protocol'; @@ -18,7 +18,7 @@ export class MainThreadConfiguration extends MainThreadConfigurationShape { constructor( @IConfigurationEditingService configurationEditingService: IConfigurationEditingService, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, @IThreadService threadService: IThreadService ) { super(); diff --git a/src/vs/workbench/api/node/mainThreadDocuments.ts b/src/vs/workbench/api/node/mainThreadDocuments.ts index 5289778d638..351cffb8297 100644 --- a/src/vs/workbench/api/node/mainThreadDocuments.ts +++ b/src/vs/workbench/api/node/mainThreadDocuments.ts @@ -4,7 +4,8 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import {toErrorMessage, onUnexpectedError} from 'vs/base/common/errors'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {EmitterEvent} from 'vs/base/common/eventEmitter'; import {IModelService} from 'vs/editor/common/services/modelService'; import * as editorCommon from 'vs/editor/common/editorCommon'; @@ -194,7 +195,7 @@ export class MainThreadDocuments extends MainThreadDocumentsShape { // don't create a new file ontop of an existing file return TPromise.wrapError('file already exists on disk'); }, err => { - let input = this._untitledEditorService.createOrGet(asFileUri); // using file-uri makes it show in 'Working Files' section + let input = this._untitledEditorService.createOrGet(asFileUri); return input.resolve(true).then(model => { if (input.getResource().toString() !== uri.toString()) { throw new Error(`expected URI ${uri.toString() } BUT GOT ${input.getResource().toString() }`); diff --git a/src/vs/workbench/api/node/mainThreadEditors.ts b/src/vs/workbench/api/node/mainThreadEditors.ts index d8972be076b..669620f53f9 100644 --- a/src/vs/workbench/api/node/mainThreadEditors.ts +++ b/src/vs/workbench/api/node/mainThreadEditors.ts @@ -8,14 +8,13 @@ import URI from 'vs/base/common/uri'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {TPromise} from 'vs/base/common/winjs.base'; import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; -import {EndOfLine} from './extHostTypes'; import {ISingleEditOperation, ISelection, IRange, IEditor, EditorType, ICommonCodeEditor, ICommonDiffEditor, IDecorationRenderOptions, IDecorationOptions} from 'vs/editor/common/editorCommon'; import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {Position as EditorPosition} from 'vs/platform/editor/common/editor'; import {IModelService} from 'vs/editor/common/services/modelService'; -import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, ITextEditorConfigurationUpdate} from 'vs/workbench/api/node/mainThreadEditorsTracker'; +import {MainThreadEditorsTracker, TextEditorRevealType, MainThreadTextEditor, IApplyEditsOptions, ITextEditorConfigurationUpdate} from 'vs/workbench/api/node/mainThreadEditorsTracker'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IEventService} from 'vs/platform/event/common/event'; import {equals as arrayEquals} from 'vs/base/common/arrays'; @@ -282,11 +281,11 @@ export class MainThreadEditors extends MainThreadEditorsShape { return TPromise.as(null); } - $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], setEndOfLine:EndOfLine): TPromise { + $tryApplyEdits(id: string, modelVersionId: number, edits: ISingleEditOperation[], opts:IApplyEditsOptions): TPromise { if (!this._textEditorsMap[id]) { return TPromise.wrapError('TextEditor disposed'); } - return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, setEndOfLine)); + return TPromise.as(this._textEditorsMap[id].applyEdits(modelVersionId, edits, opts)); } $registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void { diff --git a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts index af7668e2689..c466544950f 100644 --- a/src/vs/workbench/api/node/mainThreadEditorsTracker.ts +++ b/src/vs/workbench/api/node/mainThreadEditorsTracker.ts @@ -56,6 +56,12 @@ export enum TextEditorRevealType { InCenterIfOutsideViewport = 2 } +export interface IApplyEditsOptions { + undoStopBefore: boolean; + undoStopAfter: boolean; + setEndOfLine: EndOfLine; +} + /** * Text Editor that is permanently bound to the same model. * It can be bound or not to a CodeEditor. @@ -314,7 +320,7 @@ export class MainThreadTextEditor { return editor.getControl() === this._codeEditor; } - public applyEdits(versionIdCheck:number, edits:EditorCommon.ISingleEditOperation[], setEndOfLine:EndOfLine): boolean { + public applyEdits(versionIdCheck:number, edits:EditorCommon.ISingleEditOperation[], opts:IApplyEditsOptions): boolean { if (this._model.getVersionId() !== versionIdCheck) { console.warn('Model has changed in the meantime!'); // throw new Error('Model has changed in the meantime!'); @@ -323,9 +329,9 @@ export class MainThreadTextEditor { } if (this._codeEditor) { - if (setEndOfLine === EndOfLine.CRLF) { + if (opts.setEndOfLine === EndOfLine.CRLF) { this._model.setEOL(EditorCommon.EndOfLineSequence.CRLF); - } else if (setEndOfLine === EndOfLine.LF) { + } else if (opts.setEndOfLine === EndOfLine.LF) { this._model.setEOL(EditorCommon.EndOfLineSequence.LF); } @@ -338,9 +344,13 @@ export class MainThreadTextEditor { }; }); - this._codeEditor.pushUndoStop(); + if (opts.undoStopBefore) { + this._codeEditor.pushUndoStop(); + } this._codeEditor.executeEdits('MainThreadTextEditor', transformedEdits); - this._codeEditor.pushUndoStop(); + if (opts.undoStopAfter) { + this._codeEditor.pushUndoStop(); + } return true; } diff --git a/src/vs/workbench/api/node/mainThreadHeapService.ts b/src/vs/workbench/api/node/mainThreadHeapService.ts new file mode 100644 index 00000000000..4f3f6f53a26 --- /dev/null +++ b/src/vs/workbench/api/node/mainThreadHeapService.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {IDisposable} from 'vs/base/common/lifecycle'; +import {IThreadService} from 'vs/workbench/services/thread/common/threadService'; +import {ExtHostContext} from './extHost.protocol'; +import {onDidGarbageCollectSignals, consumeSignals} from 'gc-signals'; + +export class MainThreadHeapService { + + private _subscription: IDisposable; + private _consumeHandle: number; + + constructor( @IThreadService threadService: IThreadService) { + const proxy = threadService.get(ExtHostContext.ExtHostHeapService); + + this._subscription = onDidGarbageCollectSignals(ids => { + proxy.$onGarbageCollection(ids); + }); + + this._consumeHandle = setInterval(consumeSignals, 15 * 1000); + } + + dispose() { + clearInterval(this._consumeHandle); + this._subscription.dispose(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts index 4df3d0f6dc9..ca3e536223a 100644 --- a/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/node/mainThreadLanguageFeatures.ts @@ -15,8 +15,9 @@ import {wireCancellationToken} from 'vs/base/common/async'; import {CancellationToken} from 'vs/base/common/cancellation'; import {Position as EditorPosition} from 'vs/editor/common/core/position'; import {Range as EditorRange} from 'vs/editor/common/core/range'; -import {ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape} from './extHost.protocol'; +import {ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier} from './extHost.protocol'; import {LanguageConfigurationRegistry, LanguageConfiguration} from 'vs/editor/common/modes/languageConfigurationRegistry'; +import {trackGarbageCollection} from 'gc-signals'; export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape { @@ -180,7 +181,12 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape this._registrations[handle] = modes.SuggestRegistry.register(selector, { triggerCharacters: triggerCharacters, provideCompletionItems: (model:IReadOnlyModel, position:EditorPosition, token:CancellationToken): Thenable => { - return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)); + return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)).then(result => { + for (const suggestion of result.suggestions) { + trackGarbageCollection(suggestion, ObjectIdentifier.get(suggestion)); + } + return result; + }); }, resolveCompletionItem: (model:IReadOnlyModel, position:EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable => { return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion)); diff --git a/src/vs/workbench/api/node/mainThreadMessageService.ts b/src/vs/workbench/api/node/mainThreadMessageService.ts index f00ee755f90..b3dcf3a23ae 100644 --- a/src/vs/workbench/api/node/mainThreadMessageService.ts +++ b/src/vs/workbench/api/node/mainThreadMessageService.ts @@ -49,7 +49,7 @@ export class MainThreadMessageService extends MainThreadMessageServiceShape { }); if (!hasCloseAffordance) { - actions.unshift(new MessageItemAction('__close', nls.localize('close', "Close"), undefined)); + actions.push(new MessageItemAction('__close', nls.localize('close', "Close"), undefined)); } messageHide = this._messageService.show(severity, { diff --git a/src/vs/workbench/api/node/mainThreadTerminalService.ts b/src/vs/workbench/api/node/mainThreadTerminalService.ts index 742510fb4c9..ae6d299c027 100644 --- a/src/vs/workbench/api/node/mainThreadTerminalService.ts +++ b/src/vs/workbench/api/node/mainThreadTerminalService.ts @@ -26,6 +26,10 @@ export class MainThreadTerminalService extends MainThreadTerminalServiceShape { public $show(terminalId: number, preserveFocus: boolean): void { this._terminalService.show(!preserveFocus).then((terminalPanel) => { this._terminalService.setActiveTerminalById(terminalId); + if (!preserveFocus) { + // If the panel was already showing an explicit focus call is necessary here. + terminalPanel.focus(); + } }); } diff --git a/src/vs/workbench/browser/actions/openSettings.ts b/src/vs/workbench/browser/actions/openSettings.ts index a8c32bab5e0..dd151ac7453 100644 --- a/src/vs/workbench/browser/actions/openSettings.ts +++ b/src/vs/workbench/browser/actions/openSettings.ts @@ -17,7 +17,7 @@ import {StringEditorInput} from 'vs/workbench/common/editor/stringEditorInput'; import {getDefaultValuesContent} from 'vs/platform/configuration/common/model'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IWorkbenchConfigurationService, WORKSPACE_CONFIG_DEFAULT_PATH} from 'vs/workbench/services/configuration/common/configuration'; +import {IWorkspaceConfigurationService, WORKSPACE_CONFIG_DEFAULT_PATH} from 'vs/workbench/services/configuration/common/configuration'; import {Position} from 'vs/platform/editor/common/editor'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; @@ -45,7 +45,7 @@ export class BaseTwoEditorsAction extends Action { @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, @IFileService protected fileService: IFileService, - @IWorkbenchConfigurationService protected configurationService: IWorkbenchConfigurationService, + @IWorkspaceConfigurationService protected configurationService: IWorkspaceConfigurationService, @IMessageService protected messageService: IMessageService, @IWorkspaceContextService protected contextService: IWorkspaceContextService, @IKeybindingService protected keybindingService: IKeybindingService, @@ -94,7 +94,7 @@ export class BaseOpenSettingsAction extends BaseTwoEditorsAction { @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, @IFileService fileService: IFileService, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IKeybindingService keybindingService: IKeybindingService, @@ -130,7 +130,7 @@ export class OpenGlobalSettingsAction extends BaseOpenSettingsAction { @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, @IFileService fileService: IFileService, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IKeybindingService keybindingService: IKeybindingService, @@ -148,19 +148,19 @@ export class OpenGlobalSettingsAction extends BaseOpenSettingsAction { this.messageService.show(Severity.Info, { message: nls.localize('workspaceHasSettings', "The currently opened folder contains workspace settings that may override user settings"), actions: [ - CloseAction, - new Action('neverShowAgain', nls.localize('neverShowAgain', "Don't show again"), null, true, () => { - this.storageService.store(OpenGlobalSettingsAction.SETTINGS_INFO_IGNORE_KEY, true, StorageScope.WORKSPACE); - - return TPromise.as(true); - }), new Action('open.workspaceSettings', nls.localize('openWorkspaceSettings', "Open Workspace Settings"), null, true, () => { let editorCount = this.editorService.getVisibleEditors().length; return this.editorService.createInput({ resource: this.contextService.toResource(WORKSPACE_CONFIG_DEFAULT_PATH) }).then((typedInput) => { return this.editorService.openEditor(typedInput, { pinned: true }, editorCount === 2 ? Position.RIGHT : editorCount === 1 ? Position.CENTER : void 0); }); - }) + }), + new Action('neverShowAgain', nls.localize('neverShowAgain', "Don't show again"), null, true, () => { + this.storageService.store(OpenGlobalSettingsAction.SETTINGS_INFO_IGNORE_KEY, true, StorageScope.WORKSPACE); + + return TPromise.as(true); + }), + CloseAction ] }); } @@ -183,7 +183,7 @@ export class OpenGlobalKeybindingsAction extends BaseTwoEditorsAction { @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IEditorGroupService editorGroupService: IEditorGroupService, @IFileService fileService: IFileService, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, + @IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService, @IMessageService messageService: IMessageService, @IWorkspaceContextService contextService: IWorkspaceContextService, @IKeybindingService keybindingService: IKeybindingService, @@ -225,7 +225,7 @@ export class OpenWorkspaceSettingsAction extends BaseOpenSettingsAction { class DefaultSettingsInput extends StringEditorInput { private static INSTANCE: DefaultSettingsInput; - public static getInstance(instantiationService: IInstantiationService, configurationService: IWorkbenchConfigurationService): DefaultSettingsInput { + public static getInstance(instantiationService: IInstantiationService, configurationService: IWorkspaceConfigurationService): DefaultSettingsInput { if (!DefaultSettingsInput.INSTANCE) { let editorConfig = configurationService.getConfiguration(); let defaults = getDefaultValuesContent(editorConfig.editor.insertSpaces ? strings.repeat(' ', editorConfig.editor.tabSize) : '\t'); diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index a3b87d3bb6c..e5c6aa533e6 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -12,7 +12,7 @@ import {Builder, $} from 'vs/base/browser/builder'; import {Action} from 'vs/base/common/actions'; import errors = require('vs/base/common/errors'); import {ActionsOrientation, ActionBar, IActionItem} from 'vs/base/browser/ui/actionbar/actionbar'; -import {/*CONTEXT,*/ ToolBar} from 'vs/base/browser/ui/toolbar/toolbar'; +import {ToolBar} from 'vs/base/browser/ui/toolbar/toolbar'; import {Registry} from 'vs/platform/platform'; import {IViewlet} from 'vs/workbench/common/viewlet'; import {ViewletDescriptor, ViewletRegistry, Extensions as ViewletExtensions} from 'vs/workbench/browser/viewlet'; @@ -26,15 +26,10 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IMessageService} from 'vs/platform/message/common/message'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; -// import {Scope, IActionBarRegistry, Extensions as ActionBarExtensions, prepareActions} from 'vs/workbench/browser/actionBarRegistry'; -// import Severity from 'vs/base/common/severity'; -// import {IAction} from 'vs/base/common/actions'; -// import events = require('vs/base/common/events'); export class ActivitybarPart extends Part implements IActivityService { public _serviceBrand: any; private viewletSwitcherBar: ActionBar; - // private globalViewletSwitcherBar: ActionBar; private globalToolBar: ToolBar; private activityActionItems: { [actionId: string]: IActionItem; }; private viewletIdToActions: { [viewletId: string]: ActivityAction; }; @@ -105,9 +100,6 @@ export class ActivitybarPart extends Part implements IActivityService { // Top Actionbar with action items for each viewlet action this.createViewletSwitcher($result.clone()); - // Bottom Toolbar with action items for global actions - // this.createGlobalToolBarArea($result.clone()); // not used currently - return $result; } @@ -121,14 +113,6 @@ export class ActivitybarPart extends Part implements IActivityService { }); this.viewletSwitcherBar.getContainer().addClass('position-top'); - // Global viewlet switcher is right below - // this.globalViewletSwitcherBar = new ActionBar(div, { - // actionItemProvider: (action: Action) => this.activityActionItems[action.id], - // orientation: ActionsOrientation.VERTICAL, - // ariaLabel: nls.localize('globalActivityBarAriaLabel', "Active Global View Switcher") - // }); - // this.globalViewletSwitcherBar.getContainer().addClass('position-bottom'); - // Build Viewlet Actions in correct order const activeViewlet = this.viewletService.getActiveViewlet(); const registry = (Registry.as(ViewletExtensions.Viewlets)); @@ -160,86 +144,9 @@ export class ActivitybarPart extends Part implements IActivityService { .filter(v => !v.isGlobal) .sort((v1, v2) => v1.order - v2.order) .map(toAction) - , actionOptions); - - // Add to viewlet switcher - // this.globalViewletSwitcherBar.push(allViewletActions - // .filter(v => v.isGlobal) - // .sort((v1, v2) => v1.order - v2.order) - // .map(toAction), - // actionOptions); + , actionOptions); } - // private createGlobalToolBarArea(div: Builder): void { - - // // Global action bar is on the bottom - // this.globalToolBar = new ToolBar(div.getHTMLElement(), this.contextMenuService, { - // actionItemProvider: (action: Action) => this.activityActionItems[action.id], - // orientation: ActionsOrientation.VERTICAL - // }); - // this.globalToolBar.getContainer().addClass('global'); - - // this.globalToolBar.actionRunner.addListener2(events.EventType.RUN, (e: any) => { - - // // Check for Error - // if (e.error && !errors.isPromiseCanceledError(e.error)) { - // this.messageService.show(Severity.Error, e.error); - // } - - // // Log in telemetry - // if (this.telemetryService) { - // this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'activityBar' }); - // } - // }); - - // // Build Global Actions in correct order - // let primaryActions = this.getGlobalActions(true); - // let secondaryActions = this.getGlobalActions(false); - - // if (primaryActions.length + secondaryActions.length > 0) { - // this.globalToolBar.getContainer().addClass('position-bottom'); - // } - - // // Add to global action bar - // this.globalToolBar.setActions(prepareActions(primaryActions), prepareActions(secondaryActions))(); - // } - - // private getGlobalActions(primary: boolean): IAction[] { - // let actionBarRegistry = Registry.as(ActionBarExtensions.Actionbar); - - // // Collect actions from actionbar contributor - // let actions: IAction[]; - // if (primary) { - // actions = actionBarRegistry.getActionBarActionsForContext(Scope.GLOBAL, CONTEXT); - // } else { - // actions = actionBarRegistry.getSecondaryActionBarActionsForContext(Scope.GLOBAL, CONTEXT); - // } - - // return actions.map((action: Action) => { - // if (primary) { - // let keybinding: string = null; - // let keys = this.keybindingService.lookupKeybindings(action.id).map(k => this.keybindingService.getLabelFor(k)); - // if (keys && keys.length) { - // keybinding = keys[0]; - // } - - // let actionItem = actionBarRegistry.getActionItemForContext(Scope.GLOBAL, CONTEXT, action); - - // if (!actionItem) { - // actionItem = new ActivityActionItem(action, action.label, keybinding); - // } - - // if (actionItem instanceof ActivityActionItem) { - // ( actionItem).keybinding = keybinding; - // } - - // this.activityActionItems[action.id] = actionItem; - // } - - // return action; - // }); - // } - public dispose(): void { if (this.viewletSwitcherBar) { this.viewletSwitcherBar.dispose(); diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index e1d9fd61e40..68fdca90b1d 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -13,6 +13,7 @@ import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {Dimension, Builder, $} from 'vs/base/browser/builder'; import events = require('vs/base/common/events'); import strings = require('vs/base/common/strings'); +import {Emitter} from 'vs/base/common/event'; import types = require('vs/base/common/types'); import errors = require('vs/base/common/errors'); import {CONTEXT as ToolBarContext, ToolBar} from 'vs/base/browser/ui/toolbar/toolbar'; @@ -50,6 +51,8 @@ export abstract class CompositePart extends Part { private contentAreaSize: Dimension; private telemetryActionsListener: IDisposable; private currentCompositeOpenToken: string; + protected _onDidCompositeOpen = new Emitter(); + protected _onDidCompositeClose = new Emitter(); constructor( private messageService: IMessageService, @@ -146,6 +149,12 @@ export abstract class CompositePart extends Part { return composite; }); }); + }).then(composite => { + if (composite) { + this._onDidCompositeOpen.fire(composite); + } + + return composite; }); } @@ -177,7 +186,6 @@ export abstract class CompositePart extends Part { // Remove from Promises Cache since Loaded delete this.compositeLoaderPromises[id]; - progressService.dispose(); return composite; }); @@ -390,6 +398,7 @@ export abstract class CompositePart extends Part { // Empty Actions this.toolBar.setActions([])(); + this._onDidCompositeClose.fire(composite); return composite; }); diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index fb414b5d727..e952316483e 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -109,15 +109,15 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas // Render original let original = resolvedModel.originalModel; - this.renderInput(original.getName(), original.getResource(), original.getSize(), true); + this.renderInput(original.getName(), original.getResource(), original.getSize(), original.getETag(), true); // Render modified let modified = resolvedModel.modifiedModel; - this.renderInput(modified.getName(), modified.getResource(), modified.getSize(), false); + this.renderInput(modified.getName(), modified.getResource(), modified.getSize(), modified.getETag(), false); }); } - private renderInput(name: string, resource: URI, size: number, isOriginal: boolean): void { + private renderInput(name: string, resource: URI, size: number, etag: string, isOriginal: boolean): void { // Reset Sash to default 50/50 ratio if needed if (this.leftContainerWidth && this.dimension && this.leftContainerWidth !== this.dimension.width / 2) { @@ -130,7 +130,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas let container = isOriginal ? this.leftBinaryContainer : this.rightBinaryContainer; let scrollbar = isOriginal ? this.leftScrollbar : this.rightScrollbar; - ResourceViewer.show({ name, resource, size }, container, scrollbar); + ResourceViewer.show({ name, resource, size, etag }, container, scrollbar); } public clearInput(): void { diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index a561fa124a6..9aea2c66ca4 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -76,7 +76,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { // Render Input let model = resolvedModel; - ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize() }, this.binaryContainer, this.scrollbar); + ResourceViewer.show({ name: model.getName(), resource: model.getResource(), size: model.getSize(), etag: model.getETag() }, this.binaryContainer, this.scrollbar); return TPromise.as(null); }); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index 591f2936763..b2a06eb5352 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -905,12 +905,12 @@ export class ReopenClosedEditorAction extends Action { // Find an editor that was closed and is currently not opened in the group let lastClosedEditor = this.historyService.popLastClosedEditor(); - while (lastClosedEditor && stacks.activeGroup && stacks.activeGroup.indexOf(lastClosedEditor) >= 0) { + while (lastClosedEditor && stacks.activeGroup && stacks.activeGroup.indexOf(lastClosedEditor.editor) >= 0) { lastClosedEditor = this.historyService.popLastClosedEditor(); } if (lastClosedEditor) { - this.editorService.openEditor(lastClosedEditor, { pinned: true }); + this.editorService.openEditor(lastClosedEditor.editor, { pinned: true, index: lastClosedEditor.index }); } return TPromise.as(false); diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index 5eee2ec04bf..b206187d7d0 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -228,10 +228,10 @@ function handleCommandDeprecations(): void { messageService.show(Severity.Warning, { message: nls.localize('commandDeprecated', "Command **{0}** has been removed. You can use **{1}** instead", deprecatedCommandId, newCommandId), actions: [ - CloseAction, new Action('openKeybindings', nls.localize('openKeybindings', "Configure Keyboard Shortcuts"), null, true, () => { return commandService.executeCommand('workbench.action.openGlobalKeybindings'); - }) + }), + CloseAction ] }); }, diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 0b2403b3709..c4ff4265f2a 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -16,6 +16,7 @@ import strings = require('vs/base/common/strings'); import arrays = require('vs/base/common/arrays'); import types = require('vs/base/common/types'); import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {Scope as MementoScope} from 'vs/workbench/common/memento'; import {Scope} from 'vs/workbench/browser/actionBarRegistry'; import {Part} from 'vs/workbench/browser/part'; @@ -30,6 +31,7 @@ import {IPartService} from 'vs/workbench/services/part/common/partService'; import {Position, POSITIONS, Direction} from 'vs/platform/editor/common/editor'; import {IStorageService} from 'vs/platform/storage/common/storage'; import {IEventService} from 'vs/platform/event/common/event'; +import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; import {IMessageService, IMessageWithAction, Severity} from 'vs/platform/message/common/message'; @@ -438,7 +440,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Report error only if this was not us restoring previous error state if (this.partService.isCreated() && !errors.isPromiseCanceledError(e)) { - const errorMessage = nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), errors.toErrorMessage(e)); + const errorMessage = nls.localize('editorOpenError', "Unable to open '{0}': {1}.", input.getName(), toErrorMessage(e)); let error: any; if (e && (e).actions && (e).actions.length) { @@ -632,7 +634,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService // Thus we make the non-active one active and then close the others else { this.openEditor(except, null, this.stacks.positionOfGroup(group)).done(() => { - this.doCloseEditors(group, except, direction); + + // since the opening might have failed, we have to check again for the active editor + // being the expected one, otherwise we end up in an endless loop trying to open the + // editor + if (except.matches(group.activeEditor)) { + this.doCloseEditors(group, except, direction); + } }, errors.onUnexpectedError); } } @@ -652,7 +660,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } private doHandleDirty(identifier: EditorIdentifier, ignoreIfOpenedInOtherGroup?: boolean): TPromise { - if (!identifier || !identifier.editor || !identifier.editor.isDirty() || (ignoreIfOpenedInOtherGroup && this.stacks.count(identifier.editor) > 1 /* allow to close a dirty editor if it is opened in another group */)) { + if (!identifier || !identifier.editor || !identifier.editor.isDirty() || (ignoreIfOpenedInOtherGroup && this.countEditors(identifier.editor, true /* include diff editors */) > 1 /* allow to close a dirty editor if it is opened in another group */)) { return TPromise.as(false); // no veto } @@ -671,6 +679,15 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } } + private countEditors(editor: EditorInput, includeDiffEditors: boolean): number { + const editors = [editor]; + if (includeDiffEditors && editor instanceof DiffEditorInput) { + editors.push(editor.modifiedInput); + } + + return editors.reduce((prev, e) => prev += this.stacks.count(editor), 0); + } + public getStacksModel(): EditorStacksModel { return this.stacks; } @@ -954,6 +971,14 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService } } + let focusGroup = false; + const activeGroup = this.stacks.groupAt(activePosition); + if (!this.stacks.activeGroup || !activeGroup) { + focusGroup = true; // always focus group if this is the first group or we are about to open a new group + } else { + focusGroup = editors.some(e => !e.options || (!e.options.inactive && !e.options.preserveFocus)); // only focus if the editors to open are not opening as inactive or preserveFocus + } + // Open each input respecting the options. Since there can only be one active editor in each // position, we have to pick the first input from each position and add the others as inactive const promises: TPromise[] = []; @@ -981,8 +1006,10 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService return TPromise.join(promises).then(editors => { - // Ensure active position - this.focusGroup(activePosition); + // Adjust focus as needed + if (focusGroup) { + this.focusGroup(activePosition); + } // Update stacks model for remaining inactive editors [leftEditors, centerEditors, rightEditors].forEach((editors, index) => { diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index e7aaf6faeaf..ecc6d901344 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -8,7 +8,7 @@ import 'vs/css!./media/editorstatus'; import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; -import { $, append, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import {$, append, runAtThisOrScheduleAtNextAnimationFrame} from 'vs/base/browser/dom'; import strings = require('vs/base/common/strings'); import paths = require('vs/base/common/paths'); import types = require('vs/base/common/types'); @@ -59,7 +59,7 @@ function getTextModel(editorWidget: IEditor): ITextModel { let textModel: ITextModel; // Support for diff - let model = editorWidget.getModel(); + const model = editorWidget.getModel(); if (model && !!(model).modified) { textModel = (model).modified; } @@ -151,7 +151,7 @@ class State { } public update(update: StateDelta): StateChange { - let e = new StateChange(); + const e = new StateChange(); let somethingChanged = false; if (typeof update.selectionStatus !== 'undefined') { @@ -303,7 +303,7 @@ export class EditorStatus implements IStatusbarItem { } private updateState(update: StateDelta): void { - let changed = this.state.update(update); + const changed = this.state.update(update); if (!changed) { // Nothing really changed return; @@ -313,7 +313,7 @@ export class EditorStatus implements IStatusbarItem { this.toRender = changed; this.delayedRender = runAtThisOrScheduleAtNextAnimationFrame(() => { this.delayedRender = null; - let toRender = this.toRender; + const toRender = this.toRender; this.toRender = null; this._renderNow(toRender); }); @@ -391,14 +391,14 @@ export class EditorStatus implements IStatusbarItem { } else { if (info.charactersSelected) { return strings.format(nlsMultiSelectionRange, info.selections.length, info.charactersSelected); - } else { + } else if (info.selections.length > 0) { return strings.format(nlsMultiSelection, info.selections.length); } } } private onModeClick(): void { - let action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL); + const action = this.instantiationService.createInstance(ChangeModeAction, ChangeModeAction.ID, ChangeModeAction.LABEL); action.run().done(null, errors.onUnexpectedError); action.dispose(); @@ -415,14 +415,14 @@ export class EditorStatus implements IStatusbarItem { } private onEOLClick(): void { - let action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL); + const action = this.instantiationService.createInstance(ChangeEOLAction, ChangeEOLAction.ID, ChangeEOLAction.LABEL); action.run().done(null, errors.onUnexpectedError); action.dispose(); } private onEncodingClick(): void { - let action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL); + const action = this.instantiationService.createInstance(ChangeEncodingAction, ChangeEncodingAction.ID, ChangeEncodingAction.LABEL); action.run().done(null, errors.onUnexpectedError); action.dispose(); @@ -480,11 +480,11 @@ export class EditorStatus implements IStatusbarItem { // We only support text based editors if (editorWidget) { - let textModel = getTextModel(editorWidget); + const textModel = getTextModel(editorWidget); if (textModel) { // Compute mode if (!!(textModel).getMode) { - let mode = (textModel).getMode(); + const mode = (textModel).getMode(); if (mode) { info = { mode: this.modeService.getLanguageName(mode.getId()) }; } @@ -518,7 +518,7 @@ export class EditorStatus implements IStatusbarItem { } private onSelectionChange(editorWidget?: IEditor): void { - let info: IEditorSelectionStatus = {}; + const info: IEditorSelectionStatus = {}; // We only support text based editors if (editorWidget) { @@ -528,7 +528,7 @@ export class EditorStatus implements IStatusbarItem { // Compute selection length info.charactersSelected = 0; - let textModel = getTextModel(editorWidget); + const textModel = getTextModel(editorWidget); if (textModel) { info.selections.forEach((selection) => { info.charactersSelected += textModel.getValueLengthInRange(selection); @@ -537,9 +537,9 @@ export class EditorStatus implements IStatusbarItem { // Compute the visible column for one selection. This will properly handle tabs and their configured widths if (info.selections.length === 1) { - let visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()); + const visibleColumn = editorWidget.getVisibleColumnFromPosition(editorWidget.getPosition()); - let selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor + const selectionClone = info.selections[0].clone(); // do not modify the original position we got from the editor selectionClone.positionColumn = visibleColumn; info.selections[0] = selectionClone; @@ -550,11 +550,11 @@ export class EditorStatus implements IStatusbarItem { } private onEOLChange(editorWidget?: IEditor): void { - let info: StateDelta = { EOL: null }; + const info: StateDelta = { EOL: null }; - let codeEditor = getCodeEditor(editorWidget); + const codeEditor = getCodeEditor(editorWidget); if (codeEditor && !codeEditor.getConfiguration().readOnly) { - let codeEditorModel = codeEditor.getModel(); + const codeEditorModel = codeEditor.getModel(); if (codeEditorModel) { info.EOL = codeEditorModel.getEOL(); } @@ -568,14 +568,14 @@ export class EditorStatus implements IStatusbarItem { return; } - let info: StateDelta = { encoding: null }; + const info: StateDelta = { encoding: null }; // We only support text based editors if (e instanceof BaseTextEditor) { - let encodingSupport: IEncodingSupport = asFileOrUntitledEditorInput(e.input); + const encodingSupport: IEncodingSupport = asFileOrUntitledEditorInput(e.input); if (encodingSupport && types.isFunction(encodingSupport.getEncoding)) { - let rawEncoding = encodingSupport.getEncoding(); - let encodingInfo = SUPPORTED_ENCODINGS[rawEncoding]; + const rawEncoding = encodingSupport.getEncoding(); + const encodingInfo = SUPPORTED_ENCODINGS[rawEncoding]; if (encodingInfo) { info.encoding = encodingInfo.labelShort; // if we have a label, take it from there } else { @@ -588,9 +588,9 @@ export class EditorStatus implements IStatusbarItem { } private onResourceEncodingChange(resource: uri): void { - let activeEditor = this.editorService.getActiveEditor(); + const activeEditor = this.editorService.getActiveEditor(); if (activeEditor) { - let activeResource = getUntitledOrFileResource(activeEditor.input, true); + const activeResource = getUntitledOrFileResource(activeEditor.input, true); if (activeResource && activeResource.toString() === resource.toString()) { return this.onEncodingChange(activeEditor); // only update if the encoding changed for the active resource } @@ -598,13 +598,13 @@ export class EditorStatus implements IStatusbarItem { } private onTabFocusModeChange(): void { - let info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() }; + const info: StateDelta = { tabFocusMode: TabFocus.getTabFocusMode() }; this.updateState(info); } private isActiveEditor(e: IBaseEditor): boolean { - let activeEditor = this.editorService.getActiveEditor(); + const activeEditor = this.editorService.getActiveEditor(); return activeEditor && e && activeEditor === e; } @@ -638,27 +638,27 @@ export class ChangeModeAction extends Action { } public run(): TPromise { - let languages = this.modeService.getRegisteredLanguageNames(); + const languages = this.modeService.getRegisteredLanguageNames(); let activeEditor = this.editorService.getActiveEditor(); if (!(activeEditor instanceof BaseTextEditor)) { return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); } - let editorWidget = (activeEditor).getControl(); - let textModel = getTextModel(editorWidget); - let fileinput = asFileEditorInput(activeEditor.input, true); + const editorWidget = (activeEditor).getControl(); + const textModel = getTextModel(editorWidget); + const fileinput = asFileEditorInput(activeEditor.input, true); // Compute mode let currentModeId: string; if (!!(textModel).getMode) { - let mode = (textModel).getMode(); + const mode = (textModel).getMode(); if (mode) { currentModeId = this.modeService.getLanguageName(mode.getId()); } } // All languages are valid picks - let picks: IPickOpenEntry[] = languages.sort().map((lang, index) => { + const picks: IPickOpenEntry[] = languages.sort().map((lang, index) => { return { label: lang, description: currentModeId === lang ? nls.localize('configuredLanguage', "Configured Language") : void 0 @@ -676,13 +676,13 @@ export class ChangeModeAction extends Action { } } - let configureModeAssociations: IPickOpenEntry = { + const configureModeAssociations: IPickOpenEntry = { label: configureLabel }; picks.unshift(configureModeAssociations); // Offer to "Auto Detect" - let autoDetectMode: IPickOpenEntry = { + const autoDetectMode: IPickOpenEntry = { label: nls.localize('autoDetect', "Auto Detect") }; if (fileinput) { @@ -693,14 +693,14 @@ export class ChangeModeAction extends Action { if (language) { activeEditor = this.editorService.getActiveEditor(); if (activeEditor instanceof BaseTextEditor) { - let editorWidget = activeEditor.getControl(); - let models: ITextModel[] = []; + const editorWidget = activeEditor.getControl(); + const models: ITextModel[] = []; - let textModel = getTextModel(editorWidget); + const textModel = getTextModel(editorWidget); models.push(textModel); // Support for original side of diff - let model = editorWidget.getModel(); + const model = editorWidget.getModel(); if (model && !!(model).original) { models.push((model).original); } @@ -799,7 +799,6 @@ export class ChangeEOLAction extends Action { } public run(): TPromise { - let activeEditor = this.editorService.getActiveEditor(); if (!(activeEditor instanceof BaseTextEditor)) { return this.quickOpenService.pick([{ label: nls.localize('noEditor', "No text editor active at this time") }]); @@ -809,22 +808,22 @@ export class ChangeEOLAction extends Action { return this.quickOpenService.pick([{ label: nls.localize('noWritableCodeEditor', "The active code editor is read-only.") }]); } - let editorWidget = (activeEditor).getControl(); - let textModel = getTextModel(editorWidget); + const editorWidget = (activeEditor).getControl(); + const textModel = getTextModel(editorWidget); - let EOLOptions: IChangeEOLEntry[] = [ + const EOLOptions: IChangeEOLEntry[] = [ { label: nlsEOLLF, eol: EndOfLineSequence.LF }, { label: nlsEOLCRLF, eol: EndOfLineSequence.CRLF }, ]; - let selectedIndex = (textModel.getEOL() === '\n') ? 0 : 1; + const selectedIndex = (textModel.getEOL() === '\n') ? 0 : 1; return this.quickOpenService.pick(EOLOptions, { placeHolder: nls.localize('pickEndOfLine', "Select End of Line Sequence"), autoFocus: { autoFocusIndex: selectedIndex } }).then((eol) => { if (eol) { activeEditor = this.editorService.getActiveEditor(); if (activeEditor instanceof BaseTextEditor && isWritableCodeEditor(activeEditor)) { - let editorWidget = activeEditor.getControl(); - let textModel = getTextModel(editorWidget); + const editorWidget = activeEditor.getControl(); + const textModel = getTextModel(editorWidget); textModel.setEOL(eol.eol); } } @@ -886,13 +885,13 @@ export class ChangeEncodingAction extends Action { return TPromise.timeout(50 /* quick open is sensitive to being opened so soon after another */).then(() => { const configuration = this.configurationService.getConfiguration(); - let isReopenWithEncoding = (action === reopenWithEncodingPick); - let configuredEncoding = configuration && configuration.files && configuration.files.encoding; + const isReopenWithEncoding = (action === reopenWithEncodingPick); + const configuredEncoding = configuration && configuration.files && configuration.files.encoding; let directMatchIndex: number; let aliasMatchIndex: number; // All encodings are valid picks - let picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS) + const picks: IPickOpenEntry[] = Object.keys(SUPPORTED_ENCODINGS) .sort((k1, k2) => { if (k1 === configuredEncoding) { return -1; diff --git a/src/vs/workbench/browser/parts/editor/media/tabstitle.css b/src/vs/workbench/browser/parts/editor/media/tabstitle.css index 0e401e46d10..edf4d47507d 100644 --- a/src/vs/workbench/browser/parts/editor/media/tabstitle.css +++ b/src/vs/workbench/browser/parts/editor/media/tabstitle.css @@ -30,6 +30,10 @@ border-top: 1px solid #403F3F; } +.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs::before { + border-top: 1px solid #6FC3DF; +} + .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title.tabs > .monaco-scrollable-element { flex: 1; } @@ -106,8 +110,13 @@ border-left-color: transparent; } +.hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab { + border-left-color: #6FC3DF; +} + .hc-black .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container > .tab.active { - border: 1px solid #f38518; + outline: 2px solid #f38518; + outline-offset: -1px; } .vs .monaco-workbench > .part.editor > .content > .one-editor-silo > .container > .title .tabs-container.dropfeedback, diff --git a/src/vs/workbench/browser/parts/editor/stringEditor.ts b/src/vs/workbench/browser/parts/editor/stringEditor.ts index f83a5b09106..d996aef7eed 100644 --- a/src/vs/workbench/browser/parts/editor/stringEditor.ts +++ b/src/vs/workbench/browser/parts/editor/stringEditor.ts @@ -13,7 +13,7 @@ import {TextEditorOptions, EditorModel, EditorInput, EditorOptions} from 'vs/wor import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel'; import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; -import {UntitledEditorEvent, EventType} from 'vs/workbench/common/events'; +import URI from 'vs/base/common/uri'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IStorageService} from 'vs/platform/storage/common/storage'; @@ -23,6 +23,7 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IMessageService} from 'vs/platform/message/common/message'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; /** * An editor implementation that is capable of showing string inputs or promise inputs that resolve to a string. @@ -43,17 +44,20 @@ export class StringEditor extends BaseTextEditor { @IConfigurationService configurationService: IConfigurationService, @IEventService eventService: IEventService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IThemeService themeService: IThemeService + @IThemeService themeService: IThemeService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { super(StringEditor.ID, telemetryService, instantiationService, contextService, storageService, messageService, configurationService, eventService, editorService, themeService); this.mapResourceToEditorViewState = Object.create(null); - this.toUnbind.push(this.eventService.addListener2(EventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onUntitledSavedEvent(e))); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDirtyChange(e))); } - private onUntitledSavedEvent(e: UntitledEditorEvent): void { - delete this.mapResourceToEditorViewState[e.resource.toString()]; + private onUntitledDirtyChange(resource: URI): void { + if (!this.untitledEditorService.isDirty(resource)) { + delete this.mapResourceToEditorViewState[resource.toString()]; // untitled file got reverted, so remove view state + } } public getTitle(): string { @@ -150,11 +154,14 @@ export class StringEditor extends BaseTextEditor { /** * Reveals the last line of this editor if it has a model set. + * If smart reveal is true will only reveal the last line if the line before last is visible #3351 */ - public revealLastLine(): void { + public revealLastLine(smartReveal = false): void { let codeEditor = this.getControl(); let model = codeEditor.getModel(); - if (model) { + const lineBeforeLastRevealed = codeEditor.getScrollTop() + codeEditor.getLayoutInfo().height >= codeEditor.getScrollHeight(); + + if (model && (!smartReveal || lineBeforeLastRevealed)) { let lastLine = model.getLineCount(); codeEditor.revealLine(lastLine); } diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 599aaefafa7..ea9b9056795 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -8,8 +8,7 @@ import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; import {Action, IAction} from 'vs/base/common/actions'; -import Event, {Emitter} from 'vs/base/common/event'; -import {IComposite} from 'vs/workbench/common/composite'; +import Event from 'vs/base/common/event'; import {Builder} from 'vs/base/browser/builder'; import {Registry} from 'vs/platform/platform'; import {Scope} from 'vs/workbench/browser/actionBarRegistry'; @@ -33,8 +32,6 @@ export class PanelPart extends CompositePart implements IPanelService { public _serviceBrand: any; - private _onDidPanelOpen = new Emitter(); - private _onDidPanelClose = new Emitter(); private blockOpeningPanel: boolean; constructor( @@ -65,11 +62,11 @@ export class PanelPart extends CompositePart implements IPanelService { } public get onDidPanelOpen(): Event { - return this._onDidPanelOpen.event; + return this._onDidCompositeOpen.event; } public get onDidPanelClose(): Event { - return this._onDidPanelClose.event; + return this._onDidCompositeClose.event; } public create(parent: Builder): void { @@ -91,10 +88,7 @@ export class PanelPart extends CompositePart implements IPanelService { } } - return this.openComposite(id, focus).then(composite => { - this._onDidPanelOpen.fire(composite as IComposite as IPanel); - return composite; - }); + return this.openComposite(id, focus); } protected getActions(): IAction[] { @@ -110,7 +104,7 @@ export class PanelPart extends CompositePart implements IPanelService { } public hideActivePanel(): TPromise { - return this.hideActiveComposite().then(composite => this._onDidPanelClose.fire(composite as IComposite as IPanel)); + return this.hideActiveComposite().then(composite => void 0); } } diff --git a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts index b53859656c9..da207be9230 100644 --- a/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts +++ b/src/vs/workbench/browser/parts/quickopen/quickOpenController.ts @@ -27,7 +27,7 @@ import {Registry} from 'vs/platform/platform'; import {EditorInput, getUntitledOrFileResource, IWorkbenchEditorConfiguration} from 'vs/workbench/common/editor'; import {WorkbenchComponent} from 'vs/workbench/common/component'; import Event, {Emitter} from 'vs/base/common/event'; -import {Identifiers} from 'vs/workbench/common/constants'; +import {IPartService} from 'vs/workbench/services/part/common/partService'; import {KeyMod} from 'vs/base/common/keyCodes'; import {QuickOpenHandler, QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions, EditorQuickOpenEntry} from 'vs/workbench/browser/quickopen'; import errors = require('vs/base/common/errors'); @@ -72,6 +72,8 @@ interface IInternalPickOptions { export class QuickOpenController extends WorkbenchComponent implements IQuickOpenService { + private static MAX_SHORT_RESPONSE_TIME = 500; + public _serviceBrand: any; private static ID = 'workbench.component.quickopen'; @@ -102,7 +104,8 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe @IContextKeyService contextKeyService: IContextKeyService, @IConfigurationService private configurationService: IConfigurationService, @IHistoryService private historyService: IHistoryService, - @IInstantiationService private instantiationService: IInstantiationService + @IInstantiationService private instantiationService: IInstantiationService, + @IPartService private partService: IPartService ) { super(QuickOpenController.ID); @@ -144,6 +147,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } public input(options: IInputOptions = {}, token: CancellationToken = CancellationToken.None): TPromise { + + if (this.pickOpenWidget && this.pickOpenWidget.isVisible()) { + this.pickOpenWidget.hide(HideReason.CANCELED); + } + const defaultMessage = options.prompt ? nls.localize('inputModeEntryDescription', "{0} (Press 'Enter' to confirm or 'Escape' to cancel)", options.prompt) : nls.localize('inputModeEntry', "Press 'Enter' to confirm your input or 'Escape' to cancel"); @@ -175,7 +183,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe currentValidation = TPromise.timeout(100).then(() => { return options.validateInput(value).then(message => { currentDecoration = !!message ? Severity.Error : void 0; - let newPick = message || defaultMessage; + const newPick = message || defaultMessage; if (newPick !== currentPick) { currentPick = newPick; resolve(new TPromise(init)); @@ -218,10 +226,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } let isAboutStrings = false; - let entryPromise = arrayPromise.then(elements => { + const entryPromise = arrayPromise.then(elements => { return (>elements).map(element => { if (typeof element === 'string') { isAboutStrings = true; + return { label: element }; } else { return element; @@ -229,6 +238,10 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe }); }); + if (this.pickOpenWidget && this.pickOpenWidget.isVisible()) { + this.pickOpenWidget.hide(HideReason.CANCELED); + } + return new TPromise((resolve, reject, progress) => { function onItem(item: IPickOpenEntry): string | IPickOpenEntry { @@ -240,16 +253,16 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } private doPick(picksPromise: TPromise, options: IInternalPickOptions, token: CancellationToken = CancellationToken.None): TPromise { - let autoFocus = options.autoFocus; + const autoFocus = options.autoFocus; // Use a generated token to avoid race conditions from long running promises - let currentPickerToken = uuid.generateUuid(); + const currentPickerToken = uuid.generateUuid(); this.currentPickerToken = currentPickerToken; // Create upon first open if (!this.pickOpenWidget) { this.pickOpenWidget = new QuickOpenWidget( - withElementById(Identifiers.WORKBENCH_CONTAINER).getHTMLElement(), + withElementById(this.partService.getWorkbenchElementId()).getHTMLElement(), { onOk: () => { /* ignore, handle later */ }, onCancel: () => { /* ignore, handle later */ }, @@ -293,7 +306,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe return new TPromise((complete, error, progress) => { // hide widget when being cancelled - token.onCancellationRequested(e => this.pickOpenWidget.hide(HideReason.CANCELED)); + token.onCancellationRequested(e => { + if (this.currentPickerToken === currentPickerToken) { + this.pickOpenWidget.hide(HideReason.CANCELED); + } + }); let picksPromiseDone = false; @@ -309,9 +326,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe this.pickOpenWidget.getProgressBar().stop().getContainer().hide(); // Model - let model = new QuickOpenModel(); - let entries = picks.map(e => { - let entry = (e); + const model = new QuickOpenModel(); + const entries = picks.map(e => { + const entry = (e); if (entry.height && entry.render) { return new PickOpenItem(entry, () => progress(e)); } @@ -376,9 +393,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe // Filter by value else { entries.forEach(entry => { - let labelHighlights = filters.matchesFuzzy(value, entry.getLabel()); - let descriptionHighlights = options.matchOnDescription && filters.matchesFuzzy(value, entry.getDescription()); - let detailHighlights = options.matchOnDetail && entry.getDetail() && filters.matchesFuzzy(value, entry.getDetail()); + const labelHighlights = filters.matchesFuzzy(value, entry.getLabel()); + const descriptionHighlights = options.matchOnDescription && filters.matchesFuzzy(value, entry.getDescription()); + const detailHighlights = options.matchOnDetail && entry.getDetail() && filters.matchesFuzzy(value, entry.getDetail()); if (entry.shouldAlwaysShow() || labelHighlights || descriptionHighlights || detailHighlights) { entry.setHighlights(labelHighlights, descriptionHighlights, detailHighlights); @@ -467,28 +484,23 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe this.previousValue = prefix; - let promiseCompletedOnHide = new TPromise(c => { + const promiseCompletedOnHide = new TPromise(c => { this.promisesToCompleteOnHide.push(c); }); // Telemetry: log that quick open is shown and log the mode - let registry = (Registry.as(Extensions.Quickopen)); - let handlerDescriptors = [registry.getQuickOpenHandler(prefix)]; - if (!handlerDescriptors[0]) { - handlerDescriptors = registry.getDefaultQuickOpenHandlers(); - } + const registry = Registry.as(Extensions.Quickopen); + const handlerDescriptor = registry.getQuickOpenHandler(prefix) || registry.getDefaultQuickOpenHandler(); - if (handlerDescriptors[0]) { - this.telemetryService.publicLog('quickOpenWidgetShown', { mode: handlerDescriptors[0].getId(), quickNavigate: quickNavigateConfiguration }); - } + this.telemetryService.publicLog('quickOpenWidgetShown', { mode: handlerDescriptor.getId(), quickNavigate: quickNavigateConfiguration }); // Trigger onOpen - handlerDescriptors.forEach(desc => this.resolveHandler(desc)); + this.resolveHandler(handlerDescriptor); // Create upon first open if (!this.quickOpenWidget) { this.quickOpenWidget = new QuickOpenWidget( - withElementById(Identifiers.WORKBENCH_CONTAINER).getHTMLElement(), + withElementById(this.partService.getWorkbenchElementId()).getHTMLElement(), { onOk: () => { /* ignore */ }, onCancel: () => { /* ignore */ }, @@ -515,7 +527,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe if (prefix) { this.quickOpenWidget.show(prefix, { quickNavigateConfiguration }); } else { - let editorHistory = this.getEditorHistoryWithGroupLabel(); + const editorHistory = this.getEditorHistoryWithGroupLabel(); if (editorHistory.getEntries().length < 2) { quickNavigateConfiguration = null; // If no entries can be shown, default to normal quick open mode } @@ -524,7 +536,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe if (!quickNavigateConfiguration) { autoFocus = { autoFocusFirstEntry: true }; } else { - let visibleEditorCount = this.editorService.getVisibleEditors().length; + const visibleEditorCount = this.editorService.getVisibleEditors().length; autoFocus = { autoFocusFirstEntry: visibleEditorCount === 0, autoFocusSecondEntry: visibleEditorCount !== 0 }; } @@ -560,7 +572,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe // Pass to handlers for (let prefix in this.mapResolvedHandlersToPrefix) { if (this.mapResolvedHandlersToPrefix.hasOwnProperty(prefix)) { - let promise = this.mapResolvedHandlersToPrefix[prefix]; + const promise = this.mapResolvedHandlersToPrefix[prefix]; promise.then(handler => { this.handlerOnOpenCalled[prefix] = false; // Don't check if onOpen was called to preserve old behaviour for now @@ -584,11 +596,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } private hasHandler(prefix: string): boolean { - return !!(Registry.as(Extensions.Quickopen)).getQuickOpenHandler(prefix); + return !!Registry.as(Extensions.Quickopen).getQuickOpenHandler(prefix); } private getEditorHistoryWithGroupLabel(): QuickOpenModel { - let entries: QuickOpenEntry[] = this.getEditorHistoryEntries(); + const entries: QuickOpenEntry[] = this.getEditorHistoryEntries(); // Apply label to first entry if (entries.length > 0) { @@ -601,7 +613,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe private restoreFocus(): void { // Try to focus active editor - let editor = this.editorService.getActiveEditor(); + const editor = this.editorService.getActiveEditor(); if (editor) { editor.focus(); } @@ -613,10 +625,11 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe // look for a handler const registry = Registry.as(Extensions.Quickopen); const handlerDescriptor = registry.getQuickOpenHandler(value); + const defaultHandlerDescriptor = registry.getDefaultQuickOpenHandler(); const instantProgress = handlerDescriptor && handlerDescriptor.instantProgress; // Use a generated token to avoid race conditions from long running promises - let currentResultToken = uuid.generateUuid(); + const currentResultToken = uuid.generateUuid(); this.currentResultToken = currentResultToken; // Reset Progress @@ -628,14 +641,10 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe this.quickOpenWidget.setExtraClass(null); // Trigger onOpen - if (handlerDescriptor) { - this.resolveHandler(handlerDescriptor); - } else { - registry.getDefaultQuickOpenHandlers().forEach(desc => this.resolveHandler(desc)); - } + this.resolveHandler(handlerDescriptor || defaultHandlerDescriptor); // Remove leading and trailing whitespace - let trimmedValue = strings.trim(value); + const trimmedValue = strings.trim(value); // If no value provided, default to editor history if (!trimmedValue) { @@ -652,8 +661,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe // Otherwise handle default handlers if no specific handler present else { - let defaultHandlers = registry.getDefaultQuickOpenHandlers(); - resultPromise = this.handleDefaultHandlers(defaultHandlers, value, currentResultToken); + resultPromise = this.handleDefaultHandler(defaultHandlerDescriptor, value, currentResultToken); } // Remember as the active one @@ -680,51 +688,48 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe }); } - private handleDefaultHandlers(defaultHandlers: QuickOpenHandlerDescriptor[], value: string, currentResultToken: string): TPromise { - const previousInput = this.quickOpenWidget.getInput(); - const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); + private handleDefaultHandler(handler: QuickOpenHandlerDescriptor, value: string, currentResultToken: string): TPromise { // Fill in history results if matching - let matchingHistoryEntries = this.getEditorHistoryEntries(value); + const matchingHistoryEntries = this.getEditorHistoryEntries(value); if (matchingHistoryEntries.length > 0) { matchingHistoryEntries[0] = new EditorHistoryEntryGroup(matchingHistoryEntries[0], nls.localize('historyMatches', "recently opened"), false); } - // If we have matching entries from history we want to show them directly and not wait for the other results to come in - // This also applies when we used to have entries from a previous run and now there are no more history results matching - let inputSet = false; - let quickOpenModel = new QuickOpenModel(matchingHistoryEntries, this.actionProvider); - if (wasShowingHistory || matchingHistoryEntries.length > 0) { - this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - inputSet = true; - } + // Resolve + return this.resolveHandler(handler).then(resolvedHandler => { + const quickOpenModel = new QuickOpenModel(matchingHistoryEntries, this.actionProvider); - // Resolve all default handlers - let resolvePromises: TPromise[] = []; - defaultHandlers.forEach(defaultHandler => { - resolvePromises.push(this.resolveHandler(defaultHandler)); - }); + let inputSet = false; - return TPromise.join(resolvePromises).then((resolvedHandlers: QuickOpenHandler[]) => { - let resultPromises: TPromise[] = []; - resolvedHandlers.forEach(resolvedHandler => { - - // Receive Results from Handler and apply - resultPromises.push(resolvedHandler.getResults(value).then(result => { - if (this.currentResultToken === currentResultToken) { - let handlerResults = (result && result.entries) || []; - - // now is the time to show the input if we did not have set it before - if (!inputSet) { - this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); - } - - this.mergeResults(quickOpenModel, handlerResults, resolvedHandler.getGroupLabel()); + // If we have matching entries from history we want to show them directly and not wait for the other results to come in + // This also applies when we used to have entries from a previous run and now there are no more history results matching + const previousInput = this.quickOpenWidget.getInput(); + const wasShowingHistory = previousInput && previousInput.entries && previousInput.entries.some(e => e instanceof EditorHistoryEntry || e instanceof EditorHistoryEntryGroup); + if (wasShowingHistory || matchingHistoryEntries.length > 0) { + (resolvedHandler.hasShortResponseTime() ? TPromise.timeout(QuickOpenController.MAX_SHORT_RESPONSE_TIME) : TPromise.as(undefined)).then(() => { + if (this.currentResultToken === currentResultToken && !inputSet) { + this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); + inputSet = true; } - })); - }); + }); + } - return TPromise.join(resultPromises).then(() => void 0); + // Get results + return resolvedHandler.getResults(value).then(result => { + if (this.currentResultToken === currentResultToken) { + + // now is the time to show the input if we did not have set it before + if (!inputSet) { + this.quickOpenWidget.setInput(quickOpenModel, { autoFocusFirstEntry: true }); + inputSet = true; + } + + // merge history and default handler results + const handlerResults = (result && result.entries) || []; + this.mergeResults(quickOpenModel, handlerResults, resolvedHandler.getGroupLabel()); + } + }); }); } @@ -741,7 +746,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe const searchInPath = searchValue.indexOf(paths.nativeSep) >= 0; - let results: QuickOpenEntry[] = []; + const results: QuickOpenEntry[] = []; history.forEach(input => { const resource = getUntitledOrFileResource(input); if (!resource) { @@ -749,12 +754,12 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } // Check if this entry is a match for the search value - let targetToMatch = searchInPath ? labels.getPathLabel(resource, this.contextService) : input.getName(); + const targetToMatch = searchInPath ? labels.getPathLabel(resource, this.contextService) : input.getName(); if (!filters.matchesFuzzy(searchValue, targetToMatch)) { return; } - let entry = this.instantiationService.createInstance(EditorHistoryEntry, input); + const entry = this.instantiationService.createInstance(EditorHistoryEntry, input); const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue); entry.setHighlights(labelHighlights, descriptionHighlights); @@ -769,20 +774,20 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe private mergeResults(quickOpenModel: QuickOpenModel, handlerResults: QuickOpenEntry[], groupLabel: string): void { // Remove results already showing by checking for a "resource" property - let mapEntryToResource = this.mapEntriesToResource(quickOpenModel); - let additionalHandlerResults: QuickOpenEntry[] = []; + const mapEntryToResource = this.mapEntriesToResource(quickOpenModel); + const additionalHandlerResults: QuickOpenEntry[] = []; for (let i = 0; i < handlerResults.length; i++) { - let result = handlerResults[i]; - let resource = result.getResource(); + const result = handlerResults[i]; + const resource = result.getResource(); - if (!resource || !mapEntryToResource[resource.toString()]) { + if (!result.isFile() || !resource || !mapEntryToResource[resource.toString()]) { additionalHandlerResults.push(result); } } // Show additional handler results below any existing results if (additionalHandlerResults.length > 0) { - let useTopBorder = quickOpenModel.getEntries().length > 0; + const useTopBorder = quickOpenModel.getEntries().length > 0; additionalHandlerResults[0] = new QuickOpenEntryGroup(additionalHandlerResults[0], groupLabel, useTopBorder); quickOpenModel.addEntries(additionalHandlerResults); this.quickOpenWidget.refresh(quickOpenModel, { autoFocusFirstEntry: true }); @@ -802,9 +807,9 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe value = value.substr(handlerDescriptor.prefix.length); // Return early if the handler can not run in the current environment and inform the user - let canRun = resolvedHandler.canRun(); + const canRun = resolvedHandler.canRun(); if (types.isUndefinedOrNull(canRun) || (typeof canRun === 'boolean' && !canRun) || typeof canRun === 'string') { - let placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); + const placeHolderLabel = (typeof canRun === 'string') ? canRun : nls.localize('canNotRunPlaceholder', "This quick open handler can not be used in the current context"); const model = new QuickOpenModel([new PlaceholderQuickOpenEntry(placeHolderLabel)], this.actionProvider); this.showModel(model, resolvedHandler.getAutoFocus(value, this.quickOpenWidget.getQuickNavigateConfiguration()), resolvedHandler.getAriaLabel()); @@ -813,7 +818,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } // Support extra class from handler - let extraClass = resolvedHandler.getClass(); + const extraClass = resolvedHandler.getClass(); if (extraClass) { this.quickOpenWidget.setExtraClass(extraClass); } @@ -855,8 +860,8 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe } private mapEntriesToResource(model: QuickOpenModel): { [resource: string]: QuickOpenEntry; } { - let entries = model.getEntries(); - let mapEntryToPath: { [path: string]: QuickOpenEntry; } = {}; + const entries = model.getEntries(); + const mapEntryToPath: { [path: string]: QuickOpenEntry; } = {}; entries.forEach((entry: QuickOpenEntry) => { if (entry.getResource()) { mapEntryToPath[entry.getResource().toString()] = entry; @@ -868,6 +873,7 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe private resolveHandler(handler: QuickOpenHandlerDescriptor): TPromise { let result = this._resolveHandler(handler); + const id = handler.getId(); if (!this.handlerOnOpenCalled[id]) { const original = result; @@ -875,17 +881,20 @@ export class QuickOpenController extends WorkbenchComponent implements IQuickOpe result = this.mapResolvedHandlersToPrefix[id] = original.then(resolved => { this.mapResolvedHandlersToPrefix[id] = original; resolved.onOpen(); + return resolved; }); } + return result.then(null, (error) => { delete this.mapResolvedHandlersToPrefix[id]; + return TPromise.wrapError('Unable to instantiate quick open handler ' + handler.moduleName + ' - ' + handler.ctorName + ': ' + JSON.stringify(error)); }); } private _resolveHandler(handler: QuickOpenHandlerDescriptor): TPromise { - let id = handler.getId(); + const id = handler.getId(); // Return Cached if (this.mapResolvedHandlersToPrefix[id]) { diff --git a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css index 0a35404ba2d..87a4e5529cf 100644 --- a/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css +++ b/src/vs/workbench/browser/parts/sidebar/media/sidebarpart.css @@ -20,6 +20,11 @@ visibility: hidden !important; } +.monaco-workbench.nopanel > .panel { + display: none !important; + visibility: hidden !important; +} + .monaco-workbench .viewlet .collapsible.header .title { overflow: hidden; text-overflow: ellipsis; diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index 7891c01fb7e..7872e59a17e 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -8,7 +8,6 @@ import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import {Registry} from 'vs/platform/platform'; import {Action} from 'vs/base/common/actions'; -import {IComposite} from 'vs/workbench/common/composite'; import {CompositePart} from 'vs/workbench/browser/parts/compositePart'; import {Viewlet, ViewletRegistry, Extensions as ViewletExtensions} from 'vs/workbench/browser/viewlet'; import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry'; @@ -24,7 +23,7 @@ import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import Event, {Emitter} from 'vs/base/common/event'; +import Event from 'vs/base/common/event'; export class SidebarPart extends CompositePart implements IViewletService { @@ -32,8 +31,6 @@ export class SidebarPart extends CompositePart implements IViewletServi public _serviceBrand: any; - private _onDidViewletOpen = new Emitter(); - private _onDidViewletClose = new Emitter(); private blockOpeningViewlet: boolean; constructor( @@ -64,11 +61,11 @@ export class SidebarPart extends CompositePart implements IViewletServi } public get onDidViewletOpen(): Event { - return this._onDidViewletOpen.event; + return this._onDidCompositeOpen.event; } public get onDidViewletClose(): Event { - return this._onDidViewletClose.event; + return this._onDidCompositeClose.event; } public openViewlet(id: string, focus?: boolean): TPromise { @@ -86,10 +83,7 @@ export class SidebarPart extends CompositePart implements IViewletServi } } - return this.openComposite(id, focus).then(composite => { - this._onDidViewletOpen.fire(composite as IComposite as IViewlet); - return composite; - }); + return this.openComposite(id, focus); } public getActiveViewlet(): IViewlet { @@ -101,7 +95,7 @@ export class SidebarPart extends CompositePart implements IViewletServi } public hideActiveViewlet(): TPromise { - return this.hideActiveComposite().then(composite => this._onDidViewletClose.fire(composite as IComposite as IViewlet)); + return this.hideActiveComposite().then(composite => void 0); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index c3b94ea4b93..834005e5af8 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -9,7 +9,7 @@ import 'vs/css!./media/statusbarpart'; import dom = require('vs/base/browser/dom'); import types = require('vs/base/common/types'); import nls = require('vs/nls'); -import {toErrorMessage} from 'vs/base/common/errors'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {TPromise} from 'vs/base/common/winjs.base'; import {dispose, IDisposable} from 'vs/base/common/lifecycle'; import {Builder, $} from 'vs/base/browser/builder'; diff --git a/src/vs/workbench/browser/quickopen.ts b/src/vs/workbench/browser/quickopen.ts index 6f4fbda55b0..72f5874fc41 100644 --- a/src/vs/workbench/browser/quickopen.ts +++ b/src/vs/workbench/browser/quickopen.ts @@ -59,6 +59,13 @@ export class QuickOpenHandler { return true; } + /** + * Hints to the outside that this quick open handler typically returns results fast. + */ + public hasShortResponseTime(): boolean { + return false; + } + /** * Indicates if the handler wishes the quick open widget to automatically select the first result entry or an entry * based on a specific prefix match. @@ -165,18 +172,17 @@ export interface IQuickOpenRegistry { getQuickOpenHandler(prefix: string): QuickOpenHandlerDescriptor; /** - * Returns the default quick open handlers. + * Returns the default quick open handler. */ - getDefaultQuickOpenHandlers(): QuickOpenHandlerDescriptor[]; + getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor; } class QuickOpenRegistry implements IQuickOpenRegistry { private handlers: QuickOpenHandlerDescriptor[]; - private defaultHandlers: QuickOpenHandlerDescriptor[]; + private defaultHandler: QuickOpenHandlerDescriptor; constructor() { this.handlers = []; - this.defaultHandlers = []; } public registerQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { @@ -188,7 +194,7 @@ class QuickOpenRegistry implements IQuickOpenRegistry { } public registerDefaultQuickOpenHandler(descriptor: QuickOpenHandlerDescriptor): void { - this.defaultHandlers.push(descriptor); + this.defaultHandler = descriptor; } public getQuickOpenHandlers(): QuickOpenHandlerDescriptor[] { @@ -199,8 +205,8 @@ class QuickOpenRegistry implements IQuickOpenRegistry { return text ? arrays.first(this.handlers, h => strings.startsWith(text, h.prefix), null) : null; } - public getDefaultQuickOpenHandlers(): QuickOpenHandlerDescriptor[] { - return this.defaultHandlers; + public getDefaultQuickOpenHandler(): QuickOpenHandlerDescriptor { + return this.defaultHandler; } } diff --git a/src/vs/workbench/buildfile.js b/src/vs/workbench/buildfile.js index d7ba5b7a778..4c3491a8526 100644 --- a/src/vs/workbench/buildfile.js +++ b/src/vs/workbench/buildfile.js @@ -16,9 +16,6 @@ function createModuleDescription(name, exclude) { } exports.collectModules = function(excludes) { - var languageMainExcludes = ['vs/editor/common/languages.common']; - var languageWorkerExcludes = ['vs/base/common/worker/workerServer', 'vs/editor/common/worker/editorWorkerServer']; - var modules = [ createModuleDescription('vs/workbench/parts/search/browser/searchViewlet', excludes), createModuleDescription('vs/workbench/parts/search/browser/openAnythingHandler', excludes), @@ -27,8 +24,7 @@ exports.collectModules = function(excludes) { createModuleDescription('vs/workbench/parts/git/node/gitApp', []), createModuleDescription('vs/workbench/parts/git/node/askpass', []), - createModuleDescription('vs/workbench/parts/output/common/outputMode', languageMainExcludes), - createModuleDescription('vs/workbench/parts/output/common/outputWorker', languageWorkerExcludes), + createModuleDescription('vs/workbench/parts/output/common/outputLinkComputer', ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']), createModuleDescription('vs/workbench/parts/output/browser/outputPanel', excludes), createModuleDescription('vs/workbench/parts/debug/browser/debugViewlet', excludes), diff --git a/src/vs/workbench/common/actionRegistry.ts b/src/vs/workbench/common/actionRegistry.ts index f490ef5477a..c3285c46cc3 100644 --- a/src/vs/workbench/common/actionRegistry.ts +++ b/src/vs/workbench/common/actionRegistry.ts @@ -149,10 +149,10 @@ export function createCommandHandler(descriptor: SyncActionDescriptor): ICommand let messageService = accessor.get(IMessageService); let instantiationService = accessor.get(IInstantiationService); - let telemetryServce = accessor.get(ITelemetryService); + let telemetryService = accessor.get(ITelemetryService); let partService = accessor.get(IPartService); - TPromise.as(triggerAndDisposeAction(instantiationService, telemetryServce, partService, descriptor, args)).done(null, (err) => { + TPromise.as(triggerAndDisposeAction(instantiationService, telemetryService, partService, descriptor, args)).done(null, (err) => { messageService.show(Severity.Error, err); }); }; diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index 265a4046cba..0b82b4a192f 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -121,7 +121,9 @@ export interface IEditorInputFactory { * Each editor input is mapped to an editor that is capable of opening it through the Platform facade. */ export abstract class EditorInput extends EventEmitter implements IEditorInput { + protected _onDidChangeDirty: Emitter; + private disposed: boolean; constructor() { @@ -831,6 +833,7 @@ export interface IEditorContext extends IEditorIdentifier { export interface IGroupEvent { editor: IEditorInput; pinned: boolean; + index: number; } export type GroupIdentifier = number; diff --git a/src/vs/workbench/common/editor/binaryEditorModel.ts b/src/vs/workbench/common/editor/binaryEditorModel.ts index 4e52f089c91..4f619860b69 100644 --- a/src/vs/workbench/common/editor/binaryEditorModel.ts +++ b/src/vs/workbench/common/editor/binaryEditorModel.ts @@ -16,11 +16,12 @@ export class BinaryEditorModel extends EditorModel { private name: string; private resource: URI; private size: number; + private etag: string; constructor( resource: URI, name: string, - @IFileService protected fileService: IFileService + @IFileService private fileService: IFileService ) { super(); @@ -49,8 +50,16 @@ export class BinaryEditorModel extends EditorModel { return this.size; } + /** + * The etag of the binary file if known. + */ + public getETag(): string { + return this.etag; + } + public load(): TPromise { return this.fileService.resolveFile(this.resource).then(stat => { + this.etag = stat.etag; this.size = stat.size; return this; diff --git a/src/vs/workbench/common/editor/editorStacksModel.ts b/src/vs/workbench/common/editor/editorStacksModel.ts index 7d5ce5cf673..44c9391e79d 100644 --- a/src/vs/workbench/common/editor/editorStacksModel.ts +++ b/src/vs/workbench/common/editor/editorStacksModel.ts @@ -115,7 +115,7 @@ export class EditorGroup implements IEditorGroup { } private onConfigurationUpdated(config: IWorkbenchEditorConfiguration): void { - this.editorOpenPositioning = config.workbench.editor.openPositioning; + this.editorOpenPositioning = config && config.workbench && config.workbench.editor && config.workbench.editor.openPositioning; } public get id(): GroupIdentifier { @@ -346,7 +346,7 @@ export class EditorGroup implements IEditorGroup { this.splice(index, true); // Event - this.fireEvent(this._onEditorClosed, { editor, pinned }, true); + this.fireEvent(this._onEditorClosed, { editor, pinned, index }, true); } public closeEditors(except: EditorInput, direction?: Direction): void { diff --git a/src/vs/workbench/common/editor/untitledEditorInput.ts b/src/vs/workbench/common/editor/untitledEditorInput.ts index a5e88527de7..1cf37109845 100644 --- a/src/vs/workbench/common/editor/untitledEditorInput.ts +++ b/src/vs/workbench/common/editor/untitledEditorInput.ts @@ -17,7 +17,6 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IEventService} from 'vs/platform/event/common/event'; -import {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events'; import {ITextFileService} from 'vs/workbench/parts/files/common/files'; // TODO@Ben layer breaker @@ -53,19 +52,6 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { this.hasAssociatedFilePath = hasAssociatedFilePath; this.modeId = modeId; this.toUnbind = []; - - this.registerListeners(); - } - - private registerListeners(): void { - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onDirtyStateChange(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onDirtyStateChange(e))); - } - - private onDirtyStateChange(e: UntitledEditorEvent): void { - if (e.resource.toString() === this.resource.toString()) { - this._onDidChangeDirty.fire(); - } } public getTypeId(): string { @@ -97,7 +83,9 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } public revert(): TPromise { - this.cachedModel.revert(); + if (this.cachedModel) { + this.cachedModel.revert(); + } this.dispose(); // a reverted untitled editor is no longer valid, so we dispose it @@ -106,7 +94,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { public suggestFileName(): string { if (!this.hasAssociatedFilePath) { - let mime = this.getMime(); + const mime = this.getMime(); if (mime && mime !== MIME_TEXT /* do not suggest when the mime type is simple plain text */) { return suggestFilename(mime, this.getName()); } @@ -145,7 +133,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } // Otherwise Create Model and load - let model = this.createModel(); + const model = this.createModel(); return model.load().then((resolvedModel: UntitledEditorModel) => { this.cachedModel = resolvedModel; @@ -154,16 +142,21 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } private createModel(): UntitledEditorModel { - let content = ''; + const content = ''; let mime = this.modeId; if (!mime && this.hasAssociatedFilePath) { - let mimeFromPath = guessMimeTypes(this.resource.fsPath)[0]; + const mimeFromPath = guessMimeTypes(this.resource.fsPath)[0]; if (!isUnspecific(mimeFromPath)) { mime = mimeFromPath; // take most specific mime type if file path is associated and mime is specific } } - return this.instantiationService.createInstance(UntitledEditorModel, content, mime || MIME_TEXT, this.resource, this.hasAssociatedFilePath); + const model = this.instantiationService.createInstance(UntitledEditorModel, content, mime || MIME_TEXT, this.resource, this.hasAssociatedFilePath); + + // detect dirty state changes on model and re-emit + this.toUnbind.push(model.onDidChangeDirty(() => this._onDidChangeDirty.fire())); + + return model; } public matches(otherInput: any): boolean { @@ -172,7 +165,7 @@ export class UntitledEditorInput extends AbstractUntitledEditorInput { } if (otherInput instanceof UntitledEditorInput) { - let otherUntitledEditorInput = otherInput; + const otherUntitledEditorInput = otherInput; // Otherwise compare by properties return otherUntitledEditorInput.resource.toString() === this.resource.toString(); diff --git a/src/vs/workbench/common/editor/untitledEditorModel.ts b/src/vs/workbench/common/editor/untitledEditorModel.ts index fae42740555..a21fc4a9140 100644 --- a/src/vs/workbench/common/editor/untitledEditorModel.ts +++ b/src/vs/workbench/common/editor/untitledEditorModel.ts @@ -10,24 +10,31 @@ import {EditorModel, IEncodingSupport} from 'vs/workbench/common/editor'; import {StringEditorModel} from 'vs/workbench/common/editor/stringEditorModel'; import URI from 'vs/base/common/uri'; import {EventType, EndOfLinePreference} from 'vs/editor/common/editorCommon'; -import {EventType as WorkbenchEventType, UntitledEditorEvent, ResourceEvent} from 'vs/workbench/common/events'; +import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events'; import {IFilesConfiguration} from 'vs/platform/files/common/files'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {IEventService} from 'vs/platform/event/common/event'; import {IModeService} from 'vs/editor/common/services/modeService'; import {IModelService} from 'vs/editor/common/services/modelService'; +import {IMode} from 'vs/editor/common/modes'; +import {isUnspecific} from 'vs/base/common/mime'; +import Event, {Emitter} from 'vs/base/common/event'; export class UntitledEditorModel extends StringEditorModel implements IEncodingSupport { private textModelChangeListener: IDisposable; private configurationChangeListener: IDisposable; private dirty: boolean; + private _onDidChangeDirty: Emitter; + private configuredEncoding: string; private preferredEncoding: string; + private hasAssociatedFilePath: boolean; + constructor( value: string, - modeId: string, + mime: string, resource: URI, hasAssociatedFilePath: boolean, @IModeService modeService: IModeService, @@ -35,13 +42,28 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS @IEventService private eventService: IEventService, @IConfigurationService private configurationService: IConfigurationService ) { - super(value, modeId, resource, modeService, modelService); + super(value, mime, resource, modeService, modelService); + this.hasAssociatedFilePath = hasAssociatedFilePath; this.dirty = hasAssociatedFilePath; // untitled associated to file path are dirty right away + this._onDidChangeDirty = new Emitter(); + this.registerListeners(); } + public get onDidChangeDirty(): Event { + return this._onDidChangeDirty.event; + } + + protected getOrCreateMode(modeService: IModeService, mime: string, firstLineText?: string): TPromise { + if (isUnspecific(mime)) { + return modeService.getOrCreateModeByFilenameOrFirstLine(this.resource.fsPath, firstLineText); // lookup mode via resource path if the provided mime is unspecific + } + + return super.getOrCreateMode(modeService, mime, firstLineText); + } + private registerListeners(): void { // Config Changes @@ -73,7 +95,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS } public setEncoding(encoding: string): void { - let oldEncoding = this.getEncoding(); + const oldEncoding = this.getEncoding(); this.preferredEncoding = encoding; // Emit if it changed @@ -89,7 +111,7 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS public revert(): void { this.dirty = false; - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_SAVED, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); } public load(): TPromise { @@ -100,12 +122,12 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS this.configuredEncoding = configuration && configuration.files && configuration.files.encoding; // Listen to content changes - this.textModelChangeListener = this.textEditorModel.onDidChangeContent(() => this.onModelContentChanged()); + this.textModelChangeListener = this.textEditorModel.onDidChangeContent(e => this.onModelContentChanged()); // Emit initial dirty event if we are if (this.dirty) { setTimeout(() => { - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_DIRTY, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); }, 0 /* prevent race condition between creating model and emitting dirty event */); } @@ -114,9 +136,18 @@ export class UntitledEditorModel extends StringEditorModel implements IEncodingS } private onModelContentChanged(): void { + + // turn dirty if we were not if (!this.dirty) { this.dirty = true; - this.eventService.emit(WorkbenchEventType.UNTITLED_FILE_DIRTY, new UntitledEditorEvent(this.resource)); + this._onDidChangeDirty.fire(); + } + + // mark the untitled editor as non-dirty once its content becomes empty and we do + // not have an associated path set + else if (!this.hasAssociatedFilePath && this.textEditorModel.getLineCount() === 1 && this.textEditorModel.getLineContent(1) === '') { + this.dirty = false; + this._onDidChangeDirty.fire(); } } diff --git a/src/vs/workbench/common/events.ts b/src/vs/workbench/common/events.ts index 3e0492a7bac..714f37ac2ac 100644 --- a/src/vs/workbench/common/events.ts +++ b/src/vs/workbench/common/events.ts @@ -12,16 +12,6 @@ import {Event} from 'vs/base/common/events'; */ export class EventType { - /** - * Event type for when an untitled file is becoming dirty. - */ - static UNTITLED_FILE_DIRTY = 'untitledFileDirty'; - - /** - * Event type for when an untitled file is saved. - */ - static UNTITLED_FILE_SAVED = 'untitledFileSaved'; - /** * Event type for when a resources encoding changes. */ @@ -36,8 +26,4 @@ export class ResourceEvent extends Event { this.resource = resource; } -} - -export class UntitledEditorEvent extends ResourceEvent { - // No new methods } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/actions.ts b/src/vs/workbench/electron-browser/actions.ts index fe1d1f1a9f3..dd5fb9bf23e 100644 --- a/src/vs/workbench/electron-browser/actions.ts +++ b/src/vs/workbench/electron-browser/actions.ts @@ -14,12 +14,17 @@ import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/edito import {EditorInput} from 'vs/workbench/common/editor'; import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput'; import nls = require('vs/nls'); +import errors = require('vs/base/common/errors'); import {IMessageService, Severity} from 'vs/platform/message/common/message'; -import {IWindowConfiguration} from 'vs/workbench/electron-browser/window'; +import {IWindowConfiguration} from 'vs/workbench/electron-browser/common'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IEnvironmentService} from 'vs/platform/environment/common/environment'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {CommandsRegistry} from 'vs/platform/commands/common/commands'; +import paths = require('vs/base/common/paths'); +import {isMacintosh} from 'vs/base/common/platform'; +import {IQuickOpenService, IPickOpenEntry, ISeparator} from 'vs/workbench/services/quickopen/common/quickOpenService'; +import {KeyMod} from 'vs/base/common/keyCodes'; import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation'; import * as browser from 'vs/base/browser/browser'; @@ -66,6 +71,38 @@ export class CloseWindowAction extends Action { } } +export class SwitchWindow extends Action { + + public static ID = 'workbench.action.switchWindow'; + public static LABEL = nls.localize('switchWindow', "Switch Window"); + + constructor( + id: string, + label: string, + @IWindowService private windowService: IWindowService, + @IQuickOpenService private quickOpenService: IQuickOpenService + ) { + super(id, label); + } + + public run(): TPromise { + ipc.send('vscode:switchWindow', this.windowService.getWindowId()); + ipc.once('vscode:switchWindow', (event, workspaces) => { + const picks: IPickOpenEntry[] = workspaces.map(w => { + return { + label: w.title, + run: () => { + ipc.send('vscode:showWindow', w.id); + } + }; + }); + this.quickOpenService.pick(picks, {placeHolder: nls.localize('switchWindowPlaceHolder', "Select a window")}); + }); + + return TPromise.as(true); + } +} + export class CloseFolderAction extends Action { public static ID = 'workbench.action.closeFolder'; @@ -371,7 +408,9 @@ export class OpenRecentAction extends Action { constructor( id: string, label: string, - @IWindowService private windowService: IWindowService + @IWindowService private windowService: IWindowService, + @IQuickOpenService private quickOpenService: IQuickOpenService, + @IWorkspaceContextService private contextService: IWorkspaceContextService ) { super(id, label); } @@ -379,7 +418,41 @@ export class OpenRecentAction extends Action { public run(): TPromise { ipc.send('vscode:openRecent', this.windowService.getWindowId()); - return TPromise.as(true); + return new TPromise((c, e, p) => { + ipc.once('vscode:openRecent', (event, files: string[], folders: string[]) => { + this.openRecent(files, folders); + + c(true); + }); + }); + } + + private openRecent(recentFiles: string[], recentFolders: string[]): void { + function toPick(path: string, separator: ISeparator): IPickOpenEntry { + return { + label: paths.basename(path), + description: paths.dirname(path), + separator, + run: (context) => runPick(path, context) + }; + } + + function runPick(path: string, context): void { + const newWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0; + + ipc.send('vscode:windowOpen', [path], newWindow); + } + + const folderPicks: IPickOpenEntry[] = recentFolders.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('folders', "folders") } : void 0)); + const filePicks: IPickOpenEntry[] = recentFiles.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('files', "files"), border: true } : void 0)); + + const hasWorkspace = !!this.contextService.getWorkspace(); + + this.quickOpenService.pick(folderPicks.concat(...filePicks), { + autoFocus: { autoFocusFirstEntry: !hasWorkspace, autoFocusSecondEntry: hasWorkspace }, + placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select a path (hold Cmd-key to open in new window)") : nls.localize('openRecentPlaceHolder', "Select a path to open (hold Ctrl-key to open in new window)"), + matchOnDescription: true + }).done(null, errors.onUnexpectedError); } } @@ -447,4 +520,4 @@ CommandsRegistry.registerCommand('_workbench.open', function (accessor: Services return editorService.openEditor({ resource }, column).then(() => { return void 0; }); -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/electron-browser/common.ts b/src/vs/workbench/electron-browser/common.ts new file mode 100644 index 00000000000..605aeefb9cb --- /dev/null +++ b/src/vs/workbench/electron-browser/common.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +export interface IPath { + filePath: string; + lineNumber?: number; + columnNumber?: number; +} + +export interface IOpenFileRequest { + filesToOpen?: IPath[]; + filesToCreate?: IPath[]; + filesToDiff?: IPath[]; +} + +export interface IWindowConfiguration { + window: { + openFilesInNewWindow: boolean; + reopenFolders: string; + restoreFullscreen: boolean; + zoomLevel: number; + }; +} \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/integration.ts b/src/vs/workbench/electron-browser/integration.ts index d5b7f45c33b..ae64144bdfb 100644 --- a/src/vs/workbench/electron-browser/integration.ts +++ b/src/vs/workbench/electron-browser/integration.ts @@ -6,12 +6,10 @@ 'use strict'; import nls = require('vs/nls'); -import paths = require('vs/base/common/paths'); import {TPromise} from 'vs/base/common/winjs.base'; import errors = require('vs/base/common/errors'); import arrays = require('vs/base/common/arrays'); import Severity from 'vs/base/common/severity'; -import {isMacintosh} from 'vs/base/common/platform'; import {Separator} from 'vs/base/browser/ui/actionbar/actionbar'; import {IAction, Action} from 'vs/base/common/actions'; import {IPartService} from 'vs/workbench/services/part/common/partService'; @@ -23,12 +21,17 @@ import {ICommandService} from 'vs/platform/commands/common/commands'; import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {IWorkspaceContextService}from 'vs/platform/workspace/common/workspace'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; -import {IWindowConfiguration} from 'vs/workbench/electron-browser/window'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {ElectronWindow} from 'vs/workbench/electron-browser/window'; import * as browser from 'vs/base/browser/browser'; -import {IQuickOpenService, IPickOpenEntry, ISeparator} from 'vs/workbench/services/quickopen/common/quickOpenService'; -import {KeyMod} from 'vs/base/common/keyCodes'; +import {DiffEditorInput, toDiffLabel} from 'vs/workbench/common/editor/diffEditorInput'; +import {Position} from 'vs/platform/editor/common/editor'; +import {EditorInput} from 'vs/workbench/common/editor'; +import {IPath, IOpenFileRequest, IWindowConfiguration} from 'vs/workbench/electron-browser/common'; +import {IResourceInput} from 'vs/platform/editor/common/editor'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import URI from 'vs/base/common/uri'; import {ipcRenderer as ipc, webFrame, remote} from 'electron'; @@ -57,8 +60,9 @@ export class ElectronIntegration { @ICommandService private commandService: ICommandService, @IKeybindingService private keybindingService: IKeybindingService, @IMessageService private messageService: IMessageService, - @IQuickOpenService private quickOpenService: IQuickOpenService, - @IContextMenuService private contextMenuService: IContextMenuService + @IContextMenuService private contextMenuService: IContextMenuService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { } @@ -102,6 +106,9 @@ export class ElectronIntegration { } }); + // Support openFiles event for existing and new files + ipc.on('vscode:openFiles', (event, request: IOpenFileRequest) => this.onOpenFiles(request)); + // Emit event when vscode has loaded this.partService.joinCreation().then(() => { ipc.send('vscode:workbenchLoaded', this.windowService.getWindowId()); @@ -112,11 +119,6 @@ export class ElectronIntegration { this.messageService.show(Severity.Info, message); }); - // Recent files / folders - ipc.on('vscode:openRecent', (event, files: string[], folders: string[]) => { - this.openRecent(files, folders); - }); - // Ensure others can listen to zoom level changes browser.setZoomLevel(webFrame.getZoomLevel()); @@ -168,34 +170,6 @@ export class ElectronIntegration { }); } - private openRecent(recentFiles: string[], recentFolders: string[]): void { - function toPick(path: string, separator: ISeparator): IPickOpenEntry { - return { - label: paths.basename(path), - description: paths.dirname(path), - separator, - run: (context) => runPick(path, context) - }; - } - - function runPick(path: string, context): void { - const newWindow = context.keymods.indexOf(KeyMod.CtrlCmd) >= 0; - - ipc.send('vscode:windowOpen', [path], newWindow); - } - - const folderPicks: IPickOpenEntry[] = recentFolders.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('folders', "folders") } : void 0)); - const filePicks: IPickOpenEntry[] = recentFiles.map((p, index) => toPick(p, index === 0 ? { label: nls.localize('files', "files"), border: true } : void 0)); - - const hasWorkspace = !!this.contextService.getWorkspace(); - - this.quickOpenService.pick(folderPicks.concat(...filePicks), { - autoFocus: { autoFocusFirstEntry: !hasWorkspace, autoFocusSecondEntry: hasWorkspace }, - placeHolder: isMacintosh ? nls.localize('openRecentPlaceHolderMac', "Select a path (hold Cmd-key to open in new window)") : nls.localize('openRecentPlaceHolder', "Select a path to open (hold Ctrl-key to open in new window)"), - matchOnDescription: true - }).done(null, errors.onUnexpectedError); - } - private resolveKeybindings(actionIds: string[]): TPromise<{ id: string; binding: number; }[]> { return this.partService.joinCreation().then(() => { return arrays.coalesce(actionIds.map((id) => { @@ -217,4 +191,71 @@ export class ElectronIntegration { })); }); } + + private onOpenFiles(request: IOpenFileRequest): void { + let inputs: IResourceInput[] = []; + let diffMode = (request.filesToDiff.length === 2); + + if (!diffMode && request.filesToOpen) { + inputs.push(...this.toInputs(request.filesToOpen, false)); + } + + if (!diffMode && request.filesToCreate) { + inputs.push(...this.toInputs(request.filesToCreate, true)); + } + + if (diffMode) { + inputs.push(...this.toInputs(request.filesToDiff, false)); + } + + if (inputs.length) { + this.openResources(inputs, diffMode).done(null, errors.onUnexpectedError); + } + } + + private openResources(resources: IResourceInput[], diffMode: boolean): TPromise { + return this.partService.joinCreation().then(() => { + + // In diffMode we open 2 resources as diff + if (diffMode) { + return TPromise.join(resources.map(f => this.editorService.createInput(f))).then((inputs: EditorInput[]) => { + return this.editorService.openEditor(new DiffEditorInput(toDiffLabel(resources[0].resource, resources[1].resource, this.contextService), null, inputs[0], inputs[1])); + }); + } + + // For one file, just put it into the current active editor + if (resources.length === 1) { + return this.editorService.openEditor(resources[0]); + } + + // Otherwise open all + const activeEditor = this.editorService.getActiveEditor(); + return this.editorService.openEditors(resources.map((r, index) => { + return { + input: r, + position: activeEditor ? activeEditor.position : Position.LEFT + }; + })); + }); + } + + private toInputs(paths: IPath[], isNew: boolean): IResourceInput[] { + return paths.map(p => { + let input = { + resource: isNew ? this.untitledEditorService.createOrGet(URI.file(p.filePath)).getResource() : URI.file(p.filePath), + options: { + pinned: true + } + }; + + if (!isNew && p.lineNumber) { + input.options.selection = { + startLineNumber: p.lineNumber, + startColumn: p.columnNumber + }; + } + + return input; + }); + } } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/main.contribution.ts b/src/vs/workbench/electron-browser/main.contribution.ts index 7bdcc44946a..e4d69052539 100644 --- a/src/vs/workbench/electron-browser/main.contribution.ts +++ b/src/vs/workbench/electron-browser/main.contribution.ts @@ -15,7 +15,7 @@ import platform = require('vs/base/common/platform'); import {IKeybindings} from 'vs/platform/keybinding/common/keybinding'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; -import {CloseEditorAction, ReloadWindowAction, ShowStartupPerformance, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleDevToolsAction, ToggleFullScreenAction, ToggleMenuBarAction, OpenRecentAction, CloseFolderAction, CloseWindowAction, NewWindowAction, CloseMessagesAction} from 'vs/workbench/electron-browser/actions'; +import {CloseEditorAction, ReloadWindowAction, ShowStartupPerformance, ZoomResetAction, ZoomOutAction, ZoomInAction, ToggleDevToolsAction, ToggleFullScreenAction, ToggleMenuBarAction, OpenRecentAction, CloseFolderAction, CloseWindowAction, SwitchWindow, NewWindowAction, CloseMessagesAction} from 'vs/workbench/electron-browser/actions'; import {MessagesVisibleContext, NoEditorsVisibleContext} from 'vs/workbench/electron-browser/workbench'; const closeEditorOrWindowKeybindings: IKeybindings = { primary: KeyMod.CtrlCmd | KeyCode.KEY_W, win: { primary: KeyMod.CtrlCmd | KeyCode.F4, secondary: [KeyMod.CtrlCmd | KeyCode.KEY_W] }}; @@ -27,11 +27,18 @@ const fileCategory = nls.localize('file', "File"); const workbenchActionsRegistry = Registry.as(Extensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(NewWindowAction, NewWindowAction.ID, NewWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_N }), 'New Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseWindowAction, CloseWindowAction.ID, CloseWindowAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_W }), 'Close Window'); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SwitchWindow, SwitchWindow.ID, SwitchWindow.LABEL), 'Switch Window'); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(CloseFolderAction, CloseFolderAction.ID, CloseFolderAction.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_F) }), 'File: Close Folder', fileCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenRecentAction, OpenRecentAction.ID, OpenRecentAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_R, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_R } }), 'File: Open Recent', fileCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleDevToolsAction, ToggleDevToolsAction.ID, ToggleDevToolsAction.LABEL), 'Developer: Toggle Developer Tools', developerCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ZoomInAction, ZoomInAction.ID, ZoomInAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_EQUAL, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_EQUAL] }), 'View: Zoom In', viewCategory); -workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS] }), 'View: Zoom Out', viewCategory); +workbenchActionsRegistry.registerWorkbenchAction( + new SyncActionDescriptor(ZoomOutAction, ZoomOutAction.ID, ZoomOutAction.LABEL, { + primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, + secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_MINUS], + linux: { primary: KeyMod.CtrlCmd | KeyCode.US_MINUS, secondary: []} + }), 'View: Zoom Out', viewCategory +); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ZoomResetAction, ZoomResetAction.ID, ZoomResetAction.LABEL), 'View: Reset Zoom', viewCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowStartupPerformance, ShowStartupPerformance.ID, ShowStartupPerformance.LABEL), 'Developer: Startup Performance', developerCategory); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReloadWindowAction, ReloadWindowAction.ID, ReloadWindowAction.LABEL), 'Reload Window'); @@ -74,7 +81,7 @@ configurationRegistry.registerConfiguration({ }, 'workbench.editor.enablePreviewFromQuickOpen': { 'type': 'boolean', - 'description': nls.localize('enablePreviewFromQuickOpen', "Controls if opened editors from quick open show as preview. Preview editors are reused until they are kept (e.g. via double click or editing)."), + 'description': nls.localize('enablePreviewFromQuickOpen', "Controls if opened editors from Quick Open show as preview. Preview editors are reused until they are kept (e.g. via double click or editing)."), 'default': true }, 'workbench.editor.openPositioning': { @@ -85,7 +92,7 @@ configurationRegistry.registerConfiguration({ }, 'workbench.quickOpen.closeOnFocusLost': { 'type': 'boolean', - 'description': nls.localize('closeOnFocusLost', "Controls if quick open should close automatically once it looses focus."), + 'description': nls.localize('closeOnFocusLost', "Controls if Quick Open should close automatically once it loses focus."), 'default': true }, 'workbench.settings.openDefaultSettings': { @@ -141,4 +148,4 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('updateChannel', "Configure whether you receive automatic updates from an update channel. Requires a restart after change.") } } -}); \ No newline at end of file +}); diff --git a/src/vs/workbench/electron-browser/main.ts b/src/vs/workbench/electron-browser/main.ts index 17497675600..e9d09967581 100644 --- a/src/vs/workbench/electron-browser/main.ts +++ b/src/vs/workbench/electron-browser/main.ts @@ -5,6 +5,7 @@ 'use strict'; +import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; import {WorkbenchShell} from 'vs/workbench/electron-browser/shell'; import {IOptions} from 'vs/workbench/common/options'; @@ -20,36 +21,26 @@ import {EventService} from 'vs/platform/event/common/eventService'; import {LegacyWorkspaceContextService} from 'vs/workbench/services/workspace/common/contextService'; import {IWorkspace} from 'vs/platform/workspace/common/workspace'; import {WorkspaceConfigurationService} from 'vs/workbench/services/configuration/node/configurationService'; -import {IProcessEnvironment} from 'vs/code/electron-main/env'; -import {ParsedArgs} from 'vs/code/node/argv'; +import {ParsedArgs} from 'vs/platform/environment/node/argv'; import {realpath} from 'vs/base/node/pfs'; import {EnvironmentService} from 'vs/platform/environment/node/environmentService'; import path = require('path'); import fs = require('fs'); import gracefulFs = require('graceful-fs'); +import {IPath, IOpenFileRequest} from 'vs/workbench/electron-browser/common'; gracefulFs.gracefulify(fs); // enable gracefulFs const timers = (window).MonacoEnvironment.timers; -export interface IPath { - filePath: string; - lineNumber?: number; - columnNumber?: number; -} - -export interface IWindowConfiguration extends ParsedArgs { +export interface IWindowConfiguration extends ParsedArgs, IOpenFileRequest { appRoot: string; execPath: string; - userEnv: IProcessEnvironment; + userEnv: any; /* vs/code/electron-main/env/IProcessEnvironment*/ workspacePath?: string; - filesToOpen?: IPath[]; - filesToCreate?: IPath[]; - filesToDiff?: IPath[]; - extensionsToInstall?: string[]; } @@ -160,10 +151,18 @@ function openWorkbench(environment: IWindowConfiguration, workspace: IWorkspace, (self).require.config({ onError: (err: any) => { if (err.errorCode === 'load') { - shell.onUnexpectedError(errors.loaderError(err)); + shell.onUnexpectedError(loaderError(err)); } } }); }); }); +} + +function loaderError(err: Error): Error { + if (platform.isWeb) { + return new Error(nls.localize('loaderError', "Failed to load a required file. Either you are no longer connected to the internet or the server you are connected to is offline. Please refresh the browser to try again.")); + } + + return new Error(nls.localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err))); } \ No newline at end of file diff --git a/src/vs/workbench/electron-browser/shell.ts b/src/vs/workbench/electron-browser/shell.ts index 303c4ed199b..b91536b7549 100644 --- a/src/vs/workbench/electron-browser/shell.ts +++ b/src/vs/workbench/electron-browser/shell.ts @@ -15,6 +15,7 @@ import dom = require('vs/base/browser/dom'); import aria = require('vs/base/browser/ui/aria/aria'); import {dispose, IDisposable, Disposables} from 'vs/base/common/lifecycle'; import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import product from 'vs/platform/product'; import pkg from 'vs/platform/package'; import {ContextViewService} from 'vs/platform/contextview/browser/contextViewService'; @@ -35,7 +36,6 @@ import {MessageService} from 'vs/workbench/services/message/electron-browser/mes import {IRequestService} from 'vs/platform/request/common/request'; import {RequestService} from 'vs/platform/request/node/requestService'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {FileService} from 'vs/workbench/services/files/electron-browser/fileService'; import {SearchService} from 'vs/workbench/services/search/node/searchService'; import {LifecycleService} from 'vs/workbench/services/lifecycle/electron-browser/lifecycleService'; import {MainThreadService} from 'vs/workbench/services/thread/electron-browser/threadService'; @@ -55,7 +55,6 @@ import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollect import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; import {IContextViewService} from 'vs/platform/contextview/browser/contextView'; import {IEventService} from 'vs/platform/event/common/event'; -import {IFileService} from 'vs/platform/files/common/files'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; import {IMarkerService} from 'vs/platform/markers/common/markers'; import {IEnvironmentService} from 'vs/platform/environment/common/environment'; @@ -147,7 +146,7 @@ export class WorkbenchShell { const workbenchContainer = $(parent).div(); // Instantiation service with services - const [instantiationService, serviceCollection] = this.initServiceCollection(); + const [instantiationService, serviceCollection] = this.initServiceCollection(parent.getHTMLElement()); //crash reporting if (!!product.crashReporter) { @@ -209,7 +208,7 @@ export class WorkbenchShell { } } - private initServiceCollection(): [InstantiationService, ServiceCollection] { + private initServiceCollection(container: HTMLElement): [InstantiationService, ServiceCollection] { const disposables = new Disposables(); const sharedProcess = connectNet(process.env['VSCODE_SHARED_IPC_HOOK']); @@ -272,12 +271,9 @@ export class WorkbenchShell { serviceCollection.set(ITelemetryService, this.telemetryService); - this.messageService = instantiationService.createInstance(MessageService); + this.messageService = instantiationService.createInstance(MessageService, container); serviceCollection.set(IMessageService, this.messageService); - const fileService = disposables.add(instantiationService.createInstance(FileService)); - serviceCollection.set(IFileService, fileService); - const lifecycleService = instantiationService.createInstance(LifecycleService); this.toUnbind.push(lifecycleService.onShutdown(() => disposables.dispose())); serviceCollection.set(ILifecycleService, lifecycleService); @@ -411,7 +407,7 @@ export class WorkbenchShell { } public onUnexpectedError(error: any): void { - const errorMsg = errors.toErrorMessage(error, true); + const errorMsg = toErrorMessage(error, true); if (!errorMsg) { return; } @@ -433,7 +429,7 @@ export class WorkbenchShell { } } - public layout(): void { + private layout(): void { const clArea = $(this.container).getClientArea(); const contentsSize = new Dimension(clArea.width, clArea.height); diff --git a/src/vs/workbench/electron-browser/update.ts b/src/vs/workbench/electron-browser/update.ts index 037fe13bebc..1a55fda1e71 100644 --- a/src/vs/workbench/electron-browser/update.ts +++ b/src/vs/workbench/electron-browser/update.ts @@ -59,14 +59,14 @@ export class Update { ipc.on('vscode:update-downloaded', (event, update: IUpdate) => { this.messageService.show(severity.Info, { message: nls.localize('updateAvailable', "{0} will be updated after it restarts.", product.nameLong), - actions: [ShowReleaseNotesAction(product.releaseNotesUrl), NotNowAction, ApplyUpdateAction] + actions: [ApplyUpdateAction, NotNowAction, ShowReleaseNotesAction(product.releaseNotesUrl)] }); }); ipc.on('vscode:update-available', (event, url: string) => { this.messageService.show(severity.Info, { message: nls.localize('thereIsUpdateAvailable', "There is an available update."), - actions: [ShowReleaseNotesAction(product.releaseNotesUrl), NotNowAction, DownloadAction(url)] + actions: [DownloadAction(url), NotNowAction, ShowReleaseNotesAction(product.releaseNotesUrl)] }); }); diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index 8df2e2a6889..06f635016b3 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -10,7 +10,7 @@ import URI from 'vs/base/common/uri'; import DOM = require('vs/base/browser/dom'); import DND = require('vs/base/browser/dnd'); import {Builder, $} from 'vs/base/browser/builder'; -import {Identifiers} from 'vs/workbench/common/constants'; +import {IPartService} from 'vs/workbench/services/part/common/partService'; import {asFileEditorInput} from 'vs/workbench/common/editor'; import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; @@ -24,15 +24,6 @@ import * as path from 'path'; const dialog = remote.dialog; -export interface IWindowConfiguration { - window: { - openFilesInNewWindow: boolean; - reopenFolders: string; - restoreFullscreen: boolean; - zoomLevel: number; - }; -} - enum DraggedFileType { UNKNOWN, FILE, @@ -51,7 +42,8 @@ export class ElectronWindow { @IStorageService private storageService: IStorageService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEditorGroupService private editorGroupService: IEditorGroupService, - @IViewletService private viewletService: IViewletService + @IViewletService private viewletService: IViewletService, + @IPartService private partService: IPartService ) { this.win = win; this.windowId = win.id; @@ -98,7 +90,7 @@ export class ElectronWindow { return kind === DraggedFileType.FOLDER || kind === DraggedFileType.EXTENSION; })) { - dropOverlay = $(window.document.getElementById(Identifiers.WORKBENCH_CONTAINER)) + dropOverlay = $(window.document.getElementById(this.partService.getWorkbenchElementId())) .div({ id: 'monaco-workbench-drop-overlay' }) .on(DOM.EventType.DROP, (e: DragEvent) => { DOM.EventHelper.stop(e, true); diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index a98a5ff2bec..fd8673f415c 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -16,8 +16,8 @@ import {Delayer} from 'vs/base/common/async'; import assert = require('vs/base/common/assert'); import timer = require('vs/base/common/timer'); import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {Registry} from 'vs/platform/platform'; -import {Identifiers} from 'vs/workbench/common/constants'; import {isWindows, isLinux} from 'vs/base/common/platform'; import {IOptions} from 'vs/workbench/common/options'; import {IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions} from 'vs/workbench/common/contributions'; @@ -52,6 +52,8 @@ import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding'; import {ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey} from 'vs/platform/contextkey/common/contextkey'; import {IActivityService} from 'vs/workbench/services/activity/common/activityService'; import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService'; +import {FileService} from 'vs/workbench/services/files/electron-browser/fileService'; +import {IFileService} from 'vs/platform/files/common/files'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; import {WorkbenchMessageService} from 'vs/workbench/services/message/browser/messageService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; @@ -86,6 +88,15 @@ export interface IWorkbenchCallbacks { onWorkbenchStarted?: (customKeybindingsCount: number) => void; } +const Identifiers = { + WORKBENCH_CONTAINER: 'workbench.main.container', + ACTIVITYBAR_PART: 'workbench.parts.activitybar', + SIDEBAR_PART: 'workbench.parts.sidebar', + PANEL_PART: 'workbench.parts.panel', + EDITOR_PART: 'workbench.parts.editor', + STATUSBAR_PART: 'workbench.parts.statusbar' +}; + /** * The workbench creates and lays out all parts that make up the workbench. */ @@ -227,7 +238,7 @@ export class Workbench implements IPartService { // Load Editors const editorTimerEvent = timer.start(timer.Topic.STARTUP, strings.format('Restoring Editor(s)')); - compositeAndEditorPromises.push(this.resolveEditorsToOpen().then((inputsWithOptions) => { + compositeAndEditorPromises.push(this.resolveEditorsToOpen().then(inputsWithOptions => { let editorOpenPromise: TPromise; if (inputsWithOptions.length) { const editors = inputsWithOptions.map((inputWithOptions, index) => { @@ -264,11 +275,11 @@ export class Workbench implements IPartService { }; // Join viewlet, panel and editor promises - TPromise.join(compositeAndEditorPromises).then(() => workbenchDone(), (error) => workbenchDone(error)); + TPromise.join(compositeAndEditorPromises).then(() => workbenchDone(), error => workbenchDone(error)); } catch (error) { // Print out error - console.error(errors.toErrorMessage(error, true)); + console.error(toErrorMessage(error, true)); // Rethrow throw error; @@ -286,7 +297,7 @@ export class Workbench implements IPartService { // Files to diff is exclusive if (filesToDiff && filesToDiff.length) { - return TPromise.join(filesToDiff.map((resourceInput) => this.editorService.createInput(resourceInput))).then((inputsToDiff) => { + return TPromise.join(filesToDiff.map(resourceInput => this.editorService.createInput(resourceInput))).then((inputsToDiff) => { return [{ input: new DiffEditorInput(toDiffLabel(filesToDiff[0].resource, filesToDiff[1].resource, this.contextService), null, inputsToDiff[0], inputsToDiff[1]) }]; }); } @@ -297,11 +308,11 @@ export class Workbench implements IPartService { const options: EditorOptions[] = []; // Files to create - inputs.push(...filesToCreate.map((resourceInput) => this.untitledEditorService.createOrGet(resourceInput.resource))); + inputs.push(...filesToCreate.map(resourceInput => this.untitledEditorService.createOrGet(resourceInput.resource))); options.push(...filesToCreate.map(r => null)); // fill empty options for files to create because we dont have options there // Files to open - return TPromise.join(filesToOpen.map((resourceInput) => this.editorService.createInput(resourceInput))).then((inputsToOpen) => { + return TPromise.join(filesToOpen.map(resourceInput => this.editorService.createInput(resourceInput))).then((inputsToOpen) => { inputs.push(...inputsToOpen); options.push(...filesToOpen.map(resourceInput => TextEditorOptions.from(resourceInput))); @@ -371,6 +382,10 @@ export class Workbench implements IPartService { serviceCollection.set(IWorkbenchEditorService, this.editorService); serviceCollection.set(IEditorGroupService, this.editorPart); + // File Service + const fileService = this.instantiationService.createInstance(FileService); + serviceCollection.set(IFileService, fileService); + // History serviceCollection.set(IHistoryService, this.instantiationService.createInstance(HistoryService)); @@ -547,6 +562,13 @@ export class Workbench implements IPartService { public setPanelHidden(hidden: boolean, skipLayout?: boolean): void { this.panelHidden = hidden; + // Adjust CSS + if (hidden) { + this.workbench.addClass('nopanel'); + } else { + this.workbench.removeClass('nopanel'); + } + // Layout if (!skipLayout) { this.workbenchLayout.layout(true); @@ -705,6 +727,9 @@ export class Workbench implements IPartService { if (this.sideBarHidden) { this.workbench.addClass('nosidebar'); } + if (this.panelHidden) { + this.workbench.addClass('nopanel'); + } // Apply no-workspace state as CSS class if (!this.workbenchParams.workspace) { @@ -811,4 +836,8 @@ export class Workbench implements IPartService { this.workbench.removeClass(clazz); } } + + public getWorkbenchElementId(): string { + return Identifiers.WORKBENCH_CONTAINER; + } } \ No newline at end of file diff --git a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts index 1711c4e21e0..43cf9dcf42e 100644 --- a/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts +++ b/src/vs/workbench/parts/cli/electron-browser/cli.contribution.ts @@ -78,8 +78,8 @@ class InstallAction extends Action { return this.editorService.openEditor(input).then(() => { const message = nls.localize('again', "Please remove the '{0}' alias from '{1}' before continuing.", product.applicationName, file); const actions = [ - new Action('cancel', nls.localize('cancel', "Cancel")), - new Action('continue', nls.localize('continue', "Continue"), '', true, () => this.run()) + new Action('continue', nls.localize('continue', "Continue"), '', true, () => this.run()), + new Action('cancel', nls.localize('cancel', "Cancel")) ]; this.messageService.show(Severity.Info, { message, actions }); @@ -128,7 +128,6 @@ class InstallAction extends Action { return new TPromise((c, e) => { const message = nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."); const actions = [ - new Action('cancel2', nls.localize('cancel2', "Cancel"), '', true, () => { e(new Error(nls.localize('aborted', "Aborted"))); return null; }), new Action('ok', nls.localize('ok', "OK"), '', true, () => { const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && chown \\" & (do shell script (\\"whoami\\")) & \\" /usr/local/bin\\" with administrator privileges"'; @@ -137,7 +136,8 @@ class InstallAction extends Action { .done(c, e); return null; - }) + }), + new Action('cancel2', nls.localize('cancel2', "Cancel"), '', true, () => { e(new Error(nls.localize('aborted', "Aborted"))); return null; }) ]; this.messageService.show(Severity.Info, { message, actions }); @@ -211,7 +211,7 @@ class DarwinCLIHelper implements IWorkbenchContribution { messageService.show(Severity.Info, nls.localize('laterInfo', "Remember you can always run the '{0}' action from the Command Palette.", installAction.label)); return null; }); - const actions = [later, now]; + const actions = [now, later]; messageService.show(Severity.Info, { message, actions }); } diff --git a/src/vs/workbench/parts/debug/browser/debugActionItems.ts b/src/vs/workbench/parts/debug/browser/debugActionItems.ts index e8e0008aff5..4bb92482f06 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionItems.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionItems.ts @@ -38,14 +38,15 @@ export class DebugSelectActionItem extends SelectActionItem { } private updateOptions(changeDebugConfiguration: boolean): TPromise { - return this.debugService.getConfigurationManager().loadLaunchConfig().then(config => { + const configurationManager = this.debugService.getConfigurationManager(); + return configurationManager.loadLaunchConfig().then(config => { if (!config || !config.configurations || config.configurations.length === 0) { this.setOptions([nls.localize('noConfigurations', "No Configurations")], 0); return changeDebugConfiguration ? this.actionRunner.run(this._action, null) : null; } const configurationNames = config.configurations.filter(cfg => !!cfg.name).map(cfg => cfg.name); - const configurationName = this.debugService.getConfigurationManager().configurationName; + const configurationName = configurationManager.configuration ? configurationManager.configuration.name : null; let selected = configurationNames.indexOf(configurationName); this.setOptions(configurationNames, selected); diff --git a/src/vs/workbench/parts/debug/browser/debugActions.ts b/src/vs/workbench/parts/debug/browser/debugActions.ts index 71ff31e3053..6e7204854db 100644 --- a/src/vs/workbench/parts/debug/browser/debugActions.ts +++ b/src/vs/workbench/parts/debug/browser/debugActions.ts @@ -142,12 +142,12 @@ export class RestartAction extends AbstractDebugAction { constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) { super(id, label, 'debug-action restart', debugService, keybindingService); - this.toDispose.push(this.debugService.onDidChangeState(() => { - const session = this.debugService.getActiveSession(); - if (session) { - this.updateLabel(session.configuration.isAttach ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL); - } - })); + this.setLabel(this.debugService.getConfigurationManager().configuration); + this.toDispose.push(this.debugService.getConfigurationManager().onDidConfigurationChange(config => this.setLabel(config))); + } + + private setLabel(config: debug.IConfig): void { + this.updateLabel(config.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL); } public run(): TPromise { @@ -273,8 +273,7 @@ export class DisconnectAction extends AbstractDebugAction { } protected isEnabled(state: debug.State): boolean { - const session = this.debugService.getActiveSession(); - return super.isEnabled(state) && state !== debug.State.Inactive && session && session.configuration.isAttach; + return super.isEnabled(state) && state !== debug.State.Inactive; } } @@ -615,7 +614,7 @@ class RunToCursorAction extends EditorAction { id: 'editor.debug.action.runToCursor', label: nls.localize('runToCursor', "Debug: Run to Cursor"), alias: 'Debug: Run to Cursor', - precondition: debug.CONTEXT_IN_DEBUG_MODE, + precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_MODE, debug.CONTEXT_NOT_IN_DEBUG_REPL), menuOpts: { group: 'debug', order: 2 @@ -676,7 +675,7 @@ class SelectionToReplAction extends EditorAction { id: 'editor.debug.action.selectionToRepl', label: nls.localize('debugEvaluate', "Debug: Evaluate"), alias: 'Debug: Evaluate', - precondition: ContextKeyExpr.and(EditorContextKeys.HasNonEmptySelection, debug.CONTEXT_IN_DEBUG_MODE), + precondition: ContextKeyExpr.and(EditorContextKeys.HasNonEmptySelection, debug.CONTEXT_IN_DEBUG_MODE, debug.CONTEXT_NOT_IN_DEBUG_REPL), menuOpts: { group: 'debug', order: 0 @@ -703,7 +702,7 @@ class SelectionToWatchExpressionsAction extends EditorAction { id: 'editor.debug.action.selectionToWatch', label: nls.localize('debugAddToWatch', "Debug: Add to Watch"), alias: 'Debug: Add to Watch', - precondition: ContextKeyExpr.and(EditorContextKeys.HasNonEmptySelection, debug.CONTEXT_IN_DEBUG_MODE), + precondition: ContextKeyExpr.and(EditorContextKeys.HasNonEmptySelection, debug.CONTEXT_IN_DEBUG_MODE, debug.CONTEXT_NOT_IN_DEBUG_REPL), menuOpts: { group: 'debug', order: 1 diff --git a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts index a5377113885..8f3fea28435 100644 --- a/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts +++ b/src/vs/workbench/parts/debug/browser/debugActionsWidget.ts @@ -12,7 +12,7 @@ import {StandardMouseEvent} from 'vs/base/browser/mouseEvent'; import actions = require('vs/base/common/actions'); import events = require('vs/base/common/events'); import actionbar = require('vs/base/browser/ui/actionbar/actionbar'); -import constants = require('vs/workbench/common/constants'); +import {IPartService} from 'vs/workbench/services/part/common/partService'; import wbext = require('vs/workbench/common/contributions'); import debug = require('vs/workbench/parts/debug/common/debug'); import {PauseAction, ContinueAction, StepBackAction, StopAction, DisconnectAction, StepOverAction, StepIntoAction, StepOutAction, RestartAction} from 'vs/workbench/parts/debug/browser/debugActions'; @@ -47,6 +47,7 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution { @ITelemetryService private telemetryService: ITelemetryService, @IDebugService private debugService: IDebugService, @IInstantiationService private instantiationService: IInstantiationService, + @IPartService private partService: IPartService, @IStorageService private storageService: IStorageService ) { this.$el = $().div().addClass('debug-actions-widget'); @@ -146,7 +147,7 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution { } if (!this.isBuilt) { this.isBuilt = true; - this.$el.build(builder.withElementById(constants.Identifiers.WORKBENCH_CONTAINER).getHTMLElement()); + this.$el.build(builder.withElementById(this.partService.getWorkbenchElementId()).getHTMLElement()); } this.isVisible = true; @@ -180,10 +181,11 @@ export class DebugActionsWidget implements wbext.IWorkbenchContribution { this.toDispose.push(this.pauseAction); this.toDispose.push(this.disconnectAction); } - + this.actions[0] = state === debug.State.Running ? this.pauseAction : this.continueAction; const session = this.debugService.getActiveSession(); - this.actions[5] = session && session.configuration.isAttach ? this.disconnectAction : this.stopAction; + const configuration = this.debugService.getConfigurationManager().configuration; + this.actions[5] = configuration && configuration.request === 'attach' ? this.disconnectAction : this.stopAction; if (session && session.configuration.capabilities.supportsStepBack) { if (!this.stepBackAction) { diff --git a/src/vs/workbench/parts/debug/browser/debugEditorInputs.ts b/src/vs/workbench/parts/debug/browser/debugEditorInputs.ts index a8248db86a6..6cde849cf52 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorInputs.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorInputs.ts @@ -3,8 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import {TPromise} from 'vs/base/common/winjs.base'; import mime = require('vs/base/common/mime'); import strinput = require('vs/workbench/common/editor/stringEditorInput'); +import {EditorInput, EditorModel} from 'vs/workbench/common/editor'; import uri from 'vs/base/common/uri'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; @@ -26,3 +28,24 @@ export class DebugStringEditorInput extends strinput.StringEditorInput { return this.resourceUrl; } } + +export class DebugErrorEditorInput extends EditorInput { + + public static ID = 'workbench.editors.debugErrorEditorInput'; + + constructor(private name: string, public value: string) { + super(); + } + + public getTypeId(): string { + return DebugErrorEditorInput.ID; + } + + public resolve(refresh?: boolean): TPromise { + return TPromise.as(null); + } + + public getName(): string { + return this.name; + } +} diff --git a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts index fbbde7441a4..864b9f2529b 100644 --- a/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts +++ b/src/vs/workbench/parts/debug/browser/debugEditorModelManager.ts @@ -287,12 +287,23 @@ export class DebugEditorModelManager implements IWorkbenchContribution { result.glyphMarginHoverMessage = breakpoint.message; } - return result ? result : - !session || session.configuration.capabilities.supportsConditionalBreakpoints ? { + if (result) { + return result; + } + + if (!session || session.configuration.capabilities.supportsConditionalBreakpoints) { + const mode = modelData.model.getMode(); + const modeId = mode ? mode.getId() : ''; + const glyphMarginHoverMessage = `\`\`\`${modeId}\n${ breakpoint.condition }\`\`\``; + + return { glyphMarginClassName: 'debug-breakpoint-conditional-glyph', - glyphMarginHoverMessage: breakpoint.condition, + glyphMarginHoverMessage, stickiness: editorcommon.TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges - } : DebugEditorModelManager.BREAKPOINT_UNSUPPORTED_DECORATION; + }; + } + + return DebugEditorModelManager.BREAKPOINT_UNSUPPORTED_DECORATION; } // editor decorations diff --git a/src/vs/workbench/parts/debug/browser/debugErrorEditor.ts b/src/vs/workbench/parts/debug/browser/debugErrorEditor.ts new file mode 100644 index 00000000000..70a850ecb39 --- /dev/null +++ b/src/vs/workbench/parts/debug/browser/debugErrorEditor.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import {TPromise} from 'vs/base/common/winjs.base'; +import dom = require('vs/base/browser/dom'); +import {Dimension, Builder} from 'vs/base/browser/builder'; +import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {EditorOptions} from 'vs/workbench/common/editor'; +import {DebugErrorEditorInput} from 'vs/workbench/parts/debug/browser/debugEditorInputs'; +import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor'; +const $ = dom.$; + +export class DebugErrorEditor extends BaseEditor { + static ID = 'workbench.editor.debugError'; + private container: HTMLElement; + + constructor(@ITelemetryService telemetryService: ITelemetryService) { + super(DebugErrorEditor.ID, telemetryService); + } + + public createEditor(parent: Builder): void { + this.container = dom.append(parent.getHTMLElement(), $('.')); + this.container.style.paddingLeft = '20px'; + } + + public layout(dimension: Dimension): void { + // we take the padding we set on create into account + this.container.style.width = `${Math.max(dimension.width - 20, 0)}px`; + this.container.style.height = `${dimension.height}px`; + } + + public setInput(input: DebugErrorEditorInput, options: EditorOptions): TPromise { + this.container.textContent = input.value; + return super.setInput(input, options); + } +} diff --git a/src/vs/workbench/parts/debug/browser/media/debugHover.css b/src/vs/workbench/parts/debug/browser/media/debugHover.css index 73c87347b2a..4f3121b073e 100644 --- a/src/vs/workbench/parts/debug/browser/media/debugHover.css +++ b/src/vs/workbench/parts/debug/browser/media/debugHover.css @@ -43,16 +43,6 @@ background-color: inherit; } -/* Allign twistie since hover tree is smaller */ - -.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { - top: 6px; -} - -.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { - top: 8px; -} - .monaco-editor .debug-hover-widget .debug-hover-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row { cursor: default; } diff --git a/src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg b/src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg old mode 100644 new mode 100755 index 606d91dd9cd..705376bb0fb --- a/src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg +++ b/src/vs/workbench/parts/debug/browser/media/disconnect-inverse.svg @@ -1,12 +1 @@ - - - - - Disconnect_16x_inverse - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/disconnect.svg b/src/vs/workbench/parts/debug/browser/media/disconnect.svg old mode 100644 new mode 100755 index b412a9b21af..dd1d27e74b5 --- a/src/vs/workbench/parts/debug/browser/media/disconnect.svg +++ b/src/vs/workbench/parts/debug/browser/media/disconnect.svg @@ -1,12 +1 @@ - - - - - Disconnect_16x - - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/browser/media/repl.css b/src/vs/workbench/parts/debug/browser/media/repl.css index d76cafaa93a..da25304d6af 100644 --- a/src/vs/workbench/parts/debug/browser/media/repl.css +++ b/src/vs/workbench/parts/debug/browser/media/repl.css @@ -74,20 +74,12 @@ /* Allign twistie since repl tree is smaller and sometimes has paired elements */ -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:before { - top: 6px; -} - -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children > .content:after { +.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row.has-children > .content.input-output-pair:before { top: 8px; } -.monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row.has-children > .content.input-output-pair:before { - top: 23px; -} - .monaco-workbench .repl .repl-tree .monaco-tree .monaco-tree-row.has-children > .content.input-output-pair:after { - top: 25px; + top: 10px; } .monaco-workbench .repl .repl-input-wrapper { @@ -117,11 +109,6 @@ content: ''; } -.monaco-workbench .repl .repl-input-wrapper .monaco-editor .current-line { - /* Hide selected line highlight */ - border: none; -} - .hc-black .monaco-workbench .repl .repl-input-wrapper { border-top-color: #6FC3DF; } diff --git a/src/vs/workbench/parts/debug/common/debug.ts b/src/vs/workbench/parts/debug/common/debug.ts index 3ecae97d403..170793ec2fd 100644 --- a/src/vs/workbench/parts/debug/common/debug.ts +++ b/src/vs/workbench/parts/debug/common/debug.ts @@ -19,7 +19,11 @@ export const VIEWLET_ID = 'workbench.view.debug'; export const REPL_ID = 'workbench.panel.repl'; export const DEBUG_SERVICE_ID = 'debugService'; export const CONTEXT_IN_DEBUG_MODE = new RawContextKey('inDebugMode', false); -export const CONTEXT_NOT_IN_DEBUG_MODE:ContextKeyExpr = CONTEXT_IN_DEBUG_MODE.toNegated(); +export const CONTEXT_NOT_IN_DEBUG_MODE: ContextKeyExpr = CONTEXT_IN_DEBUG_MODE.toNegated(); +export const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); +export const CONTEXT_NOT_IN_DEBUG_REPL: ContextKeyExpr = CONTEXT_IN_DEBUG_REPL.toNegated(); +export const CONTEXT_ON_FIRST_DEBUG_REPL_LINE = new RawContextKey('onFirsteDebugReplLine', false); +export const CONTEXT_ON_LAST_DEBUG_REPL_LINE = new RawContextKey('onLastDebugReplLine', false); export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug'; export const DEBUG_SCHEME = 'debug'; @@ -248,7 +252,7 @@ export interface IRawBreakpointContribution { } export interface IRawDebugSession { - configuration: { type: string, isAttach: boolean, capabilities: DebugProtocol.Capabilites }; + configuration: { type: string, capabilities: DebugProtocol.Capabilities }; disconnect(restart?: boolean, force?: boolean): TPromise; @@ -263,7 +267,7 @@ export interface IRawDebugSession { } export interface IConfigurationManager { - configurationName: string; + configuration: IConfig; setConfiguration(name: string): TPromise; openConfigFile(sideBySide: boolean): TPromise; loadLaunchConfig(): TPromise; @@ -272,7 +276,7 @@ export interface IConfigurationManager { /** * Allows to register on change of debug configuration. */ - onDidConfigurationChange: Event; + onDidConfigurationChange: Event; } export const IDebugService = createDecorator(DEBUG_SERVICE_ID); diff --git a/src/vs/workbench/parts/debug/common/debugModel.ts b/src/vs/workbench/parts/debug/common/debugModel.ts index cd03b1cfc3e..bda50621ce2 100644 --- a/src/vs/workbench/parts/debug/common/debugModel.ts +++ b/src/vs/workbench/parts/debug/common/debugModel.ts @@ -343,7 +343,7 @@ export class Variable extends ExpressionContainer implements debug.IExpression { public available = true, startOfVariables = 0 ) { - super(reference, `variable:${ parent.getId() }:${ name }`, true, namedVariables, indexedVariables, startOfVariables); + super(reference, `variable:${parent.getId()}:${name}:${reference}`, true, namedVariables, indexedVariables, startOfVariables); this.value = massageValue(value); } } diff --git a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts index aa29f8a2808..1db607cae3a 100644 --- a/src/vs/workbench/parts/debug/common/debugProtocol.d.ts +++ b/src/vs/workbench/parts/debug/common/debugProtocol.d.ts @@ -3,41 +3,45 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -/** Declaration module describing the VS Code debug protocol - */ +/** Declaration module describing the VS Code debug protocol. + Auto-generated from json schema. Do not edit manually. +*/ declare module DebugProtocol { /** Base class of requests, responses, and events. */ export interface ProtocolMessage { - /** Sequence number */ + /** Sequence number. */ seq: number; - /** One of "request", "response", or "event" */ + /** One of 'request', 'response', or 'event'. */ type: string; } - /** Client-initiated request */ + /** A client or server-initiated request. */ export interface Request extends ProtocolMessage { - /** The command to execute */ + // type: 'request'; + /** The command to execute. */ command: string; - /** Object containing arguments for the command */ + /** Object containing arguments for the command. */ arguments?: any; } - /** Server-initiated event */ + /** Server-initiated event. */ export interface Event extends ProtocolMessage { - /** Type of event */ + // type: 'event'; + /** Type of event. */ event: string; - /** Event-specific information */ + /** Event-specific information. */ body?: any; } - /** Server-initiated response to client request */ + /** Response to a request. */ export interface Response extends ProtocolMessage { - /** Sequence number of the corresponding request */ + // type: 'response'; + /** Sequence number of the corresponding request. */ request_seq: number; - /** Outcome of the request */ + /** Outcome of the request. */ success: boolean; - /** The command requested */ + /** The command requested. */ command: string; /** Contains error message if success == false. */ message?: string; @@ -45,9 +49,7 @@ declare module DebugProtocol { body?: any; } - //---- Events - - /** Event message for "initialized" event type. + /** Event message for 'initialized' event type. This event indicates that the debug adapter is ready to accept configuration requests (e.g. SetBreakpointsRequest, SetExceptionBreakpointsRequest). A debug adapter is expected to send this event when it is ready to accept configuration requests (but not before the InitializeRequest has finished). The sequence of events/requests is as follows: @@ -59,13 +61,15 @@ declare module DebugProtocol { - frontend sends one ConfigurationDoneRequest to indicate the end of the configuration */ export interface InitializedEvent extends Event { + // event: 'initialized'; } - /** Event message for "stopped" event type. + /** Event message for 'stopped' event type. The event indicates that the execution of the debuggee has stopped due to some condition. This can be caused by a break point previously set, a stepping action has completed, by executing a debugger statement etc. */ export interface StoppedEvent extends Event { + // event: 'stopped'; body: { /** The reason for the event (such as: 'step', 'breakpoint', 'exception', 'pause'). This string is shown in the UI. */ reason: string; @@ -74,51 +78,55 @@ declare module DebugProtocol { /** Additional information. E.g. if reason is 'exception', text contains the exception name. This string is shown in the UI. */ text?: string; /** If allThreadsStopped is true, a debug adapter can announce that all threads have stopped. - * The client should use this information to enable that all threads can be expanded to access their stacktraces. - * If the attribute is missing or false, only the thread with the given threadId can be expanded. - **/ + * The client should use this information to enable that all threads can be expanded to access their stacktraces. + * If the attribute is missing or false, only the thread with the given threadId can be expanded. + */ allThreadsStopped?: boolean; }; } - /** Event message for "continued" event type. + /** Event message for 'continued' event type. The event indicates that the execution of the debuggee has continued. Please note: a debug adapter is not expected to send this event in response to a request that implies that execution continues, e.g. 'launch' or 'continue'. It is only necessary to send a ContinuedEvent if there was no previous request that implied this. */ export interface ContinuedEvent extends Event { + // event: 'continued'; body: { /** The thread which was continued. */ threadId: number; - /** If allThreadsContinued is true, a debug adapter can announce that all threads have continued. **/ + /** If allThreadsContinued is true, a debug adapter can announce that all threads have continued. */ allThreadsContinued?: boolean; }; } - /** Event message for "exited" event type. + /** Event message for 'exited' event type. The event indicates that the debuggee has exited. */ export interface ExitedEvent extends Event { + // event: 'exited'; body: { /** The exit code returned from the debuggee. */ exitCode: number; }; } - /** Event message for "terminated" event types. + /** Event message for 'terminated' event types. The event indicates that debugging of the debuggee has terminated. */ export interface TerminatedEvent extends Event { + // event: 'terminated'; body?: { /** A debug adapter may set 'restart' to true to request that the front end restarts the session. */ restart?: boolean; - } + }; } - /** Event message for "thread" event type. + /** Event message for 'thread' event type. The event indicates that a thread has started or exited. */ export interface ThreadEvent extends Event { + // event: 'thread'; body: { /** The reason for the event (such as: 'started', 'exited'). */ reason: string; @@ -127,10 +135,11 @@ declare module DebugProtocol { }; } - /** Event message for "output" event type. + /** Event message for 'output' event type. The event indicates that the target has produced output. */ export interface OutputEvent extends Event { + // event: 'output'; body: { /** The category of output (such as: 'console', 'stdout', 'stderr', 'telemetry'). If not specified, 'console' is assumed. */ category?: string; @@ -141,39 +150,41 @@ declare module DebugProtocol { }; } - /** Event message for "breakpoint" event type. + /** Event message for 'breakpoint' event type. The event indicates that some information about a breakpoint has changed. */ export interface BreakpointEvent extends Event { + // event: 'breakpoint'; body: { /** The reason for the event (such as: 'changed', 'new'). */ reason: string; /** The breakpoint. */ breakpoint: Breakpoint; - } + }; } - /** Event message for "module" event type. + /** Event message for 'module' event type. The event indicates that some information about a module has changed. - */ + */ export interface ModuleEvent extends Event { + // event: 'module'; body: { /** The reason for the event. */ reason: 'new' | 'changed' | 'removed'; /** The new, changed, or removed module. In case of 'removed' only the module id is used. */ module: Module; - } + }; } - //---- Frontend Requests - - /** runInTerminal request; value of command field is "runInTerminal". + /** runInTerminal request; value of command field is 'runInTerminal'. With this request a debug adapter can run a command in a terminal. */ export interface RunInTerminalRequest extends Request { + // command: 'runInTerminal'; arguments: RunInTerminalRequestArguments; } - /** Arguments for "runInTerminal" request. */ + + /** Arguments for 'runInTerminal' request. */ export interface RunInTerminalRequestArguments { /** What kind of terminal to launch. */ kind?: 'integrated' | 'external'; @@ -184,33 +195,32 @@ declare module DebugProtocol { /** List of arguments. The first argument is the command to run. */ args: string[]; /** Environment key-value pairs that are added to the default environment. */ - env?: { [key: string]: string; } + env?: { [key: string]: string; }; } + /** Response to Initialize request. */ export interface RunInTerminalResponse extends Response { body: { - /** The process ID */ + /** The process ID. */ processId?: number; }; } - //---- Debug Adapter Requests - - /** On error that is whenever 'success' is false, the body can provide more details. - */ + /** On error that is whenever 'success' is false, the body can provide more details. */ export interface ErrorResponse extends Response { body: { /** An optional, structured error message. */ - error?: Message - } + error?: Message; + }; } - /** Initialize request; value of command field is "initialize". - */ + /** Initialize request; value of command field is 'initialize'. */ export interface InitializeRequest extends Request { + // command: 'initialize'; arguments: InitializeRequestArguments; } - /** Arguments for "initialize" request. */ + + /** Arguments for 'initialize' request. */ export interface InitializeRequestArguments { /** The ID of the debugger adapter. Used to select or verify debugger adapter. */ adapterID: string; @@ -220,7 +230,6 @@ declare module DebugProtocol { columnsStartAt1?: boolean; /** Determines in what format paths are specified. Possible values are 'path' or 'uri'. The default is 'path', which is the native format. */ pathFormat?: string; - /** Client supports the optional type attribute for variables. */ supportsVariableType?: boolean; /** Client supports the paging of variables. */ @@ -228,74 +237,90 @@ declare module DebugProtocol { /** Client supports the runInTerminal request. */ supportsRunInTerminalRequest?: boolean; } - /** Response to Initialize request. */ + + /** Response to 'initialize' request. */ export interface InitializeResponse extends Response { - /** The capabilities of this debug adapter */ - body?: Capabilites; + /** The capabilities of this debug adapter. */ + body?: Capabilities; } - /** ConfigurationDone request; value of command field is "configurationDone". - The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the InitializedEvent) + /** ConfigurationDone request; value of command field is 'configurationDone'. + The client of the debug protocol must send this request at the end of the sequence of configuration requests (which was started by the InitializedEvent). */ export interface ConfigurationDoneRequest extends Request { + // command: 'configurationDone'; arguments?: ConfigurationDoneArguments; } - /** Arguments for "configurationDone" request. */ + + /** Arguments for 'configurationDone' request. + The configurationDone request has no standardized attributes. + */ export interface ConfigurationDoneArguments { - /* The configurationDone request has no standardized attributes. */ } - /** Response to "configurationDone" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'configurationDone' request. This is just an acknowledgement, so no body field is required. */ export interface ConfigurationDoneResponse extends Response { } - /** Launch request; value of command field is "launch". - */ + /** Launch request; value of command field is 'launch'. */ export interface LaunchRequest extends Request { + // command: 'launch'; arguments: LaunchRequestArguments; } - /** Arguments for "launch" request. */ + + /** Arguments for 'launch' request. */ export interface LaunchRequestArguments { - /* If noDebug is true the launch request should launch the program without enabling debugging. */ + /** If noDebug is true the launch request should launch the program without enabling debugging. */ noDebug?: boolean; } - /** Response to "launch" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'launch' request. This is just an acknowledgement, so no body field is required. */ export interface LaunchResponse extends Response { } - /** Attach request; value of command field is "attach". - */ + /** Attach request; value of command field is 'attach'. */ export interface AttachRequest extends Request { + // command: 'attach'; arguments: AttachRequestArguments; } - /** Arguments for "attach" request. */ + + /** Arguments for 'attach' request. + The attach request has no standardized attributes. + */ export interface AttachRequestArguments { - /* The attach request has no standardized attributes. */ } - /** Response to "attach" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'attach' request. This is just an acknowledgement, so no body field is required. */ export interface AttachResponse extends Response { } - /** Disconnect request; value of command field is "disconnect". - */ + /** Disconnect request; value of command field is 'disconnect'. */ export interface DisconnectRequest extends Request { + // command: 'disconnect'; arguments?: DisconnectArguments; } - /** Arguments for "disconnect" request. */ + + /** Arguments for 'disconnect' request. + The disconnect request has no standardized attributes. + */ export interface DisconnectArguments { } - /** Response to "disconnect" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'disconnect' request. This is just an acknowledgement, so no body field is required. */ export interface DisconnectResponse extends Response { } - /** SetBreakpoints request; value of command field is "setBreakpoints". + /** SetBreakpoints request; value of command field is 'setBreakpoints'. Sets multiple breakpoints for a single source and clears all previous breakpoints in that source. To clear all breakpoint for a source, specify an empty array. When a breakpoint is hit, a StoppedEvent (event type 'breakpoint') is generated. */ export interface SetBreakpointsRequest extends Request { + // command: 'setBreakpoints'; arguments: SetBreakpointsArguments; } - /** Arguments for "setBreakpoints" request. */ + + /** Arguments for 'setBreakpoints' request. */ export interface SetBreakpointsArguments { /** The source location of the breakpoints; either source.path or source.reference must be specified. */ source: Source; @@ -306,7 +331,8 @@ declare module DebugProtocol { /** A value of true indicates that the underlying source has been modified which results in new breakpoint locations. */ sourceModified?: boolean; } - /** Response to "setBreakpoints" request. + + /** Response to 'setBreakpoints' request. Returned is information about each breakpoint created by this request. This includes the actual code location and whether the breakpoint could be verified. The breakpoints returned are in the same order as the elements of the 'breakpoints' @@ -319,20 +345,23 @@ declare module DebugProtocol { }; } - /** SetFunctionBreakpoints request; value of command field is "setFunctionBreakpoints". + /** SetFunctionBreakpoints request; value of command field is 'setFunctionBreakpoints'. Sets multiple function breakpoints and clears all previous function breakpoints. To clear all function breakpoint, specify an empty array. When a function breakpoint is hit, a StoppedEvent (event type 'function breakpoint') is generated. */ export interface SetFunctionBreakpointsRequest extends Request { + // command: 'setFunctionBreakpoints'; arguments: SetFunctionBreakpointsArguments; } - /** Arguments for "setFunctionBreakpoints" request. */ + + /** Arguments for 'setFunctionBreakpoints' request. */ export interface SetFunctionBreakpointsArguments { /** The function names of the breakpoints. */ breakpoints: FunctionBreakpoint[]; } - /** Response to "setFunctionBreakpoints" request. + + /** Response to 'setFunctionBreakpoints' request. Returned is information about each breakpoint created by this request. */ export interface SetFunctionBreakpointsResponse extends Response { @@ -342,33 +371,39 @@ declare module DebugProtocol { }; } - /** SetExceptionBreakpoints request; value of command field is "setExceptionBreakpoints". + /** SetExceptionBreakpoints request; value of command field is 'setExceptionBreakpoints'. Enable that the debuggee stops on exceptions with a StoppedEvent (event type 'exception'). */ export interface SetExceptionBreakpointsRequest extends Request { + // command: 'setExceptionBreakpoints'; arguments: SetExceptionBreakpointsArguments; } - /** Arguments for "setExceptionBreakpoints" request. */ + + /** Arguments for 'setExceptionBreakpoints' request. */ export interface SetExceptionBreakpointsArguments { /** Names of enabled exception breakpoints. */ filters: string[]; } - /** Response to "setExceptionBreakpoints" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'setExceptionBreakpoints' request. This is just an acknowledgement, so no body field is required. */ export interface SetExceptionBreakpointsResponse extends Response { } - /** Continue request; value of command field is "continue". + /** Continue request; value of command field is 'continue'. The request starts the debuggee to run again. */ export interface ContinueRequest extends Request { + // command: 'continue'; arguments: ContinueArguments; } - /** Arguments for "continue" request. */ + + /** Arguments for 'continue' request. */ export interface ContinueArguments { /** Continue execution for the specified thread (if possible). If the backend cannot continue on a single thread but will continue on all threads, it should set the allThreadsContinued attribute in the response to true. */ threadId: number; } - /** Response to "continue" request. */ + + /** Response to 'continue' request. */ export interface ContinueResponse extends Response { body: { /** If true, the continue request has ignored the specified thread and continued all threads instead. If this attribute is missing a value of 'true' is assumed for backward compatibility. */ @@ -376,135 +411,156 @@ declare module DebugProtocol { }; } - /** Next request; value of command field is "next". + /** Next request; value of command field is 'next'. The request starts the debuggee to run again for one step. The debug adapter first sends the NextResponse and then a StoppedEvent (event type 'step') after the step has completed. */ export interface NextRequest extends Request { + // command: 'next'; arguments: NextArguments; } - /** Arguments for "next" request. */ + + /** Arguments for 'next' request. */ export interface NextArguments { /** Continue execution for this thread. */ threadId: number; } - /** Response to "next" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'next' request. This is just an acknowledgement, so no body field is required. */ export interface NextResponse extends Response { } - /** StepIn request; value of command field is "stepIn". + /** StepIn request; value of command field is 'stepIn'. The request starts the debuggee to step into a function/method if possible. - If it cannot step into a target, "stepIn" behaves like "next". + If it cannot step into a target, 'stepIn' behaves like 'next'. The debug adapter first sends the StepInResponse and then a StoppedEvent (event type 'step') after the step has completed. If there are multiple function/method calls (or other targets) on the source line, - the optional argument 'targetId' can be used to control into which target the "stepIn" should occur. - The list of possible targets for a given source line can be retrieved via the "stepInTargets" request. + the optional argument 'targetId' can be used to control into which target the 'stepIn' should occur. + The list of possible targets for a given source line can be retrieved via the 'stepInTargets' request. */ export interface StepInRequest extends Request { + // command: 'stepIn'; arguments: StepInArguments; } - /** Arguments for "stepIn" request. */ + + /** Arguments for 'stepIn' request. */ export interface StepInArguments { /** Continue execution for this thread. */ threadId: number; /** Optional id of the target to step into. */ targetId?: number; } - /** Response to "stepIn" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'stepIn' request. This is just an acknowledgement, so no body field is required. */ export interface StepInResponse extends Response { } - /** StepOut request; value of command field is "stepOut". + /** StepOut request; value of command field is 'stepOut'. The request starts the debuggee to run again for one step. The debug adapter first sends the StepOutResponse and then a StoppedEvent (event type 'step') after the step has completed. */ export interface StepOutRequest extends Request { + // command: 'stepOut'; arguments: StepOutArguments; } - /** Arguments for "stepOut" request. */ + + /** Arguments for 'stepOut' request. */ export interface StepOutArguments { /** Continue execution for this thread. */ threadId: number; } - /** Response to "stepOut" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'stepOut' request. This is just an acknowledgement, so no body field is required. */ export interface StepOutResponse extends Response { } - /** StepBack request; value of command field is "stepBack". + /** StepBack request; value of command field is 'stepBack'. The request starts the debuggee to run one step backwards. The debug adapter first sends the StepBackResponse and then a StoppedEvent (event type 'step') after the step has completed. */ export interface StepBackRequest extends Request { + // command: 'stepBack'; arguments: StepBackArguments; } - /** Arguments for "stepBack" request. */ + + /** Arguments for 'stepBack' request. */ export interface StepBackArguments { /** Continue execution for this thread. */ threadId: number; } - /** Response to "stepBack" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'stepBack' request. This is just an acknowledgement, so no body field is required. */ export interface StepBackResponse extends Response { } - /** RestartFrame request; value of command field is "restartFrame". + /** RestartFrame request; value of command field is 'restartFrame'. The request restarts execution of the specified stackframe. The debug adapter first sends the RestartFrameResponse and then a StoppedEvent (event type 'restart') after the restart has completed. */ export interface RestartFrameRequest extends Request { + // command: 'restartFrame'; arguments: RestartFrameArguments; } - /** Arguments for "restartFrame" request. */ + + /** Arguments for 'restartFrame' request. */ export interface RestartFrameArguments { /** Restart this stackframe. */ frameId: number; } - /** Response to "restartFrame" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'restartFrame' request. This is just an acknowledgement, so no body field is required. */ export interface RestartFrameResponse extends Response { } - /** Goto request; value of command field is "goto". + /** Goto request; value of command field is 'goto'. The request sets the location where the debuggee will continue to run. This makes it possible to skip the execution of code or to executed code again. The code between the current location and the goto target is not executed but skipped. The debug adapter first sends the GotoResponse and then a StoppedEvent (event type 'goto'). */ export interface GotoRequest extends Request { + // command: 'goto'; arguments: GotoArguments; } - /** Arguments for "goto" request. */ + + /** Arguments for 'goto' request. */ export interface GotoArguments { /** Set the goto target for this thread. */ threadId: number; /** The location where the debuggee will continue to run. */ targetId: number; } - /** Response to "goto" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'goto' request. This is just an acknowledgement, so no body field is required. */ export interface GotoResponse extends Response { } - /** Pause request; value of command field is "pause". + /** Pause request; value of command field is 'pause'. The request suspenses the debuggee. The debug adapter first sends the PauseResponse and then a StoppedEvent (event type 'pause') after the thread has been paused successfully. */ export interface PauseRequest extends Request { + // command: 'pause'; arguments: PauseArguments; } - /** Arguments for "pause" request. */ + + /** Arguments for 'pause' request. */ export interface PauseArguments { /** Pause execution for this thread. */ threadId: number; } - /** Response to "pause" request. This is just an acknowledgement, so no body field is required. */ + + /** Response to 'pause' request. This is just an acknowledgement, so no body field is required. */ export interface PauseResponse extends Response { } - /** StackTrace request; value of command field is "stackTrace". - The request returns a stacktrace from the current execution state. - */ + /** StackTrace request; value of command field is 'stackTrace'. The request returns a stacktrace from the current execution state. */ export interface StackTraceRequest extends Request { + // command: 'stackTrace'; arguments: StackTraceArguments; } - /** Arguments for "stackTrace" request. */ + + /** Arguments for 'stackTrace' request. */ export interface StackTraceArguments { /** Retrieve the stacktrace for this thread. */ threadId: number; @@ -513,29 +569,34 @@ declare module DebugProtocol { /** The maximum number of frames to return. If levels is not specified or 0, all frames are returned. */ levels?: number; } - /** Response to "stackTrace" request. */ + + /** Response to 'stackTrace' request. */ export interface StackTraceResponse extends Response { body: { /** The frames of the stackframe. If the array has length zero, there are no stackframes available. - This means that there is no location information available. */ + This means that there is no location information available. + */ stackFrames: StackFrame[]; /** The total number of frames available. */ totalFrames?: number; }; } - /** Scopes request; value of command field is "scopes". + /** Scopes request; value of command field is 'scopes'. The request returns the variable scopes for a given stackframe ID. */ export interface ScopesRequest extends Request { + // command: 'scopes'; arguments: ScopesArguments; } - /** Arguments for "scopes" request. */ + + /** Arguments for 'scopes' request. */ export interface ScopesArguments { /** Retrieve the scopes for this stackframe. */ frameId: number; } - /** Response to "scopes" request. */ + + /** Response to 'scopes' request. */ export interface ScopesResponse extends Response { body: { /** The scopes of the stackframe. If the array has length zero, there are no scopes available. */ @@ -543,25 +604,28 @@ declare module DebugProtocol { }; } - /** Variables request; value of command field is "variables". + /** Variables request; value of command field is 'variables'. Retrieves all child variables for the given variable reference. An optional filter can be used to limit the fetched children to either named or indexed children. */ export interface VariablesRequest extends Request { + // command: 'variables'; arguments: VariablesArguments; } - /** Arguments for "variables" request. */ + + /** Arguments for 'variables' request. */ export interface VariablesArguments { /** The Variable reference. */ variablesReference: number; /** Optional filter to limit the child variables to either named or indexed. If ommited, both types are fetched. */ - filter?: "indexed" | "named"; + filter?: 'indexed' | 'named'; /** The index of the first variable to return; if omitted children start at 0. */ start?: number; /** The number of variables to return. If count is missing or 0, all variables are returned. */ count?: number; } - /** Response to "variables" request. */ + + /** Response to 'variables' request. */ export interface VariablesResponse extends Response { body: { /** All (or a range) of variables for the given variable reference. */ @@ -569,13 +633,15 @@ declare module DebugProtocol { }; } - /** setVariable request; value of command field is "setVariable". + /** setVariable request; value of command field is 'setVariable'. Set the variable with the given name in the variable container to a new value. */ export interface SetVariableRequest extends Request { + // command: 'setVariable'; arguments: SetVariableArguments; } - /** Arguments for "setVariable" request. */ + + /** Arguments for 'setVariable' request. */ export interface SetVariableArguments { /** The reference of the variable container. */ variablesReference: number; @@ -584,7 +650,8 @@ declare module DebugProtocol { /** The value of the variable. */ value: string; } - /** Response to "setVariable" request. */ + + /** Response to 'setVariable' request. */ export interface SetVariableResponse extends Response { body: { /** the new value of the variable. */ @@ -592,33 +659,38 @@ declare module DebugProtocol { }; } - /** Source request; value of command field is "source". + /** Source request; value of command field is 'source'. The request retrieves the source code for a given source reference. */ export interface SourceRequest extends Request { + // command: 'source'; arguments: SourceArguments; } - /** Arguments for "source" request. */ + + /** Arguments for 'source' request. */ export interface SourceArguments { /** The reference to the source. This is the value received in Source.reference. */ sourceReference: number; } - /** Response to "source" request. */ + + /** Response to 'source' request. */ export interface SourceResponse extends Response { body: { - /** Content of the source reference */ + /** Content of the source reference. */ content: string; /** Optional content type (mime type) of the source. */ mimeType?: string; }; } - /** Thread request; value of command field is "threads". + /** Thread request; value of command field is 'threads'. The request retrieves a list of all threads. */ export interface ThreadsRequest extends Request { + // command: 'threads'; } - /** Response to "threads" request. */ + + /** Response to 'threads' request. */ export interface ThreadsResponse extends Response { body: { /** All threads. */ @@ -626,20 +698,21 @@ declare module DebugProtocol { }; } - /** - * Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. - */ + /** Modules can be retrieved from the debug adapter with the ModulesRequest which can either return all modules or a range of modules to support paging. */ export interface ModulesRequest extends Request { + // command: 'modules'; arguments: ModulesArguments; } - /** Arguments for "modules" request. */ + + /** Arguments for 'modules' request. */ export interface ModulesArguments { /** The index of the first module to return; if omitted modules start at 0. */ startModule?: number; /** The number of modules to return. If moduleCount is not specified or 0, all modules are returned. */ moduleCount?: number; } - /** Response to "modules" request. */ + + /** Response to 'modules' request. */ export interface ModulesResponse extends Response { body: { /** All modules or range of modules. */ @@ -649,14 +722,16 @@ declare module DebugProtocol { }; } - /** Evaluate request; value of command field is "evaluate". + /** Evaluate request; value of command field is 'evaluate'. Evaluates the given expression in the context of the top most stack frame. The expression has access to any variables and arguments that are in scope. */ export interface EvaluateRequest extends Request { + // command: 'evaluate'; arguments: EvaluateArguments; } - /** Arguments for "evaluate" request. */ + + /** Arguments for 'evaluate' request. */ export interface EvaluateArguments { /** The expression to evaluate. */ expression: string; @@ -665,38 +740,44 @@ declare module DebugProtocol { /** The context in which the evaluate request is run. Possible values are 'watch' if evaluate is run in a watch, 'repl' if run from the REPL console, or 'hover' if run from a data hover. */ context?: string; } - /** Response to "evaluate" request. */ + + /** Response to 'evaluate' request. */ export interface EvaluateResponse extends Response { body: { /** The result of the evaluate request. */ result: string; /** The optional type of the evaluate result. */ type?: string; - /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest */ + /** If variablesReference is > 0, the evaluate result is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ variablesReference: number; /** The number of named child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ namedVariables?: number; /** The number of indexed child variables. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ indexedVariables?: number; }; } - /** StepInTargets request; value of command field is "stepInTargets". + /** StepInTargets request; value of command field is 'stepInTargets'. This request retrieves the possible stepIn targets for the specified stack frame. - These targets can be used in the "stepIn" request. - The StepInTargets may only be called if the "supportsStepInTargetsRequest" capability exists and is true. - */ + These targets can be used in the 'stepIn' request. + The StepInTargets may only be called if the 'supportsStepInTargetsRequest' capability exists and is true. + */ export interface StepInTargetsRequest extends Request { + // command: 'stepInTargets'; arguments: StepInTargetsArguments; } - /** Arguments for "stepInTargets" request. */ + + /** Arguments for 'stepInTargets' request. */ export interface StepInTargetsArguments { /** The stack frame for which to retrieve the possible stepIn targets. */ frameId: number; } - /** Response to "stepInTargets" request. */ + + /** Response to 'stepInTargets' request. */ export interface StepInTargetsResponse extends Response { body: { /** The possible stepIn targets of the specified source location. */ @@ -704,15 +785,17 @@ declare module DebugProtocol { }; } - /** GotoTargets request; value of command field is "gotoTargets". + /** GotoTargets request; value of command field is 'gotoTargets'. This request retrieves the possible goto targets for the specified source location. - These targets can be used in the "goto" request. - The GotoTargets request may only be called if the "supportsGotoTargetsRequest" capability exists and is true. - */ + These targets can be used in the 'goto' request. + The GotoTargets request may only be called if the 'supportsGotoTargetsRequest' capability exists and is true. + */ export interface GotoTargetsRequest extends Request { + // command: 'gotoTargets'; arguments: GotoTargetsArguments; } - /** Arguments for "gotoTargets" request. */ + + /** Arguments for 'gotoTargets' request. */ export interface GotoTargetsArguments { /** The source location for which the goto targets are determined. */ source: Source; @@ -721,7 +804,8 @@ declare module DebugProtocol { /** An optional column location for which the goto targets are determined. */ column?: number; } - /** Response to "gotoTargets" request. */ + + /** Response to 'gotoTargets' request. */ export interface GotoTargetsResponse extends Response { body: { /** The possible goto targets of the specified location. */ @@ -729,15 +813,16 @@ declare module DebugProtocol { }; } - /** EXPERIMENTAL, DO NOT USE! - CompletionsRequest request; value of command field is "completions". + /** CompletionsRequest request; value of command field is 'completions'. Returns a list of possible completions for a given caret position and text. - The CompletionsRequest may only be called if the "supportsCompletionsRequest" capability exists and is true. - */ + The CompletionsRequest may only be called if the 'supportsCompletionsRequest' capability exists and is true. + */ export interface CompletionsRequest extends Request { + // command: 'completions'; arguments: CompletionsArguments; } - /** Arguments for "completions" request. */ + + /** Arguments for 'completions' request. */ export interface CompletionsArguments { /** Returns completions in the scope of this stack frame. If not specified, the completions are returned for the global scope. */ frameId?: number; @@ -748,7 +833,8 @@ declare module DebugProtocol { /** An optional line for which to determine the completion proposals. If missing the first line of the text is assumed. */ line?: number; } - /** Response to "completions" request. */ + + /** Response to 'completions' request. */ export interface CompletionsResponse extends Response { body: { /** The possible completions for . */ @@ -756,10 +842,8 @@ declare module DebugProtocol { }; } - //---- Types - /** Information about the capabilities of a debug adapter. */ - export interface Capabilites { + export interface Capabilities { /** The debug adapter supports the configurationDoneRequest. */ supportsConfigurationDoneRequest?: boolean; /** The debug adapter supports functionBreakpoints. */ @@ -787,11 +871,11 @@ declare module DebugProtocol { /** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */ export interface ExceptionBreakpointsFilter { /** The internal ID of the filter. This value is passed to the setExceptionBreakpoints request. */ - filter: string, + filter: string; /** The name of the filter. This will be shown in the UI. */ - label: string, + label: string; /** Initial value of the filter. If not specified a value 'false' is assumed. */ - default?: boolean + default?: boolean; } /** A structured message object. Used to return errors from requests. */ @@ -799,13 +883,14 @@ declare module DebugProtocol { /** Unique identifier for the message. */ id: number; /** A format string for the message. Embedded variables have the form '{name}'. - If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. */ + If variable name starts with an underscore character, the variable does not contain user data (PII) and can be safely used for telemetry purposes. + */ format: string; /** An object used as a dictionary for looking up the variables in the format string. */ - variables?: { [key: string]: string }; - /** if true send to telemetry */ + variables?: { [key: string]: string; }; + /** If true send to telemetry. */ sendTelemetry?: boolean; - /** if true show user */ + /** If true show user. */ showUser?: boolean; /** An optional url where additional information about this message can be found. */ url?: string; @@ -813,47 +898,45 @@ declare module DebugProtocol { urlLabel?: string; } - /** - * A Module object represents a row in the modules view. - * Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. - * The name is used to minimally render the module in the UI. - * - * Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. - * - * To avoid an unnecessary proliferation of additional attributes with similar semantics but different names - * we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. - */ + /** A Module object represents a row in the modules view. + Two attributes are mandatory: an id identifies a module in the modules view and is used in a ModuleEvent for identifying a module for adding, updating or deleting. + The name is used to minimally render the module in the UI. + + Additional attributes can be added to the module. They will show up in the module View if they have a corresponding ColumnDescriptor. + + To avoid an unnecessary proliferation of additional attributes with similar semantics but different names + we recommend to re-use attributes from the 'recommended' list below first, and only introduce new attributes if nothing appropriate could be found. + */ export interface Module { /** Unique identifier for the module. */ id: number | string; /** A name of the module. */ name: string; + /** optional but recommended attributes. + always try to use these first before introducing additional attributes. - // optional but recommended attributes. - // always try to use these first before introducing additional attributes. - - /** Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. */ - path?: string + Logical full path to the module. The exact definition is implementation defined, but usually this would be a full path to the on-disk file for the module. + */ + path?: string; /** True if the module is optimized. */ - isOptimized?: boolean + isOptimized?: boolean; /** True if the module is considered 'user code' by a debugger that supports 'Just My Code'. */ - isUserCode?: boolean + isUserCode?: boolean; /** Version of Module. */ - version? : string - /** User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc */ - symbolStatus?: string + version?: string; + /** User understandable description of if symbols were found for the module (ex: 'Symbols Loaded', 'Symbols not found', etc. */ + symbolStatus?: string; /** Logical full path to the symbol file. The exact definition is implementation defined. */ - symbolFilePath?: string + symbolFilePath?: string; /** Module created or modified. */ - dateTimeStamp?: string + dateTimeStamp?: string; /** Address range covered by this module. */ - addressRange?: string + addressRange?: string; } - /** - * A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. - * It is only used if the underlying UI actually supports this level of customization. - */ + /** A ColumnDescriptor specifies what module attribute to show in a column of the ModulesView, how to format it, and what the column's label should be. + It is only used if the underlying UI actually supports this level of customization. + */ export interface ColumnDescriptor { /** Name of the attribute rendered in this column. */ attributeName: string; @@ -865,10 +948,9 @@ declare module DebugProtocol { width: number; } - /** - * The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView. - * For now it only specifies the columns to be shown in the modules view. - */ + /** The ModulesViewDescriptor is the container for all declarative configuration options of a ModuleView. + For now it only specifies the columns to be shown in the modules view. + */ export interface ModulesViewDescriptor { columns: ColumnDescriptor[]; } @@ -889,7 +971,7 @@ declare module DebugProtocol { path?: string; /** If sourceReference > 0 the contents of the source can be retrieved through the SourceRequest. A sourceReference is only valid for a session, so it must not be used to persist a source. */ sourceReference?: number; - /** The (optional) origin of this source: possible values "internal module", "inlined content from source map", etc. */ + /** The (optional) origin of this source: possible values 'internal module', 'inlined content from source map', etc. */ origin?: string; /** Optional data that a debug adapter might want to loop through the client. The client should leave the data intact and persist it across sessions. The client should not interpret the data. */ adapterData?: any; @@ -899,7 +981,7 @@ declare module DebugProtocol { export interface StackFrame { /** An identifier for the stack frame. It must be unique across all threads. This id can be used to retrieve the scopes of the frame with the 'scopesRequest' or to restart the execution of a stackframe. */ id: number; - /** The name of the stack frame, typically a method name */ + /** The name of the stack frame, typically a method name. */ name: string; /** The optional source of the frame. */ source?: Source; @@ -915,15 +997,17 @@ declare module DebugProtocol { /** A Scope is a named container for variables. */ export interface Scope { - /** name of the scope (as such 'Arguments', 'Locals') */ + /** Name of the scope such as 'Arguments', 'Locals'. */ name: string; /** The variables of this scope can be retrieved by passing the value of variablesReference to the VariablesRequest. */ variablesReference: number; /** The number of named variables in this scope. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ namedVariables?: number; /** The number of indexed variables in this scope. - The client can use this optional information to present the variables in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the variables in a paged UI and fetch them in chunks. + */ indexedVariables?: number; /** If true, the number of variables in this scope is large or expensive to retrieve. */ expensive: boolean; @@ -948,15 +1032,16 @@ declare module DebugProtocol { /** If variablesReference is > 0, the variable is structured and its children can be retrieved by passing variablesReference to the VariablesRequest. */ variablesReference: number; /** The number of named child variables in this scope. - The client can use this optional information to present the children in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ namedVariables?: number; /** The number of indexed child variables in this scope. - The client can use this optional information to present the children in a paged UI and fetch them in chunks. */ + The client can use this optional information to present the children in a paged UI and fetch them in chunks. + */ indexedVariables?: number; } - /** Properties of a breakpoint passed to the setBreakpoints request. - */ + /** Properties of a breakpoint passed to the setBreakpoints request. */ export interface SourceBreakpoint { /** The source line of the breakpoint. */ line: number; @@ -966,8 +1051,7 @@ declare module DebugProtocol { condition?: string; } - /** Properties of a breakpoint passed to the setFunctionBreakpoints request. - */ + /** Properties of a breakpoint passed to the setFunctionBreakpoints request. */ export interface FunctionBreakpoint { /** The name of the function. */ name: string; @@ -975,12 +1059,11 @@ declare module DebugProtocol { condition?: string; } - /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. - */ + /** Information about a Breakpoint created in setBreakpoints or setFunctionBreakpoints. */ export interface Breakpoint { /** An optional unique identifier for the breakpoint. */ id?: number; - /** If true breakpoint could be set (but not necessarily at the desired location). */ + /** If true breakpoint could be set (but not necessarily at the desired location). */ verified: boolean; /** An optional message about the state of the breakpoint. This is shown to the user and can be used to explain why a breakpoint could not be verified. */ message?: string; @@ -992,28 +1075,26 @@ declare module DebugProtocol { column?: number; /** An optional end line of the actual range covered by the breakpoint. */ endLine?: number; - /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ + /** An optional end column of the actual range covered by the breakpoint. If no end line is given, then the end column is assumed to be in the start line. */ endColumn?: number; } - /** A StepInTarget can be used in the 'stepIn' request and determines into - * which single target the stepIn request should step. - */ + /** A StepInTarget can be used in the 'stepIn' request and determines into which single target the stepIn request should step. */ export interface StepInTarget { /** Unique identifier for a stepIn target. */ id: number; /** The name of the stepIn target (shown in the UI). */ - label: string; + label: string; } /** A GotoTarget describes a code location that can be used as a target in the 'goto' request. The possible goto targets can be determined via the 'gotoTargets' request. - */ + */ export interface GotoTarget { /** Unique identifier for a goto target. This is used in the goto request. */ id: number; /** The name of the goto target (shown in the UI). */ - label: string; + label: string; /** The line of the goto target. */ line: number; /** An optional column of the goto target. */ @@ -1024,40 +1105,22 @@ declare module DebugProtocol { endColumn?: number; } - /** CompletionItems are the suggestions returned from the CompletionsRequest. - */ + /** CompletionItems are the suggestions returned from the CompletionsRequest. */ export interface CompletionItem { /** The label of this completion item. By default this is also the text that is inserted when selecting this completion. */ label: string; /** If text is not falsy then it is inserted instead of the label. */ text?: string; - /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ - type?: CompletionItemType; + /** The item's type. Typically the client uses this information to render the item in the UI with an icon. */ + type?: CompletionItemType; /** When a completion is selected it replaces 'length' characters starting at 'start' in the text passed to the CompletionsRequest. - If missing the frontend will try to determine these values heuristically. */ + If missing the frontend will try to determine these values heuristically. + */ start?: number; length?: number; } - /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. - */ - export type CompletionItemType = 'method' - | 'function' - | 'constructor' - | 'field' - | 'variable' - | 'class' - | 'interface' - | 'module' - | 'property' - | 'unit' - | 'value' - | 'enum' - | 'keyword' - | 'snippet' - | 'text' - | 'color' - | 'file' - | 'reference' - | 'customcolor'; + /** Some predefined types for the CompletionItem. Please note that not all clients have specific icons for all of them. */ + export type CompletionItemType = 'method' | 'function' | 'constructor' | 'field' | 'variable' | 'class' | 'interface' | 'module' | 'property' | 'unit' | 'value' | 'enum' | 'keyword' | 'snippet' | 'text' | 'color' | 'file' | 'reference' | 'customcolor'; } + diff --git a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts index b05e936bf5b..d0d12a0a847 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debug.contribution.ts @@ -8,27 +8,31 @@ import 'vs/css!../browser/media/debugHover'; import nls = require('vs/nls'); import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; import {TPromise} from 'vs/base/common/winjs.base'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; import {SyncActionDescriptor} from 'vs/platform/actions/common/actions'; import platform = require('vs/platform/platform'); import {registerSingleton} from 'vs/platform/instantiation/common/extensions'; import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegistry'; import {IKeybindings} from 'vs/platform/keybinding/common/keybinding'; import {ServicesAccessor} from 'vs/platform/instantiation/common/instantiation'; +import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors'; import wbaregistry = require('vs/workbench/common/actionRegistry'); import viewlet = require('vs/workbench/browser/viewlet'); import panel = require('vs/workbench/browser/panel'); import {DebugViewRegistry} from 'vs/workbench/parts/debug/browser/debugViewRegistry'; import {VariablesView, WatchExpressionsView, CallStackView, BreakpointsView} from 'vs/workbench/parts/debug/electron-browser/debugViews'; import wbext = require('vs/workbench/common/contributions'); +import {EditorDescriptor} from 'vs/workbench/browser/parts/editor/baseEditor'; import * as debug from 'vs/workbench/parts/debug/common/debug'; import {DebugEditorModelManager} from 'vs/workbench/parts/debug/browser/debugEditorModelManager'; import {StepOverAction, ClearReplAction, FocusReplAction, StepIntoAction, StepOutAction, StartAction, StepBackAction, RestartAction, ContinueAction, StopAction, DisconnectAction, PauseAction, AddFunctionBreakpointAction, ConfigureAction, ToggleReplAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction} from 'vs/workbench/parts/debug/browser/debugActions'; import debugwidget = require('vs/workbench/parts/debug/browser/debugActionsWidget'); import service = require('vs/workbench/parts/debug/electron-browser/debugService'); -import {DebugEditorContribution} from 'vs/workbench/parts/debug/electron-browser/debugEditorContribution'; +import {DebugErrorEditorInput} from 'vs/workbench/parts/debug/browser/debugEditorInputs'; +import {DebugErrorEditor} from 'vs/workbench/parts/debug/browser/debugErrorEditor'; +import 'vs/workbench/parts/debug/electron-browser/debugEditorContribution'; import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService'; +import {IEditorRegistry, Extensions as EditorExtensions} from 'vs/workbench/common/editor'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import IDebugService = debug.IDebugService; @@ -47,8 +51,6 @@ class OpenDebugViewletAction extends viewlet.ToggleViewletAction { } } -EditorBrowserRegistry.registerEditorContribution(DebugEditorContribution); - // register viewlet (platform.Registry.as(viewlet.Extensions.Viewlets)).registerViewlet(new viewlet.ViewletDescriptor( 'vs/workbench/parts/debug/browser/debugViewlet', @@ -118,7 +120,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ const configurationManager = debugService.getConfigurationManager(); return configurationManager.setConfiguration(configuration) .then(() => { - return configurationManager.configurationName ? debugService.createSession(false) + return configurationManager.configuration ? debugService.createSession(false) : TPromise.wrapError(new Error(nls.localize('launchConfigDoesNotExist', "Launch configuration '{0}' does not exist.", configuration))); }); } @@ -132,3 +134,11 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ // register service registerSingleton(IDebugService, service.DebugService); + +// Register Debug Error Editor #9062 +(platform.Registry.as(EditorExtensions.Editors)).registerEditor(new EditorDescriptor(DebugErrorEditor.ID, + nls.localize('debugErrorEditor', "Debug Error"), + 'vs/workbench/parts/debug/browser/debugErrorEditor', + 'DebugErrorEditor'), + [new SyncDescriptor(DebugErrorEditorInput)] +); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index 56749d3d6e4..81fafe8f502 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -13,6 +13,7 @@ import {IAction, Action} from 'vs/base/common/actions'; import {KeyCode} from 'vs/base/common/keyCodes'; import keyboard = require('vs/base/browser/keyboardEvent'); import editorbrowser = require('vs/editor/browser/editorBrowser'); +import {editorContribution} from 'vs/editor/browser/editorBrowserExtensions'; import editorcommon = require('vs/editor/common/editorCommon'); import {DebugHoverWidget} from 'vs/workbench/parts/debug/electron-browser/debugHover'; import debugactions = require('vs/workbench/parts/debug/browser/debugActions'); @@ -24,6 +25,7 @@ import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; const HOVER_DELAY = 300; +@editorContribution export class DebugEditorContribution implements debug.IDebugEditorContribution { private toDispose: lifecycle.IDisposable[]; @@ -34,10 +36,6 @@ export class DebugEditorContribution implements debug.IDebugEditorContribution { private hoverRange: Range; private hoveringOver: string; - static get(editor: editorcommon.ICommonCodeEditor): DebugEditorContribution { - return editor.getContribution(debug.EDITOR_CONTRIBUTION_ID); - } - constructor( private editor: editorbrowser.ICodeEditor, @debug.IDebugService private debugService: debug.IDebugService, diff --git a/src/vs/workbench/parts/debug/electron-browser/debugService.ts b/src/vs/workbench/parts/debug/electron-browser/debugService.ts index 1e4141c527b..d7f264091d7 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugService.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugService.ts @@ -5,7 +5,7 @@ import nls = require('vs/nls'); import lifecycle = require('vs/base/common/lifecycle'); -import {guessMimeTypes, MIME_TEXT} from 'vs/base/common/mime'; +import {guessMimeTypes} from 'vs/base/common/mime'; import Event, {Emitter} from 'vs/base/common/event'; import uri from 'vs/base/common/uri'; import {RunOnceScheduler} from 'vs/base/common/async'; @@ -32,11 +32,11 @@ import {TelemetryService} from 'vs/platform/telemetry/common/telemetryService'; import {TelemetryAppenderClient} from 'vs/platform/telemetry/common/telemetryIpc'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; -import wbeditorcommon = require('vs/workbench/common/editor'); +import {asFileEditorInput} from 'vs/workbench/common/editor'; import debug = require('vs/workbench/parts/debug/common/debug'); -import session = require('vs/workbench/parts/debug/electron-browser/rawDebugSession'); +import {RawDebugSession} from 'vs/workbench/parts/debug/electron-browser/rawDebugSession'; import model = require('vs/workbench/parts/debug/common/debugModel'); -import {DebugStringEditorInput} from 'vs/workbench/parts/debug/browser/debugEditorInputs'; +import {DebugStringEditorInput, DebugErrorEditorInput} from 'vs/workbench/parts/debug/browser/debugEditorInputs'; import viewmodel = require('vs/workbench/parts/debug/common/debugViewModel'); import debugactions = require('vs/workbench/parts/debug/browser/debugActions'); import {ConfigurationManager} from 'vs/workbench/parts/debug/node/debugConfigurationManager'; @@ -67,7 +67,7 @@ export class DebugService implements debug.IDebugService { private _state: debug.State; private _onDidChangeState: Emitter; - private session: session.RawDebugSession; + private session: RawDebugSession; private model: model.Model; private viewModel: viewmodel.ViewModel; private configurationManager: ConfigurationManager; @@ -553,7 +553,7 @@ export class DebugService implements debug.IDebugService { if (!this.configurationManager.adapter) { return configuration.type ? TPromise.wrapError(new Error(nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", configuration.type))) : TPromise.wrapError(errors.create(nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration."), - { actions: [CloseAction, this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL)] })); + { actions: [this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), CloseAction] })); } return this.runPreLaunchTask(configuration.preLaunchTask).then((taskSummary: ITaskSummary) => { @@ -568,10 +568,10 @@ export class DebugService implements debug.IDebugService { message: errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", configuration.preLaunchTask) : errorCount === 1 ? nls.localize('preLaunchTaskError', "Build error has been detected during preLaunchTask '{0}'.", configuration.preLaunchTask) : nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", configuration.preLaunchTask, taskSummary.exitCode), - actions: [CloseAction, new Action('debug.continue', nls.localize('debugAnyway', "Debug Anyway"), null, true, () => { + actions: [new Action('debug.continue', nls.localize('debugAnyway', "Debug Anyway"), null, true, () => { this.messageService.hideAll(); return this.doCreateSession(configuration); - })] + }), CloseAction] }); }, (err: TaskError) => { if (err.code !== TaskErrors.NotConfigured) { @@ -580,7 +580,7 @@ export class DebugService implements debug.IDebugService { this.messageService.show(err.severity, { message: err.message, - actions: [CloseAction, this.taskService.configureAction()] + actions: [this.taskService.configureAction(), CloseAction] }); }); })))); @@ -621,7 +621,7 @@ export class DebugService implements debug.IDebugService { this.customTelemetryService = new TelemetryService({ appender }, this.configurationService); } - this.session = this.instantiationService.createInstance(session.RawDebugSession, configuration.debugServer, this.configurationManager.adapter, this.customTelemetryService); + this.session = this.instantiationService.createInstance(RawDebugSession, configuration.debugServer, this.configurationManager.adapter, this.customTelemetryService); this.registerSessionListeners(); return this.session.initialize({ @@ -704,9 +704,9 @@ export class DebugService implements debug.IDebugService { if (filteredTasks.length !== 1) { return TPromise.wrapError(errors.create(nls.localize('DebugTaskNotFound', "Could not find the preLaunchTask \'{0}\'.", taskName), { actions: [ - CloseAction, + this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), this.taskService.configureAction(), - this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL) + CloseAction ] })); } @@ -739,11 +739,7 @@ export class DebugService implements debug.IDebugService { private rawAttach(port: number): TPromise { if (this.session) { - if (!this.session.configuration.isAttach) { - return this.session.attach({ port }); - } - - this.session.disconnect().done(null, errors.onUnexpectedError); + return this.session.attach({ port }); } this.setStateAndEmit(debug.State.Initializing); @@ -820,7 +816,7 @@ export class DebugService implements debug.IDebugService { public openOrRevealSource(source: Source, lineNumber: number, preserveFocus: boolean, sideBySide: boolean): TPromise { const visibleEditors = this.editorService.getVisibleEditors(); for (let i = 0; i < visibleEditors.length; i++) { - const fileInput = wbeditorcommon.asFileEditorInput(visibleEditors[i].input); + const fileInput = asFileEditorInput(visibleEditors[i].input); if ((fileInput && fileInput.getResource().toString() === source.uri.toString()) || (visibleEditors[i].input instanceof DebugStringEditorInput && (visibleEditors[i].input).getResource().toString() === source.uri.toString())) { @@ -846,7 +842,7 @@ export class DebugService implements debug.IDebugService { return this.getDebugStringEditorInput(source, response.body.content, mime); }, (err: DebugProtocol.ErrorResponse) => { // Display the error from debug adapter using a temporary editor #8836 - return this.getDebugStringEditorInput(source, err.message, MIME_TEXT); + return this.getDebugErrorEditorInput(source, err.message); }).then(editorInput => { return this.editorService.openEditor(editorInput, { preserveFocus, selection: { startLineNumber: lineNumber, startColumn: 1, endLineNumber: lineNumber, endColumn: 1 } }, sideBySide); }); @@ -873,7 +869,7 @@ export class DebugService implements debug.IDebugService { private sourceIsUnavailable(source: Source, sideBySide: boolean): TPromise { this.model.sourceIsUnavailable(source); - const editorInput = this.getDebugStringEditorInput(source, nls.localize('debugSourceNotAvailable', "Source {0} is not available.", source.uri.fsPath), 'text/plain'); + const editorInput = this.getDebugErrorEditorInput(source, nls.localize('debugSourceNotAvailable', "Source {0} is not available.", source.name)); return this.editorService.openEditor(editorInput, { preserveFocus: true }, sideBySide); } @@ -950,7 +946,7 @@ export class DebugService implements debug.IDebugService { } public completions(text: string, position: Position): TPromise { - if (!this.session) { + if (!this.session || !this.session.configuration.capabilities.supportsCompletionsRequest) { return TPromise.as([]); } const focussedStackFrame = this.viewModel.getFocusedStackFrame(); @@ -1005,6 +1001,14 @@ export class DebugService implements debug.IDebugService { private getDebugStringEditorInput(source: Source, value: string, mtype: string): DebugStringEditorInput { const result = this.instantiationService.createInstance(DebugStringEditorInput, source.name, source.uri, source.origin, value, mtype, void 0); this.toDisposeOnSessionEnd.push(result); + + return result; + } + + private getDebugErrorEditorInput(source: Source, value: string): DebugErrorEditorInput { + const result = this.instantiationService.createInstance(DebugErrorEditorInput, source.name, value); + this.toDisposeOnSessionEnd.push(result); + return result; } diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts index 785e197a5dd..2853f911b5c 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViewer.ts @@ -582,7 +582,7 @@ export class VariablesDataSource implements tree.IDataSource { } let variable = element; - return variable.reference !== 0 && !strings.equalsIgnoreCase(variable.value, 'null'); + return variable.reference !== 0 && variable.value && !strings.equalsIgnoreCase(variable.value, 'null'); } public getChildren(tree: tree.ITree, element: any): TPromise { diff --git a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts index 213819583cb..44a7fa12248 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugViews.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugViews.ts @@ -30,7 +30,6 @@ import IDebugService = debug.IDebugService; const debugTreeOptions = (ariaLabel: string) => { return { - indentPixels: 8, twistiePixels: 20, ariaLabel }; @@ -179,7 +178,7 @@ export class WatchExpressionsView extends viewlet.CollapsibleViewletView { this.tree.setInput(this.debugService.getModel()); const addWatchExpressionAction = this.instantiationService.createInstance(debugactions.AddWatchExpressionAction, debugactions.AddWatchExpressionAction.ID, debugactions.AddWatchExpressionAction.LABEL); - const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, false, 'explorer-action collapse-explorer'); + const collapseAction = this.instantiationService.createInstance(viewlet.CollapseAction, this.tree, true, 'explorer-action collapse-explorer'); const removeAllWatchExpressionsAction = this.instantiationService.createInstance(debugactions.RemoveAllWatchExpressionsAction, debugactions.RemoveAllWatchExpressionsAction.ID, debugactions.RemoveAllWatchExpressionsAction.LABEL); this.toolBar.setActions(actionbarregistry.prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))(); diff --git a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts index c91ae59635d..1c19f36b60e 100644 --- a/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts +++ b/src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts @@ -18,11 +18,14 @@ import stdfork = require('vs/base/node/stdFork'); import {IMessageService, CloseAction} from 'vs/platform/message/common/message'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal'; +import {ITerminalService as IExternalTerminalService} from 'vs/workbench/parts/execution/common/execution'; import debug = require('vs/workbench/parts/debug/common/debug'); import {Adapter} from 'vs/workbench/parts/debug/node/debugAdapter'; import v8 = require('vs/workbench/parts/debug/node/v8Protocol'); import {IOutputService} from 'vs/workbench/parts/output/common/output'; import {ExtensionsChannelId} from 'vs/platform/extensionManagement/common/extensionManagement'; +import {TerminalSupport} from 'vs/workbench/parts/debug/electron-browser/terminalSupport'; + import {shell} from 'electron'; @@ -52,8 +55,7 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes private startTime: number; private stopServerPending: boolean; private sentPromises: TPromise[]; - private isAttach: boolean; - private capabilities: DebugProtocol.Capabilites; + private capabilities: DebugProtocol.Capabilities; private _onDidInitialize: Emitter; private _onDidStop: Emitter; @@ -72,7 +74,8 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes @IMessageService private messageService: IMessageService, @ITelemetryService private telemetryService: ITelemetryService, @IOutputService private outputService: IOutputService, - @ITerminalService private terminalService: ITerminalService + @ITerminalService private terminalService: ITerminalService, + @IExternalTerminalService private nativeTerminalService: IExternalTerminalService ) { super(); this.emittedStopped = false; @@ -212,10 +215,9 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes this._onDidEvent.fire(event); } - public get configuration(): { type: string, isAttach: boolean, capabilities: DebugProtocol.Capabilites } { + public get configuration(): { type: string, capabilities: DebugProtocol.Capabilities } { return { type: this.adapter.type, - isAttach: this.isAttach, capabilities: this.capabilities || {} }; } @@ -233,12 +235,10 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes } public launch(args: DebugProtocol.LaunchRequestArguments): TPromise { - this.isAttach = false; return this.send('launch', args).then(response => this.readCapabilities(response)); } public attach(args: DebugProtocol.AttachRequestArguments): TPromise { - this.isAttach = true; return this.send('attach', args).then(response => this.readCapabilities(response)); } @@ -344,13 +344,22 @@ export class RawDebugSession extends v8.V8Protocol implements debug.IRawDebugSes return (new Date().getTime() - this.startTime) / 1000; } - protected runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): TPromise { - return this.terminalService.createNew(args.title || nls.localize('debuggee', "debuggee")).then(id => { - return this.terminalService.show(false).then(terminalPanel => { - this.terminalService.setActiveTerminalById(id); - terminalPanel.sendTextToActiveTerminal(args.args.join(' '), true); + protected dispatchRequest(request: DebugProtocol.Request, response: DebugProtocol.Response): void { + + if (request.command === 'runInTerminal') { + + TerminalSupport.runInTerminal(this.terminalService, this.nativeTerminalService, request.arguments, response).then(() => { + this.sendResponse(response); + }, e => { + response.success = false; + response.message = e.message; + this.sendResponse(response); }); - }); + } else { + response.success = false; + response.message = `unknown request '${request.command}'`; + this.sendResponse(response); + } } private connectServer(port: number): TPromise { diff --git a/src/vs/workbench/parts/debug/electron-browser/repl.ts b/src/vs/workbench/parts/debug/electron-browser/repl.ts index 596ba5a74aa..86699ae9827 100644 --- a/src/vs/workbench/parts/debug/electron-browser/repl.ts +++ b/src/vs/workbench/parts/debug/electron-browser/repl.ts @@ -23,16 +23,17 @@ import {Position} from 'vs/editor/common/core/position'; import * as modes from 'vs/editor/common/modes'; import {editorAction, ServicesAccessor, EditorAction} from 'vs/editor/common/editorCommonExtensions'; import {IModelService} from 'vs/editor/common/services/modelService'; -import {CodeEditor} from 'vs/editor/browser/codeEditor'; import {ServiceCollection} from 'vs/platform/instantiation/common/serviceCollection'; -import {IContextKeyService, RawContextKey} from 'vs/platform/contextkey/common/contextkey'; +import {IContextKeyService, ContextKeyExpr} from 'vs/platform/contextkey/common/contextkey'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IContextViewService, IContextMenuService} from 'vs/platform/contextview/browser/contextView'; import {IInstantiationService, createDecorator} from 'vs/platform/instantiation/common/instantiation'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import viewer = require('vs/workbench/parts/debug/electron-browser/replViewer'); +import {ReplEditor} from 'vs/workbench/parts/debug/electron-browser/replEditor'; import debug = require('vs/workbench/parts/debug/common/debug'); +import {Expression} from 'vs/workbench/parts/debug/common/debugModel'; import debugactions = require('vs/workbench/parts/debug/browser/debugActions'); import replhistory = require('vs/workbench/parts/debug/common/replHistory'); import {Panel} from 'vs/workbench/browser/panel'; @@ -40,12 +41,9 @@ import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; const $ = dom.$; -const CONTEXT_IN_DEBUG_REPL = new RawContextKey('inDebugRepl', false); const replTreeOptions: tree.ITreeOptions = { - indentPixels: 8, twistiePixels: 20, - paddingOnRow: false, ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel") }; @@ -65,17 +63,20 @@ export class Repl extends Panel implements IPrivateReplService { private static HISTORY: replhistory.ReplHistory; private static REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show + private static REPL_INPUT_INITIAL_HEIGHT = 22; + private static REPL_INPUT_MAX_HEIGHT = 170; private toDispose: lifecycle.IDisposable[]; private tree: tree.ITree; private renderer: viewer.ReplExpressionsRenderer; private characterWidthSurveyor: HTMLElement; private treeContainer: HTMLElement; - private replInput: CodeEditor; + private replInput: ReplEditor; private replInputContainer: HTMLElement; private refreshTimeoutHandle: number; private actions: actions.IAction[]; private dimension: builder.Dimension; + private replInputHeight: number; constructor( @debug.IDebugService private debugService: debug.IDebugService, @@ -92,6 +93,7 @@ export class Repl extends Panel implements IPrivateReplService { ) { super(debug.REPL_ID, telemetryService); + this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.toDispose = []; this.registerListeners(); } @@ -117,10 +119,28 @@ export class Repl extends Panel implements IPrivateReplService { return; // refresh already triggered } + const elements = this.debugService.getModel().getReplElements(); + const delay = elements.length > 0 ? Repl.REFRESH_DELAY : 0; + this.refreshTimeoutHandle = setTimeout(() => { this.refreshTimeoutHandle = null; - this.tree.refresh().done(() => this.tree.setScrollPosition(1), errors.onUnexpectedError); - }, Repl.REFRESH_DELAY); + const previousScrollPosition = this.tree.getScrollPosition(); + this.tree.refresh().then(() => { + if (previousScrollPosition === 1) { + // Only scroll if we were scrolled all the way down before tree refreshed #10486 + this.tree.setScrollPosition(1); + } + + // If the last repl element has children - auto expand it #6019 + const elements = this.debugService.getModel().getReplElements(); + const lastElement = elements.length > 0 ? elements[elements.length - 1] : null; + if (lastElement instanceof Expression && lastElement.reference > 0) { + return this.tree.expand(elements[elements.length - 1]).then(() => + this.tree.reveal(elements[elements.length - 1], 0) + ); + } + }, errors.onUnexpectedError); + }, delay); } } @@ -128,41 +148,7 @@ export class Repl extends Panel implements IPrivateReplService { super.create(parent); const container = dom.append(parent.getHTMLElement(), $('.repl')); this.treeContainer = dom.append(container, $('.repl-tree')); - this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); - - const scopedContextKeyService = this.contextKeyService.createScoped(this.replInputContainer); - this.toDispose.push(scopedContextKeyService); - CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true); - const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( - [IContextKeyService, scopedContextKeyService], [IPrivateReplService, this])); - - this.replInput = scopedInstantiationService.createInstance(CodeEditor, this.replInputContainer, this.getReplInputOptions()); - const model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`)); - this.replInput.setModel(model); - - modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, { - triggerCharacters: ['.'], - provideCompletionItems: (model: IReadOnlyModel, position: Position, token: CancellationToken): Thenable => { - const word = this.replInput.getModel().getWordAtPosition(position); - const text = this.replInput.getModel().getLineContent(position.lineNumber); - return wireCancellationToken(token, this.debugService.completions(text, position).then(suggestions => ({ - currentWord: word ? word.word : '', - suggestions - }))); - } - }, - true - ); - - this.toDispose.push(this.replInput.onDidScrollChange(e => { - if (!e.scrollHeightChanged) { - return; - } - this.layout(this.dimension, Math.min(170, e.scrollHeight)); - })); - - this.toDispose.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus'))); - this.toDispose.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus'))); + this.createReplInput(container); this.characterWidthSurveyor = dom.append(container, $('.surveyor')); this.characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL; @@ -186,6 +172,53 @@ export class Repl extends Panel implements IPrivateReplService { return this.tree.setInput(this.debugService.getModel()); } + private createReplInput(container: HTMLElement): void { + this.replInputContainer = dom.append(container, $('.repl-input-wrapper')); + + const scopedContextKeyService = this.contextKeyService.createScoped(this.replInputContainer); + this.toDispose.push(scopedContextKeyService); + debug.CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true); + const onFirstReplLine = debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService); + onFirstReplLine.set(true); + const onLastReplLine = debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService); + onLastReplLine.set(true); + + const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection( + [IContextKeyService, scopedContextKeyService], [IPrivateReplService, this])); + this.replInput = scopedInstantiationService.createInstance(ReplEditor, this.replInputContainer, this.getReplInputOptions()); + const model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`)); + this.replInput.setModel(model); + + modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, { + triggerCharacters: ['.'], + provideCompletionItems: (model: IReadOnlyModel, position: Position, token: CancellationToken): Thenable => { + const word = this.replInput.getModel().getWordAtPosition(position); + const text = this.replInput.getModel().getLineContent(position.lineNumber); + return wireCancellationToken(token, this.debugService.completions(text, position).then(suggestions => ({ + currentWord: word ? word.word : '', + suggestions + }))); + } + }, + true + ); + + this.toDispose.push(this.replInput.onDidScrollChange(e => { + if (!e.scrollHeightChanged) { + return; + } + this.replInputHeight = Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height); + this.layout(this.dimension); + })); + this.toDispose.push(this.replInput.onDidChangeCursorPosition(e => { + onFirstReplLine.set(e.position.lineNumber === 1); + onLastReplLine.set(e.position.lineNumber === this.replInput.getModel().getLineCount()); + })); + + this.toDispose.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus'))); + this.toDispose.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus'))); + } + public navigateHistory(previous: boolean): void { const historyInput = previous ? Repl.HISTORY.previous() : Repl.HISTORY.next(); if (historyInput) { @@ -201,20 +234,21 @@ export class Repl extends Panel implements IPrivateReplService { Repl.HISTORY.evaluated(this.replInput.getValue()); this.replInput.setValue(''); // Trigger a layout to shrink a potential multi line input + this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT; this.layout(this.dimension); } - public layout(dimension: builder.Dimension, replInputHeight = 22): void { + public layout(dimension: builder.Dimension): void { this.dimension = dimension; if (this.tree) { this.renderer.setWidth(dimension.width - 25, this.characterWidthSurveyor.clientWidth / this.characterWidthSurveyor.textContent.length); - const treeHeight = dimension.height - replInputHeight; + const treeHeight = dimension.height - this.replInputHeight; this.treeContainer.style.height = `${treeHeight}px`; this.tree.layout(treeHeight); } - this.replInputContainer.style.height = `${replInputHeight}px`; + this.replInputContainer.style.height = `${this.replInputHeight}px`; - this.replInput.layout({ width: dimension.width - 20, height: replInputHeight }); + this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight }); } public focus(): void { @@ -249,13 +283,13 @@ export class Repl extends Panel implements IPrivateReplService { selectOnLineNumbers: false, selectionHighlight: false, scrollbar: { - horizontal: 'hidden', - vertical: 'hidden' + horizontal: 'hidden' }, lineDecorationsWidth: 0, scrollBeyondLastLine: false, lineHeight: 21, - theme: this.themeService.getColorTheme() + theme: this.themeService.getColorTheme(), + renderLineHighlight: false }; } @@ -274,9 +308,9 @@ class ReplHistoryPreviousAction extends EditorAction { id: 'repl.action.historyPrevious', label: nls.localize('actions.repl.historyPrevious', "History Previous"), alias: 'History Previous', - precondition: CONTEXT_IN_DEBUG_REPL, + precondition: debug.CONTEXT_IN_DEBUG_REPL, kbOpts: { - kbExpr: EditorContextKeys.TextFocus, + kbExpr: ContextKeyExpr.and(EditorContextKeys.TextFocus, debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE), primary: KeyCode.UpArrow, weight: 50 }, @@ -299,9 +333,9 @@ class ReplHistoryNextAction extends EditorAction { id: 'repl.action.historyNext', label: nls.localize('actions.repl.historyNext', "History Next"), alias: 'History Next', - precondition: CONTEXT_IN_DEBUG_REPL, + precondition: debug.CONTEXT_IN_DEBUG_REPL, kbOpts: { - kbExpr: EditorContextKeys.TextFocus, + kbExpr: ContextKeyExpr.and(EditorContextKeys.TextFocus, debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE), primary: KeyCode.DownArrow, weight: 50 }, @@ -324,7 +358,7 @@ class AcceptReplInputAction extends EditorAction { id: 'repl.action.acceptInput', label: nls.localize('actions.repl.acceptInput', "REPL Accept Input"), alias: 'REPL Accept Input', - precondition: CONTEXT_IN_DEBUG_REPL, + precondition: debug.CONTEXT_IN_DEBUG_REPL, kbOpts: { kbExpr: EditorContextKeys.TextFocus, primary: KeyCode.Enter, diff --git a/src/vs/workbench/parts/debug/electron-browser/replEditor.ts b/src/vs/workbench/parts/debug/electron-browser/replEditor.ts new file mode 100644 index 00000000000..09aa441dda5 --- /dev/null +++ b/src/vs/workbench/parts/debug/electron-browser/replEditor.ts @@ -0,0 +1,49 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import {IEditorOptions} from 'vs/editor/common/editorCommon'; +import {EditorAction, CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; +import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService'; +import {IEditorContributionCtor} from 'vs/editor/browser/editorBrowser'; +import {CodeEditorWidget} from 'vs/editor/browser/widget/codeEditorWidget'; +import {IContextKeyService} from 'vs/platform/contextkey/common/contextkey'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {ICommandService} from 'vs/platform/commands/common/commands'; + +// Allowed Editor Contributions: +import {MenuPreventer} from 'vs/editor/contrib/multicursor/browser/menuPreventer'; +import {SelectionClipboard} from 'vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard'; +import {ContextMenuController} from 'vs/editor/contrib/contextmenu/browser/contextmenu'; +import {SuggestController} from 'vs/editor/contrib/suggest/browser/suggestController'; +import {SnippetController} from 'vs/editor/contrib/snippet/common/snippetController'; +import {TabCompletionController} from 'vs/editor/contrib/suggest/browser/tabCompletion'; + +export class ReplEditor extends CodeEditorWidget { + constructor( + domElement: HTMLElement, + options: IEditorOptions, + @IInstantiationService instantiationService: IInstantiationService, + @ICodeEditorService codeEditorService: ICodeEditorService, + @ICommandService commandService: ICommandService, + @IContextKeyService contextKeyService: IContextKeyService + ) { + super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService); + } + + protected _getContributions(): IEditorContributionCtor[] { + return [ + MenuPreventer, + SelectionClipboard, + ContextMenuController, + SuggestController, + SnippetController, + TabCompletionController, + ]; + } + + protected _getActions(): EditorAction[] { + return CommonEditorRegistry.getEditorActions(); + } +} diff --git a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts index ccde673fcad..d58708accd9 100644 --- a/src/vs/workbench/parts/debug/electron-browser/replViewer.ts +++ b/src/vs/workbench/parts/debug/electron-browser/replViewer.ts @@ -18,7 +18,7 @@ import mouse = require('vs/base/browser/mouseEvent'); import tree = require('vs/base/parts/tree/browser/tree'); import renderer = require('vs/base/parts/tree/browser/actionsRenderer'); import treedefaults = require('vs/base/parts/tree/browser/treeDefaults'); -import {CodeEditor} from 'vs/editor/browser/codeEditor'; +import {ICodeEditor} from 'vs/editor/browser/editorBrowser'; import debug = require('vs/workbench/parts/debug/common/debug'); import model = require('vs/workbench/parts/debug/common/debugModel'); import debugviewer = require('vs/workbench/parts/debug/electron-browser/debugViewer'); @@ -121,7 +121,7 @@ export class ReplExpressionsRenderer implements tree.IRenderer { return ReplExpressionsRenderer.LINE_HEIGHT_PX; } - const lines = s.split(/\r\n|\r|\n/g); + const lines = s.trim().split(/\r\n|\r|\n/g); const numLines = lines.reduce((lineCount: number, line: string) => { let lineLength = 0; for (let i = 0; i < line.length; i++) { @@ -487,7 +487,7 @@ export class ReplExpressionsController extends debugviewer.BaseDebugController { debugService: debug.IDebugService, contextMenuService: IContextMenuService, actionProvider: renderer.IActionProvider, - private replInput: CodeEditor, + private replInput: ICodeEditor, focusOnContextMenu = true ) { super(debugService, contextMenuService, actionProvider, focusOnContextMenu); diff --git a/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts new file mode 100644 index 00000000000..26c581c9830 --- /dev/null +++ b/src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts @@ -0,0 +1,85 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import nls = require('vs/nls'); +import platform = require('vs/base/common/platform'); +import {TPromise} from 'vs/base/common/winjs.base'; +import {ITerminalService} from 'vs/workbench/parts/terminal/electron-browser/terminal'; +import {ITerminalService as IExternalTerminalService} from 'vs/workbench/parts/execution/common/execution'; + + +export class TerminalSupport { + + private static terminalId: number; + + public static runInTerminal(terminalService: ITerminalService, nativeTerminalService: IExternalTerminalService, args: DebugProtocol.RunInTerminalRequestArguments, response: DebugProtocol.RunInTerminalResponse): TPromise { + + if (args.kind === 'external') { + return nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env); + } + return this.runInIntegratedTerminal(terminalService, args); + } + + private static runInIntegratedTerminal(terminalService: ITerminalService, args: DebugProtocol.RunInTerminalRequestArguments): TPromise { + + return (!TerminalSupport.terminalId ? terminalService.createNew(args.title || nls.localize('debuggee', "debuggee")) : TPromise.as(TerminalSupport.terminalId)).then(id => { + TerminalSupport.terminalId = id; + return terminalService.show(false).then(terminalPanel => { + terminalService.setActiveTerminalById(id); + const command = this.prepareCommand(args); + terminalPanel.sendTextToActiveTerminal(command, true); + }); + }); + } + + private static prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments): string { + let command = ''; + + if (platform.isWindows) { + + const quote = (s: string) => { + s = s.replace(/\"/g, '""'); + return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s; + }; + + if (args.cwd) { + command += `cd ${quote(args.cwd)} && `; + } + if (args.env) { + command += 'cmd /C "'; + for (let key in args.env) { + command += `set "${key}=${args.env[key]}" && `; + } + } + for (let a of args.args) { + command += `${quote(a)} `; + } + if (args.env) { + command += '"'; + } + } else { + const quote = (s: string) => { + s = s.replace(/\"/g, '\\"'); + return s.indexOf(' ') >= 0 ? `"${s}"` : s; + }; + + if (args.cwd) { + command += `cd ${quote(args.cwd)} ; `; + } + if (args.env) { + command += 'env'; + for (let key in args.env) { + command += ` ${quote(key + '=' + args.env[key])}`; + } + command += ' '; + } + for (let a of args.args) { + command += `${quote(a)} `; + } + } + + return command; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts index 9c3859677d8..f21726a3157 100644 --- a/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/node/debugConfigurationManager.ts @@ -175,7 +175,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { private systemVariables: ISystemVariables; private adapters: Adapter[]; private allModeIdsForBreakpoints: { [key: string]: boolean }; - private _onDidConfigurationChange: Emitter; + private _onDidConfigurationChange: Emitter; constructor( configName: string, @@ -189,7 +189,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { @ICommandService private commandService: ICommandService ) { this.systemVariables = this.contextService.getWorkspace() ? new ConfigVariables(this.configurationService, this.editorService, this.contextService, this.environmentService) : null; - this._onDidConfigurationChange = new Emitter(); + this._onDidConfigurationChange = new Emitter(); this.setConfiguration(configName); this.adapters = []; this.registerListeners(); @@ -252,7 +252,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { }); } - public get onDidConfigurationChange(): Event { + public get onDidConfigurationChange(): Event { return this._onDidConfigurationChange.event; } @@ -364,7 +364,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { }); } } - }).then(() => this._onDidConfigurationChange.fire(this.configurationName)); + }).then(() => this._onDidConfigurationChange.fire(this.configuration)); } public openConfigFile(sideBySide: boolean): TPromise { diff --git a/src/vs/workbench/parts/debug/node/v8Protocol.ts b/src/vs/workbench/parts/debug/node/v8Protocol.ts index de54fd29ae9..049912de743 100644 --- a/src/vs/workbench/parts/debug/node/v8Protocol.ts +++ b/src/vs/workbench/parts/debug/node/v8Protocol.ts @@ -31,6 +31,10 @@ export abstract class V8Protocol { return this.id; } + protected abstract onServerError(err: Error): void; + protected abstract onEvent(event: DebugProtocol.Event): void; + protected abstract dispatchRequest(request: DebugProtocol.Request, response: DebugProtocol.Response); + protected connect(readable: stream.Readable, writable: stream.Writable): void { this.outputStream = writable; @@ -55,30 +59,6 @@ export abstract class V8Protocol { }, () => errorCallback(canceled())); } - protected dispatchRequest(request: DebugProtocol.Request): void { - - const response: DebugProtocol.Response = { - type: 'response', - seq: 0, - command: request.command, - request_seq: request.seq, - success: true - }; - - if (request.command === 'runInTerminal') { - this.runInTerminal(request.arguments).then(() => { - (response).body = { - processId: 12345 // send back process id - }; - this.sendResponse(response); - }, e => { - response.success = false; - response.message = 'error while handling request'; - this.sendResponse(response); - }); - } - } - public sendResponse(response: DebugProtocol.Response): void { if (response.seq > 0) { console.error(`attempt to send more than one response for command ${response.command}`); @@ -144,10 +124,6 @@ export abstract class V8Protocol { } } - protected abstract runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): TPromise; - protected abstract onServerError(err: Error): void; - protected abstract onEvent(event: DebugProtocol.Event): void; - private dispatch(body: string): void { try { const rawData = JSON.parse(body); @@ -164,7 +140,15 @@ export abstract class V8Protocol { } break; case 'request': - this.dispatchRequest(rawData); + const request = rawData; + const resp: DebugProtocol.Response = { + type: 'response', + seq: 0, + command: request.command, + request_seq: request.seq, + success: true + }; + this.dispatchRequest(request, resp); break; } } catch (e) { diff --git a/src/vs/workbench/parts/debug/test/common/mockDebugService.ts b/src/vs/workbench/parts/debug/test/common/mockDebugService.ts index 5f02269972b..bac57bd3d26 100644 --- a/src/vs/workbench/parts/debug/test/common/mockDebugService.ts +++ b/src/vs/workbench/parts/debug/test/common/mockDebugService.ts @@ -145,10 +145,9 @@ export class MockDebugService implements debug.IDebugService { class MockRawSession implements debug.IRawDebugSession { - public get configuration(): { type: string, isAttach: boolean, capabilities: DebugProtocol.Capabilites } { + public get configuration(): { type: string, capabilities: DebugProtocol.Capabilities } { return { type: 'mock', - isAttach: false, capabilities: {} }; } diff --git a/src/vs/workbench/parts/emmet/node/actions/base64.ts b/src/vs/workbench/parts/emmet/node/actions/base64.ts index 2481a5a763b..636ab0c1dc4 100644 --- a/src/vs/workbench/parts/emmet/node/actions/base64.ts +++ b/src/vs/workbench/parts/emmet/node/actions/base64.ts @@ -83,11 +83,11 @@ class EncodeDecodeDataUrlAction extends EmmetEditorAction { const message = nls.localize('warnEscalation', "File **{0}** already exists. Do you want to overwrite the existing file?", this.imageFilePath); const actions = [ - new Action('cancel', nls.localize('cancel', "Cancel"), '', true), new Action('ok', nls.localize('ok', "OK"), '', true, () => { this.encodeDecode(ctx, this.imageFilePath); return null; - }) + }), + new Action('cancel', nls.localize('cancel', "Cancel"), '', true) ]; messageService.show(Severity.Warning, { message, actions }); }); diff --git a/src/vs/workbench/parts/emmet/node/editorAccessor.ts b/src/vs/workbench/parts/emmet/node/editorAccessor.ts index a1bc9f0d858..f70126f004c 100644 --- a/src/vs/workbench/parts/emmet/node/editorAccessor.ts +++ b/src/vs/workbench/parts/emmet/node/editorAccessor.ts @@ -22,15 +22,17 @@ export class EditorAccessor implements emmet.Editor { private _editor: ICommonCodeEditor; private _syntaxProfiles: any; + private _excludedLanguages: any; private _grammars: IGrammarContributions; private _hasMadeEdits: boolean; private emmetSupportedModes = ['html', 'xhtml', 'css', 'xml', 'xsl', 'haml', 'jade', 'jsx', 'slim', 'scss', 'sass', 'less', 'stylus', 'styl']; - constructor(editor: ICommonCodeEditor, syntaxProfiles: any, grammars: IGrammarContributions) { + constructor(editor: ICommonCodeEditor, syntaxProfiles: any, excludedLanguages: String[], grammars: IGrammarContributions) { this._editor = editor; this._syntaxProfiles = syntaxProfiles; + this._excludedLanguages = excludedLanguages; this._hasMadeEdits = false; this._grammars = grammars; } @@ -129,6 +131,10 @@ export class EditorAccessor implements emmet.Editor { let modeId = this._editor.getModel().getModeIdAtPosition(position.lineNumber, position.column); let syntax = modeId.split('.').pop(); + if (this._excludedLanguages.indexOf(syntax) !== -1) { + return ''; + } + // user can overwrite the syntax using the emmet syntaxProfiles setting let profile = this.getSyntaxProfile(syntax); if (profile) { @@ -139,7 +145,7 @@ export class EditorAccessor implements emmet.Editor { return syntax; } - if (/\b(razor|handlebars|erb|php|hbs|ejs|twig)\b/.test(syntax)) { // treat like html + if (/\b(razor|handlebars)\b/.test(syntax)) { // treat like html return 'html'; } if (/\b(typescriptreact|javascriptreact)\b/.test(syntax)) { // treat tsx like jsx @@ -166,7 +172,7 @@ export class EditorAccessor implements emmet.Editor { return syntax; } let languages = languageGrammar.split('.'); - let thisLanguage = languages[languages.length-1]; + let thisLanguage = languages[languages.length - 1]; if (syntax !== thisLanguage || languages.length < 2) { return syntax; } diff --git a/src/vs/workbench/parts/emmet/node/emmet.contribution.ts b/src/vs/workbench/parts/emmet/node/emmet.contribution.ts index ce53c85be2c..46b5a432265 100644 --- a/src/vs/workbench/parts/emmet/node/emmet.contribution.ts +++ b/src/vs/workbench/parts/emmet/node/emmet.contribution.ts @@ -49,6 +49,11 @@ configurationRegistry.registerConfiguration({ 'type': 'object', 'default': {}, 'description': nls.localize('emmetSyntaxProfiles', "Define profile for specified syntax or use your own profile with specific rules.") - } + }, + 'emmet.excludeLanguages': { + 'type': 'array', + 'default': [], + 'description': nls.localize('emmetExclude', "An array of languages where emmet abbreviations should not be expanded.") + }, } }); diff --git a/src/vs/workbench/parts/emmet/node/emmet.d.ts b/src/vs/workbench/parts/emmet/node/emmet.d.ts index 8e975877b73..3a878163b9a 100644 --- a/src/vs/workbench/parts/emmet/node/emmet.d.ts +++ b/src/vs/workbench/parts/emmet/node/emmet.d.ts @@ -11,9 +11,7 @@ declare module 'emmet' { } export interface Preferences { - set(key:string, value: string); - define(key:string, value: string); - remove(key:string); + reset(); } export interface Profiles { @@ -150,5 +148,13 @@ declare module 'emmet' { export const profile: Profiles; - export function loadProfiles(profiles: any); + /** + * Loads preferences from JSON object + */ + export function loadPreferences(preferences: any): void; + + /** + * Loads named profiles from JSON object + */ + export function loadProfiles(profiles: any): void; } diff --git a/src/vs/workbench/parts/emmet/node/emmetActions.ts b/src/vs/workbench/parts/emmet/node/emmetActions.ts index 6a4d4465cca..e3fa80ce25f 100644 --- a/src/vs/workbench/parts/emmet/node/emmetActions.ts +++ b/src/vs/workbench/parts/emmet/node/emmetActions.ts @@ -20,7 +20,8 @@ interface IEmmetConfiguration { emmet: { preferences: any; syntaxProfiles: any; - triggerExpansionOnTab: boolean + triggerExpansionOnTab: boolean, + excludeLanguages: string[] }; } @@ -89,27 +90,18 @@ class LazyEmmet { } private updateEmmetPreferences(configurationService: IConfigurationService, _emmet: typeof emmet) { - let preferences = configurationService.getConfiguration().emmet.preferences; - for (let key in preferences) { - try { - _emmet.preferences.set(key, preferences[key]); - } catch (err) { - _emmet.preferences.define(key, preferences[key]); - } + let emmetPreferences = configurationService.getConfiguration().emmet; + try { + _emmet.loadPreferences(emmetPreferences.preferences); + _emmet.loadProfiles(emmetPreferences.syntaxProfiles); + } catch (err) { + // ignore } - let syntaxProfiles = configurationService.getConfiguration().emmet.syntaxProfiles; - _emmet.profile.reset(); - _emmet.loadProfiles(syntaxProfiles); } private resetEmmetPreferences(configurationService: IConfigurationService, _emmet: typeof emmet) { - let preferences = configurationService.getConfiguration().emmet.preferences; - for (let key in preferences) { - try { - _emmet.preferences.remove(key); - } catch (err) { - } - } + _emmet.preferences.reset(); + _emmet.profile.reset(); } private _withEmmetPreferences(configurationService: IConfigurationService, _emmet: typeof emmet, callback: (_emmet: typeof emmet) => void): void { @@ -149,6 +141,7 @@ export abstract class EmmetEditorAction extends EditorAction { let editorAccessor = new EditorAccessor( editor, configurationService.getConfiguration().emmet.syntaxProfiles, + configurationService.getConfiguration().emmet.excludeLanguages, new GrammarContributions() ); diff --git a/src/vs/workbench/parts/emmet/test/common/editorAccessor.test.ts b/src/vs/workbench/parts/emmet/test/common/editorAccessor.test.ts index 15c50b4e17e..753ac92e942 100644 --- a/src/vs/workbench/parts/emmet/test/common/editorAccessor.test.ts +++ b/src/vs/workbench/parts/emmet/test/common/editorAccessor.test.ts @@ -1,3 +1,10 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + import {EditorAccessor, IGrammarContributions} from 'vs/workbench/parts/emmet/node/editorAccessor'; import {withMockCodeEditor} from 'vs/editor/test/common/mocks/mockCodeEditor'; import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; @@ -33,7 +40,6 @@ class MockGrammarContributions implements IGrammarContributions { } } - export interface IGrammarContributions { getGrammar(mode: string): string; } @@ -43,9 +49,9 @@ suite('Emmet', () => { test('emmet isEnabled', () => { withMockCodeEditor([], {}, (editor) => { - function testIsEnabled(mode: string, scopeName: string, isEnabled = true, profile = {}) { + function testIsEnabled(mode: string, scopeName: string, isEnabled = true, profile = {}, excluded = []) { editor.getModel().setMode(new MockMode(mode)); - let editorAccessor = new EditorAccessor(editor, profile, new MockGrammarContributions(scopeName)); + let editorAccessor = new EditorAccessor(editor, profile, excluded, new MockGrammarContributions(scopeName)); assert.equal(editorAccessor.isEmmetEnabledMode(), isEnabled); } @@ -56,7 +62,7 @@ suite('Emmet', () => { }); // syntaxes mapped to html, hard coded - let html = ['razor', 'handlebars', 'erb', 'php', 'hbs', 'ejs', 'twig']; // twig?? + let html = ['razor', 'handlebars']; html.forEach(each => { testIsEnabled(each, null); }); @@ -78,15 +84,18 @@ suite('Emmet', () => { testIsEnabled('java', 'source.java', true, { 'java': 'html' }); + + // emmet enabled language that is disabled + testIsEnabled('php', 'text.html.php', false, {}, ['php']); }); }); test('emmet syntax profiles', () => { withMockCodeEditor([], {}, (editor) => { - function testSyntax(mode: string, scopeName: string, expectedSyntax: string, profile = {}) { + function testSyntax(mode: string, scopeName: string, expectedSyntax: string, profile = {}, excluded = []) { editor.getModel().setMode(new MockMode(mode)); - let editorAccessor = new EditorAccessor(editor, profile, new MockGrammarContributions(scopeName)); + let editorAccessor = new EditorAccessor(editor, profile, excluded, new MockGrammarContributions(scopeName)); assert.equal(editorAccessor.getSyntax(), expectedSyntax); } @@ -97,7 +106,7 @@ suite('Emmet', () => { }); // syntaxes mapped to html, hard coded - let html = ['razor', 'handlebars', 'erb', 'php', 'hbs', 'ejs', 'twig']; // twig?? + let html = ['razor', 'handlebars']; html.forEach(each => { testSyntax(each, null, 'html'); }); diff --git a/src/vs/workbench/parts/execution/common/execution.ts b/src/vs/workbench/parts/execution/common/execution.ts index 0ecd56a3935..b10f1b3a668 100644 --- a/src/vs/workbench/parts/execution/common/execution.ts +++ b/src/vs/workbench/parts/execution/common/execution.ts @@ -5,10 +5,12 @@ 'use strict'; import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; +import {TPromise} from 'vs/base/common/winjs.base'; export const ITerminalService = createDecorator('nativeTerminalService'); export interface ITerminalService { _serviceBrand: any; openTerminal(path: string): void; + runInTerminal(title: string, cwd: string, args: string[], env: { [key: string]: string; }): TPromise; } \ No newline at end of file diff --git a/src/vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt b/src/vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt new file mode 100644 index 00000000000..9c04e24140f Binary files /dev/null and b/src/vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt differ diff --git a/src/vs/workbench/parts/execution/electron-browser/terminal.ts b/src/vs/workbench/parts/execution/electron-browser/terminal.ts index f7ffe8f3bcc..f313af0a9cf 100644 --- a/src/vs/workbench/parts/execution/electron-browser/terminal.ts +++ b/src/vs/workbench/parts/execution/electron-browser/terminal.ts @@ -11,6 +11,8 @@ if (env.isLinux) { defaultTerminalLinux = 'x-terminal-emulator'; } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { defaultTerminalLinux = 'gnome-terminal'; + } else if (process.env.DESKTOP_SESSION === 'kde-plasma') { + defaultTerminalLinux = 'konsole'; } else if (process.env.COLORTERM) { defaultTerminalLinux = process.env.COLORTERM; } else if (process.env.TERM) { diff --git a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts b/src/vs/workbench/parts/execution/electron-browser/terminalService.ts index 9781b7d3fa9..a09d94e1380 100644 --- a/src/vs/workbench/parts/execution/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/execution/electron-browser/terminalService.ts @@ -4,18 +4,24 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import cp = require('child_process'); +import fs = require('fs'); +import processes = require('vs/base/node/processes'); +import nls = require('vs/nls'); import errors = require('vs/base/common/errors'); import {TPromise} from 'vs/base/common/winjs.base'; import {ITerminalService} from 'vs/workbench/parts/execution/common/execution'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {ITerminalConfiguration, DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX, DEFAULT_TERMINAL_OSX} from 'vs/workbench/parts/execution/electron-browser/terminal'; +import uri from 'vs/base/common/uri'; -import cp = require('child_process'); -import processes = require('vs/base/node/processes'); +const TERMINAL_TITLE = nls.localize('console.title', "VS Code Console"); export class WinTerminalService implements ITerminalService { public _serviceBrand: any; + private static CMD = 'cmd.exe'; + constructor( @IConfigurationService private _configurationService: IConfigurationService ) { @@ -28,6 +34,34 @@ export class WinTerminalService implements ITerminalService { .done(null, errors.onUnexpectedError); } + public runInTerminal(title: string, dir: string, args: string[], envVars: { [key: string]: string; }): TPromise { + + return new TPromise( (c, e) => { + + const title = `"${dir} - ${TERMINAL_TITLE}"`; + const command = `""${args.join('" "')}" & pause"`; // use '|' to only pause on non-zero exit code + + const cmdArgs = [ + '/c', 'start', title, '/wait', + 'cmd.exe', '/c', command + ]; + + // merge environment variables into a copy of the process.env + const env = extendObject(extendObject( { }, process.env), envVars); + + const options: any = { + cwd: dir, + env: env, + windowsVerbatimArguments: true + }; + + const cmd = cp.spawn(WinTerminalService.CMD, cmdArgs, options); + cmd.on('error', e); + + c(null); + }); + } + private spawnTerminal(spawner, configuration: ITerminalConfiguration, command: string, path?: string): TPromise { let terminalConfig = configuration.terminal.external; let exec = terminalConfig.windowsExec || DEFAULT_TERMINAL_WINDOWS; @@ -47,6 +81,8 @@ export class WinTerminalService implements ITerminalService { export class MacTerminalService implements ITerminalService { public _serviceBrand: any; + private static OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X + constructor( @IConfigurationService private _configurationService: IConfigurationService ) { } @@ -57,6 +93,54 @@ export class MacTerminalService implements ITerminalService { this.spawnTerminal(cp, configuration, path).done(null, errors.onUnexpectedError); } + public runInTerminal(title: string, dir: string, args: string[], envVars: { [key: string]: string; }): TPromise { + + return new TPromise( (c, e) => { + + // On OS X we do not launch the program directly but we launch an AppleScript that creates (or reuses) a Terminal window + // and then launches the program inside that window. + + const script = uri.parse(require.toUrl('vs/workbench/parts/execution/electron-browser/TerminalHelper.scpt')).fsPath; + + const osaArgs = [ + script, + '-t', title || TERMINAL_TITLE, + '-w', dir, + ]; + + for (let a of args) { + osaArgs.push('-pa'); + osaArgs.push(a); + } + + if (envVars) { + for (let key in envVars) { + osaArgs.push('-e'); + osaArgs.push(key + '=' + envVars[key]); + } + } + + let stderr = ''; + const osa = cp.spawn(MacTerminalService.OSASCRIPT, osaArgs); + osa.on('error', e); + osa.stderr.on('data', (data) => { + stderr += data.toString(); + }); + osa.on('exit', (code: number) => { + if (code === 0) { // OK + c(null); + } else { + if (stderr) { + const lines = stderr.split('\n', 1); + e(new Error(lines[0])); + } else { + e(new Error(nls.localize('mac.term.failed', "osascript failed with exit code {0}", code))); + } + } + }); + }); + } + private spawnTerminal(spawner, configuration: ITerminalConfiguration, path?: string): TPromise { let terminalConfig = configuration.terminal.external; let terminalApp = terminalConfig.osxExec || DEFAULT_TERMINAL_OSX; @@ -72,6 +156,9 @@ export class MacTerminalService implements ITerminalService { export class LinuxTerminalService implements ITerminalService { public _serviceBrand: any; + private static LINUX_TERM = '/usr/bin/gnome-terminal'; //private const string LINUX_TERM = "/usr/bin/x-terminal-emulator"; + private static WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue..."); + constructor( @IConfigurationService private _configurationService: IConfigurationService ) { } @@ -84,6 +171,43 @@ export class LinuxTerminalService implements ITerminalService { .done(null, errors.onUnexpectedError); } + public runInTerminal(title: string, dir: string, args: string[], envVars: { [key: string]: string; }): TPromise { + + return new TPromise( (c, e) => { + + if (!fs.existsSync(LinuxTerminalService.LINUX_TERM)) { + e(nls.localize('program.not.found', "'{0}' not found", LinuxTerminalService.LINUX_TERM)); + return; + } + + const bashCommand = `${quote(args)}; echo; read -p "${LinuxTerminalService.WAIT_MESSAGE}" -n1;`; + + const termArgs = [ + '--title', `"${TERMINAL_TITLE}"`, + '-x', 'bash', '-c', + `''${bashCommand}''` // wrapping argument in two sets of ' because node is so "friendly" that it removes one set... + ]; + + // merge environment variables into a copy of the process.env + const env = extendObject(extendObject( { }, process.env), envVars); + + const options: any = { + cwd: dir, + env: env + }; + + const cmd = cp.spawn(LinuxTerminalService.LINUX_TERM, termArgs, options); + cmd.on('error', e); + cmd.on('exit', (code: number) => { + if (code === 0) { // OK + c(null); + } else { + e(nls.localize('linux.term.failed', "{0} failed with exit code {1}", LinuxTerminalService.LINUX_TERM, code)); + } + }); + }); + } + private spawnTerminal(spawner, configuration: ITerminalConfiguration, path?: string): TPromise { let terminalConfig = configuration.terminal.external; let exec = terminalConfig.linuxExec || DEFAULT_TERMINAL_LINUX; @@ -96,3 +220,30 @@ export class LinuxTerminalService implements ITerminalService { }); } } + +function extendObject (objectCopy: T, object: T): T { + + for (let key in object) { + if (object.hasOwnProperty(key)) { + objectCopy[key] = object[key]; + } + } + + return objectCopy; +} + +/** + * Quote args if necessary and combine into a space separated string. + */ +function quote(args: string[]): string { + let r = ''; + for (let a of args) { + if (a.indexOf(' ') >= 0) { + r += '"' + a + '"'; + } else { + r += a; + } + r += ' '; + } + return r; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/common/extensionQuery.ts b/src/vs/workbench/parts/extensions/common/extensionQuery.ts index 8920115e476..ee7992e27fa 100644 --- a/src/vs/workbench/parts/extensions/common/extensionQuery.ts +++ b/src/vs/workbench/parts/extensions/common/extensionQuery.ts @@ -41,7 +41,7 @@ export class Query { } isValid(): boolean { - return !!this.sortBy || !this.sortOrder; + return !/@outdated/.test(this.value) && (!!this.sortBy || !this.sortOrder); } equals(other: Query): boolean { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts index f31e633b260..1dbfdd133a4 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionEditor.ts @@ -11,16 +11,16 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { marked } from 'vs/base/common/marked/marked'; import { always } from 'vs/base/common/async'; import * as arrays from 'vs/base/common/arrays'; -import Event, { Emitter, once } from 'vs/base/common/event'; +import Event, { Emitter, once, fromEventEmitter, filterEvent, mapEvent } from 'vs/base/common/event'; import Cache from 'vs/base/common/cache'; import { Action } from 'vs/base/common/actions'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; +import Severity from 'vs/base/common/severity'; import { IDisposable, empty, dispose, toDisposable } from 'vs/base/common/lifecycle'; import { Builder } from 'vs/base/browser/builder'; import { domEvent } from 'vs/base/browser/event'; import { append, $, addClass, removeClass, finalHandler, join } from 'vs/base/browser/dom'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -41,6 +41,7 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi import { Keybinding } from 'vs/base/common/keyCodes'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { IMessageService } from 'vs/platform/message/common/message'; function renderBody(body: string): string { return ` @@ -108,11 +109,11 @@ export class ExtensionEditor extends BaseEditor { static ID: string = 'workbench.editor.extension'; private icon: HTMLImageElement; - private name: HTMLAnchorElement; - private license: HTMLAnchorElement; - private publisher: HTMLAnchorElement; + private name: HTMLElement; + private license: HTMLElement; + private publisher: HTMLElement; private installCount: HTMLElement; - private rating: HTMLAnchorElement; + private rating: HTMLElement; private description: HTMLElement; private extensionActionBar: ActionBar; private navbar: NavBar; @@ -138,7 +139,8 @@ export class ExtensionEditor extends BaseEditor { @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IThemeService private themeService: IThemeService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IKeybindingService private keybindingService: IKeybindingService + @IKeybindingService private keybindingService: IKeybindingService, + @IMessageService private messageService: IMessageService ) { super(ExtensionEditor.ID, telemetryService); this._highlight = null; @@ -146,8 +148,6 @@ export class ExtensionEditor extends BaseEditor { this.disposables = []; this.extensionReadme = null; this.extensionManifest = null; - - this.disposables.push(viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); } createEditor(parent: Builder): void { @@ -160,20 +160,16 @@ export class ExtensionEditor extends BaseEditor { const details = append(header, $('.details')); const title = append(details, $('.title')); - this.name = append(title, $('a.name')); - this.name.href = '#'; + this.name = append(title, $('span.name.clickable')); const subtitle = append(details, $('.subtitle')); - this.publisher = append(subtitle, $('a.publisher')); - this.publisher.href = '#'; + this.publisher = append(subtitle, $('span.publisher.clickable')); this.installCount = append(subtitle, $('span.install')); - this.rating = append(subtitle, $('a.rating')); - this.rating.href = '#'; + this.rating = append(subtitle, $('span.rating.clickable')); - this.license = append(subtitle, $('a.license')); - this.license.href = '#'; + this.license = append(subtitle, $('span.license.clickable')); this.license.textContent = localize('license', 'License'); this.license.style.display = 'none'; @@ -183,6 +179,11 @@ export class ExtensionEditor extends BaseEditor { this.extensionActionBar = new ActionBar(extensionActions, { animated: false }); this.disposables.push(this.extensionActionBar); + let onActionError = fromEventEmitter<{ error?: any; }>(this.extensionActionBar, 'run'); + onActionError = mapEvent(onActionError, ({ error }) => error); + onActionError = filterEvent(onActionError, error => !!error); + onActionError(this.onError, this, this.disposables); + const body = append(root, $('.body')); this.navbar = new NavBar(body); @@ -275,7 +276,7 @@ export class ExtensionEditor extends BaseEditor { webview.style(this.themeService.getColorTheme()); webview.contents = [body]; - const linkListener = webview.onDidClickLink(link => shell.openExternal(link.toString())); + const linkListener = webview.onDidClickLink(link => shell.openExternal(link.toString(true))); const themeListener = this.themeService.onDidColorThemeChange(themeId => webview.style(themeId)); this.contentDisposables.push(webview, linkListener, themeListener); }) @@ -545,12 +546,12 @@ export class ExtensionEditor extends BaseEditor { this.layoutParticipants.forEach(p => p.layout()); } - private onViewletOpen(viewlet: IViewlet): void { - if (!viewlet || viewlet.getId() === VIEWLET_ID) { + private onError(err: any): void { + if (isPromiseCanceledError(err)) { return; } - this.editorService.closeEditor(this.position, this.input).done(null, onUnexpectedError); + this.messageService.show(Severity.Error, err); } dispose(): void { diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts index 4af77ab5c32..c349c71e48e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionTipsService.ts @@ -125,7 +125,7 @@ export class ExtensionTipsService implements IExtensionTipsService { this.messageService.show(Severity.Info, { message, - actions: [CloseAction, neverAgainAction, recommendationsAction] + actions: [recommendationsAction, neverAgainAction, CloseAction] }); }); }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts index b2def00113c..b318df8ac13 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.contribution.ts @@ -21,11 +21,12 @@ import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/co import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { VIEWLET_ID, IExtensionsWorkbenchService } from './extensions'; import { ExtensionsWorkbenchService } from './extensionsWorkbenchService'; -import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction } from './extensionsActions'; +import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, UpdateAllAction, OpenExtensionsFolderAction } from './extensionsActions'; import { ExtensionsInput } from './extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; import { ExtensionEditor } from './extensionEditor'; import { IQuickOpenRegistry, Extensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen'; +import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; // Singletons registerSingleton(IExtensionGalleryService, ExtensionGalleryService); @@ -100,10 +101,31 @@ const listOutdatedActionDescriptor = new SyncActionDescriptor(ShowOutdatedExtens actionRegistry.registerWorkbenchAction(listOutdatedActionDescriptor, 'Extensions: Show Outdated Extensions', ExtensionsLabel); const recommendationsActionDescriptor = new SyncActionDescriptor(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL); -actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, `Extensions: ${ ShowRecommendedExtensionsAction.LABEL }`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(recommendationsActionDescriptor, 'Extensions: Show Recommended Extensions', ExtensionsLabel); const popularActionDescriptor = new SyncActionDescriptor(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL); -actionRegistry.registerWorkbenchAction(popularActionDescriptor, `Extensions: ${ ShowPopularExtensionsAction.LABEL }`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(popularActionDescriptor, 'Extensions: Show Popular Extensions', ExtensionsLabel); const installedActionDescriptor = new SyncActionDescriptor(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL); -actionRegistry.registerWorkbenchAction(installedActionDescriptor, `Extensions: ${ ShowInstalledExtensionsAction.LABEL }`, ExtensionsLabel); +actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: Show Installed Extensions', ExtensionsLabel); + +const updateAllActionDescriptor = new SyncActionDescriptor(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); +actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel); + +const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); +actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); + +Registry.as(ConfigurationExtensions.Configuration) + .registerConfiguration({ + id: 'extensions', + order: 30, + title: localize('extensionsConfigurationTitle', "Extensions"), + type: 'object', + properties: { + 'extensions.autoUpdate': { + type: 'boolean', + description: localize('extensionsAutoUpdate', "Automatically update extensions"), + default: false + } + } + }); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensions.ts index 6b23be42b4b..72d745cf147 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensions.ts @@ -57,4 +57,8 @@ export interface IExtensionsWorkbenchService { canInstall(extension: IExtension): boolean; install(extension: IExtension): TPromise; uninstall(extension: IExtension): TPromise; +} + +export interface IExtensionsConfiguration { + autoUpdate: boolean; } \ No newline at end of file diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts index c162ad875e6..1258c9ba391 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsActions.ts @@ -8,16 +8,19 @@ import { localize } from 'vs/nls'; import { TPromise } from 'vs/base/common/winjs.base'; import { Action } from 'vs/base/common/actions'; import severity from 'vs/base/common/severity'; +import paths = require('vs/base/common/paths'); import Event from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions'; import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet } from './extensions'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IMessageService, LaterAction } from 'vs/platform/message/common/message'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { ToggleViewletAction } from 'vs/workbench/browser/viewlet'; import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Query } from '../common/extensionQuery'; +import { shell } from 'electron'; export class InstallAction extends Action { @@ -94,7 +97,7 @@ export class UninstallAction extends Action { return this.extensionsWorkbenchService.uninstall(this.extension).then(() => { this.messageService.show(severity.Info, { message: localize('postUninstallMessage', "{0} was successfully uninstalled. Restart to deactivate it.", this.extension.displayName), - actions: [LaterAction, this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now"))] + actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('restartNow', "Restart Now")), LaterAction] }); }); } @@ -262,6 +265,45 @@ export class EnableAction extends Action { } } +export class UpdateAllAction extends Action { + + static ID = 'extensions.update-all'; + static LABEL = localize('updateAll', "Update All Extensions"); + + private disposables: IDisposable[] = []; + + constructor( + id = UpdateAllAction.ID, + label = UpdateAllAction.LABEL, + @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService + ) { + super(id, label, '', false); + + this.disposables.push(this.extensionsWorkbenchService.onChange(() => this.update())); + this.update(); + } + + private get outdated(): IExtension[] { + return this.extensionsWorkbenchService.local + .filter(e => this.extensionsWorkbenchService.canInstall(e) + && (e.state === ExtensionState.Installed || e.state === ExtensionState.NeedsRestart) + && e.outdated); + } + + private update(): void { + this.enabled = this.outdated.length > 0; + } + + run(): TPromise { + return TPromise.join(this.outdated.map(e => this.extensionsWorkbenchService.install(e))); + } + + dispose(): void { + super.dispose(); + this.disposables = dispose(this.disposables); + } +} + export class OpenExtensionsViewletAction extends ToggleViewletAction { static ID = VIEWLET_ID; @@ -431,7 +473,7 @@ export class ChangeSortAction extends Action { ) { super(id, label, null, true); - if (this.sortBy === undefined && this.sortOrder === undefined) { + if (sortBy === undefined && sortOrder === undefined) { throw new Error('bad arguments'); } @@ -443,7 +485,7 @@ export class ChangeSortAction extends Action { private onSearchChange(value: string): void { const query = Query.parse(value); this.query = new Query(query.value, this.sortBy || query.sortBy, this.sortOrder || query.sortOrder); - this.enabled = this.query.isValid() && !this.query.equals(query); + this.enabled = value && this.query.isValid() && !this.query.equals(query); } run(): TPromise { @@ -455,6 +497,31 @@ export class ChangeSortAction extends Action { }); } + protected isEnabled(): boolean { + return true; + } +} + +export class OpenExtensionsFolderAction extends Action { + + static ID = 'workbench.extensions.action.openExtensionsFolder'; + static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); + + constructor( + id: string, + label: string, + @IEnvironmentService private environmentService: IEnvironmentService + ) { + super(id, label, null, true); + } + + run(): TPromise { + const extensionsHome = this.environmentService.extensionsPath; + shell.showItemInFolder(paths.normalize(extensionsHome, true)); + + return TPromise.as(true); + } + protected isEnabled(): boolean { return true; } diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts index 63130efe6f0..84f2362f4de 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsViewlet.ts @@ -9,10 +9,10 @@ import 'vs/css!./media/extensionsViewlet'; import { localize } from 'vs/nls'; import { ThrottledDelayer, always } from 'vs/base/common/async'; import { TPromise } from 'vs/base/common/winjs.base'; +import { isPromiseCanceledError, onUnexpectedError, create as createError } from 'vs/base/common/errors'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { Builder, Dimension } from 'vs/base/browser/builder'; import { assign } from 'vs/base/common/objects'; -import { onUnexpectedError } from 'vs/base/common/errors'; import EventOf, { mapEvent, filterEvent } from 'vs/base/common/event'; import { IAction } from 'vs/base/common/actions'; import { domEvent } from 'vs/base/browser/event'; @@ -20,6 +20,8 @@ import { Separator } from 'vs/base/browser/ui/actionbar/actionbar'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { Viewlet } from 'vs/workbench/browser/viewlet'; +import { IViewlet } from 'vs/workbench/common/viewlet'; +import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService'; import { append, $, addStandardDisposableListener, EventType, addClass, removeClass, toggleClass } from 'vs/base/browser/dom'; import { PagedModel } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -27,12 +29,16 @@ import { PagedList } from 'vs/base/browser/ui/list/listPaging'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Delegate, Renderer } from './extensionsList'; import { IExtensionsWorkbenchService, IExtension, IExtensionsViewlet, VIEWLET_ID } from './extensions'; -import { ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction } from './extensionsActions'; +import { ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowInstalledExtensionsAction, ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction } from './extensionsActions'; import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { ExtensionsInput } from './extensionsInput'; import { Query } from '../common/extensionQuery'; +import { OpenGlobalSettingsAction } from 'vs/workbench/browser/actions/openSettings'; import { IProgressService } from 'vs/platform/progress/common/progress'; import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; +import { IMessageService, CloseAction } from 'vs/platform/message/common/message'; +import Severity from 'vs/base/common/severity'; import { IURLService } from 'vs/platform/url/common/url'; import URI from 'vs/base/common/uri'; @@ -60,15 +66,20 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { @IProgressService private progressService: IProgressService, @IInstantiationService private instantiationService: IInstantiationService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IEditorGroupService private editorInputService: IEditorGroupService, @IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService, @IURLService urlService: IURLService, - @IExtensionTipsService private tipsService: IExtensionTipsService + @IExtensionTipsService private tipsService: IExtensionTipsService, + @IMessageService private messageService: IMessageService, + @IViewletService private viewletService: IViewletService ) { super(VIEWLET_ID, telemetryService); this.searchDelayer = new ThrottledDelayer(500); const onOpenExtensionUrl = filterEvent(urlService.onOpenURL, uri => /^extension/.test(uri.path)); onOpenExtensionUrl(this.onOpenExtensionUrl, this, this.disposables); + + this.disposables.push(viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables)); } create(parent: Builder): TPromise { @@ -153,6 +164,8 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { getSecondaryActions(): IAction[] { if (!this.secondaryActions) { this.secondaryActions = [ + this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL), + new Separator(), this.instantiationService.createInstance(ShowInstalledExtensionsAction, ShowInstalledExtensionsAction.ID, ShowInstalledExtensionsAction.LABEL), this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL), this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL), @@ -178,7 +191,8 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { } private triggerSearch(value: string, immediate = false, suggestPopular = false): void { - this.searchDelayer.trigger(() => this.doSearch(value, suggestPopular), immediate || !value ? 0 : 500); + this.searchDelayer.trigger(() => this.doSearch(value, suggestPopular), immediate || !value ? 0 : 500) + .done(null, err => this.onError(err)); } private doSearch(value: string = '', suggestPopular = false): TPromise { @@ -207,23 +221,6 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { const query = Query.parse(value); let options: IQueryOptions = {}; - if (/@recommended/i.test(query.value)) { - const value = query.value.replace(/@recommended/g, '').trim(); - - return this.extensionsWorkbenchService.queryLocal().then(local => { - const names = this.tipsService.getRecommendations() - .filter(name => local.every(ext => `${ ext.publisher }.${ ext.name }` !== name)) - .filter(name => name.indexOf(value) > -1); - - if (!names.length) { - return new PagedModel([]); - } - - return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) - .then(result => new PagedModel(result)); - }); - } - switch(query.sortBy) { case 'installs': options = assign(options, { sortBy: SortBy.InstallCount }); break; case 'rating': options = assign(options, { sortBy: SortBy.AverageRating }); break; @@ -234,8 +231,27 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { case 'desc': options = assign(options, { sortOrder: SortOrder.Descending }); break; } + if (/@recommended/i.test(query.value)) { + const value = query.value.replace(/@recommended/g, '').trim().toLowerCase(); + + return this.extensionsWorkbenchService.queryLocal().then(local => { + const names = this.tipsService.getRecommendations() + .filter(name => local.every(ext => `${ ext.publisher }.${ ext.name }` !== name)) + .filter(name => name.toLowerCase().indexOf(value) > -1); + + this.telemetryService.publicLog('extensionRecommendations:open', { count: names.length }); + + if (!names.length) { + return new PagedModel([]); + } + + return this.extensionsWorkbenchService.queryGallery(assign(options, { names, pageSize: names.length })) + .then(result => new PagedModel(result)); + }); + } + if (query.value) { - options = assign(options, { text: query.value }); + options = assign(options, { text: query.value.substr(0, 200) }); } return this.extensionsWorkbenchService.queryGallery(options) @@ -244,7 +260,7 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { private openExtension(extension: IExtension): void { this.editorService.openEditor(this.instantiationService.createInstance(ExtensionsInput, extension)) - .done(null, onUnexpectedError); + .done(null, err => this.onError(err)); } private onEnter(): void { @@ -300,6 +316,46 @@ export class ExtensionsViewlet extends Viewlet implements IExtensionsViewlet { return always(promise, () => progressRunner.done()); } + private onViewletOpen(viewlet: IViewlet): void { + if (!viewlet || viewlet.getId() === VIEWLET_ID) { + return; + } + + const model = this.editorInputService.getStacksModel(); + + const promises = model.groups.map(group => { + const position = model.positionOfGroup(group); + const inputs = group.getEditors().filter(input => input instanceof ExtensionsInput); + const promises = inputs.map(input => this.editorService.closeEditor(position, input)); + + return TPromise.join(promises); + }); + + TPromise.join(promises).done(null, onUnexpectedError); + } + + private onError(err: any): void { + if (isPromiseCanceledError(err)) { + return; + } + + const message = err && err.message || ''; + + if (!/ECONNREFUSED/.test(message)) { + const error = createError(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), { + actions: [ + this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL), + CloseAction + ] + }); + + this.messageService.show(Severity.Error, error); + return; + } + + this.messageService.show(Severity.Error, err); + } + dispose(): void { this.disposables = dispose(this.disposables); super.dispose(); diff --git a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchService.ts index e739b4e1766..8d766531de7 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/electron-browser/extensionsWorkbenchService.ts @@ -9,19 +9,27 @@ import 'vs/css!./media/extensionsViewlet'; import Event, { Emitter } from 'vs/base/common/event'; import { index } from 'vs/base/common/arrays'; import { assign } from 'vs/base/common/objects'; +import { isUUID } from 'vs/base/common/uuid'; import { ThrottledDelayer } from 'vs/base/common/async'; +import { isPromiseCanceledError } from 'vs/base/common/errors'; import { TPromise } from 'vs/base/common/winjs.base'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest } from 'vs/platform/extensionManagement/common/extensionManagement'; import { getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData } from 'vs/platform/extensionManagement/common/extensionTelemetry'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IMessageService } from 'vs/platform/message/common/message'; +import Severity from 'vs/base/common/severity'; import * as semver from 'semver'; import * as path from 'path'; import URI from 'vs/base/common/uri'; import { readFile } from 'vs/base/node/pfs'; import { asText } from 'vs/base/node/request'; -import { IExtension, ExtensionState, IExtensionsWorkbenchService } from './extensions'; +import { IExtension, ExtensionState, IExtensionsWorkbenchService, IExtensionsConfiguration } from './extensions'; +import { UpdateAllAction } from './extensionsActions'; + interface IExtensionStateProvider { (extension: Extension): ExtensionState; @@ -215,9 +223,12 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { get onChange(): Event { return this._onChange.event; } constructor( + @IInstantiationService private instantiationService: IInstantiationService, @IExtensionManagementService private extensionService: IExtensionManagementService, @IExtensionGalleryService private galleryService: IExtensionGalleryService, - @ITelemetryService private telemetryService: ITelemetryService + @IConfigurationService private configurationService: IConfigurationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IMessageService private messageService: IMessageService ) { this.stateProvider = ext => this.getExtensionState(ext); @@ -228,7 +239,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { this.syncDelayer = new ThrottledDelayer(ExtensionsWorkbenchService.SyncPeriod); - this.queryLocal().done(() => this.syncWithGallery(true)); + this.queryLocal().done(() => this.eventuallySyncWithGallery(true)); } get local(): IExtension[] { @@ -280,23 +291,34 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { return new Extension(this.galleryService, this.stateProvider, null, gallery); } - private syncWithGallery(immediate = false): void { - const loop = () => this.doSyncWithGallery().then(() => this.syncWithGallery()); + private eventuallySyncWithGallery(immediate = false): void { + const loop = () => this.syncWithGallery().then(() => this.eventuallySyncWithGallery()); const delay = immediate ? 0 : ExtensionsWorkbenchService.SyncPeriod; - this.syncDelayer.trigger(loop, delay); + this.syncDelayer.trigger(loop, delay) + .done(null, err => this.onError(err)); } - private doSyncWithGallery(): TPromise { + private syncWithGallery(): TPromise { const ids = this.installed .filter(e => !!(e.local && e.local.metadata)) - .map(e => e.local.metadata.id); + .map(e => e.local.metadata.id) + .filter(id => isUUID(id)); if (ids.length === 0) { return TPromise.as(null); } - return this.queryGallery({ ids, pageSize: ids.length }) as TPromise; + return this.queryGallery({ ids, pageSize: ids.length }).then(() => { + const config = this.configurationService.getConfiguration('extensions'); + + if (!config.autoUpdate) { + return; + } + + const action = this.instantiationService.createInstance(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL); + return action.enabled && action.run(); + }); } canInstall(extension: IExtension): boolean { @@ -370,7 +392,6 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { extension.local = local; extension.needsRestart = true; - let eventName: string = 'extensionGallery:install'; this.installing = this.installing.filter(e => e.id !== id); if (!error) { @@ -378,7 +399,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { const installed = this.installed.filter(e => (e.local.metadata && e.local.metadata.id) === galleryId)[0]; if (galleryId && installed) { - eventName = 'extensionGallery:update'; + installing.operation = Operation.Updating; installed.local = local; } else { this.installed.push(extension); @@ -439,6 +460,20 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { this.telemetryService.publicLog(eventName, assign(data, { success, duration })); } + private onError(err: any): void { + if (isPromiseCanceledError(err)) { + return; + } + + const message = err && err.message || ''; + + if (/getaddrinfo ENOTFOUND/.test(message)) { + return; + } + + this.messageService.show(Severity.Error, err); + } + dispose(): void { this.disposables = dispose(this.disposables); } diff --git a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css index 2f3104dad5f..bbe9e8f565e 100644 --- a/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css +++ b/src/vs/workbench/parts/extensions/electron-browser/media/extensionEditor.css @@ -8,8 +8,8 @@ overflow: hidden; } -.extension-editor a { - color: inherit; +.extension-editor .clickable { + cursor: pointer; } .extension-editor > .header { diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index 161b09e55e5..302ec42f8a0 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -7,6 +7,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {MIME_BINARY, MIME_TEXT} from 'vs/base/common/mime'; import types = require('vs/base/common/types'); import paths = require('vs/base/common/paths'); @@ -122,10 +123,14 @@ export class TextFileEditor extends BaseTextEditor { // Check Model state const textFileModel = resolvedModel; + + const hasInput = !!this.getInput(); + const modelDisposed = textFileModel.isDisposed(); + const inputChanged = (this.getInput()).getResource().toString() !== textFileModel.getResource().toString(); if ( - !this.getInput() || // editor got hidden meanwhile - textFileModel.isDisposed() || // input got disposed meanwhile - (this.getInput()).getResource().toString() !== textFileModel.getResource().toString() // a different input was set meanwhile + !hasInput || // editor got hidden meanwhile + modelDisposed || // input got disposed meanwhile + inputChanged // a different input was set meanwhile ) { return null; } @@ -173,9 +178,8 @@ export class TextFileEditor extends BaseTextEditor { // Offer to create a file from the error if we have a file not found and the name is valid if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND && paths.isValidBasename(paths.basename((input).getResource().fsPath))) { - return TPromise.wrapError(errors.create(errors.toErrorMessage(error), { + return TPromise.wrapError(errors.create(toErrorMessage(error), { actions: [ - CancelAction, new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), null, true, () => { return this.fileService.updateContent((input).getResource(), '').then(() => { @@ -188,7 +192,8 @@ export class TextFileEditor extends BaseTextEditor { } }); }); - }) + }), + CancelAction ] })); } diff --git a/src/vs/workbench/parts/files/browser/fileActions.ts b/src/vs/workbench/parts/files/browser/fileActions.ts index 64d059e2ed8..fd7a3c9a40c 100644 --- a/src/vs/workbench/parts/files/browser/fileActions.ts +++ b/src/vs/workbench/parts/files/browser/fileActions.ts @@ -14,6 +14,7 @@ import {MIME_TEXT, isUnspecific, isBinaryMime, guessMimeTypes} from 'vs/base/com import paths = require('vs/base/common/paths'); import URI from 'vs/base/common/uri'; import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import strings = require('vs/base/common/strings'); import {Event, EventType as CommonEventType} from 'vs/base/common/events'; import severity from 'vs/base/common/severity'; @@ -23,7 +24,6 @@ import {Action, IAction} from 'vs/base/common/actions'; import {MessageType, IInputValidator} from 'vs/base/browser/ui/inputbox/inputBox'; import {ITree, IHighlightEvent} from 'vs/base/parts/tree/browser/tree'; import {dispose, IDisposable} from 'vs/base/common/lifecycle'; -import {EventType as WorkbenchEventType} from 'vs/workbench/common/events'; import {LocalFileChangeEvent, VIEWLET_ID, ITextFileService, TextFileChangeEvent, EventType as FileEventType} from 'vs/workbench/parts/files/common/files'; import {IFileService, IFileStat, IImportResult} from 'vs/platform/files/common/files'; import {DiffEditorInput, toDiffLabel} from 'vs/workbench/common/editor/diffEditorInput'; @@ -32,7 +32,6 @@ import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEdito import {FileStat, NewStatPlaceholder} from 'vs/workbench/parts/files/common/explorerViewModel'; import {ExplorerView} from 'vs/workbench/parts/files/browser/views/explorerView'; import {ExplorerViewlet} from 'vs/workbench/parts/files/browser/explorerViewlet'; -import {CACHE} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {IActionProvider} from 'vs/base/parts/tree/browser/actionsRenderer'; import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; @@ -129,17 +128,17 @@ export class BaseFileAction extends Action { protected onErrorWithRetry(error: any, retry: () => TPromise, extraAction?: Action): void { let actions = [ - CancelAction, - new Action(this.id, nls.localize('retry', "Retry"), null, true, () => retry()) + new Action(this.id, nls.localize('retry', "Retry"), null, true, () => retry()), + CancelAction ]; if (extraAction) { - actions.push(extraAction); + actions.unshift(extraAction); } let errorWithRetry: IMessageWithAction = { - actions: actions, - message: errors.toErrorMessage(error, false) + actions, + message: toErrorMessage(error, false) }; this._messageService.show(Severity.Error, errorWithRetry); @@ -1395,7 +1394,7 @@ export abstract class BaseActionWithErrorReporting extends Action { public run(context?: any): TPromise { return this.doRun(context).then(() => true, (error) => { - this.messageService.show(Severity.Error, errors.toErrorMessage(error, false)); + this.messageService.show(Severity.Error, toErrorMessage(error, false)); }); } @@ -1449,7 +1448,7 @@ export abstract class BaseSaveFileAction extends BaseActionWithErrorReporting { if (source.scheme === 'untitled') { encodingOfSource = this.untitledEditorService.get(source).getEncoding(); } else if (source.scheme === 'file') { - let textModel = CACHE.get(source); + let textModel = this.textFileService.models.get(source); encodingOfSource = textModel && textModel.getEncoding(); // text model can be null e.g. if this is a binary file! } @@ -1565,8 +1564,7 @@ export abstract class BaseSaveAllAction extends BaseActionWithErrorReporting { this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.updateEnablement(true))); if (this.includeUntitled()) { - this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, () => this.updateEnablement(true))); - this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, () => this.updateEnablement(false))); + this.toDispose.push(this.untitledEditorService.onDidChangeDirty(resource => this.updateEnablement(this.untitledEditorService.isDirty(resource)))); } } diff --git a/src/vs/workbench/parts/files/browser/files.contribution.ts b/src/vs/workbench/parts/files/browser/files.contribution.ts index 1463df5a2a3..7b64c085dc9 100644 --- a/src/vs/workbench/parts/files/browser/files.contribution.ts +++ b/src/vs/workbench/parts/files/browser/files.contribution.ts @@ -19,7 +19,7 @@ import {IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory, Ed import {FileEditorDescriptor} from 'vs/workbench/parts/files/browser/files'; import {AutoSaveConfiguration, SUPPORTED_ENCODINGS} from 'vs/platform/files/common/files'; import {FILE_EDITOR_INPUT_ID, VIEWLET_ID} from 'vs/workbench/parts/files/common/files'; -import {FileTracker} from 'vs/workbench/parts/files/browser/fileTracker'; +import {FileEditorTracker} from 'vs/workbench/parts/files/common/editors/fileEditorTracker'; import {SaveParticipant} from 'vs/workbench/parts/files/common/editors/saveParticipant'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import {TextFileEditor} from 'vs/workbench/parts/files/browser/editors/textFileEditor'; @@ -145,9 +145,9 @@ class FileEditorInputFactory implements IEditorInputFactory { (Registry.as(EditorExtensions.Editors)).registerEditorInputFactory(FILE_EDITOR_INPUT_ID, FileEditorInputFactory); -// Register File Tracker +// Register File Editor Tracker (Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution( - FileTracker + FileEditorTracker ); // Register Save Participant @@ -216,7 +216,7 @@ configurationRegistry.registerConfiguration({ 'type': 'string', 'enum': [AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, , AutoSaveConfiguration.ON_WINDOW_CHANGE], 'default': AutoSaveConfiguration.OFF, - 'description': nls.localize('autoSave', "Controls auto save of dirty files. Accepted values: \"{0}\", \"{1}\", \"{2}\" (editor looses focus), \"{3}\" (window looses focus). If set to \"{4}\" you can configure the delay in \"files.autoSaveDelay\".", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) + 'description': nls.localize('autoSave', "Controls auto save of dirty files. Accepted values: \"{0}\", \"{1}\", \"{2}\" (editor loses focus), \"{3}\" (window loses focus). If set to \"{4}\", you can configure the delay in \"files.autoSaveDelay\".", AutoSaveConfiguration.OFF, AutoSaveConfiguration.AFTER_DELAY, AutoSaveConfiguration.ON_FOCUS_CHANGE, AutoSaveConfiguration.ON_WINDOW_CHANGE, AutoSaveConfiguration.AFTER_DELAY) }, 'files.autoSaveDelay': { 'type': 'number', diff --git a/src/vs/workbench/parts/files/browser/media/AddFolder.svg b/src/vs/workbench/parts/files/browser/media/AddFolder.svg index 796d98c9be1..722f48507f7 100644 --- a/src/vs/workbench/parts/files/browser/media/AddFolder.svg +++ b/src/vs/workbench/parts/files/browser/media/AddFolder.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css index e1a25ce4612..5dbaac7ee5e 100644 --- a/src/vs/workbench/parts/files/browser/media/explorerviewlet.css +++ b/src/vs/workbench/parts/files/browser/media/explorerviewlet.css @@ -11,6 +11,7 @@ .explorer-viewlet .explorer-item, .explorer-viewlet .open-editor, .explorer-viewlet .editor-group { + height: 22px; line-height: 22px; } @@ -32,6 +33,7 @@ .explorer-viewlet .explorer-folders-view.show-file-icons .folder-icon::before, .explorer-viewlet .explorer-folders-view.show-file-icons .file-icon::before { + /* svg icons rendered as background image */ background-size: 16px; background-position: left center; @@ -42,9 +44,8 @@ display: inline-block; /* fonts icons */ - vertical-align: top; -webkit-font-smoothing: antialiased; - + vertical-align: top; } .explorer-viewlet .explorer-open-editors .monaco-tree .monaco-tree-row > .content > .monaco-action-bar { @@ -96,10 +97,6 @@ line-height: normal; } -.explorer-viewlet .explorer-folders-view.show-file-icons .explorer-item .monaco-inputbox { - width: calc(100% - 20px); /* accomodate for icons pushing (16px width + 4px padding) */ -} - .monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox, .monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox { height: 22px; @@ -157,4 +154,15 @@ .hc-black .monaco-workbench .explorer-viewlet .open-editor, .hc-black .monaco-workbench .explorer-viewlet .editor-group { line-height: 20px; +} + +.hc-black .monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox > .wrapper > .input:focus { + outline-offset: -2px !important; + outline: 1px solid #f38518 !important; + padding-left: 2px; /* push text by the width of the outline offset to prevent overlaying */ +} + +.hc-black .monaco-workbench .explorer-viewlet .explorer-item .monaco-inputbox.synthetic-focus { + outline: none !important; + border: none; } \ No newline at end of file diff --git a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts index d977ae010b5..0a7d7a74d90 100644 --- a/src/vs/workbench/parts/files/browser/saveErrorHandler.ts +++ b/src/vs/workbench/parts/files/browser/saveErrorHandler.ts @@ -7,6 +7,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import paths = require('vs/base/common/paths'); import {Action} from 'vs/base/common/actions'; import URI from 'vs/base/common/uri'; @@ -20,10 +21,9 @@ import {DiffEditorModel} from 'vs/workbench/common/editor/diffEditorModel'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import {SaveFileAsAction, RevertFileAction, SaveFileAction} from 'vs/workbench/parts/files/browser/fileActions'; import {IFileService, IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files'; -import {TextFileEditorModel, ISaveErrorHandler} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IEventService} from 'vs/platform/event/common/event'; -import {EventType as FileEventType, TextFileChangeEvent, ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {EventType as FileEventType, TextFileChangeEvent, ITextFileService, ISaveErrorHandler, ITextFileEditorModel} from 'vs/workbench/parts/files/common/files'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IMessageService, IMessageWithAction, Severity, CancelAction} from 'vs/platform/message/common/message'; import {IModeService} from 'vs/editor/common/services/modeService'; @@ -56,7 +56,7 @@ export class SaveErrorHandler implements ISaveErrorHandler { } } - public onSaveError(error: any, model: TextFileEditorModel): void { + public onSaveError(error: any, model: ITextFileEditorModel): void { let message: IMessageWithAction; const resource = model.getResource(); @@ -70,14 +70,29 @@ export class SaveErrorHandler implements ISaveErrorHandler { const isReadonly = (error).fileOperationResult === FileOperationResult.FILE_READ_ONLY; const actions: Action[] = []; - // Cancel - actions.push(CancelAction); + // Save As + actions.push(new Action('workbench.files.action.saveAs', SaveFileAsAction.LABEL, null, true, () => { + const saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); + saveAsAction.setResource(resource); + saveAsAction.run().done(() => saveAsAction.dispose(), errors.onUnexpectedError); + + return TPromise.as(true); + })); + + // Discard + actions.push(new Action('workbench.files.action.discard', nls.localize('discard', "Discard"), null, true, () => { + const revertFileAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); + revertFileAction.setResource(resource); + revertFileAction.run().done(() => revertFileAction.dispose(), errors.onUnexpectedError); + + return TPromise.as(true); + })); // Retry if (isReadonly) { actions.push(new Action('workbench.files.action.overwrite', nls.localize('overwrite', "Overwrite"), null, true, () => { if (!model.isDisposed()) { - return model.save(true /* overwrite readonly */).then(() => true); + model.save(true /* overwrite readonly */).done(null, errors.onUnexpectedError); } return TPromise.as(true); @@ -86,37 +101,25 @@ export class SaveErrorHandler implements ISaveErrorHandler { actions.push(new Action('workbench.files.action.retry', nls.localize('retry', "Retry"), null, true, () => { const saveFileAction = this.instantiationService.createInstance(SaveFileAction, SaveFileAction.ID, SaveFileAction.LABEL); saveFileAction.setResource(resource); + saveFileAction.run().done(() => saveFileAction.dispose(), errors.onUnexpectedError); - return saveFileAction.run().then(() => { saveFileAction.dispose(); return true; }); + return TPromise.as(true); })); } - // Discard - actions.push(new Action('workbench.files.action.discard', nls.localize('discard', "Discard"), null, true, () => { - const revertFileAction = this.instantiationService.createInstance(RevertFileAction, RevertFileAction.ID, RevertFileAction.LABEL); - revertFileAction.setResource(resource); - - return revertFileAction.run().then(() => { revertFileAction.dispose(); return true; }); - })); - - // Save As - actions.push(new Action('workbench.files.action.saveAs', SaveFileAsAction.LABEL, null, true, () => { - const saveAsAction = this.instantiationService.createInstance(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL); - saveAsAction.setResource(resource); - - return saveAsAction.run().then(() => { saveAsAction.dispose(); return true; }); - })); + // Cancel + actions.push(CancelAction); let errorMessage: string; if (isReadonly) { errorMessage = nls.localize('readonlySaveError', "Failed to save '{0}': File is write protected. Select 'Overwrite' to remove protection.", paths.basename(resource.fsPath)); } else { - errorMessage = nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(resource.fsPath), errors.toErrorMessage(error, false)); + errorMessage = nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(resource.fsPath), toErrorMessage(error, false)); } message = { message: errorMessage, - actions: actions + actions }; } @@ -130,10 +133,10 @@ export class ConflictResolutionDiffEditorInput extends DiffEditorInput { public static ID = 'workbench.editors.files.conflictResolutionDiffEditorInput'; - private model: TextFileEditorModel; + private model: ITextFileEditorModel; constructor( - model: TextFileEditorModel, + model: ITextFileEditorModel, name: string, description: string, originalInput: FileOnDiskEditorInput, @@ -144,7 +147,7 @@ export class ConflictResolutionDiffEditorInput extends DiffEditorInput { this.model = model; } - public getModel(): TextFileEditorModel { + public getModel(): ITextFileEditorModel { return this.model; } @@ -223,10 +226,10 @@ class ResolveSaveConflictMessage implements IMessageWithAction { public message: string; public actions: Action[]; - private model: TextFileEditorModel; + private model: ITextFileEditorModel; constructor( - model: TextFileEditorModel, + model: ITextFileEditorModel, message: string, @IMessageService private messageService: IMessageService, @IInstantiationService private instantiationService: IInstantiationService, diff --git a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts index 8f38f59add2..430c861adb4 100644 --- a/src/vs/workbench/parts/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/parts/files/browser/views/explorerViewer.ts @@ -85,10 +85,10 @@ export class FileDataSource implements IDataSource { else { // Resolve - let promise = this.fileService.resolveFile(stat.resource, { resolveSingleChildDescendants: true }).then((dirStat: IFileStat) => { + const promise = this.fileService.resolveFile(stat.resource, { resolveSingleChildDescendants: true }).then(dirStat => { // Convert to view model - let modelDirStat = FileStat.create(dirStat); + const modelDirStat = FileStat.create(dirStat); // Add children to folder for (let i = 0; i < modelDirStat.children.length; i++) { @@ -182,7 +182,7 @@ export class FileActionProvider extends ContributableActionProvider { }, context); if (!isString(arg)) { - let action = arg; + const action = arg; if (action.enabled) { return action.run(context); } @@ -190,7 +190,7 @@ export class FileActionProvider extends ContributableActionProvider { return null; } - let id = arg; + const id = arg; let promise = this.hasActions(tree, stat) ? this.getActions(tree, stat) : TPromise.as([]); return promise.then((actions: IAction[]) => { @@ -307,10 +307,10 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { } public renderContents(tree: ITree, stat: FileStat, domElement: HTMLElement, previousCleanupFn: IElementCallback): IElementCallback { - let el = $(domElement).clearChildren(); + const el = $(domElement).clearChildren(); // Item Container - let item = $('.explorer-item'); + const item = $('.explorer-item'); if (stat.isDirectory || (stat instanceof NewStatPlaceholder && stat.isDirectoryPlaceholder())) { item.addClass(...this.folderIconClasses(stat.resource.fsPath)); } else { @@ -325,7 +325,7 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { item.appendTo(el); // File/Folder label - let editableData: IEditableData = this.state.getEditableData(stat); + const editableData: IEditableData = this.state.getEditableData(stat); if (!editableData) { return this.renderFileFolderLabel(item, stat); } @@ -335,7 +335,7 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { } private renderFileFolderLabel(container: Builder, stat: IFileStat): IElementCallback { - let label = $('.explorer-item-label').appendTo(container); + const label = $('.explorer-item-label').appendTo(container); $('a.plain').text(stat.name).title(stat.resource.fsPath).appendTo(label); return null; @@ -344,7 +344,7 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { private renderNameInput(container: Builder, tree: ITree, stat: FileStat, editableData: IEditableData): IElementCallback { // Input field (when creating a new file or folder or renaming) - let inputBox = new InputBox(container.getHTMLElement(), this.contextViewService, { + const inputBox = new InputBox(container.getHTMLElement(), this.contextViewService, { validationOptions: { validation: editableData.validator, showMessage: true @@ -352,14 +352,14 @@ export class FileRenderer extends ActionsRenderer implements IRenderer { ariaLabel: nls.localize('fileInputAriaLabel', "Type file name. Press Enter to confirm or Escape to cancel.") }); - let value = stat.name || ''; - let lastDot = value.lastIndexOf('.'); + const value = stat.name || ''; + const lastDot = value.lastIndexOf('.'); inputBox.value = value; inputBox.select({ start: 0, end: lastDot > 0 && !stat.isDirectory ? lastDot : value.length }); inputBox.focus(); - let done = async.once(commit => { + const done = async.once(commit => { tree.clearHighlight(); if (commit && inputBox.value) { @@ -489,8 +489,8 @@ export class FileController extends DefaultController { } /* protected */ public onLeftClick(tree: ITree, stat: FileStat, event: IMouseEvent, origin: string = 'mouse'): boolean { - let payload = { origin: origin }; - let isDoubleClick = (origin === 'mouse' && event.detail === 2); + const payload = { origin: origin }; + const isDoubleClick = (origin === 'mouse' && event.detail === 2); // Handle Highlight Mode if (tree.getHighlight()) { @@ -513,7 +513,7 @@ export class FileController extends DefaultController { } // Cancel Event - let isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown'; + const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown'; if (!isMouseDown) { event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise } @@ -527,7 +527,7 @@ export class FileController extends DefaultController { // Allow to unselect if (event.shiftKey && !(stat instanceof NewStatPlaceholder)) { - let selection = tree.getSelection(); + const selection = tree.getSelection(); if (selection && selection.length > 0 && selection[0] === stat) { tree.clearSelection(payload); } @@ -535,7 +535,7 @@ export class FileController extends DefaultController { // Select, Focus and open files else if (!(stat instanceof NewStatPlaceholder)) { - let preserveFocus = !isDoubleClick; + const preserveFocus = !isDoubleClick; tree.setFocus(stat, payload); if (isDoubleClick) { @@ -566,7 +566,7 @@ export class FileController extends DefaultController { return true; } - let anchor = { x: event.posx + 1, y: event.posy }; + const anchor = { x: event.posx + 1, y: event.posy }; this.contextMenuService.showContextMenu({ getAnchor: () => anchor, getActions: () => { @@ -598,9 +598,9 @@ export class FileController extends DefaultController { return false; } - let payload = { origin: 'keyboard' }; + const payload = { origin: 'keyboard' }; - let stat: FileStat = tree.getFocus(); + const stat: FileStat = tree.getFocus(); if (stat) { // Directory: Toggle expansion @@ -625,7 +625,7 @@ export class FileController extends DefaultController { return false; } - let stat: FileStat = tree.getFocus(); + const stat: FileStat = tree.getFocus(); if (stat && !stat.isDirectory) { this.openEditor(stat, false, false); } @@ -640,7 +640,7 @@ export class FileController extends DefaultController { return false; } - let stat: FileStat = tree.getFocus(); + const stat: FileStat = tree.getFocus(); if (stat && !stat.isDirectory) { this.openEditor(stat, false, true); } @@ -651,7 +651,7 @@ export class FileController extends DefaultController { } private onCopy(tree: ITree, event: IKeyboardEvent): boolean { - let stat: FileStat = tree.getFocus(); + const stat: FileStat = tree.getFocus(); if (stat) { this.runAction(tree, stat, 'workbench.files.action.copyFile').done(); @@ -662,9 +662,9 @@ export class FileController extends DefaultController { } private onPaste(tree: ITree, event: IKeyboardEvent): boolean { - let stat: FileStat = tree.getFocus() || tree.getInput() /* root */; + const stat: FileStat = tree.getFocus() || tree.getInput() /* root */; if (stat) { - let pasteAction = this.instantiationService.createInstance(PasteFileAction, tree, stat); + const pasteAction = this.instantiationService.createInstance(PasteFileAction, tree, stat); if (pasteAction._isEnabled()) { pasteAction.run().done(null, errors.onUnexpectedError); @@ -685,7 +685,7 @@ export class FileController extends DefaultController { } private onF2(tree: ITree, event: IKeyboardEvent): boolean { - let stat: FileStat = tree.getFocus(); + const stat: FileStat = tree.getFocus(); if (stat) { this.runAction(tree, stat, 'workbench.files.action.triggerRename').done(); @@ -697,8 +697,8 @@ export class FileController extends DefaultController { } private onDelete(tree: ITree, event: IKeyboardEvent): boolean { - let useTrash = !event.shiftKey; - let stat: FileStat = tree.getFocus(); + const useTrash = !event.shiftKey; + const stat: FileStat = tree.getFocus(); if (stat) { this.runAction(tree, stat, useTrash ? 'workbench.files.action.moveFileToTrash' : 'workbench.files.action.deleteFile').done(); @@ -753,8 +753,8 @@ export class FileFilter implements IFilter { } public updateConfiguration(configuration: IFilesConfiguration): boolean { - let excludesConfig = (configuration && configuration.files && configuration.files.exclude) || Object.create(null); - let needsRefresh = !objects.equals(this.hiddenExpression, excludesConfig); + const excludesConfig = (configuration && configuration.files && configuration.files.exclude) || Object.create(null); + const needsRefresh = !objects.equals(this.hiddenExpression, excludesConfig); this.hiddenExpression = objects.clone(excludesConfig); // do not keep the config, as it gets mutated under our hoods @@ -817,11 +817,15 @@ export class FileDragAndDrop implements IDragAndDrop { } public getDragURI(tree: ITree, stat: FileStat): string { - return stat.resource && stat.resource.toString(); + if (stat.isDirectory) { + return URI.from({ scheme: 'folder', path: stat.resource.fsPath }).toString(); // indicates that we are dragging a folder + } + + return stat.resource.toString(); } public onDragStart(tree: ITree, data: IDragAndDropData, originalEvent: DragMouseEvent): void { - let sources: FileStat[] = data.getData(); + const sources: FileStat[] = data.getData(); let source: FileStat = null; if (sources.length > 0) { source = sources[0]; @@ -846,20 +850,20 @@ export class FileDragAndDrop implements IDragAndDrop { return DRAG_OVER_REJECT; } - let isCopy = originalEvent && ((originalEvent.ctrlKey && !platform.isMacintosh) || (originalEvent.altKey && platform.isMacintosh)); - let fromDesktop = data instanceof DesktopDragAndDropData; + const isCopy = originalEvent && ((originalEvent.ctrlKey && !platform.isMacintosh) || (originalEvent.altKey && platform.isMacintosh)); + const fromDesktop = data instanceof DesktopDragAndDropData; // Desktop DND if (fromDesktop) { - let dragData = (data).getData(); + const dragData = (data).getData(); - let types = dragData.types; - let typesArray: string[] = []; + const types = dragData.types; + const typesArray: string[] = []; for (let i = 0; i < types.length; i++) { typesArray.push(types[i]); } - if (typesArray.length === 0 || !typesArray.some((type) => { return type === 'Files'; })) { + if (typesArray.length === 0 || !typesArray.some(type => { return type === 'Files'; })) { return DRAG_OVER_REJECT; } } @@ -871,7 +875,7 @@ export class FileDragAndDrop implements IDragAndDrop { // In-Explorer DND else { - let sources: FileStat[] = data.getData(); + const sources: FileStat[] = data.getData(); if (!Array.isArray(sources)) { return DRAG_OVER_REJECT; } @@ -916,7 +920,7 @@ export class FileDragAndDrop implements IDragAndDrop { // Desktop DND (Import file) if (data instanceof DesktopDragAndDropData) { - let importAction = this.instantiationService.createInstance(ImportFileAction, tree, target, null); + const importAction = this.instantiationService.createInstance(ImportFileAction, tree, target, null); promise = importAction.run({ input: { files: (data).getData().files @@ -926,14 +930,14 @@ export class FileDragAndDrop implements IDragAndDrop { // In-Explorer DND (Move/Copy file) else { - let source: FileStat = data.getData()[0]; - let isCopy = (originalEvent.ctrlKey && !platform.isMacintosh) || (originalEvent.altKey && platform.isMacintosh); + const source: FileStat = data.getData()[0]; + const isCopy = (originalEvent.ctrlKey && !platform.isMacintosh) || (originalEvent.altKey && platform.isMacintosh); promise = tree.expand(target).then(() => { // Reuse action if user copies if (isCopy) { - let copyAction = this.instantiationService.createInstance(DuplicateFileAction, tree, source, target); + const copyAction = this.instantiationService.createInstance(DuplicateFileAction, tree, source, target); return copyAction.run(); } @@ -967,29 +971,29 @@ export class FileDragAndDrop implements IDragAndDrop { } return revertPromise.then(() => { - let targetResource = URI.file(paths.join(target.resource.fsPath, source.name)); + const targetResource = URI.file(paths.join(target.resource.fsPath, source.name)); let didHandleConflict = false; - let onMove = (result: IFileStat) => { + const onMove = (result: IFileStat) => { this.eventService.emit('files.internal:fileChanged', new LocalFileChangeEvent(source.clone(), result)); }; // Move File/Folder and emit event - return this.fileService.moveFile(source.resource, targetResource).then(onMove, (error) => { + return this.fileService.moveFile(source.resource, targetResource).then(onMove, error => { // Conflict if ((error).fileOperationResult === FileOperationResult.FILE_MOVE_CONFLICT) { didHandleConflict = true; - let confirm: IConfirmation = { + const confirm: IConfirmation = { message: nls.localize('confirmOverwriteMessage', "'{0}' already exists in the destination folder. Do you want to replace it?", source.name), detail: nls.localize('irreversible', "This action is irreversible!"), primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace") }; if (this.messageService.confirm(confirm)) { - return this.fileService.moveFile(source.resource, targetResource, true).then((result) => { - let fakeTargetState = new FileStat(targetResource); + return this.fileService.moveFile(source.resource, targetResource, true).then(result => { + const fakeTargetState = new FileStat(targetResource); this.eventService.emit('files.internal:fileChanged', new LocalFileChangeEvent(fakeTargetState, null)); onMove(result); diff --git a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts index e7afbbc9b70..ee197c016ea 100644 --- a/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorInput.ts @@ -17,7 +17,6 @@ import {IEditorRegistry, Extensions, EditorModel, EncodingMode, ConfirmResult, I import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel'; import {IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files'; import {ITextFileService, BINARY_FILE_EDITOR_ID, FILE_EDITOR_INPUT_ID, FileEditorInput as CommonFileEditorInput, AutoSaveMode, ModelState, EventType as FileEventType, TextFileChangeEvent, IFileEditorDescriptor} from 'vs/workbench/parts/files/common/files'; -import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; @@ -31,9 +30,6 @@ export class FileEditorInput extends CommonFileEditorInput { // Do ref counting for all inputs that resolved to a model to be able to dispose when count = 0 private static FILE_EDITOR_MODEL_CLIENTS: { [resource: string]: FileEditorInput[]; } = Object.create(null); - // Keep promises that load a file editor model to avoid loading the same model twice - private static FILE_EDITOR_MODEL_LOADERS: { [resource: string]: TPromise; } = Object.create(null); - private resource: URI; private mime: string; private preferredEncoding: string; @@ -114,7 +110,7 @@ export class FileEditorInput extends CommonFileEditorInput { } public getEncoding(): string { - let textModel = CACHE.get(this.resource); + const textModel = this.textFileService.models.get(this.resource); if (textModel) { return textModel.getEncoding(); } @@ -125,7 +121,7 @@ export class FileEditorInput extends CommonFileEditorInput { public setEncoding(encoding: string, mode: EncodingMode): void { this.preferredEncoding = encoding; - let textModel = CACHE.get(this.resource); + const textModel = this.textFileService.models.get(this.resource); if (textModel) { textModel.setEncoding(encoding, mode); } @@ -160,7 +156,7 @@ export class FileEditorInput extends CommonFileEditorInput { } public isDirty(): boolean { - const model = CACHE.get(this.resource); + const model = this.textFileService.models.get(this.resource); if (!model) { return false; } @@ -190,21 +186,21 @@ export class FileEditorInput extends CommonFileEditorInput { } public getPreferredEditorId(candidates: string[]): string { - let editorRegistry = (Registry.as(Extensions.Editors)); + const editorRegistry = (Registry.as(Extensions.Editors)); // Lookup Editor by Mime let descriptor: IEditorDescriptor; - let mimes = this.mime.split(','); + const mimes = this.mime.split(','); for (let m = 0; m < mimes.length; m++) { - let mime = strings.trim(mimes[m]); + const mime = strings.trim(mimes[m]); for (let i = 0; i < candidates.length; i++) { descriptor = editorRegistry.getEditorById(candidates[i]); if (types.isFunction((descriptor).getMimeTypes)) { - let mimetypes = (descriptor).getMimeTypes(); + const mimetypes = (descriptor).getMimeTypes(); for (let j = 0; j < mimetypes.length; j++) { - let mimetype = mimetypes[j]; + const mimetype = mimetypes[j]; // Check for direct mime match if (mime === mimetype) { @@ -225,50 +221,24 @@ export class FileEditorInput extends CommonFileEditorInput { } public resolve(refresh?: boolean): TPromise { - let modelPromise: TPromise; - let resource = this.resource.toString(); + const resource = this.resource.toString(); // Keep clients who resolved the input to support proper disposal - let clients = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[resource]; + const clients = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[resource]; if (types.isUndefinedOrNull(clients)) { FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[resource] = [this]; } else if (this.indexOfClient() === -1) { FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[resource].push(this); } - // Check for running loader to ensure the model is only ever loaded once - if (FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource]) { - return FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource]; - } + return this.textFileService.models.loadOrCreate(this.resource, this.preferredEncoding, refresh).then(null, error => { - // Use Cached Model if present - let cachedModel = CACHE.get(this.resource); - if (cachedModel && !refresh) { - modelPromise = TPromise.as(cachedModel); - } - - // Refresh Cached Model if present - else if (cachedModel && refresh) { - modelPromise = cachedModel.load(); - FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = modelPromise; - } - - // Otherwise Create Model and Load - else { - modelPromise = this.createAndLoadModel(); - FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = modelPromise; - } - - return modelPromise.then((resolvedModel: TextFileEditorModel | BinaryEditorModel) => { - if (resolvedModel instanceof TextFileEditorModel) { - CACHE.add(this.resource, resolvedModel); // Store into the text model cache unless this file is binary + // In case of an error that indicates that the file is binary or too large, just return with the binary editor model + if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { + return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); } - FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = null; // Remove from pending loaders - - return resolvedModel; - }, (error) => { - FileEditorInput.FILE_EDITOR_MODEL_LOADERS[resource] = null; // Remove from pending loaders in case of an error + // Bubble any other error up return TPromise.wrapError(error); }); } @@ -277,7 +247,7 @@ export class FileEditorInput extends CommonFileEditorInput { const inputs = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS[this.resource.toString()]; if (inputs) { for (let i = 0; i < inputs.length; i++) { - let client = inputs[i]; + const client = inputs[i]; if (client === this) { return i; } @@ -287,28 +257,6 @@ export class FileEditorInput extends CommonFileEditorInput { return -1; } - private createAndLoadModel(): TPromise { - let descriptor = (Registry.as(Extensions.Editors)).getEditor(this); - if (!descriptor) { - throw new Error('Unable to find an editor in the registry for this input.'); - } - - // Optimistically create a text model assuming that the file is not binary - let textModel = this.instantiationService.createInstance(TextFileEditorModel, this.resource, this.preferredEncoding); - return textModel.load().then(() => textModel, (error) => { - - // In case of an error that indicates that the file is binary or too large, just return with the binary editor model - if ((error).fileOperationResult === FileOperationResult.FILE_IS_BINARY || (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE) { - textModel.dispose(); - - return this.instantiationService.createInstance(BinaryEditorModel, this.resource, this.getName()).load(); - } - - // Bubble any other error up - return TPromise.wrapError(error); - }); - } - public dispose(): void { // Listeners @@ -347,11 +295,11 @@ export class FileEditorInput extends CommonFileEditorInput { * that have been loaded during the session. */ public static getAll(desiredFileOrFolderResource: URI): FileEditorInput[] { - let inputsContainingResource: FileEditorInput[] = []; + const inputsContainingResource: FileEditorInput[] = []; - let clients = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS; - for (let resource in clients) { - let inputs = clients[resource]; + const clients = FileEditorInput.FILE_EDITOR_MODEL_CLIENTS; + for (const resource in clients) { + const inputs = clients[resource]; // Check if path is identical or path is a folder that the content is inside if (paths.isEqualOrParent(resource, desiredFileOrFolderResource.toString())) { diff --git a/src/vs/workbench/parts/files/browser/fileTracker.ts b/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts similarity index 59% rename from src/vs/workbench/parts/files/browser/fileTracker.ts rename to src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts index 20804fac4eb..a5dda07dca4 100644 --- a/src/vs/workbench/parts/files/browser/fileTracker.ts +++ b/src/vs/workbench/parts/files/common/editors/fileEditorTracker.ts @@ -6,156 +6,65 @@ import {IWorkbenchContribution} from 'vs/workbench/common/contributions'; import errors = require('vs/base/common/errors'); -import nls = require('vs/nls'); import {MIME_UNKNOWN} from 'vs/base/common/mime'; import URI from 'vs/base/common/uri'; import paths = require('vs/base/common/paths'); -import arrays = require('vs/base/common/arrays'); import {DiffEditorInput} from 'vs/workbench/common/editor/diffEditorInput'; +import {IEditor} from 'vs/editor/common/editorCommon'; +import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor'; import {EditorInput, IEditorStacksModel} from 'vs/workbench/common/editor'; -import {Position} from 'vs/platform/editor/common/editor'; -import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor'; -import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; -import {LocalFileChangeEvent, TextFileChangeEvent, VIEWLET_ID, BINARY_FILE_EDITOR_ID, EventType as FileEventType, ITextFileService, AutoSaveMode, ModelState} from 'vs/workbench/parts/files/common/files'; -import {FileChangeType, FileChangesEvent, EventType as CommonFileEventType} from 'vs/platform/files/common/files'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {LocalFileChangeEvent, BINARY_FILE_EDITOR_ID, ITextFileService, ModelState} from 'vs/workbench/parts/files/common/files'; +import {FileChangeType, FileChangesEvent, EventType as CommonFileEventType, IFileService} from 'vs/platform/files/common/files'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import {TextFileEditorModel, CACHE} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IActivityService, NumberBadge} from 'vs/workbench/services/activity/common/activityService'; import {IEventService} from 'vs/platform/event/common/event'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import {IHistoryService} from 'vs/workbench/services/history/common/history'; -// This extension tracks files for changes to update editors and inputs accordingly. -export class FileTracker implements IWorkbenchContribution { +export class FileEditorTracker implements IWorkbenchContribution { // Delay in ms that we wait at minimum before we update a model from a file change event. // This reduces the chance that a save from the client triggers an update of the editor. private static FILE_CHANGE_UPDATE_DELAY = 2000; - private lastDirtyCount: number; private stacks: IEditorStacksModel; private toUnbind: IDisposable[]; - private pendingDirtyResources: URI[]; - private pendingDirtyHandle: number; - constructor( + @IWorkspaceContextService private contextService: IWorkspaceContextService, @IEventService private eventService: IEventService, @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IActivityService private activityService: IActivityService, + @IFileService private fileService: IFileService, @ITextFileService private textFileService: ITextFileService, + @ILifecycleService private lifecycleService: ILifecycleService, @IHistoryService private historyService: IHistoryService, @IEditorGroupService private editorGroupService: IEditorGroupService, - @IInstantiationService private instantiationService: IInstantiationService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService + @IInstantiationService private instantiationService: IInstantiationService ) { this.toUnbind = []; this.stacks = editorGroupService.getStacksModel(); - this.pendingDirtyResources = []; this.registerListeners(); } public getId(): string { - return 'vs.files.filetracker'; + return 'vs.files.fileEditorTracker'; } private registerListeners(): void { // Update editors and inputs from local changes and saves - this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e: UntitledEditorEvent) => this.onUntitledEditorSaved(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onUntitledEditorDirty(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => this.onTextFileReverted(e))); this.toUnbind.push(this.eventService.addListener2('files.internal:fileChanged', (e: LocalFileChangeEvent) => this.onLocalFileChange(e))); // Update editors and inputs from disk changes this.toUnbind.push(this.eventService.addListener2(CommonFileEventType.FILE_CHANGES, (e: FileChangesEvent) => this.onFileChanges(e))); - } - private onEditorsChanged(): void { - this.disposeUnusedTextFileModels(); - } - - private onTextFileDirty(e: TextFileChangeEvent): void { - if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { - this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay - } - - // If a file becomes dirty but is not opened, we open it in the background - // Since it might be the intent of whoever created the model to show it shortly - // after, we delay this a little bit and check again if the editor has not been - // opened meanwhile - this.pendingDirtyResources.push(e.resource); - if (!this.pendingDirtyHandle) { - this.pendingDirtyHandle = setTimeout(() => this.doOpenDirtyResources(), 250); - } - } - - private doOpenDirtyResources(): void { - const dirtyNotOpenedResources = arrays.distinct(this.pendingDirtyResources.filter(r => !this.stacks.isOpen(r) && this.textFileService.isDirty(r)), r => r.toString()); - - // Reset - this.pendingDirtyHandle = void 0; - this.pendingDirtyResources = []; - - const activeEditor = this.editorService.getActiveEditor(); - const activePosition = activeEditor ? activeEditor.position : Position.LEFT; - - // Open - this.editorService.openEditors(dirtyNotOpenedResources.map(resource => { - return { - input: { - resource, - options: { inactive: true, pinned: true, preserveFocus: true } - }, - position: activePosition - }; - })).done(null, errors.onUnexpectedError); - } - - private onTextFileSaveError(e: TextFileChangeEvent): void { - this.updateActivityBadge(); - } - - private onTextFileSaved(e: TextFileChangeEvent): void { - if (this.lastDirtyCount > 0) { - this.updateActivityBadge(); - } - } - - private onTextFileReverted(e: TextFileChangeEvent): void { - if (this.lastDirtyCount > 0) { - this.updateActivityBadge(); - } - } - - private onUntitledEditorDirty(e: UntitledEditorEvent): void { - this.updateActivityBadge(); - } - - private onUntitledEditorSaved(e: UntitledEditorEvent): void { - if (this.lastDirtyCount > 0) { - this.updateActivityBadge(); - } - } - - private updateActivityBadge(): void { - const dirtyCount = this.textFileService.getDirty().length; - this.lastDirtyCount = dirtyCount; - if (dirtyCount > 0) { - this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); - } else { - this.activityService.clearActivity(VIEWLET_ID); - } + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); } // Note: there is some duplication with the other file event handler below. Since we cannot always rely on the disk events @@ -180,104 +89,6 @@ export class FileTracker implements IWorkbenchContribution { } } - private onFileChanges(e: FileChangesEvent): void { - - // Dispose inputs that got deleted - const allDeleted = e.getDeleted(); - if (allDeleted && allDeleted.length > 0) { - allDeleted.forEach(deleted => { - this.handleDeleteOrMove(deleted.resource); - }); - } - - // Dispose models that got changed and are not visible. We do this because otherwise - // cached file models will be stale from the contents on disk. - e.getUpdated() - .map(u => CACHE.get(u.resource)) - .filter(model => { - const canDispose = this.canDispose(model); - if (!canDispose) { - return false; - } - - if (Date.now() - model.getLastSaveAttemptTime() < FileTracker.FILE_CHANGE_UPDATE_DELAY) { - return false; // this is a weak check to see if the change came from outside the editor or not - } - - return true; // ok boss - }) - .forEach(model => CACHE.dispose(model.getResource())); - - // Update inputs that got updated - const editors = this.editorService.getVisibleEditors(); - editors.forEach(editor => { - let input = editor.input; - if (input instanceof DiffEditorInput) { - input = this.getMatchingFileEditorInputFromDiff(input, e); - } - - // File Editor Input - if (input instanceof FileEditorInput) { - const fileInput = input; - const fileInputResource = fileInput.getResource(); - - // Input got added or updated, so check for model and update - // Note: we also consider the added event because it could be that a file was added - // and updated right after. - if (e.contains(fileInputResource, FileChangeType.UPDATED) || e.contains(fileInputResource, FileChangeType.ADDED)) { - const textModel = CACHE.get(fileInputResource); - - // Text file: check for last save time - if (textModel) { - - // We only ever update models that are in good saved state - if (textModel.getState() === ModelState.SAVED) { - const lastSaveTime = textModel.getLastSaveAttemptTime(); - - // Force a reopen of the input if this change came in later than our wait interval before we consider it - if (Date.now() - lastSaveTime > FileTracker.FILE_CHANGE_UPDATE_DELAY) { - const codeEditor = (editor).getControl(); - const viewState = codeEditor.saveViewState(); - const currentMtime = textModel.getLastModifiedTime(); // optimize for the case where the file did actually not change - textModel.load().done(() => { - if (textModel.getLastModifiedTime() !== currentMtime && this.isEditorShowingPath(editor, textModel.getResource())) { - codeEditor.restoreViewState(viewState); - } - }, errors.onUnexpectedError); - } - } - } - - // Binary file: always update - else if (editor.getId() === BINARY_FILE_EDITOR_ID) { - this.editorService.openEditor(editor.input, { forceOpen: true, preserveFocus: true }, editor.position).done(null, errors.onUnexpectedError); - } - } - } - }); - } - - private isEditorShowingPath(editor: BaseEditor, resource: URI): boolean { - - // Only relevant if Editor is visible - if (!editor.isVisible()) { - return false; - } - - // Only relevant if Input is set - let input = editor.getInput(); - if (!input) { - return false; - } - - // Support diff editor input too - if (input instanceof DiffEditorInput) { - input = (input).modifiedInput; - } - - return input instanceof FileEditorInput && (input).getResource().toString() === resource.toString(); - } - private handleMovedFileInOpenedEditors(oldResource: URI, newResource: URI, mimeHint?: string): void { const stacks = this.editorGroupService.getStacksModel(); stacks.groups.forEach(group => { @@ -304,6 +115,87 @@ export class FileTracker implements IWorkbenchContribution { }); } + private onFileChanges(e: FileChangesEvent): void { + + // Dispose inputs that got deleted + e.getDeleted().forEach(deleted => { + this.handleDeleteOrMove(deleted.resource); + }); + + // Handle updates to visible editors + this.handleUpdatesToVisibleEditors(e); + } + + private handleUpdatesToVisibleEditors(e: FileChangesEvent) { + const editors = this.editorService.getVisibleEditors(); + editors.forEach(editor => { + let input = editor.input; + if (input instanceof DiffEditorInput) { + input = this.getMatchingFileEditorInputFromDiff(input, e); + } + + // File Editor Input + if (input instanceof FileEditorInput) { + const fileInput = input; + const fileInputResource = fileInput.getResource(); + + // Input got added or updated, so check for model and update + // Note: we also consider the added event because it could be that a file was added + // and updated right after. + if (e.contains(fileInputResource, FileChangeType.UPDATED) || e.contains(fileInputResource, FileChangeType.ADDED)) { + const textModel = this.textFileService.models.get(fileInputResource); + + // Text file: check for last save time + if (textModel) { + + // We only ever update models that are in good saved state + if (textModel.getState() === ModelState.SAVED) { + const lastSaveTime = textModel.getLastSaveAttemptTime(); + + // Force a reopen of the input if this change came in later than our wait interval before we consider it + if (Date.now() - lastSaveTime > FileEditorTracker.FILE_CHANGE_UPDATE_DELAY) { + const codeEditor = editor.getControl(); + const viewState = codeEditor.saveViewState(); + const currentMtime = textModel.getLastModifiedTime(); // optimize for the case where the file did actually not change + textModel.load().done(() => { + if (textModel.getLastModifiedTime() !== currentMtime && this.isEditorShowingPath(editor, textModel.getResource())) { + codeEditor.restoreViewState(viewState); + } + }, errors.onUnexpectedError); + } + } + } + + // Binary file: always update + else if (editor.getId() === BINARY_FILE_EDITOR_ID) { + this.editorService.openEditor(editor.input, { forceOpen: true, preserveFocus: true }, editor.position).done(null, errors.onUnexpectedError); + } + } + } + }); + } + + private isEditorShowingPath(editor: IBaseEditor, resource: URI): boolean { + + // Only relevant if Editor is visible + if (!editor.isVisible()) { + return false; + } + + // Only relevant if Input is set + let input = editor.input; + if (!input) { + return false; + } + + // Support diff editor input too + if (input instanceof DiffEditorInput) { + input = (input).modifiedInput; + } + + return input instanceof FileEditorInput && (input).getResource().toString() === resource.toString(); + } + private getMatchingFileEditorInputFromDiff(input: DiffEditorInput, deletedResource: URI): FileEditorInput; private getMatchingFileEditorInputFromDiff(input: DiffEditorInput, updatedFiles: FileChangesEvent): FileEditorInput; private getMatchingFileEditorInputFromDiff(input: DiffEditorInput, arg: any): FileEditorInput { @@ -339,7 +231,7 @@ export class FileTracker implements IWorkbenchContribution { return null; } - public handleDeleteOrMove(resource: URI, movedTo?: URI): void { + private handleDeleteOrMove(resource: URI, movedTo?: URI): void { if (this.textFileService.isDirty(resource)) { return; // never dispose dirty resources from a delete } @@ -384,9 +276,6 @@ export class FileTracker implements IWorkbenchContribution { input.dispose(); } }); - - // Clean up model if any - CACHE.dispose(resource); } private containsResource(input: FileEditorInput, resource: URI): boolean; @@ -403,42 +292,7 @@ export class FileTracker implements IWorkbenchContribution { return false; } - private disposeUnusedTextFileModels(): void { - - // To not grow our text file model cache infinitly, we dispose models that - // are not showing up in any opened editor. - - // Get all cached file models - CACHE.getAll() - - // Only take text file models and remove those that are under working files or opened - .filter(model => !this.stacks.isOpen(model.getResource()) && this.canDispose(model)) - - // Dispose - .forEach(model => CACHE.dispose(model.getResource())); - } - - private canDispose(textModel: TextFileEditorModel): boolean { - if (!textModel) { - return false; // we need data! - } - - if (textModel.isDisposed()) { - return false; // already disposed - } - - if (textModel.textEditorModel && textModel.textEditorModel.isAttachedToEditor()) { - return false; // never dispose when attached to editor - } - - if (textModel.getState() !== ModelState.SAVED) { - return false; // never dispose unsaved models - } - - return true; - } - public dispose(): void { - dispose(this.toUnbind); + this.toUnbind = dispose(this.toUnbind); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/files/common/editors/saveParticipant.ts b/src/vs/workbench/parts/files/common/editors/saveParticipant.ts index e14eadcc6a1..c143ebc94e2 100644 --- a/src/vs/workbench/parts/files/common/editors/saveParticipant.ts +++ b/src/vs/workbench/parts/files/common/editors/saveParticipant.ts @@ -59,15 +59,15 @@ export class SaveParticipant implements IWorkbenchContribution { */ private doTrimTrailingWhitespace(model: IModel, isAutoSaved: boolean): void { let prevSelection: Selection[] = [new Selection(1, 1, 1, 1)]; - let cursors: IPosition[] = []; + const cursors: IPosition[] = []; // Find `prevSelection` in any case do ensure a good undo stack when pushing the edit // Collect active cursors in `cursors` only if `isAutoSaved` to avoid having the cursors jump if (model.isAttachedToEditor()) { - let allEditors = this.codeEditorService.listCodeEditors(); + const allEditors = this.codeEditorService.listCodeEditors(); for (let i = 0, len = allEditors.length; i < len; i++) { - let editor = allEditors[i]; - let editorModel = editor.getModel(); + const editor = allEditors[i]; + const editorModel = editor.getModel(); if (!editorModel) { continue; // empty editor @@ -87,7 +87,7 @@ export class SaveParticipant implements IWorkbenchContribution { } } - let ops = trimTrailingWhitespace(model, cursors); + const ops = trimTrailingWhitespace(model, cursors); if (!ops.length) { return; // Nothing to do } diff --git a/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts b/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts index 4d812cfcfe1..06ec2314df2 100644 --- a/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts +++ b/src/vs/workbench/parts/files/common/editors/textFileEditorModel.ts @@ -6,7 +6,8 @@ import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; -import {onUnexpectedError, toErrorMessage} from 'vs/base/common/errors'; +import {onUnexpectedError} from 'vs/base/common/errors'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import URI from 'vs/base/common/uri'; import {IDisposable} from 'vs/base/common/lifecycle'; import paths = require('vs/base/common/paths'); @@ -15,8 +16,8 @@ import types = require('vs/base/common/types'); import {IModelContentChangedEvent} from 'vs/editor/common/editorCommon'; import {IMode} from 'vs/editor/common/modes'; import {EventType as WorkbenchEventType, ResourceEvent} from 'vs/workbench/common/events'; -import {EventType as FileEventType, TextFileChangeEvent, ITextFileService, IAutoSaveConfiguration, ModelState} from 'vs/workbench/parts/files/common/files'; -import {EncodingMode, EditorModel, IEncodingSupport} from 'vs/workbench/common/editor'; +import {EventType as FileEventType, TextFileChangeEvent, ITextFileService, IAutoSaveConfiguration, ModelState, ITextFileEditorModel, ISaveErrorHandler} from 'vs/workbench/parts/files/common/files'; +import {EncodingMode, EditorModel} from 'vs/workbench/common/editor'; import {BaseTextEditorModel} from 'vs/workbench/common/editor/textEditorModel'; import {IFileService, IFileStat, IFileOperationResult, FileOperationResult} from 'vs/platform/files/common/files'; import {IEventService} from 'vs/platform/event/common/event'; @@ -26,38 +27,10 @@ import {IModeService} from 'vs/editor/common/services/modeService'; import {IModelService} from 'vs/editor/common/services/modelService'; import {ITelemetryService, anonymize} from 'vs/platform/telemetry/common/telemetry'; -/** - * The save error handler can be installed on the text text file editor model to install code that executes when save errors occur. - */ -export interface ISaveErrorHandler { - - /** - * Called whenever a save fails. - */ - onSaveError(error: any, model: TextFileEditorModel): void; -} - -class DefaultSaveErrorHandler implements ISaveErrorHandler { - - constructor( @IMessageService private messageService: IMessageService) { } - - public onSaveError(error: any, model: TextFileEditorModel): void { - this.messageService.show(Severity.Error, nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(model.getResource().fsPath), toErrorMessage(error, false))); - } -} - -// Diagnostics support -let diag: (...args: any[]) => void; -if (!diag) { - diag = diagnostics.register('TextFileEditorModelDiagnostics', function (...args: any[]) { - console.log(args[1] + ' - ' + args[0] + ' (time: ' + args[2].getTime() + ' [' + args[2].toUTCString() + '])'); - }); -} - /** * The text file editor model listens to changes to its underlying code editor model and saves these changes through the file service back to the disk. */ -export class TextFileEditorModel extends BaseTextEditorModel implements IEncodingSupport { +export class TextFileEditorModel extends BaseTextEditorModel implements ITextFileEditorModel { public static ID = 'workbench.editors.files.textFileEditorModel'; @@ -717,53 +690,23 @@ export class TextFileEditorModel extends BaseTextEditorModel implements IEncodin this.cancelAutoSavePromises(); - CACHE.remove(this.resource); - super.dispose(); } } -export class TextFileEditorModelCache { - private mapResourcePathToModel: { [resource: string]: TextFileEditorModel; }; +class DefaultSaveErrorHandler implements ISaveErrorHandler { - constructor() { - this.mapResourcePathToModel = Object.create(null); - } + constructor(@IMessageService private messageService: IMessageService) { } - public dispose(resource: URI): void { - const model = this.get(resource); - if (model) { - if (model.isDirty()) { - return; // we never dispose dirty models to avoid data loss - } - - model.dispose(); - } - } - - public get(resource: URI): TextFileEditorModel { - return this.mapResourcePathToModel[resource.toString()]; - } - - public getAll(resource?: URI): TextFileEditorModel[] { - return Object.keys(this.mapResourcePathToModel) - .filter((r) => !resource || resource.toString() === r) - .map((r) => this.mapResourcePathToModel[r]); - } - - public add(resource: URI, model: TextFileEditorModel): void { - this.mapResourcePathToModel[resource.toString()] = model; - } - - // Clients should not call this method - public clear(): void { - this.mapResourcePathToModel = Object.create(null); - } - - // Clients should not call this method - public remove(resource: URI): void { - delete this.mapResourcePathToModel[resource.toString()]; + public onSaveError(error: any, model: TextFileEditorModel): void { + this.messageService.show(Severity.Error, nls.localize('genericSaveError', "Failed to save '{0}': {1}", paths.basename(model.getResource().fsPath), toErrorMessage(error, false))); } } -export const CACHE = new TextFileEditorModelCache(); \ No newline at end of file +// Diagnostics support +let diag: (...args: any[]) => void; +if (!diag) { + diag = diagnostics.register('TextFileEditorModelDiagnostics', function (...args: any[]) { + console.log(args[1] + ' - ' + args[0] + ' (time: ' + args[2].getTime() + ' [' + args[2].toUTCString() + '])'); + }); +} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts b/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts new file mode 100644 index 00000000000..7b1412e908e --- /dev/null +++ b/src/vs/workbench/parts/files/common/editors/textFileEditorModelManager.ts @@ -0,0 +1,237 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; +import {ITextFileEditorModelManager} from 'vs/workbench/parts/files/common/files'; +import {dispose, IDisposable} from 'vs/base/common/lifecycle'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {ModelState, ITextFileEditorModel, LocalFileChangeEvent} from 'vs/workbench/parts/files/common/files'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; +import {IEventService} from 'vs/platform/event/common/event'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; +import {FileChangesEvent, EventType as CommonFileEventType} from 'vs/platform/files/common/files'; + +export class TextFileEditorModelManager implements ITextFileEditorModelManager { + + // Delay in ms that we wait at minimum before we update a model from a file change event. + // This reduces the chance that a save from the client triggers an update of the editor. + private static FILE_CHANGE_UPDATE_DELAY = 2000; + + private toUnbind: IDisposable[]; + + private mapResourceToDisposeListener: { [resource: string]: IDisposable; }; + private mapResourcePathToModel: { [resource: string]: TextFileEditorModel; }; + private mapResourceToPendingModelLoaders: { [resource: string]: TPromise}; + + constructor( + @ILifecycleService private lifecycleService: ILifecycleService, + @IEventService private eventService: IEventService, + @IInstantiationService private instantiationService: IInstantiationService, + @IEditorGroupService private editorGroupService: IEditorGroupService + ) { + this.toUnbind = []; + + this.mapResourcePathToModel = Object.create(null); + this.mapResourceToDisposeListener = Object.create(null); + this.mapResourceToPendingModelLoaders = Object.create(null); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Editors changing + this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + + // File changes + this.toUnbind.push(this.eventService.addListener2('files.internal:fileChanged', (e: LocalFileChangeEvent) => this.onLocalFileChange(e))); + this.toUnbind.push(this.eventService.addListener2(CommonFileEventType.FILE_CHANGES, (e: FileChangesEvent) => this.onFileChanges(e))); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + private onEditorsChanged(): void { + this.disposeUnusedModels(); + } + + private disposeModelIfPossible(resource: URI): void { + const model = this.get(resource); + if (this.canDispose(model)) { + model.dispose(); + } + } + + private onLocalFileChange(e: LocalFileChangeEvent): void { + if (e.gotMoved() || e.gotDeleted()) { + this.disposeModelIfPossible(e.getBefore().resource); // dispose models of moved or deleted files + } + } + + private onFileChanges(e: FileChangesEvent): void { + + // Dispose inputs that got deleted + e.getDeleted().forEach(deleted => { + this.disposeModelIfPossible(deleted.resource); + }); + + // Dispose models that got changed and are not visible. We do this because otherwise + // cached file models will be stale from the contents on disk. + e.getUpdated() + .map(u => this.get(u.resource)) + .filter(model => { + if (!model) { + return false; + } + + if (Date.now() - model.getLastSaveAttemptTime() < TextFileEditorModelManager.FILE_CHANGE_UPDATE_DELAY) { + return false; // this is a weak check to see if the change came from outside the editor or not + } + + return true; // ok boss + }) + .forEach(model => this.disposeModelIfPossible(model.getResource())); + } + + private canDispose(textModel: ITextFileEditorModel): boolean { + if (!textModel) { + return false; // we need data! + } + + if (textModel.isDisposed()) { + return false; // already disposed + } + + if (textModel.textEditorModel && textModel.textEditorModel.isAttachedToEditor()) { + return false; // never dispose when attached to editor + } + + if (textModel.getState() !== ModelState.SAVED) { + return false; // never dispose unsaved models + } + + return true; + } + + public get(resource: URI): TextFileEditorModel { + return this.mapResourcePathToModel[resource.toString()]; + } + + public loadOrCreate(resource: URI, encoding: string, refresh?: boolean): TPromise { + + // Return early if model is currently being loaded + const pendingLoad = this.mapResourceToPendingModelLoaders[resource.toString()]; + if (pendingLoad) { + return pendingLoad; + } + + let modelPromise: TPromise; + + // Model exists + let model = this.get(resource); + if (model) { + if (!refresh) { + modelPromise = TPromise.as(model); + } else { + modelPromise = model.load(); + } + } + + // Model does not exist + else { + model = this.instantiationService.createInstance(TextFileEditorModel, resource, encoding); + modelPromise = model.load(); + } + + // Store pending loads to avoid race conditions + this.mapResourceToPendingModelLoaders[resource.toString()] = modelPromise; + + return modelPromise.then(model => { + + // Make known to manager (if not already known) + this.add(resource, model); + + // Remove from pending loads + this.mapResourceToPendingModelLoaders[resource.toString()] = null; + + return model; + }, error => { + + // Free resources of this invalid model + model.dispose(); + + // Remove from pending loads + this.mapResourceToPendingModelLoaders[resource.toString()] = null; + + return TPromise.wrapError(error); + }); + } + + public getAll(resource?: URI): TextFileEditorModel[] { + return Object.keys(this.mapResourcePathToModel) + .filter(r => !resource || resource.toString() === r) + .map(r => this.mapResourcePathToModel[r]); + } + + public add(resource: URI, model: TextFileEditorModel): void { + const knownModel = this.mapResourcePathToModel[resource.toString()]; + if (knownModel === model) { + return; // already cached + } + + // dispose any previously stored dispose listener for this resource + const disposeListener = this.mapResourceToDisposeListener[resource.toString()]; + if (disposeListener) { + disposeListener.dispose(); + } + + // store in cache but remove when model gets disposed + this.mapResourcePathToModel[resource.toString()] = model; + this.mapResourceToDisposeListener[resource.toString()] = model.addListener2('dispose', () => this.remove(resource)); + } + + public remove(resource: URI): void { + delete this.mapResourcePathToModel[resource.toString()]; + + const disposeListener = this.mapResourceToDisposeListener[resource.toString()]; + if (disposeListener) { + dispose(disposeListener); + delete this.mapResourceToDisposeListener[resource.toString()]; + } + } + + public clear(): void { + + // model cache + this.mapResourcePathToModel = Object.create(null); + + // dispose listeners + const keys = Object.keys(this.mapResourceToDisposeListener); + dispose(keys.map(k => this.mapResourceToDisposeListener[k])); + this.mapResourceToDisposeListener = Object.create(null); + } + + private disposeUnusedModels(): void { + + // To not grow our text file model cache infinitly, we dispose models that + // are not showing up in any opened editor. + + // Get all cached file models + this.getAll() + + // Only models that are not open inside the editor area + .filter(model => !this.editorGroupService.getStacksModel().isOpen(model.getResource())) + + // Dispose + .forEach(model => this.disposeModelIfPossible(model.getResource())); + } + + public dispose(): void { + this.toUnbind = dispose(this.toUnbind); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/common/files.ts b/src/vs/workbench/parts/files/common/files.ts index 67098931fb1..a18fb4f2045 100644 --- a/src/vs/workbench/parts/files/common/files.ts +++ b/src/vs/workbench/parts/files/common/files.ts @@ -10,11 +10,12 @@ import URI from 'vs/base/common/uri'; import Event from 'vs/base/common/event'; import {IModel, IEditorOptions, IRawText} from 'vs/editor/common/editorCommon'; import {IDisposable} from 'vs/base/common/lifecycle'; -import {EncodingMode, EditorInput, IFileEditorInput, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor} from 'vs/workbench/common/editor'; +import {IEncodingSupport, EncodingMode, EditorInput, IFileEditorInput, ConfirmResult, IWorkbenchEditorConfiguration, IEditorDescriptor} from 'vs/workbench/common/editor'; import {IFileStat, IFilesConfiguration, IBaseStat, IResolveContentOptions} from 'vs/platform/files/common/files'; import {createDecorator} from 'vs/platform/instantiation/common/instantiation'; import {FileStat} from 'vs/workbench/parts/files/common/explorerViewModel'; import {RawContextKey} from 'vs/platform/contextkey/common/contextkey'; +import {ITextEditorModel} from 'vs/platform/editor/common/editor'; /** * Explorer viewlet id. @@ -83,7 +84,7 @@ export interface IFileResource { */ export function asFileResource(obj: any): IFileResource { if (obj instanceof FileStat) { - let stat = obj; + const stat = obj; return { resource: stat.resource, @@ -95,6 +96,17 @@ export function asFileResource(obj: any): IFileResource { return null; } +/** + * The save error handler can be installed on the text text file editor model to install code that executes when save errors occur. + */ +export interface ISaveErrorHandler { + + /** + * Called whenever a save fails. + */ + onSaveError(error: any, model: ITextFileEditorModel): void; +} + /** * List of event types from files. */ @@ -274,9 +286,48 @@ export interface IRawTextContent extends IBaseStat { encoding: string; } +export interface ITextFileEditorModelManager { + + get(resource: URI): ITextFileEditorModel; + + getAll(resource?: URI): ITextFileEditorModel[]; + + loadOrCreate(resource: URI, preferredEncoding: string, refresh?: boolean): TPromise; +} + +export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport { + + getResource(): URI; + + getLastSaveAttemptTime(): number; + + getLastModifiedTime(): number; + + getState(): ModelState; + + save(overwriteReadonly?: boolean, overwriteEncoding?: boolean): TPromise; + + revert(): TPromise; + + setConflictResolutionMode(); + + getValue(): string; + + isDirty(): boolean; + + isResolved(): boolean; + + isDisposed(): boolean; +} + export interface ITextFileService extends IDisposable { _serviceBrand: any; + /** + * Access to the manager of text file editor models providing further methods to work with them. + */ + models: ITextFileEditorModelManager; + /** * Resolve the contents of a file identified by the resource. */ diff --git a/src/vs/workbench/parts/files/common/textFileService.ts b/src/vs/workbench/parts/files/common/textFileService.ts new file mode 100644 index 00000000000..312b95484c6 --- /dev/null +++ b/src/vs/workbench/parts/files/common/textFileService.ts @@ -0,0 +1,531 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import paths = require('vs/base/common/paths'); +import errors = require('vs/base/common/errors'); +import Event, {Emitter} from 'vs/base/common/event'; +import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; +import {IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode, ITextFileEditorModelManager} from 'vs/workbench/parts/files/common/files'; +import {ConfirmResult} from 'vs/workbench/common/editor'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration} from 'vs/platform/files/common/files'; +import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; +import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel'; +import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; + +/** + * The workbench file service implementation implements the raw file service spec and adds additional methods on top. + * + * It also adds diagnostics and logging around file system operations. + */ +export abstract class TextFileService implements ITextFileService { + + public _serviceBrand: any; + + private listenerToUnbind: IDisposable[]; + private _models: TextFileEditorModelManager; + + private _onAutoSaveConfigurationChange: Emitter; + private configuredAutoSaveDelay: number; + private configuredAutoSaveOnFocusChange: boolean; + private configuredAutoSaveOnWindowChange: boolean; + + constructor( + @ILifecycleService private lifecycleService: ILifecycleService, + @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IConfigurationService private configurationService: IConfigurationService, + @ITelemetryService private telemetryService: ITelemetryService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IFileService protected fileService: IFileService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService, + @IInstantiationService private instantiationService: IInstantiationService + ) { + this.listenerToUnbind = []; + this._onAutoSaveConfigurationChange = new Emitter(); + this._models = this.instantiationService.createInstance(TextFileEditorModelManager); + + const configuration = this.configurationService.getConfiguration(); + this.onConfigurationChange(configuration); + + this.telemetryService.publicLog('autoSave', this.getAutoSaveConfiguration()); + + this.registerListeners(); + } + + public get models(): ITextFileEditorModelManager { + return this._models; + } + + abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise; + + abstract promptForPath(defaultPath?: string): string; + + abstract confirmSave(resources?: URI[]): ConfirmResult; + + public get onAutoSaveConfigurationChange(): Event { + return this._onAutoSaveConfigurationChange.event; + } + + private registerListeners(): void { + + // Lifecycle + this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); + this.lifecycleService.onShutdown(this.dispose, this); + + // Configuration changes + this.listenerToUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config))); + + // Application & Editor focus change + window.addEventListener('blur', () => this.onWindowFocusLost()); + window.addEventListener('blur', () => this.onEditorFocusChanged(), true); + this.listenerToUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged())); + } + + private beforeShutdown(): boolean | TPromise { + + // Dirty files need treatment on shutdown + if (this.getDirty().length) { + + // If auto save is enabled, save all files and then check again for dirty files + if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + return this.saveAll(false /* files only */).then(() => { + if (this.getDirty().length) { + return this.confirmBeforeShutdown(); // we still have dirty files around, so confirm normally + } + + return false; // all good, no veto + }); + } + + // Otherwise just confirm what to do + return this.confirmBeforeShutdown(); + } + + return false; // no veto + } + + private confirmBeforeShutdown(): boolean | TPromise { + const confirm = this.confirmSave(); + + // Save + if (confirm === ConfirmResult.SAVE) { + return this.saveAll(true /* includeUntitled */).then(result => { + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed + } + + return false; // no veto + }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + return false; // no veto + } + + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + } + + private onWindowFocusLost(): void { + if (this.configuredAutoSaveOnWindowChange && this.isDirty()) { + this.saveAll().done(null, errors.onUnexpectedError); + } + } + + private onEditorFocusChanged(): void { + if (this.configuredAutoSaveOnFocusChange && this.isDirty()) { + this.saveAll().done(null, errors.onUnexpectedError); + } + } + + private onConfigurationChange(configuration: IFilesConfiguration): void { + const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); + + const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = void 0; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = void 0; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = void 0; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // save all dirty when enabling auto save + if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll().done(null, errors.onUnexpectedError); + } + } + + public getDirty(resources?: URI[]): URI[] { + + // Collect files + const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + + // Add untitled ones + if (!resources) { + dirty.push(...this.untitledEditorService.getDirty()); + } else { + const dirtyUntitled = resources.map(r => this.untitledEditorService.get(r)).filter(u => u && u.isDirty()).map(u => u.getResource()); + dirty.push(...dirtyUntitled); + } + + return dirty; + } + + public isDirty(resource?: URI): boolean { + + // Check for dirty file + if (this._models.getAll(resource).some(model => model.isDirty())) { + return true; + } + + // Check for dirty untitled + return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); + } + + public save(resource: URI): TPromise { + return this.saveAll([resource]).then(result => result.results.length === 1 && result.results[0].success); + } + + public saveAll(includeUntitled?: boolean): TPromise; + public saveAll(resources: URI[]): TPromise; + public saveAll(arg1?: any): TPromise { + + // get all dirty + let toSave: URI[] = []; + if (Array.isArray(arg1)) { + toSave = this.getDirty(arg1); + } else { + toSave = this.getDirty(); + } + + // split up between files and untitled + const filesToSave: URI[] = []; + const untitledToSave: URI[] = []; + toSave.forEach(s => { + if (s.scheme === 'file') { + filesToSave.push(s); + } else if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === 'untitled') { + untitledToSave.push(s); + } + }); + + return this.doSaveAll(filesToSave, untitledToSave); + } + + private doSaveAll(fileResources: URI[], untitledResources: URI[]): TPromise { + + // Handle files first that can just be saved + return this.doSaveAllFiles(fileResources).then(result => { + + // Preflight for untitled to handle cancellation from the dialog + const targetsForUntitled: URI[] = []; + for (let i = 0; i < untitledResources.length; i++) { + const untitled = this.untitledEditorService.get(untitledResources[i]); + if (untitled) { + let targetPath: string; + + // Untitled with associated file path don't need to prompt + if (this.untitledEditorService.hasAssociatedFilePath(untitled.getResource())) { + targetPath = untitled.getResource().fsPath; + } + + // Otherwise ask user + else { + targetPath = this.promptForPath(this.suggestFileName(untitledResources[i])); + if (!targetPath) { + return TPromise.as({ + results: [...fileResources, ...untitledResources].map(r => { + return { + source: r + }; + }) + }); + } + } + + targetsForUntitled.push(URI.file(targetPath)); + } + } + + // Handle untitled + const untitledSaveAsPromises: TPromise[] = []; + targetsForUntitled.forEach((target, index) => { + const untitledSaveAsPromise = this.saveAs(untitledResources[index], target).then(uri => { + result.results.push({ + source: untitledResources[index], + target: uri, + success: !!uri + }); + }); + + untitledSaveAsPromises.push(untitledSaveAsPromise); + }); + + return TPromise.join(untitledSaveAsPromises).then(() => { + return result; + }); + }); + } + + private doSaveAllFiles(arg1?: any /* URI[] */): TPromise { + const dirtyFileModels = this.getDirtyFileModels(Array.isArray(arg1) ? arg1 : void 0 /* Save All */); + + const mapResourceToResult: { [resource: string]: IResult } = Object.create(null); + dirtyFileModels.forEach(m => { + mapResourceToResult[m.getResource().toString()] = { + source: m.getResource() + }; + }); + + return TPromise.join(dirtyFileModels.map(model => { + return model.save().then(() => { + if (!model.isDirty()) { + mapResourceToResult[model.getResource().toString()].success = true; + } + }); + })).then(r => { + return { + results: Object.keys(mapResourceToResult).map(k => mapResourceToResult[k]) + }; + }); + } + + private getFileModels(resources?: URI[]): TextFileEditorModel[]; + private getFileModels(resource?: URI): TextFileEditorModel[]; + private getFileModels(arg1?: any): TextFileEditorModel[] { + if (Array.isArray(arg1)) { + const models: TextFileEditorModel[] = []; + (arg1).forEach(resource => { + models.push(...this.getFileModels(resource)); + }); + + return models; + } + + return this._models.getAll(arg1); + } + + private getDirtyFileModels(resources?: URI[]): TextFileEditorModel[]; + private getDirtyFileModels(resource?: URI): TextFileEditorModel[]; + private getDirtyFileModels(arg1?: any): TextFileEditorModel[] { + return this.getFileModels(arg1).filter(model => model.isDirty()); + } + + public saveAs(resource: URI, target?: URI): TPromise { + + // Get to target resource + if (!target) { + let dialogPath = resource.fsPath; + if (resource.scheme === 'untitled') { + dialogPath = this.suggestFileName(resource); + } + + const pathRaw = this.promptForPath(dialogPath); + if (pathRaw) { + target = URI.file(pathRaw); + } + } + + if (!target) { + return TPromise.as(null); // user canceled + } + + // Just save if target is same as models own resource + if (resource.toString() === target.toString()) { + return this.save(resource).then(() => resource); + } + + // Do it + return this.doSaveAs(resource, target); + } + + private doSaveAs(resource: URI, target?: URI): TPromise { + + // Retrieve text model from provided resource if any + let modelPromise: TPromise = TPromise.as(null); + if (resource.scheme === 'file') { + modelPromise = TPromise.as(this._models.get(resource)); + } else if (resource.scheme === 'untitled') { + const untitled = this.untitledEditorService.get(resource); + if (untitled) { + modelPromise = untitled.resolve(); + } + } + + return modelPromise.then(model => { + + // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) + if (model) { + return this.doSaveTextFileAs(model, resource, target); + } + + // Otherwise we can only copy + return this.fileService.copyFile(resource, target); + }).then(() => { + + // Revert the source + return this.revert(resource).then(() => { + + // Done: return target + return target; + }); + }); + } + + private doSaveTextFileAs(sourceModel: TextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise { + // create the target file empty if it does not exist already + return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.createFile(target)).then(stat => { + // resolve a model for the file (which can be binary if the file is not a text file) + return this.editorService.resolveEditorModel({ resource: target }).then((targetModel: TextFileEditorModel) => { + // binary model: delete the file and run the operation again + if (targetModel instanceof BinaryEditorModel) { + return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target)); + } + + // text model: take over encoding and model value from source model + targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + targetModel.textEditorModel.setValue(sourceModel.getValue()); + + // save model + return targetModel.save(); + }); + }); + } + + private suggestFileName(untitledResource: URI): string { + const workspace = this.contextService.getWorkspace(); + if (workspace) { + return URI.file(paths.join(workspace.resource.fsPath, this.untitledEditorService.get(untitledResource).suggestFileName())).fsPath; + } + + return this.untitledEditorService.get(untitledResource).suggestFileName(); + } + + public revert(resource: URI, force?: boolean): TPromise { + return this.revertAll([resource], force).then(result => result.results.length === 1 && result.results[0].success); + } + + public revertAll(resources?: URI[], force?: boolean): TPromise { + + // Revert files first + return this.doRevertAllFiles(resources, force).then(operation => { + + // Revert untitled + const reverted = this.untitledEditorService.revertAll(resources); + reverted.forEach(res => operation.results.push({ source: res, success: true })); + + return operation; + }); + } + + private doRevertAllFiles(resources?: URI[], force?: boolean): TPromise { + const fileModels = force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + + const mapResourceToResult: { [resource: string]: IResult } = Object.create(null); + fileModels.forEach(m => { + mapResourceToResult[m.getResource().toString()] = { + source: m.getResource() + }; + }); + + return TPromise.join(fileModels.map(model => { + return model.revert().then(() => { + if (!model.isDirty()) { + mapResourceToResult[model.getResource().toString()].success = true; + } + }, error => { + + // FileNotFound means the file got deleted meanwhile, so dispose + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + + // Inputs + const clients = FileEditorInput.getAll(model.getResource()); + clients.forEach(input => input.dispose()); + + // store as successful revert + mapResourceToResult[model.getResource().toString()].success = true; + } + + // Otherwise bubble up the error + else { + return TPromise.wrapError(error); + } + }); + })).then(r => { + return { + results: Object.keys(mapResourceToResult).map(k => mapResourceToResult[k]) + }; + }); + } + + public getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + public getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : void 0, + autoSaveFocusChange: this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: this.configuredAutoSaveOnWindowChange + }; + } + + public dispose(): void { + this.listenerToUnbind = dispose(this.listenerToUnbind); + + // Clear all caches + this._models.clear(); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/common/textFileServices.ts b/src/vs/workbench/parts/files/common/textFileServices.ts deleted file mode 100644 index 8e83d44b9ad..00000000000 --- a/src/vs/workbench/parts/files/common/textFileServices.ts +++ /dev/null @@ -1,257 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import {TPromise} from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; -import errors = require('vs/base/common/errors'); -import Event, {Emitter} from 'vs/base/common/event'; -import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {IResult, ITextFileOperationResult, ITextFileService, IRawTextContent, IAutoSaveConfiguration, AutoSaveMode} from 'vs/workbench/parts/files/common/files'; -import {ConfirmResult} from 'vs/workbench/common/editor'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IFileService, IResolveContentOptions, IFilesConfiguration, IFileOperationResult, FileOperationResult, AutoSaveConfiguration} from 'vs/platform/files/common/files'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IModelService} from 'vs/editor/common/services/modelService'; - -/** - * The workbench file service implementation implements the raw file service spec and adds additional methods on top. - * - * It also adds diagnostics and logging around file system operations. - */ -export abstract class TextFileService implements ITextFileService { - - public _serviceBrand: any; - - protected listenerToUnbind: IDisposable[]; - - private _onAutoSaveConfigurationChange: Emitter; - - private configuredAutoSaveDelay: number; - protected configuredAutoSaveOnFocusChange: boolean; - protected configuredAutoSaveOnWindowChange: boolean; - - constructor( - @IWorkspaceContextService protected contextService: IWorkspaceContextService, - @IInstantiationService private instantiationService: IInstantiationService, - @IConfigurationService private configurationService: IConfigurationService, - @ITelemetryService private telemetryService: ITelemetryService, - @IWorkbenchEditorService protected editorService: IWorkbenchEditorService, - @IEventService private eventService: IEventService, - @IFileService protected fileService: IFileService, - @IModelService protected modelService: IModelService - ) { - this.listenerToUnbind = []; - this._onAutoSaveConfigurationChange = new Emitter(); - } - - protected init(): void { - this.registerListeners(); - - const configuration = this.configurationService.getConfiguration(); - this.onConfigurationChange(configuration); - - this.telemetryService.publicLog('autoSave', this.getAutoSaveConfiguration()); - } - - public abstract resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise; - - public get onAutoSaveConfigurationChange(): Event { - return this._onAutoSaveConfigurationChange.event; - } - - protected registerListeners(): void { - - // Configuration changes - this.listenerToUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config))); - } - - private onConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = void 0; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = void 0; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = void 0; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll().done(null, errors.onUnexpectedError); - } - } - - public getDirty(resources?: URI[]): URI[] { - return this.getDirtyFileModels(resources).map((m) => m.getResource()); - } - - public isDirty(resource?: URI): boolean { - return CACHE - .getAll(resource) - .some((model) => model.isDirty()); - } - - public save(resource: URI): TPromise { - return this.saveAll([resource]).then((result) => result.results.length === 1 && result.results[0].success); - } - - public saveAll(arg1?: any /* URI[] */): TPromise { - let dirtyFileModels = this.getDirtyFileModels(Array.isArray(arg1) ? arg1 : void 0 /* Save All */); - - let mapResourceToResult: { [resource: string]: IResult } = Object.create(null); - dirtyFileModels.forEach((m) => { - mapResourceToResult[m.getResource().toString()] = { - source: m.getResource() - }; - }); - - return TPromise.join(dirtyFileModels.map((model) => { - return model.save().then(() => { - if (!model.isDirty()) { - mapResourceToResult[model.getResource().toString()].success = true; - } - }); - })).then((r) => { - return { - results: Object.keys(mapResourceToResult).map((k) => mapResourceToResult[k]) - }; - }); - } - - private getFileModels(resources?: URI[]): TextFileEditorModel[]; - private getFileModels(resource?: URI): TextFileEditorModel[]; - private getFileModels(arg1?: any): TextFileEditorModel[] { - if (Array.isArray(arg1)) { - let models: TextFileEditorModel[] = []; - (arg1).forEach((resource) => { - models.push(...this.getFileModels(resource)); - }); - - return models; - } - - return CACHE.getAll(arg1); - } - - private getDirtyFileModels(resources?: URI[]): TextFileEditorModel[]; - private getDirtyFileModels(resource?: URI): TextFileEditorModel[]; - private getDirtyFileModels(arg1?: any): TextFileEditorModel[] { - return this.getFileModels(arg1).filter((model) => model.isDirty()); - } - - public abstract saveAs(resource: URI, targetResource?: URI): TPromise; - - public confirmSave(resources?: URI[]): ConfirmResult { - throw new Error('Unsupported'); - } - - public revert(resource: URI, force?: boolean): TPromise { - return this.revertAll([resource], force).then((result) => result.results.length === 1 && result.results[0].success); - } - - public revertAll(resources?: URI[], force?: boolean): TPromise { - let fileModels = force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); - - let mapResourceToResult: { [resource: string]: IResult } = Object.create(null); - fileModels.forEach((m) => { - mapResourceToResult[m.getResource().toString()] = { - source: m.getResource() - }; - }); - - return TPromise.join(fileModels.map((model) => { - return model.revert().then(() => { - if (!model.isDirty()) { - mapResourceToResult[model.getResource().toString()].success = true; - } - }, (error) => { - - // FileNotFound means the file got deleted meanwhile, so dispose - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - - // Inputs - let clients = FileEditorInput.getAll(model.getResource()); - clients.forEach(input => input.dispose()); - - // Model - CACHE.dispose(model.getResource()); - - // store as successful revert - mapResourceToResult[model.getResource().toString()].success = true; - } - - // Otherwise bubble up the error - else { - return TPromise.wrapError(error); - } - }); - })).then((r) => { - return { - results: Object.keys(mapResourceToResult).map((k) => mapResourceToResult[k]) - }; - }); - } - - public getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - public getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : void 0, - autoSaveFocusChange: this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: this.configuredAutoSaveOnWindowChange - }; - } - - public dispose(): void { - this.listenerToUnbind = dispose(this.listenerToUnbind); - - // Clear all caches - CACHE.clear(); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts new file mode 100644 index 00000000000..c045895aec0 --- /dev/null +++ b/src/vs/workbench/parts/files/electron-browser/dirtyFilesTracker.ts @@ -0,0 +1,176 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import nls = require('vs/nls'); +import errors = require('vs/base/common/errors'); +import {IWorkbenchContribution} from 'vs/workbench/common/contributions'; +import {VIEWLET_ID, TextFileChangeEvent, EventType as FileEventType, ITextFileService, AutoSaveMode} from 'vs/workbench/parts/files/common/files'; +import {platform, Platform} from 'vs/base/common/platform'; +import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; +import {IEventService} from 'vs/platform/event/common/event'; +import {Position} from 'vs/platform/editor/common/editor'; +import {IEditorStacksModel} from 'vs/workbench/common/editor'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; +import URI from 'vs/base/common/uri'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IActivityService, NumberBadge} from 'vs/workbench/services/activity/common/activityService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import arrays = require('vs/base/common/arrays'); + +import {ipcRenderer as ipc} from 'electron'; + +export class DirtyFilesTracker implements IWorkbenchContribution { + private isDocumentedEdited: boolean; + private toUnbind: IDisposable[]; + + private lastDirtyCount: number; + private pendingDirtyResources: URI[]; + private pendingDirtyHandle: number; + + private stacks: IEditorStacksModel; + + constructor( + @IEventService private eventService: IEventService, + @ITextFileService private textFileService: ITextFileService, + @ILifecycleService private lifecycleService: ILifecycleService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, + @IActivityService private activityService: IActivityService, + @IWindowService private windowService: IWindowService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService + ) { + this.toUnbind = []; + this.isDocumentedEdited = false; + this.pendingDirtyResources = []; + this.stacks = editorGroupService.getStacksModel(); + + this.registerListeners(); + } + + private registerListeners(): void { + + // Local text file changes + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); + this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e))); + this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e))); + this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e))); + this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => this.onTextFileReverted(e))); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + private onUntitledDidChangeDirty(resource: URI): void { + const gotDirty = this.untitledEditorService.isDirty(resource); + + if ((!this.isDocumentedEdited && gotDirty) || (this.isDocumentedEdited && !gotDirty)) { + this.updateDocumentEdited(); + } + + if (gotDirty || this.lastDirtyCount > 0) { + this.updateActivityBadge(); + } + } + + private onTextFileDirty(e: TextFileChangeEvent): void { + if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { + this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay + } + + if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) { + this.updateActivityBadge(); // no indication needed when auto save is enabled for short delay + } + + // If a file becomes dirty but is not opened, we open it in the background + // Since it might be the intent of whoever created the model to show it shortly + // after, we delay this a little bit and check again if the editor has not been + // opened meanwhile + this.pendingDirtyResources.push(e.resource); + if (!this.pendingDirtyHandle) { + this.pendingDirtyHandle = setTimeout(() => this.doOpenDirtyResources(), 250); + } + } + + private doOpenDirtyResources(): void { + const dirtyNotOpenedResources = arrays.distinct(this.pendingDirtyResources.filter(r => !this.stacks.isOpen(r) && this.textFileService.isDirty(r)), r => r.toString()); + + // Reset + this.pendingDirtyHandle = void 0; + this.pendingDirtyResources = []; + + const activeEditor = this.editorService.getActiveEditor(); + const activePosition = activeEditor ? activeEditor.position : Position.LEFT; + + // Open + this.editorService.openEditors(dirtyNotOpenedResources.map(resource => { + return { + input: { + resource, + options: { inactive: true, pinned: true, preserveFocus: true } + }, + position: activePosition + }; + })).done(null, errors.onUnexpectedError); + } + + private onTextFileSaved(e: TextFileChangeEvent): void { + if (this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + if (this.lastDirtyCount > 0) { + this.updateActivityBadge(); + } + } + + private onTextFileSaveError(e: TextFileChangeEvent): void { + if (!this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + this.updateActivityBadge(); + } + + private onTextFileReverted(e: TextFileChangeEvent): void { + if (this.isDocumentedEdited) { + this.updateDocumentEdited(); + } + + if (this.lastDirtyCount > 0) { + this.updateActivityBadge(); + } + } + + private updateActivityBadge(): void { + const dirtyCount = this.textFileService.getDirty().length; + this.lastDirtyCount = dirtyCount; + if (dirtyCount > 0) { + this.activityService.showActivity(VIEWLET_ID, new NumberBadge(dirtyCount, num => nls.localize('dirtyFiles', "{0} unsaved files", dirtyCount)), 'explorer-viewlet-label'); + } else { + this.activityService.clearActivity(VIEWLET_ID); + } + } + + private updateDocumentEdited(): void { + if (platform === Platform.Mac) { + const hasDirtyFiles = this.textFileService.isDirty(); + this.isDocumentedEdited = hasDirtyFiles; + + ipc.send('vscode:setDocumentEdited', this.windowService.getWindowId(), hasDirtyFiles); // handled from browser process + } + } + + public getId(): string { + return 'vs.files.dirtyFilesTracker'; + } + + public dispose(): void { + this.toUnbind = dispose(this.toUnbind); + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts index 1944c862dcf..e225a333fec 100644 --- a/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts +++ b/src/vs/workbench/parts/files/electron-browser/electronFileActions.ts @@ -53,7 +53,7 @@ export class GlobalRevealInOSAction extends Action { } public run(): TPromise { - let fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); + const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); if (fileInput) { shell.showItemInFolder(paths.normalize(fileInput.getResource().fsPath, true)); } else { @@ -99,7 +99,7 @@ export class GlobalCopyPathAction extends Action { public run(): TPromise { const activeEditor = this.editorService.getActiveEditor(); - let fileInput = activeEditor ? asFileEditorInput(activeEditor.input, true) : void 0; + const fileInput = activeEditor ? asFileEditorInput(activeEditor.input, true) : void 0; if (fileInput) { clipboard.writeText(labels.getPathLabel(fileInput.getResource())); this.editorGroupService.focusGroup(activeEditor.position); // focus back to active editor group @@ -128,11 +128,11 @@ export class BaseOpenAction extends Action { } } -export const OPEN_FILE_ID = 'workbench.action.files.openFile'; -export const OPEN_FILE_LABEL = nls.localize('openFile', "Open File..."); - export class OpenFileAction extends Action { + public static ID = 'workbench.action.files.openFile'; + public static LABEL = nls.localize('openFile', "Open File..."); + constructor(id: string, label: string, @IWorkbenchEditorService private editorService: IWorkbenchEditorService) { super(id, label); } @@ -151,21 +151,21 @@ export class OpenFileAction extends Action { } } -export const OPEN_FOLDER_ID = 'workbench.action.files.openFolder'; -export const OPEN_FOLDER_LABEL = nls.localize('openFolder', "Open Folder..."); - export class OpenFolderAction extends BaseOpenAction { + public static ID = 'workbench.action.files.openFolder'; + public static LABEL = nls.localize('openFolder', "Open Folder..."); + constructor(id: string, label: string) { super(id, label, 'vscode:openFolderPicker'); } } -export const OPEN_FILE_FOLDER_ID = 'workbench.action.files.openFileFolder'; -export const OPEN_FILE_FOLDER_LABEL = nls.localize('openFileFolder', "Open..."); - export class OpenFileFolderAction extends BaseOpenAction { + public static ID = 'workbench.action.files.openFileFolder'; + public static LABEL = nls.localize('openFileFolder', "Open..."); + constructor(id: string, label: string) { super(id, label, 'vscode:openFileFolderPicker'); } @@ -186,7 +186,7 @@ export class ShowOpenedFileInNewWindow extends Action { } public run(): TPromise { - let fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); + const fileInput = asFileEditorInput(this.editorService.getActiveEditorInput(), true); if (fileInput) { ipc.send('vscode:windowOpen', [fileInput.getResource().fsPath], true /* force new window */); // handled from browser process } else { diff --git a/src/vs/workbench/parts/files/electron-browser/electronFileTracker.ts b/src/vs/workbench/parts/files/electron-browser/electronFileTracker.ts deleted file mode 100644 index edee81648cc..00000000000 --- a/src/vs/workbench/parts/files/electron-browser/electronFileTracker.ts +++ /dev/null @@ -1,255 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import {TPromise} from 'vs/base/common/winjs.base'; -import {IWorkbenchContribution} from 'vs/workbench/common/contributions'; -import {TextFileChangeEvent, EventType as FileEventType, ITextFileService, AutoSaveMode, VIEWLET_ID} from 'vs/workbench/parts/files/common/files'; -import {IFileService} from 'vs/platform/files/common/files'; -import {platform, Platform} from 'vs/base/common/platform'; -import {DiffEditorInput, toDiffLabel} from 'vs/workbench/common/editor/diffEditorInput'; -import {asFileEditorInput, EditorInput} from 'vs/workbench/common/editor'; -import errors = require('vs/base/common/errors'); -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import URI from 'vs/base/common/uri'; -import {Position} from 'vs/platform/editor/common/editor'; -import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; -import {EventType as WorkbenchEventType} from 'vs/workbench/common/events'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; -import {IPartService} from 'vs/workbench/services/part/common/partService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IResourceInput} from 'vs/platform/editor/common/editor'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; -import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService'; -import {IDisposable, dispose} from 'vs/base/common/lifecycle'; -import {ipcRenderer as ipc} from 'electron'; -import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; - -export interface IPath { - filePath: string; - lineNumber?: number; - columnNumber?: number; -} - -export interface IOpenFileRequest { - filesToOpen?: IPath[]; - filesToCreate?: IPath[]; - filesToDiff?: IPath[]; -} - -// This extension decorates the window as dirty when auto save is disabled and a file is dirty (mac only) and handles opening of files in the instance. -export class FileTracker implements IWorkbenchContribution { - private activeOutOfWorkspaceWatchers: { [resource: string]: boolean; }; - private isDocumentedEdited: boolean; - private toUnbind: IDisposable[];; - - constructor( - @IWorkspaceContextService private contextService: IWorkspaceContextService, - @IEventService private eventService: IEventService, - @IPartService private partService: IPartService, - @IFileService private fileService: IFileService, - @ITextFileService private textFileService: ITextFileService, - @IViewletService private viewletService: IViewletService, - @IWorkbenchEditorService private editorService: IWorkbenchEditorService, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IInstantiationService private instantiationService: IInstantiationService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @ILifecycleService private lifecycleService: ILifecycleService, - @IWindowService private windowService: IWindowService - ) { - this.toUnbind = []; - this.isDocumentedEdited = false; - this.activeOutOfWorkspaceWatchers = Object.create(null); - - // Make sure to reset any previous state - if (platform === Platform.Mac) { - ipc.send('vscode:setDocumentEdited', this.windowService.getWindowId(), false); // handled from browser process - } - - this.registerListeners(); - } - - private registerListeners(): void { - - // Local text file changes - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, () => this.onUntitledSavedEvent())); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, () => this.onUntitledDirtyEvent())); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e))); - this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => this.onTextFileReverted(e))); - - // Support openFiles event for existing and new files - ipc.on('vscode:openFiles', (event, request: IOpenFileRequest) => this.onOpenFiles(request)); - - // Editor input changes - this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); - - // Lifecycle - this.lifecycleService.onShutdown(this.dispose, this); - } - - private onOpenFiles(request: IOpenFileRequest): void { - let inputs: IResourceInput[] = []; - let diffMode = (request.filesToDiff.length === 2); - - if (!diffMode && request.filesToOpen) { - inputs.push(...this.toInputs(request.filesToOpen, false)); - } - - if (!diffMode && request.filesToCreate) { - inputs.push(...this.toInputs(request.filesToCreate, true)); - } - - if (diffMode) { - inputs.push(...this.toInputs(request.filesToDiff, false)); - } - - if (inputs.length) { - this.openResources(inputs, diffMode).done(null, errors.onUnexpectedError); - } - } - - private openResources(resources: IResourceInput[], diffMode: boolean): TPromise { - return this.partService.joinCreation().then(() => { - let viewletPromise = TPromise.as(null); - if (!this.partService.isSideBarHidden()) { - viewletPromise = this.viewletService.openViewlet(VIEWLET_ID, false); - } - - return viewletPromise.then(() => { - - // In diffMode we open 2 resources as diff - if (diffMode) { - return TPromise.join(resources.map(f => this.editorService.createInput(f))).then((inputs: EditorInput[]) => { - return this.editorService.openEditor(new DiffEditorInput(toDiffLabel(resources[0].resource, resources[1].resource, this.contextService), null, inputs[0], inputs[1])); - }); - } - - // For one file, just put it into the current active editor - if (resources.length === 1) { - return this.editorService.openEditor(resources[0]); - } - - // Otherwise open all - const activeEditor = this.editorService.getActiveEditor(); - return this.editorService.openEditors(resources.map((r, index) => { - return { - input: r, - position: activeEditor ? activeEditor.position : Position.LEFT - }; - })); - }); - }); - } - - private toInputs(paths: IPath[], isNew: boolean): IResourceInput[] { - return paths.map(p => { - let input = { - resource: isNew ? this.untitledEditorService.createOrGet(URI.file(p.filePath)).getResource() : URI.file(p.filePath), - options: { - pinned: true - } - }; - - if (!isNew && p.lineNumber) { - input.options.selection = { - startLineNumber: p.lineNumber, - startColumn: p.columnNumber - }; - } - - return input; - }); - } - - private onEditorsChanged(): void { - let visibleOutOfWorkspaceResources = this.editorService.getVisibleEditors().map((editor) => { - return asFileEditorInput(editor.input, true); - }).filter((input) => { - return !!input && !this.contextService.isInsideWorkspace(input.getResource()); - }).map((input) => { - return input.getResource().toString(); - }); - - // Handle no longer visible out of workspace resources - Object.keys(this.activeOutOfWorkspaceWatchers).forEach((watchedResource) => { - if (visibleOutOfWorkspaceResources.indexOf(watchedResource) < 0) { - this.fileService.unwatchFileChanges(watchedResource); - delete this.activeOutOfWorkspaceWatchers[watchedResource]; - } - }); - - // Handle newly visible out of workspace resources - visibleOutOfWorkspaceResources.forEach((resourceToWatch) => { - if (!this.activeOutOfWorkspaceWatchers[resourceToWatch]) { - this.fileService.watchFileChanges(URI.parse(resourceToWatch)); - this.activeOutOfWorkspaceWatchers[resourceToWatch] = true; - } - }); - } - - private onUntitledDirtyEvent(): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } - - private onUntitledSavedEvent(): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } - - private onTextFileDirty(e: TextFileChangeEvent): void { - if ((this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) && !this.isDocumentedEdited) { - this.updateDocumentEdited(); // no indication needed when auto save is enabled for short delay - } - } - - private onTextFileSaved(e: TextFileChangeEvent): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } - - private onTextFileSaveError(e: TextFileChangeEvent): void { - if (!this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } - - private onTextFileReverted(e: TextFileChangeEvent): void { - if (this.isDocumentedEdited) { - this.updateDocumentEdited(); - } - } - - private updateDocumentEdited(): void { - if (platform === Platform.Mac) { - let hasDirtyFiles = this.textFileService.isDirty(); - this.isDocumentedEdited = hasDirtyFiles; - - ipc.send('vscode:setDocumentEdited', this.windowService.getWindowId(), hasDirtyFiles); // handled from browser process - } - } - - public getId(): string { - return 'vs.files.electronFileTracker'; - } - - public dispose(): void { - this.toUnbind = dispose(this.toUnbind); - - // Dispose watchers if any - for (let key in this.activeOutOfWorkspaceWatchers) { - this.fileService.unwatchFileChanges(key); - } - this.activeOutOfWorkspaceWatchers = Object.create(null); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts index dc82b6c7fc7..14370627337 100644 --- a/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts +++ b/src/vs/workbench/parts/files/electron-browser/files.electron.contribution.ts @@ -15,9 +15,9 @@ import env = require('vs/base/common/platform'); import {ITextFileService, asFileResource} from 'vs/workbench/parts/files/common/files'; import {IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions} from 'vs/workbench/common/contributions'; import {GlobalNewUntitledFileAction, SaveFileAsAction} from 'vs/workbench/parts/files/browser/fileActions'; -import {FileTracker} from 'vs/workbench/parts/files/electron-browser/electronFileTracker'; -import {TextFileService} from 'vs/workbench/parts/files/electron-browser/textFileServices'; -import {OpenFolderAction, OPEN_FOLDER_ID, OPEN_FOLDER_LABEL, OpenFileAction, OPEN_FILE_ID, OPEN_FILE_LABEL, OpenFileFolderAction, OPEN_FILE_FOLDER_ID, OPEN_FILE_FOLDER_LABEL, ShowOpenedFileInNewWindow, GlobalRevealInOSAction, GlobalCopyPathAction, CopyPathAction, RevealInOSAction} from 'vs/workbench/parts/files/electron-browser/electronFileActions'; +import {DirtyFilesTracker} from 'vs/workbench/parts/files/electron-browser/dirtyFilesTracker'; +import {TextFileService} from 'vs/workbench/parts/files/electron-browser/textFileService'; +import {OpenFolderAction, OpenFileAction, OpenFileFolderAction, ShowOpenedFileInNewWindow, GlobalRevealInOSAction, GlobalCopyPathAction, CopyPathAction, RevealInOSAction} from 'vs/workbench/parts/files/electron-browser/electronFileActions'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {registerSingleton} from 'vs/platform/instantiation/common/extensions'; import {KeyMod, KeyCode} from 'vs/base/common/keyCodes'; @@ -29,18 +29,18 @@ class FileViewerActionContributor extends ActionBarContributor { } public hasSecondaryActions(context: any): boolean { - let element = context.element; + const element = context.element; // Contribute only on Files (File Explorer and Open Files Viewer) return !!asFileResource(element) || (element && element.getResource && element.getResource()); } public getSecondaryActions(context: any): IAction[] { - let actions: IAction[] = []; + const actions: IAction[] = []; if (this.hasSecondaryActions(context)) { const fileResource = asFileResource(context.element); - let resource = fileResource ? fileResource.resource : context.element.getResource(); + const resource = fileResource ? fileResource.resource : context.element.getResource(); // Reveal file in OS native explorer actions.push(this.instantiationService.createInstance(RevealInOSAction, resource)); @@ -56,7 +56,7 @@ class FileViewerActionContributor extends ActionBarContributor { // Contribute Actions const category = nls.localize('filesCategory', "Files"); -let workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); +const workbenchActionsRegistry = Registry.as(ActionExtensions.WorkbenchActions); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(SaveFileAsAction, SaveFileAsAction.ID, SaveFileAsAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_S }), 'Files: Save As...', category); workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(GlobalNewUntitledFileAction, GlobalNewUntitledFileAction.ID, GlobalNewUntitledFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_N }), 'Files: New Untitled File', category); @@ -65,20 +65,22 @@ workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(Global workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowOpenedFileInNewWindow, ShowOpenedFileInNewWindow.ID, ShowOpenedFileInNewWindow.LABEL, { primary: KeyMod.chord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_O) }), 'Files: Open Active File in New Window', category); if (env.isMacintosh) { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OPEN_FILE_FOLDER_ID, OPEN_FILE_FOLDER_LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open...', category); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open...', category); } else { - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OPEN_FILE_ID, OPEN_FILE_LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open File...', category); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OPEN_FOLDER_ID, OPEN_FOLDER_LABEL), 'Files: Open Folder...', category); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'Files: Open File...', category); + workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL), 'Files: Open Folder...', category); } // Contribute to File Viewers -let actionsRegistry = Registry.as(ActionBarExtensions.Actionbar); +const actionsRegistry = Registry.as(ActionBarExtensions.Actionbar); actionsRegistry.registerActionBarContributor(Scope.VIEWER, FileViewerActionContributor); -// Register File Workbench Extension -(Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution( - FileTracker -); +// Register Mac Integration (if we are on Mac) +if (env.isMacintosh) { + (Registry.as(WorkbenchExtensions.Workbench)).registerWorkbenchContribution( + DirtyFilesTracker + ); +} // Register Service registerSingleton(ITextFileService, TextFileService); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/textFileService.ts b/src/vs/workbench/parts/files/electron-browser/textFileService.ts new file mode 100644 index 00000000000..8fd9c242d3d --- /dev/null +++ b/src/vs/workbench/parts/files/electron-browser/textFileService.ts @@ -0,0 +1,206 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import nls = require('vs/nls'); +import {TPromise} from 'vs/base/common/winjs.base'; +import paths = require('vs/base/common/paths'); +import strings = require('vs/base/common/strings'); +import {isWindows, isLinux} from 'vs/base/common/platform'; +import URI from 'vs/base/common/uri'; +import {ConfirmResult} from 'vs/workbench/common/editor'; +import {TextFileService as AbstractTextFileService} from 'vs/workbench/parts/files/common/textFileService'; +import {IRawTextContent} from 'vs/workbench/parts/files/common/files'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {IFileService, IResolveContentOptions} from 'vs/platform/files/common/files'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; +import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {IModeService} from 'vs/editor/common/services/modeService'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {ModelBuilder} from 'vs/editor/node/model/modelBuilder'; +import product from 'vs/platform/product'; +import {IEnvironmentService} from 'vs/platform/environment/common/environment'; +import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; + +export class TextFileService extends AbstractTextFileService { + + private static MAX_CONFIRM_FILES = 10; + + constructor( + @IWorkspaceContextService contextService: IWorkspaceContextService, + @IFileService fileService: IFileService, + @IUntitledEditorService untitledEditorService: IUntitledEditorService, + @ILifecycleService lifecycleService: ILifecycleService, + @IInstantiationService instantiationService: IInstantiationService, + @ITelemetryService telemetryService: ITelemetryService, + @IConfigurationService configurationService: IConfigurationService, + @IModeService private modeService: IModeService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, + @IEditorGroupService editorGroupService: IEditorGroupService, + @IWindowService private windowService: IWindowService, + @IModelService private modelService: IModelService, + @IEnvironmentService private environmentService: IEnvironmentService + ) { + super(lifecycleService, contextService, configurationService, telemetryService, editorGroupService, editorService, fileService, untitledEditorService, instantiationService); + } + + public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { + return this.fileService.resolveStreamContent(resource, options).then(streamContent => { + return ModelBuilder.fromStringStream(streamContent.value, this.modelService.getCreationOptions()).then(res => { + const r: IRawTextContent = { + resource: streamContent.resource, + name: streamContent.name, + mtime: streamContent.mtime, + etag: streamContent.etag, + mime: streamContent.mime, + encoding: streamContent.encoding, + value: res.rawText, + valueLogicalHash: res.hash + }; + return r; + }); + }); + } + + public confirmSave(resources?: URI[]): ConfirmResult { + if (!!this.environmentService.extensionDevelopmentPath) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) + } + + const resourcesToConfirm = this.getDirty(resources); + if (resourcesToConfirm.length === 0) { + return ConfirmResult.DONT_SAVE; + } + + const message = [ + resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath)) : nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length) + ]; + + if (resourcesToConfirm.length > 1) { + message.push(''); + message.push(...resourcesToConfirm.slice(0, TextFileService.MAX_CONFIRM_FILES).map(r => paths.basename(r.fsPath))); + + if (resourcesToConfirm.length > TextFileService.MAX_CONFIRM_FILES) { + if (resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES === 1) { + message.push(nls.localize('moreFile', "...1 additional file not shown")); + } else { + message.push(nls.localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES)); + } + } + + message.push(''); + } + + // Button order + // Windows: Save | Don't Save | Cancel + // Mac: Save | Cancel | Don't Save + // Linux: Don't Save | Cancel | Save + + const save = { label: resourcesToConfirm.length > 1 ? this.mnemonicLabel(nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All")) : this.mnemonicLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE }; + const dontSave = { label: this.mnemonicLabel(nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE }; + const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; + + const buttons = []; + if (isWindows) { + buttons.push(save, dontSave, cancel); + } else if (isLinux) { + buttons.push(dontSave, cancel, save); + } else { + buttons.push(save, cancel, dontSave); + } + + const opts: Electron.ShowMessageBoxOptions = { + title: product.nameLong, + message: message.join('\n'), + type: 'warning', + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them."), + buttons: buttons.map(b => b.label), + noLink: true, + cancelId: buttons.indexOf(cancel) + }; + + if (isLinux) { + opts.defaultId = 2; + } + + const choice = this.windowService.getWindow().showMessageBox(opts); + + return buttons[choice].result; + } + + private mnemonicLabel(label: string): string { + if (!isWindows) { + return label.replace(/\(&&\w\)|&&/g, ''); // no mnemonic support on mac/linux + } + + return label.replace(/&&/g, '&'); + } + + public promptForPath(defaultPath?: string): string { + return this.windowService.getWindow().showSaveDialog(this.getSaveDialogOptions(defaultPath ? paths.normalize(defaultPath, true) : void 0)); + } + + private getSaveDialogOptions(defaultPath?: string): Electron.SaveDialogOptions { + const options: Electron.SaveDialogOptions = { + defaultPath: defaultPath + }; + + // Filters are working flaky in Electron and there are bugs. On Windows they are working + // somewhat but we see issues: + // - https://github.com/electron/electron/issues/3556 + // - https://github.com/Microsoft/vscode/issues/451 + // - Bug on Windows: When "All Files" is picked, the path gets an extra ".*" + // - Bug on Windows: Cannot save file without extension + // - Bug on Windows: Untitled files get just the first extension of the list + // Until these issues are resolved, we disable the dialog file extension filtering. + const disable = true; // Simply using if (true) flags the code afterwards as not reachable. + if (disable) { + return options; + } + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string = paths.extname(defaultPath); + let matchingFilter: IFilter; + const filters: IFilter[] = this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.map(e => strings.trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + }).filter(f => !!f); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.push(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + options.filters = filters; + + return options; + } +} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/electron-browser/textFileServices.ts b/src/vs/workbench/parts/files/electron-browser/textFileServices.ts deleted file mode 100644 index 49c3192a0f0..00000000000 --- a/src/vs/workbench/parts/files/electron-browser/textFileServices.ts +++ /dev/null @@ -1,499 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import nls = require('vs/nls'); -import {TPromise} from 'vs/base/common/winjs.base'; -import paths = require('vs/base/common/paths'); -import strings = require('vs/base/common/strings'); -import {isWindows, isLinux} from 'vs/base/common/platform'; -import URI from 'vs/base/common/uri'; -import errors = require('vs/base/common/errors'); -import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; -import {ConfirmResult} from 'vs/workbench/common/editor'; -import {IEventService} from 'vs/platform/event/common/event'; -import {TextFileService as AbstractTextFileService} from 'vs/workbench/parts/files/common/textFileServices'; -import {CACHE, TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {ITextFileOperationResult, AutoSaveMode, IRawTextContent} from 'vs/workbench/parts/files/common/files'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; -import {IFileService, IResolveContentOptions} from 'vs/platform/files/common/files'; -import {BinaryEditorModel} from 'vs/workbench/common/editor/binaryEditorModel'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {IModeService} from 'vs/editor/common/services/modeService'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; -import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import {ModelBuilder} from 'vs/editor/node/model/modelBuilder'; -import product from 'vs/platform/product'; -import {IEnvironmentService} from 'vs/platform/environment/common/environment'; - -export class TextFileService extends AbstractTextFileService { - - private static MAX_CONFIRM_FILES = 10; - - constructor( - @IWorkspaceContextService contextService: IWorkspaceContextService, - @IInstantiationService instantiationService: IInstantiationService, - @IFileService fileService: IFileService, - @IUntitledEditorService private untitledEditorService: IUntitledEditorService, - @ILifecycleService private lifecycleService: ILifecycleService, - @ITelemetryService telemetryService: ITelemetryService, - @IConfigurationService configurationService: IConfigurationService, - @IEventService eventService: IEventService, - @IModeService private modeService: IModeService, - @IWorkbenchEditorService editorService: IWorkbenchEditorService, - @IEditorGroupService private editorGroupService: IEditorGroupService, - @IWindowService private windowService: IWindowService, - @IModelService modelService: IModelService, - @IEnvironmentService private environmentService: IEnvironmentService - ) { - super(contextService, instantiationService, configurationService, telemetryService, editorService, eventService, fileService, modelService); - - this.init(); - } - - protected registerListeners(): void { - super.registerListeners(); - - // Lifecycle - this.lifecycleService.onWillShutdown(event => event.veto(this.beforeShutdown())); - this.lifecycleService.onShutdown(this.onShutdown, this); - - // Application & Editor focus change - window.addEventListener('blur', () => this.onWindowFocusLost()); - window.addEventListener('blur', () => this.onEditorFocusChanged(), true); - this.listenerToUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorFocusChanged())); - } - - private onWindowFocusLost(): void { - if (this.configuredAutoSaveOnWindowChange && this.isDirty()) { - this.saveAll().done(null, errors.onUnexpectedError); - } - } - - private onEditorFocusChanged(): void { - if (this.configuredAutoSaveOnFocusChange && this.isDirty()) { - this.saveAll().done(null, errors.onUnexpectedError); - } - } - - public resolveTextContent(resource: URI, options?: IResolveContentOptions): TPromise { - return this.fileService.resolveStreamContent(resource, options).then((streamContent) => { - return ModelBuilder.fromStringStream(streamContent.value, this.modelService.getCreationOptions()).then((res) => { - let r: IRawTextContent = { - resource: streamContent.resource, - name: streamContent.name, - mtime: streamContent.mtime, - etag: streamContent.etag, - mime: streamContent.mime, - encoding: streamContent.encoding, - value: res.rawText, - valueLogicalHash: res.hash - }; - return r; - }); - }); - } - - public beforeShutdown(): boolean | TPromise { - - // Dirty files need treatment on shutdown - if (this.getDirty().length) { - - // If auto save is enabled, save all files and then check again for dirty files - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.saveAll(false /* files only */).then(() => { - if (this.getDirty().length) { - return this.confirmBeforeShutdown(); // we still have dirty files around, so confirm normally - } - - return false; // all good, no veto - }); - } - - // Otherwise just confirm what to do - return this.confirmBeforeShutdown(); - } - - return false; // no veto - } - - private confirmBeforeShutdown(): boolean | TPromise { - let confirm = this.confirmSave(); - - // Save - if (confirm === ConfirmResult.SAVE) { - return this.saveAll(true /* includeUntitled */).then(result => { - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } - - return false; // no veto - }); - } - - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { - return false; // no veto - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - } - - private onShutdown(): void { - super.dispose(); - } - - public revertAll(resources?: URI[], force?: boolean): TPromise { - - // Revert files - return super.revertAll(resources, force).then(r => { - - // Revert untitled - const reverted = this.untitledEditorService.revertAll(resources); - reverted.forEach(res => r.results.push({ source: res, success: true })); - - return r; - }); - } - - public getDirty(resources?: URI[]): URI[] { - - // Collect files - let dirty = super.getDirty(resources); - - // Add untitled ones - if (!resources) { - dirty.push(...this.untitledEditorService.getDirty()); - } else { - let dirtyUntitled = resources.map(r => this.untitledEditorService.get(r)).filter(u => u && u.isDirty()).map(u => u.getResource()); - dirty.push(...dirtyUntitled); - } - - return dirty; - } - - public isDirty(resource?: URI): boolean { - if (super.isDirty(resource)) { - return true; - } - - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - public confirmSave(resources?: URI[]): ConfirmResult { - if (!!this.environmentService.extensionDevelopmentPath) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assum we run interactive (e.g. tests) - } - - let resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - - let message = [ - resourcesToConfirm.length === 1 ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", paths.basename(resourcesToConfirm[0].fsPath)) : nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length) - ]; - - if (resourcesToConfirm.length > 1) { - message.push(''); - message.push(...resourcesToConfirm.slice(0, TextFileService.MAX_CONFIRM_FILES).map(r => paths.basename(r.fsPath))); - - if (resourcesToConfirm.length > TextFileService.MAX_CONFIRM_FILES) { - if (resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES === 1) { - message.push(nls.localize('moreFile', "...1 additional file not shown")); - } else { - message.push(nls.localize('moreFiles', "...{0} additional files not shown", resourcesToConfirm.length - TextFileService.MAX_CONFIRM_FILES)); - } - } - - message.push(''); - } - - // Button order - // Windows: Save | Don't Save | Cancel - // Mac: Save | Cancel | Don't Save - // Linux: Don't Save | Cancel | Save - - const save = { label: resourcesToConfirm.length > 1 ? this.mnemonicLabel(nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All")) : this.mnemonicLabel(nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save")), result: ConfirmResult.SAVE }; - const dontSave = { label: this.mnemonicLabel(nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save")), result: ConfirmResult.DONT_SAVE }; - const cancel = { label: nls.localize('cancel', "Cancel"), result: ConfirmResult.CANCEL }; - - const buttons = []; - if (isWindows) { - buttons.push(save, dontSave, cancel); - } else if (isLinux) { - buttons.push(dontSave, cancel, save); - } else { - buttons.push(save, cancel, dontSave); - } - - let opts: Electron.ShowMessageBoxOptions = { - title: product.nameLong, - message: message.join('\n'), - type: 'warning', - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them."), - buttons: buttons.map(b => b.label), - noLink: true, - cancelId: buttons.indexOf(cancel) - }; - - if (isLinux) { - opts.defaultId = 2; - } - - const choice = this.windowService.getWindow().showMessageBox(opts); - - return buttons[choice].result; - } - - private mnemonicLabel(label: string): string { - if (!isWindows) { - return label.replace(/\(&&\w\)|&&/g, ''); // no mnemonic support on mac/linux - } - - return label.replace(/&&/g, '&'); - } - - public saveAll(includeUntitled?: boolean): TPromise; - public saveAll(resources: URI[]): TPromise; - public saveAll(arg1?: any): TPromise { - - // get all dirty - let toSave: URI[] = []; - if (Array.isArray(arg1)) { - toSave = this.getDirty(arg1); - } else { - toSave = this.getDirty(); - } - - // split up between files and untitled - let filesToSave: URI[] = []; - let untitledToSave: URI[] = []; - toSave.forEach(s => { - if (s.scheme === 'file') { - filesToSave.push(s); - } else if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && s.scheme === 'untitled') { - untitledToSave.push(s); - } - }); - - return this.doSaveAll(filesToSave, untitledToSave); - } - - private doSaveAll(fileResources: URI[], untitledResources: URI[]): TPromise { - - // Handle files first that can just be saved - return super.saveAll(fileResources).then(result => { - - // Preflight for untitled to handle cancellation from the dialog - let targetsForUntitled: URI[] = []; - for (let i = 0; i < untitledResources.length; i++) { - let untitled = this.untitledEditorService.get(untitledResources[i]); - if (untitled) { - let targetPath: string; - - // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled.getResource())) { - targetPath = untitled.getResource().fsPath; - } - - // Otherwise ask user - else { - targetPath = this.promptForPath(this.suggestFileName(untitledResources[i])); - if (!targetPath) { - return TPromise.as({ - results: [...fileResources, ...untitledResources].map(r => { - return { - source: r - }; - }) - }); - } - } - - targetsForUntitled.push(URI.file(targetPath)); - } - } - - // Handle untitled - let untitledSaveAsPromises: TPromise[] = []; - targetsForUntitled.forEach((target, index) => { - let untitledSaveAsPromise = this.saveAs(untitledResources[index], target).then(uri => { - result.results.push({ - source: untitledResources[index], - target: uri, - success: !!uri - }); - }); - - untitledSaveAsPromises.push(untitledSaveAsPromise); - }); - - return TPromise.join(untitledSaveAsPromises).then(() => { - return result; - }); - }); - } - - public saveAs(resource: URI, target?: URI): TPromise { - - // Get to target resource - if (!target) { - let dialogPath = resource.fsPath; - if (resource.scheme === 'untitled') { - dialogPath = this.suggestFileName(resource); - } - - const pathRaw = this.promptForPath(dialogPath); - if (pathRaw) { - target = URI.file(pathRaw); - } - } - - if (!target) { - return TPromise.as(null); // user canceled - } - - // Just save if target is same as models own resource - if (resource.toString() === target.toString()) { - return this.save(resource).then(() => resource); - } - - // Do it - return this.doSaveAs(resource, target); - } - - private doSaveAs(resource: URI, target?: URI): TPromise { - - // Retrieve text model from provided resource if any - let modelPromise: TPromise = TPromise.as(null); - if (resource.scheme === 'file') { - modelPromise = TPromise.as(CACHE.get(resource)); - } else if (resource.scheme === 'untitled') { - let untitled = this.untitledEditorService.get(resource); - if (untitled) { - modelPromise = untitled.resolve(); - } - } - - return modelPromise.then(model => { - - // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) - if (model) { - return this.doSaveTextFileAs(model, resource, target); - } - - // Otherwise we can only copy - return this.fileService.copyFile(resource, target); - }).then(() => { - - // Revert the source - return this.revert(resource).then(() => { - - // Done: return target - return target; - }); - }); - } - - private doSaveTextFileAs(sourceModel: TextFileEditorModel | UntitledEditorModel, resource: URI, target: URI): TPromise { - // create the target file empty if it does not exist already - return this.fileService.resolveFile(target).then(stat => stat, () => null).then(stat => stat || this.fileService.createFile(target)).then(stat => { - // resolve a model for the file (which can be binary if the file is not a text file) - return this.editorService.resolveEditorModel({ resource: target }).then((targetModel: TextFileEditorModel) => { - // binary model: delete the file and run the operation again - if (targetModel instanceof BinaryEditorModel) { - return this.fileService.del(target).then(() => this.doSaveTextFileAs(sourceModel, resource, target)); - } - - // text model: take over encoding and model value from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - targetModel.textEditorModel.setValue(sourceModel.getValue()); - - // save model - return targetModel.save(); - }); - }); - } - - private suggestFileName(untitledResource: URI): string { - let workspace = this.contextService.getWorkspace(); - if (workspace) { - return URI.file(paths.join(workspace.resource.fsPath, this.untitledEditorService.get(untitledResource).suggestFileName())).fsPath; - } - - return this.untitledEditorService.get(untitledResource).suggestFileName(); - } - - private promptForPath(defaultPath?: string): string { - return this.windowService.getWindow().showSaveDialog(this.getSaveDialogOptions(defaultPath ? paths.normalize(defaultPath, true) : void 0)); - } - - private getSaveDialogOptions(defaultPath?: string): Electron.SaveDialogOptions { - let options: Electron.SaveDialogOptions = { - defaultPath: defaultPath - }; - - // Filters are working flaky in Electron and there are bugs. On Windows they are working - // somewhat but we see issues: - // - https://github.com/electron/electron/issues/3556 - // - https://github.com/Microsoft/vscode/issues/451 - // - Bug on Windows: When "All Files" is picked, the path gets an extra ".*" - // - Bug on Windows: Cannot save file without extension - // - Bug on Windows: Untitled files get just the first extension of the list - // Until these issues are resolved, we disable the dialog file extension filtering. - let disable = true; // Simply using if (true) flags the code afterwards as not reachable. - if (disable) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - let ext: string = paths.extname(defaultPath); - let matchingFilter: IFilter; - let filters: IFilter[] = this.modeService.getRegisteredLanguageNames().map(languageName => { - let extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - let filter: IFilter = { name: languageName, extensions: extensions.map(e => strings.trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - }).filter(f => !!f); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - let allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.push(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - options.filters = filters; - - return options; - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/files/test/browser/viewModel.test.ts b/src/vs/workbench/parts/files/test/browser/explorerViewModel.test.ts similarity index 71% rename from src/vs/workbench/parts/files/test/browser/viewModel.test.ts rename to src/vs/workbench/parts/files/test/browser/explorerViewModel.test.ts index 15fda38121f..d8995765a01 100644 --- a/src/vs/workbench/parts/files/test/browser/viewModel.test.ts +++ b/src/vs/workbench/parts/files/test/browser/explorerViewModel.test.ts @@ -26,7 +26,7 @@ function toResource(path) { suite('Files - View Model', () => { test('Properties', function () { - let d = new Date().getTime(); + const d = new Date().getTime(); let s = createStat('/path/to/stat', 'sName', true, true, 8096, d); assert.strictEqual(s.isDirectoryResolved, false); @@ -42,12 +42,12 @@ suite('Files - View Model', () => { }); test('Add and Remove Child, check for hasChild', function () { - let d = new Date().getTime(); - let s = createStat('/path/to/stat', 'sName', true, false, 8096, d); + const d = new Date().getTime(); + const s = createStat('/path/to/stat', 'sName', true, false, 8096, d); - let child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - let child2 = createStat('/path/to/stat/bar.html', 'bar', false, false, 8096, d); - let child4 = createStat('/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); + const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat('/path/to/stat/bar.html', 'bar', false, false, 8096, d); + const child4 = createStat('/otherpath/to/other/otherbar.html', 'otherbar.html', false, false, 8096, d); assert(!s.hasChild(child1.name)); assert(!s.hasChild(child2.name)); @@ -74,12 +74,12 @@ suite('Files - View Model', () => { }); test('Move', function () { - let d = new Date().getTime(); + const d = new Date().getTime(); - let s1 = createStat('/', '/', true, false, 8096, d); - let s2 = createStat('/path', 'path', true, false, 8096, d); - let s3 = createStat('/path/to', 'to', true, false, 8096, d); - let s4 = createStat('/path/to/stat', 'stat', false, false, 8096, d); + const s1 = createStat('/', '/', true, false, 8096, d); + const s2 = createStat('/path', 'path', true, false, 8096, d); + const s3 = createStat('/path/to', 'to', true, false, 8096, d); + const s4 = createStat('/path/to/stat', 'stat', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -96,9 +96,9 @@ suite('Files - View Model', () => { assert.strictEqual(s4.resource.fsPath, toResource('/' + s4.name).fsPath); // Move a subtree with children - let leaf = createStat('/leaf', 'leaf', true, false, 8096, d); - let leafC1 = createStat('/leaf/folder', 'folder', true, false, 8096, d); - let leafCC2 = createStat('/leaf/folder/index.html', 'index.html', true, false, 8096, d); + const leaf = createStat('/leaf', 'leaf', true, false, 8096, d); + const leafC1 = createStat('/leaf/folder', 'folder', true, false, 8096, d); + const leafCC2 = createStat('/leaf/folder/index.html', 'index.html', true, false, 8096, d); leaf.addChild(leafC1); leafC1.addChild(leafCC2); @@ -110,18 +110,18 @@ suite('Files - View Model', () => { }); test('Rename', function () { - let d = new Date().getTime(); + const d = new Date().getTime(); - let s1 = createStat('/', '/', true, false, 8096, d); - let s2 = createStat('/path', 'path', true, false, 8096, d); - let s3 = createStat('/path/to', 'to', true, false, 8096, d); - let s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat('/', '/', true, false, 8096, d); + const s2 = createStat('/path', 'path', true, false, 8096, d); + const s3 = createStat('/path/to', 'to', true, false, 8096, d); + const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); s1.addChild(s2); s2.addChild(s3); s3.addChild(s4); - let s2renamed = createStat('/otherpath', 'otherpath', true, true, 8096, d); + const s2renamed = createStat('/otherpath', 'otherpath', true, true, 8096, d); s2.rename(s2renamed); // Verify the paths have changed including children @@ -130,22 +130,22 @@ suite('Files - View Model', () => { assert.strictEqual(s3.resource.fsPath, toResource('/otherpath/to').fsPath); assert.strictEqual(s4.resource.fsPath, toResource('/otherpath/to/stat').fsPath); - let s4renamed = createStat('/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); + const s4renamed = createStat('/otherpath/to/statother.js', 'statother.js', true, false, 8096, d); s4.rename(s4renamed); assert.strictEqual(s4.name, s4renamed.name); assert.strictEqual(s4.resource.fsPath, s4renamed.resource.fsPath); }); test('Find', function () { - let d = new Date().getTime(); + const d = new Date().getTime(); - let s1 = createStat('/', '/', true, false, 8096, d); - let s2 = createStat('/path', 'path', true, false, 8096, d); - let s3 = createStat('/path/to', 'to', true, false, 8096, d); - let s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat('/', '/', true, false, 8096, d); + const s2 = createStat('/path', 'path', true, false, 8096, d); + const s3 = createStat('/path/to', 'to', true, false, 8096, d); + const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); - let child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - let child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -165,15 +165,15 @@ suite('Files - View Model', () => { }); test('Find with mixed case', function () { - let d = new Date().getTime(); + const d = new Date().getTime(); - let s1 = createStat('/', '/', true, false, 8096, d); - let s2 = createStat('/path', 'path', true, false, 8096, d); - let s3 = createStat('/path/to', 'to', true, false, 8096, d); - let s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); + const s1 = createStat('/', '/', true, false, 8096, d); + const s2 = createStat('/path', 'path', true, false, 8096, d); + const s3 = createStat('/path/to', 'to', true, false, 8096, d); + const s4 = createStat('/path/to/stat', 'stat', true, false, 8096, d); - let child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); - let child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); + const child1 = createStat('/path/to/stat/foo', 'foo', true, false, 8096, d); + const child2 = createStat('/path/to/stat/foo/bar.html', 'bar.html', false, false, 8096, d); s1.addChild(s2); s2.addChild(s3); @@ -191,9 +191,9 @@ suite('Files - View Model', () => { }); test('Validate File Name (For Create)', function () { - let d = new Date().getTime(); - let s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - let sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const d = new Date().getTime(); + const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, null) !== null); @@ -218,9 +218,9 @@ suite('Files - View Model', () => { }); test('Validate File Name (For Rename)', function () { - let d = new Date().getTime(); - let s = createStat('/path/to/stat', 'sName', true, true, 8096, d); - let sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); + const d = new Date().getTime(); + const s = createStat('/path/to/stat', 'sName', true, true, 8096, d); + const sChild = createStat('/path/to/stat/alles.klar', 'alles.klar', true, true, 8096, d); s.addChild(sChild); assert(validateFileName(s, 'alles.klar') !== null); @@ -239,10 +239,10 @@ suite('Files - View Model', () => { }); test('File Change Event (with stats)', function () { - let d = new Date().toUTCString(); - let s1 = new FileStat(toResource('/path/to/sName'), false, false, 'sName', void 0, 8096 /* Size */, d); - let s2 = new FileStat(toResource('/path/to/sName'), false, false, 'sName', void 0, 16000 /* Size */, d); - let s3 = new FileStat(toResource('/path/to/sNameMoved'), false, false, 'sNameMoved', void 0, 8096 /* Size */, d); + const d = new Date().toUTCString(); + const s1 = new FileStat(toResource('/path/to/sName'), false, false, 'sName', void 0, 8096 /* Size */, d); + const s2 = new FileStat(toResource('/path/to/sName'), false, false, 'sName', void 0, 16000 /* Size */, d); + const s3 = new FileStat(toResource('/path/to/sNameMoved'), false, false, 'sNameMoved', void 0, 8096 /* Size */, d); // Got Added let event = new LocalFileChangeEvent(null, s1); @@ -281,10 +281,10 @@ suite('Files - View Model', () => { }); test('Merge Local with Disk', function () { - let d = new Date().toUTCString(); + const d = new Date().toUTCString(); - let merge1 = new FileStat(URI.file(join('C:\\', '/path/to')), true, false, 'to', void 0, 8096, d); - let merge2 = new FileStat(URI.file(join('C:\\', '/path/to')), true, false, 'to', void 0, 16000, new Date(0).toUTCString()); + const merge1 = new FileStat(URI.file(join('C:\\', '/path/to')), true, false, 'to', void 0, 8096, d); + const merge2 = new FileStat(URI.file(join('C:\\', '/path/to')), true, false, 'to', void 0, 16000, new Date(0).toUTCString()); // Merge Properties FileStat.mergeLocalWithDisk(merge2, merge1); @@ -306,7 +306,7 @@ suite('Files - View Model', () => { assert.deepEqual(merge1.children[0].parent, merge1, 'Check parent'); // Verify that merge does not replace existing children, but updates properties in that case - let existingChild = merge1.children[0]; + const existingChild = merge1.children[0]; FileStat.mergeLocalWithDisk(merge2, merge1); assert.ok(existingChild === merge1.children[0]); }); diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts b/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts index 8c448563898..cae1ab5b3f6 100644 --- a/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts +++ b/src/vs/workbench/parts/files/test/browser/fileEditorInput.test.ts @@ -4,66 +4,37 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import 'vs/workbench/parts/files/browser/files.contribution'; // load our contribution into the test import * as assert from 'assert'; -import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; import URI from 'vs/base/common/uri'; import {join} from 'vs/base/common/paths'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import {IModeService} from 'vs/editor/common/services/modeService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IStorageService} from 'vs/platform/storage/common/storage'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {ILifecycleService, NullLifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; -import {IFileService} from 'vs/platform/files/common/files'; -import {IQuickOpenService} from 'vs/workbench/services/quickopen/common/quickOpenService'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IPartService} from 'vs/workbench/services/part/common/partService'; -import {ITextFileService} from 'vs/workbench/parts/files/common/files'; -import {FileTracker} from 'vs/workbench/parts/files/browser/fileTracker'; -import {createMockModelService, TestTextFileService, TestEditorGroupService, TestFileService, TestEditorService, TestPartService, TestConfigurationService, TestEventService, TestContextService, TestQuickOpenService, TestStorageService} from 'vs/test/utils/servicesTestUtils'; -import {IHistoryService} from 'vs/workbench/services/history/common/history'; -import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; function toResource(path) { return URI.file(join('C:\\', path)); } +class ServiceAccessor { + constructor( @IWorkbenchEditorService public editorService: IWorkbenchEditorService) { + } +} + suite('Files - FileEditorInput', () => { let instantiationService: TestInstantiationService; + let accessor: ServiceAccessor; setup(() => { - instantiationService= new TestInstantiationService(); - instantiationService.stub(IHistoryService, 'getHistory', []); + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); }); test('FileEditorInput', function (done) { - let editorService = new TestEditorService(function () { }); - let eventService = new TestEventService(); - let telemetryService = NullTelemetryService; - let contextService = new TestContextService(); - - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(IFileService, TestFileService); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchEditorService, editorService); - instantiationService.stub(IPartService, new TestPartService()); - instantiationService.stub(IModeService); - instantiationService.stub(IModelService, createMockModelService(instantiationService)); - instantiationService.stub(ITelemetryService, telemetryService); - instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(ITextFileService, instantiationService.createInstance( TestTextFileService)); - let input = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0); - let otherInput = instantiationService.createInstance(FileEditorInput, toResource('foo/bar/otherfile.js'), 'text/javascript', void 0); - let otherInputSame = instantiationService.createInstance(FileEditorInput, toResource('foo/bar/file.js'), 'text/javascript', void 0); + const otherInput = instantiationService.createInstance(FileEditorInput, toResource('foo/bar/otherfile.js'), 'text/javascript', void 0); + const otherInputSame = instantiationService.createInstance(FileEditorInput, toResource('foo/bar/file.js'), 'text/javascript', void 0); assert(input.matches(input)); assert(input.matches(otherInputSame)); @@ -78,20 +49,20 @@ suite('Files - FileEditorInput', () => { input = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar.html'), 'text/html', void 0); - let inputToResolve:any = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0); - let sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0); + const inputToResolve: any = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0); + const sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/file.js'), 'text/javascript', void 0); - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { - let resolvedModelA = resolved; - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + const resolvedModelA = resolved; + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { assert(resolvedModelA === resolved); // OK: Resolved Model cached globally per input - return editorService.resolveEditorModel(sameOtherInput, true).then(function (otherResolved) { + return accessor.editorService.resolveEditorModel(sameOtherInput, true).then(function (otherResolved) { assert(otherResolved === resolvedModelA); // OK: Resolved Model cached globally per input inputToResolve.dispose(false); - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { assert(resolvedModelA === resolved); // Model is still the same because we had 2 clients inputToResolve.dispose(); @@ -99,15 +70,15 @@ suite('Files - FileEditorInput', () => { resolvedModelA.dispose(); - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { assert(resolvedModelA !== resolved); // Different instance, because input got disposed let stat = (resolved).versionOnDiskStat; - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { assert(stat !== (resolved).versionOnDiskStat); // Different stat, because resolve always goes to the server for refresh stat = (resolved).versionOnDiskStat; - return editorService.resolveEditorModel(inputToResolve, false).then(function (resolved) { + return accessor.editorService.resolveEditorModel(inputToResolve, false).then(function (resolved) { assert(stat === (resolved).versionOnDiskStat); // Same stat, because not refreshed done(); @@ -121,102 +92,11 @@ suite('Files - FileEditorInput', () => { }); test('Input.matches() - FileEditorInput', function () { - let eventService = new TestEventService(); - let contextService = new TestContextService(); - - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(ITextFileService, instantiationService.createInstance( TestTextFileService)); - - let fileEditorInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0); - let contentEditorInput2 = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0); + const fileEditorInput = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0); + const contentEditorInput2 = instantiationService.createInstance(FileEditorInput, toResource('/foo/bar/updatefile.js'), 'text/javascript', void 0); assert.strictEqual(fileEditorInput.matches(null), false); assert.strictEqual(fileEditorInput.matches(fileEditorInput), true); assert.strictEqual(fileEditorInput.matches(contentEditorInput2), true); }); - - test('FileTracker - dispose()', function (done) { - let editorService = new TestEditorService(function () { }); - let telemetryService = NullTelemetryService; - let contextService = new TestContextService(); - - let eventService = new TestEventService(); - - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(IFileService, TestFileService); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchEditorService, editorService); - instantiationService.stub(IQuickOpenService, new TestQuickOpenService()); - instantiationService.stub(IPartService, new TestPartService()); - instantiationService.stub(IModeService); - instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); - instantiationService.stub(IModelService, createMockModelService(instantiationService)); - instantiationService.stub(ITelemetryService, telemetryService); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(ITextFileService, instantiationService.createInstance( TestTextFileService)); - - let tracker = instantiationService.createInstance(FileTracker); - - let inputToResolve = instantiationService.createInstance(FileEditorInput, toResource('/fooss5/bar/file2.js'), 'text/javascript', void 0); - let sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/fooss5/bar/file2.js'), 'text/javascript', void 0); - return editorService.resolveEditorModel(inputToResolve).then(function (resolved) { - return editorService.resolveEditorModel(sameOtherInput).then(function (resolved) { - tracker.handleDeleteOrMove(toResource('/bar'), []); - assert(!inputToResolve.isDisposed()); - assert(!sameOtherInput.isDisposed()); - - tracker.handleDeleteOrMove(toResource('/fooss5/bar/file2.js'), []); - - assert(inputToResolve.isDisposed()); - assert(sameOtherInput.isDisposed()); - - done(); - }); - }); - }); - - test('FileEditorInput - dispose() also works for folders', function (done) { - let editorService = new TestEditorService(function () { }); - let telemetryService = NullTelemetryService; - let contextService = new TestContextService(); - - let eventService = new TestEventService(); - - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(IFileService, TestFileService); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchEditorService, editorService); - instantiationService.stub(IPartService, new TestPartService()); - instantiationService.stub(IModeService); - instantiationService.stub(IEditorGroupService, new TestEditorGroupService()); - instantiationService.stub(IQuickOpenService, new TestQuickOpenService()); - instantiationService.stub(IModelService, createMockModelService(instantiationService)); - instantiationService.stub(ITelemetryService, telemetryService); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(ITextFileService, instantiationService.createInstance( TestTextFileService)); - - let tracker = instantiationService.createInstance(FileTracker); - - let inputToResolve = instantiationService.createInstance(FileEditorInput, toResource('/foo6/bar/file.js'), 'text/javascript', void 0); - let sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/foo6/bar/file.js'), 'text/javascript', void 0); - return editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { - return editorService.resolveEditorModel(sameOtherInput, true).then(function (resolved) { - tracker.handleDeleteOrMove(toResource('/bar'), []); - assert(!inputToResolve.isDisposed()); - assert(!sameOtherInput.isDisposed()); - - tracker.handleDeleteOrMove(toResource('/foo6'), []); - - assert(inputToResolve.isDisposed()); - assert(sameOtherInput.isDisposed()); - - done(); - }); - }); - }); }); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorModel.test.ts b/src/vs/workbench/parts/files/test/browser/fileEditorModel.test.ts index fcbcf8ce41c..c6905ac33ce 100644 --- a/src/vs/workbench/parts/files/test/browser/fileEditorModel.test.ts +++ b/src/vs/workbench/parts/files/test/browser/fileEditorModel.test.ts @@ -6,99 +6,76 @@ 'use strict'; import * as assert from 'assert'; -import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; import {TPromise} from 'vs/base/common/winjs.base'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; import URI from 'vs/base/common/uri'; -import paths = require('vs/base/common/paths'); import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; -import {TextFileEditorModel, CACHE} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; +import paths = require('vs/base/common/paths'); +import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; import {IEventService} from 'vs/platform/event/common/event'; -import {IMessageService} from 'vs/platform/message/common/message'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import {IModeService} from 'vs/editor/common/services/modeService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IStorageService} from 'vs/platform/storage/common/storage'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {ILifecycleService, NullLifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; -import {IFileService} from 'vs/platform/files/common/files'; -import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import PartService = require('vs/workbench/services/part/common/partService'); -import {ITextFileService, EventType} from 'vs/workbench/parts/files/common/files'; -import {createMockModelService, TestTextFileService, TestFileService, TestPartService, TestEditorService, TestConfigurationService, TestUntitledEditorService, TestStorageService, TestContextService, TestMessageService, TestEventService} from 'vs/test/utils/servicesTestUtils'; +import {EventType, ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; +import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager'; function toResource(path) { return URI.file(paths.join('C:\\', path)); } -let eventService: IEventService; -let textFileService: TestTextFileService; +class ServiceAccessor { + constructor(@IEventService public eventService: IEventService, @ITextFileService public textFileService: ITextFileService) { + } +} + suite('Files - TextFileEditorModel', () => { let instantiationService: TestInstantiationService; + let accessor: ServiceAccessor; setup(() => { - instantiationService= new TestInstantiationService(); - eventService = new TestEventService(); - eventService= instantiationService.stub(IEventService, new TestEventService()); - instantiationService.stub(IMessageService, new TestMessageService()); - instantiationService.stub(IFileService, TestFileService); - instantiationService.stub(IWorkspaceContextService, new TestContextService()); - instantiationService.stub(ITelemetryService); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IUntitledEditorService, new TestUntitledEditorService()); - instantiationService.stub(IWorkbenchEditorService, new TestEditorService()); - instantiationService.stub(PartService.IPartService, new TestPartService()); - instantiationService.stub(IModeService); - instantiationService.stub(IModelService, createMockModelService(instantiationService)); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - - textFileService = instantiationService.createInstance(TestTextFileService); - instantiationService.stub(ITextFileService, textFileService); + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); }); teardown(() => { - CACHE.clear(); + (accessor.textFileService.models).clear(); }); test('Load does not trigger save', function (done) { - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index.txt'), 'utf8'); - eventService.addListener2('files:internalFileChanged', () => { + accessor.eventService.addListener2('files:internalFileChanged', () => { assert.ok(false); }); - eventService.addListener2(EventType.FILE_DIRTY, () => { + accessor.eventService.addListener2(EventType.FILE_DIRTY, () => { assert.ok(false); }); - eventService.addListener2(EventType.FILE_SAVED, () => { + accessor.eventService.addListener2(EventType.FILE_SAVED, () => { assert.ok(false); }); - m1.load().then(() => { - assert.ok(m1.isResolved()); + model.load().then(() => { + assert.ok(model.isResolved()); - m1.dispose(); + model.dispose(); done(); }); }); test('Load returns dirty model as long as model is dirty', function (done) { - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); - m1.load().then(() => { - m1.textEditorModel.setValue('foo'); + model.load().then(() => { + model.textEditorModel.setValue('foo'); - assert.ok(m1.isDirty()); - m1.load().then(() => { - assert.ok(m1.isDirty()); + assert.ok(model.isDirty()); + model.load().then(() => { + assert.ok(model.isDirty()); - m1.dispose(); + model.dispose(); done(); }); @@ -108,23 +85,23 @@ suite('Files - TextFileEditorModel', () => { test('Revert', function (done) { let eventCounter = 0; - eventService.addListener2(EventType.FILE_REVERTED, () => { + accessor.eventService.addListener2(EventType.FILE_REVERTED, () => { eventCounter++; }); - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); - m1.load().then(() => { - m1.textEditorModel.setValue('foo'); + model.load().then(() => { + model.textEditorModel.setValue('foo'); - assert.ok(m1.isDirty()); + assert.ok(model.isDirty()); - m1.revert().then(() => { - assert.ok(!m1.isDirty()); - assert.equal(m1.textEditorModel.getValue(), 'Hello Html'); + model.revert().then(() => { + assert.ok(!model.isDirty()); + assert.equal(model.textEditorModel.getValue(), 'Hello Html'); assert.equal(eventCounter, 1); - m1.dispose(); + model.dispose(); done(); }); @@ -132,23 +109,23 @@ suite('Files - TextFileEditorModel', () => { }); test('Conflict Resolution Mode', function (done) { - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); - m1.load().then(() => { - m1.setConflictResolutionMode(); - m1.textEditorModel.setValue('foo'); + model.load().then(() => { + model.setConflictResolutionMode(); + model.textEditorModel.setValue('foo'); - assert.ok(m1.isDirty()); - assert.ok(m1.isInConflictResolutionMode()); + assert.ok(model.isDirty()); + assert.ok(model.isInConflictResolutionMode()); - m1.revert().then(() => { - m1.textEditorModel.setValue('bar'); - assert.ok(m1.isDirty()); + model.revert().then(() => { + model.textEditorModel.setValue('bar'); + assert.ok(model.isDirty()); - return m1.save().then(() => { - assert.ok(!m1.isDirty()); + return model.save().then(() => { + assert.ok(!model.isDirty()); - m1.dispose(); + model.dispose(); done(); }); @@ -158,27 +135,27 @@ suite('Files - TextFileEditorModel', () => { test('Auto Save triggered when model changes', function (done) { let eventCounter = 0; - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index.txt'), 'utf8'); - (m1).autoSaveAfterMillies = 10; - (m1).autoSaveAfterMilliesEnabled = true; + (model).autoSaveAfterMillies = 10; + (model).autoSaveAfterMilliesEnabled = true; - eventService.addListener2(EventType.FILE_DIRTY, () => { + accessor.eventService.addListener2(EventType.FILE_DIRTY, () => { eventCounter++; }); - eventService.addListener2(EventType.FILE_SAVED, () => { + accessor.eventService.addListener2(EventType.FILE_SAVED, () => { eventCounter++; }); - m1.load().then(() => { - m1.textEditorModel.setValue('foo'); + model.load().then(() => { + model.textEditorModel.setValue('foo'); return TPromise.timeout(50).then(() => { - assert.ok(!m1.isDirty()); + assert.ok(!model.isDirty()); assert.equal(eventCounter, 2); - m1.dispose(); + model.dispose(); done(); }); @@ -186,36 +163,36 @@ suite('Files - TextFileEditorModel', () => { }); test('save() and isDirty() - proper with check for mtimes', function (done) { - let c1 = instantiationService.createInstance(FileEditorInput, toResource('/path/index_async2.txt'), 'text/plain', 'utf8'); - let c2 = instantiationService.createInstance(FileEditorInput, toResource('/path/index_async.txt'), 'text/plain', 'utf8'); + const input1 = instantiationService.createInstance(FileEditorInput, toResource('/path/index_async2.txt'), 'text/plain', 'utf8'); + const input2 = instantiationService.createInstance(FileEditorInput, toResource('/path/index_async.txt'), 'text/plain', 'utf8'); - c1.resolve().then((m1: TextFileEditorModel) => { - c2.resolve().then((m2: TextFileEditorModel) => { - m1.textEditorModel.setValue('foo'); + input1.resolve().then((model1: TextFileEditorModel) => { + input2.resolve().then((model2: TextFileEditorModel) => { + model1.textEditorModel.setValue('foo'); - let m1Mtime = m1.getLastModifiedTime(); - let m2Mtime = m2.getLastModifiedTime(); + const m1Mtime = model1.getLastModifiedTime(); + const m2Mtime = model2.getLastModifiedTime(); assert.ok(m1Mtime > 0); assert.ok(m2Mtime > 0); - assert.ok(textFileService.isDirty()); - assert.ok(textFileService.isDirty(toResource('/path/index_async2.txt'))); - assert.ok(!textFileService.isDirty(toResource('/path/index_async.txt'))); + assert.ok(accessor.textFileService.isDirty()); + assert.ok(accessor.textFileService.isDirty(toResource('/path/index_async2.txt'))); + assert.ok(!accessor.textFileService.isDirty(toResource('/path/index_async.txt'))); - m2.textEditorModel.setValue('foo'); - assert.ok(textFileService.isDirty(toResource('/path/index_async.txt'))); + model2.textEditorModel.setValue('foo'); + assert.ok(accessor.textFileService.isDirty(toResource('/path/index_async.txt'))); return TPromise.timeout(10).then(() => { - textFileService.saveAll().then(() => { - assert.ok(!textFileService.isDirty(toResource('/path/index_async.txt'))); - assert.ok(!textFileService.isDirty(toResource('/path/index_async2.txt'))); - assert.ok(m1.getLastModifiedTime() > m1Mtime); - assert.ok(m2.getLastModifiedTime() > m2Mtime); - assert.ok(m1.getLastSaveAttemptTime() > m1Mtime); - assert.ok(m2.getLastSaveAttemptTime() > m2Mtime); + accessor.textFileService.saveAll().then(() => { + assert.ok(!accessor.textFileService.isDirty(toResource('/path/index_async.txt'))); + assert.ok(!accessor.textFileService.isDirty(toResource('/path/index_async2.txt'))); + assert.ok(model1.getLastModifiedTime() > m1Mtime); + assert.ok(model2.getLastModifiedTime() > m2Mtime); + assert.ok(model1.getLastSaveAttemptTime() > m1Mtime); + assert.ok(model2.getLastSaveAttemptTime() > m2Mtime); - m1.dispose(); - m2.dispose(); + model1.dispose(); + model2.dispose(); done(); }); @@ -226,26 +203,26 @@ suite('Files - TextFileEditorModel', () => { test('Save Participant', function (done) { let eventCounter = 0; - let m1 = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); + const model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/index_async.txt'), 'utf8'); - eventService.addListener2(EventType.FILE_SAVED, (e) => { - assert.equal(m1.getValue(), 'bar'); - assert.ok(!m1.isDirty()); + accessor.eventService.addListener2(EventType.FILE_SAVED, (e) => { + assert.equal(model.getValue(), 'bar'); + assert.ok(!model.isDirty()); eventCounter++; }); - eventService.addListener2(EventType.FILE_SAVING, (e) => { - assert.ok(m1.isDirty()); - m1.textEditorModel.setValue('bar'); - assert.ok(m1.isDirty()); + accessor.eventService.addListener2(EventType.FILE_SAVING, (e) => { + assert.ok(model.isDirty()); + model.textEditorModel.setValue('bar'); + assert.ok(model.isDirty()); eventCounter++; }); - m1.load().then(() => { - m1.textEditorModel.setValue('foo'); + model.load().then(() => { + model.textEditorModel.setValue('foo'); - m1.save().then(() => { - m1.dispose(); + model.save().then(() => { + model.dispose(); assert.equal(eventCounter, 2); diff --git a/src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts b/src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts new file mode 100644 index 00000000000..6d3201dfbab --- /dev/null +++ b/src/vs/workbench/parts/files/test/browser/fileEditorTracker.test.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; +import URI from 'vs/base/common/uri'; +import {join} from 'vs/base/common/paths'; +import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; +import {FileEditorTracker} from 'vs/workbench/parts/files/common/editors/fileEditorTracker'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; + +function toResource(path) { + return URI.file(join('C:\\', path)); +} + +class ServiceAccessor { + constructor( @IWorkbenchEditorService public editorService: IWorkbenchEditorService) { + } +} + +suite('Files - FileEditorTracker', () => { + + let instantiationService: TestInstantiationService; + let accessor: ServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + }); + + test('FileEditorTracker - dispose()', function (done) { + const tracker = instantiationService.createInstance(FileEditorTracker); + + const inputToResolve = instantiationService.createInstance(FileEditorInput, toResource('/fooss5/bar/file2.js'), 'text/javascript', void 0); + const sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/fooss5/bar/file2.js'), 'text/javascript', void 0); + return accessor.editorService.resolveEditorModel(inputToResolve).then(function (resolved) { + return accessor.editorService.resolveEditorModel(sameOtherInput).then(function (resolved) { + tracker.handleDeleteOrMove(toResource('/bar'), []); + assert(!inputToResolve.isDisposed()); + assert(!sameOtherInput.isDisposed()); + + tracker.handleDeleteOrMove(toResource('/fooss5/bar/file2.js'), []); + + assert(inputToResolve.isDisposed()); + assert(sameOtherInput.isDisposed()); + + done(); + }); + }); + }); + + test('FileEditorTracker - dispose() also works for folders', function (done) { + const tracker = instantiationService.createInstance(FileEditorTracker); + + const inputToResolve = instantiationService.createInstance(FileEditorInput, toResource('/foo6/bar/file.js'), 'text/javascript', void 0); + const sameOtherInput = instantiationService.createInstance(FileEditorInput, toResource('/foo6/bar/file.js'), 'text/javascript', void 0); + return accessor.editorService.resolveEditorModel(inputToResolve, true).then(function (resolved) { + return accessor.editorService.resolveEditorModel(sameOtherInput, true).then(function (resolved) { + tracker.handleDeleteOrMove(toResource('/bar'), []); + assert(!inputToResolve.isDisposed()); + assert(!sameOtherInput.isDisposed()); + + tracker.handleDeleteOrMove(toResource('/foo6'), []); + + assert(inputToResolve.isDisposed()); + assert(sameOtherInput.isDisposed()); + + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/test/browser/events.test.ts b/src/vs/workbench/parts/files/test/browser/fileEvents.test.ts similarity index 84% rename from src/vs/workbench/parts/files/test/browser/events.test.ts rename to src/vs/workbench/parts/files/test/browser/fileEvents.test.ts index 84bcfda8abb..c645c0eb359 100644 --- a/src/vs/workbench/parts/files/test/browser/events.test.ts +++ b/src/vs/workbench/parts/files/test/browser/fileEvents.test.ts @@ -12,10 +12,10 @@ import {FileImportedEvent} from 'vs/workbench/parts/files/browser/fileActions'; suite('Files - Events', () => { test('File Change Event (simple)', function () { - let origEvent: any = {}; - let oldValue: any = { foo: 'bar' }; - let newValue: any = { foo: 'foo' }; - let event = new LocalFileChangeEvent(oldValue, newValue, origEvent); + const origEvent: any = {}; + const oldValue: any = { foo: 'bar' }; + const newValue: any = { foo: 'foo' }; + const event = new LocalFileChangeEvent(oldValue, newValue, origEvent); assert.strictEqual(event.originalEvent, origEvent); assert.strictEqual(event.oldValue, oldValue); @@ -24,8 +24,8 @@ suite('Files - Events', () => { }); test('File Upload Event', function () { - let origEvent: any = {}; - let value: any = { foo: 'bar' }; + const origEvent: any = {}; + const value: any = { foo: 'bar' }; let event = new FileImportedEvent(value, true, origEvent); assert.strictEqual(event.originalEvent, origEvent); diff --git a/src/vs/workbench/parts/files/test/browser/textFileEditor.test.ts b/src/vs/workbench/parts/files/test/browser/textFileEditor.test.ts index b83a182b0d1..cfa29d9fd6f 100644 --- a/src/vs/workbench/parts/files/test/browser/textFileEditor.test.ts +++ b/src/vs/workbench/parts/files/test/browser/textFileEditor.test.ts @@ -8,16 +8,12 @@ import {strictEqual, equal} from 'assert'; import {join} from 'vs/base/common/paths'; import URI from 'vs/base/common/uri'; -import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; import {FileEditorDescriptor} from 'vs/workbench/parts/files/browser/files'; import {Registry} from 'vs/platform/platform'; import {SyncDescriptor} from 'vs/platform/instantiation/common/descriptors'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import {Extensions} from 'vs/workbench/common/editor'; -import {TestTextFileService, TestEventService, TestContextService} from 'vs/test/utils/servicesTestUtils'; -import {IEventService} from 'vs/platform/event/common/event'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; const ExtensionId = Extensions.Editors; @@ -27,14 +23,14 @@ class MyOtherClass { } suite('Files - TextFileEditor', () => { test('TextFile Editor Registration', function () { - let d1 = new FileEditorDescriptor('ce-id1', 'name', 'vs/workbench/parts/files/browser/tests/contentEditor.test', 'MyClass', ['test-text/html', 'test-text/javascript']); - let d2 = new FileEditorDescriptor('ce-id2', 'name', 'vs/workbench/parts/files/browser/tests/contentEditor.test', 'MyOtherClass', ['test-text/css', 'test-text/javascript']); + const d1 = new FileEditorDescriptor('ce-id1', 'name', 'vs/workbench/parts/files/browser/tests/contentEditor.test', 'MyClass', ['test-text/html', 'test-text/javascript']); + const d2 = new FileEditorDescriptor('ce-id2', 'name', 'vs/workbench/parts/files/browser/tests/contentEditor.test', 'MyOtherClass', ['test-text/css', 'test-text/javascript']); - let oldEditors = Registry.as(ExtensionId).getEditors(); + const oldEditors = Registry.as(ExtensionId).getEditors(); Registry.as(ExtensionId).setEditors([]); - let oldEditorCnt = Registry.as(ExtensionId).getEditors().length; - let oldInputCnt = Registry.as(ExtensionId).getEditorInputs().length; + const oldEditorCnt = Registry.as(ExtensionId).getEditors().length; + const oldInputCnt = Registry.as(ExtensionId).getEditorInputs().length; Registry.as(ExtensionId).registerEditor(d1, new SyncDescriptor(FileEditorInput)); Registry.as(ExtensionId).registerEditor(d2, new SyncDescriptor(FileEditorInput)); @@ -42,14 +38,7 @@ suite('Files - TextFileEditor', () => { equal(Registry.as(ExtensionId).getEditors().length, oldEditorCnt + 2); equal(Registry.as(ExtensionId).getEditorInputs().length, oldInputCnt + 2); - let eventService = new TestEventService(); - let contextService = new TestContextService(); - - let instantiationService = new TestInstantiationService(); - - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(ITextFileService, instantiationService.createInstance( TestTextFileService)); + const instantiationService = workbenchInstantiationService(); strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.html')), 'test-text/html', void 0)), d1); strictEqual(Registry.as(ExtensionId).getEditor(instantiationService.createInstance(FileEditorInput, URI.file(join('C:\\', '/foo/bar/foobar.js')), 'test-text/javascript', void 0)), d1); diff --git a/src/vs/workbench/parts/files/test/browser/textFileEditorModelCache.test.ts b/src/vs/workbench/parts/files/test/browser/textFileEditorModelCache.test.ts deleted file mode 100644 index ce07d590892..00000000000 --- a/src/vs/workbench/parts/files/test/browser/textFileEditorModelCache.test.ts +++ /dev/null @@ -1,55 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -'use strict'; - -import * as assert from 'assert'; -import URI from 'vs/base/common/uri'; -import {TextFileEditorModelCache} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {EditorModel} from 'vs/workbench/common/editor'; - -suite('Files - TextFileEditorModelCache', () => { - - test('add, remove, clear', function () { - let cache = new TextFileEditorModelCache(); - - let m1 = new EditorModel(); - let m2 = new EditorModel(); - let m3 = new EditorModel(); - - cache.add(URI.file('/test.html'), m1); - cache.add(URI.file('/some/other.html'), m2); - cache.add(URI.file('/some/this.txt'), m3); - - assert(!cache.get(URI.file('foo'))); - assert.strictEqual(cache.get(URI.file('/test.html')), m1); - - let result = cache.getAll(); - assert.strictEqual(3, result.length); - - result = cache.getAll(URI.file('/yes')); - assert.strictEqual(0, result.length); - - result = cache.getAll(URI.file('/some/other.txt')); - assert.strictEqual(0, result.length); - - result = cache.getAll(URI.file('/some/other.html')); - assert.strictEqual(1, result.length); - - cache.remove(URI.file('')); - - result = cache.getAll(); - assert.strictEqual(3, result.length); - - cache.remove(URI.file('/test.html')); - - result = cache.getAll(); - assert.strictEqual(2, result.length); - - cache.clear(); - result = cache.getAll(); - assert.strictEqual(0, result.length); - }); -}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts b/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts new file mode 100644 index 00000000000..3a81b80795d --- /dev/null +++ b/src/vs/workbench/parts/files/test/browser/textFileEditorModelManager.test.ts @@ -0,0 +1,260 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import URI from 'vs/base/common/uri'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; +import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager'; +import {EditorModel} from 'vs/workbench/common/editor'; +import {join, basename} from 'vs/base/common/paths'; +import {workbenchInstantiationService, TestEditorGroupService} from 'vs/test/utils/servicesTestUtils'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; +import {IEventService} from 'vs/platform/event/common/event'; +import {LocalFileChangeEvent} from 'vs/workbench/parts/files/common/files'; +import {FileChangesEvent, EventType as CommonFileEventType, FileChangeType} from 'vs/platform/files/common/files'; + +class ServiceAccessor { + constructor( + @IEditorGroupService public editorGroupService: TestEditorGroupService, + @IEventService public eventService: IEventService + ) { + } +} + +function toResource(path: string): URI { + return URI.file(join('C:\\', path)); +} + +function toStat(resource: URI) { + return { + resource, + isDirectory: false, + hasChildren: false, + name: basename(resource.fsPath), + mtime: Date.now(), + etag: 'etag', + mime: 'text/plain' + }; +} + +suite('Files - TextFileEditorModelManager', () => { + + let instantiationService: TestInstantiationService; + let accessor: ServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + }); + + test('add, remove, clear, get, getAll', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const model1 = new EditorModel(); + const model2 = new EditorModel(); + const model3 = new EditorModel(); + + manager.add(URI.file('/test.html'), model1); + manager.add(URI.file('/some/other.html'), model2); + manager.add(URI.file('/some/this.txt'), model3); + + assert(!manager.get(URI.file('foo'))); + assert.strictEqual(manager.get(URI.file('/test.html')), model1); + + let result = manager.getAll(); + assert.strictEqual(3, result.length); + + result = manager.getAll(URI.file('/yes')); + assert.strictEqual(0, result.length); + + result = manager.getAll(URI.file('/some/other.txt')); + assert.strictEqual(0, result.length); + + result = manager.getAll(URI.file('/some/other.html')); + assert.strictEqual(1, result.length); + + manager.remove(URI.file('')); + + result = manager.getAll(); + assert.strictEqual(3, result.length); + + manager.remove(URI.file('/test.html')); + + result = manager.getAll(); + assert.strictEqual(2, result.length); + + manager.clear(); + result = manager.getAll(); + assert.strictEqual(0, result.length); + }); + + test('loadOrCreate', function (done) { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + const resource = URI.file('/test.html'); + const encoding = 'utf8'; + + manager.loadOrCreate(resource, encoding, true).then(model => { + assert.ok(model); + assert.equal(model.getEncoding(), encoding); + assert.equal(manager.get(resource), model); + + return manager.loadOrCreate(resource, encoding).then(model2 => { + assert.equal(model2, model); + + model.dispose(); + + return manager.loadOrCreate(resource, encoding).then(model3 => { + assert.notEqual(model3, model2); + assert.equal(manager.get(resource), model3); + + done(); + }); + }); + }); + }); + + test('removed from cache when model disposed', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const model1 = new EditorModel(); + const model2 = new EditorModel(); + const model3 = new EditorModel(); + + manager.add(URI.file('/test.html'), model1); + manager.add(URI.file('/some/other.html'), model2); + manager.add(URI.file('/some/this.txt'), model3); + + assert.strictEqual(manager.get(URI.file('/test.html')), model1); + + model1.dispose(); + assert(!manager.get(URI.file('/test.html'))); + }); + + test('disposes model when not open anymore', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + const input = instantiationService.createInstance(FileEditorInput, resource, 'text/plain', void 0); + + const stacks = accessor.editorGroupService.getStacksModel(); + const group = stacks.openGroup('group', true); + group.openEditor(input); + + accessor.editorGroupService.fireChange(); + + assert.ok(!model.isDisposed()); + + group.closeEditor(input); + accessor.editorGroupService.fireChange(); + assert.ok(model.isDisposed()); + + manager.dispose(); + }); + + test('local file changes dispose model - delete', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + assert.ok(!model.isDisposed()); + + // delete event (local) + accessor.eventService.emit('files.internal:fileChanged', new LocalFileChangeEvent(toStat(resource))); + + assert.ok(model.isDisposed()); + + manager.dispose(); + }); + + test('local file changes dispose model - move', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + assert.ok(!model.isDisposed()); + + // move event (local) + accessor.eventService.emit('files.internal:fileChanged', new LocalFileChangeEvent(toStat(resource), toStat(toResource('/path/index_moved.txt')))); + + assert.ok(model.isDisposed()); + + manager.dispose(); + }); + + test('file event delete dispose model', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + assert.ok(!model.isDisposed()); + + // delete event (watcher) + accessor.eventService.emit(CommonFileEventType.FILE_CHANGES, new FileChangesEvent([{ resource, type: FileChangeType.DELETED }])); + + assert.ok(model.isDisposed()); + + manager.dispose(); + }); + + test('file change event dispose model if happening > 2 second after last save', function () { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + assert.ok(!model.isDisposed()); + + // change event (watcher) + accessor.eventService.emit(CommonFileEventType.FILE_CHANGES, new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); + + assert.ok(model.isDisposed()); + + manager.dispose(); + }); + + test('file change event does NOT dispose model if happening < 2 second after last save', function (done) { + const manager: TextFileEditorModelManager = instantiationService.createInstance(TextFileEditorModelManager); + + const resource = toResource('/path/index.txt'); + + const model: TextFileEditorModel = instantiationService.createInstance(TextFileEditorModel, resource, 'utf8'); + manager.add(resource, model); + + assert.ok(!model.isDisposed()); + + model.load().then(resolved => { + model.textEditorModel.setValue('changed'); + model.save().then(() => { + + // change event (watcher) + accessor.eventService.emit(CommonFileEventType.FILE_CHANGES, new FileChangesEvent([{ resource, type: FileChangeType.UPDATED }])); + + assert.ok(!model.isDisposed()); + + manager.dispose(); + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/files/test/browser/textFileService.test.ts b/src/vs/workbench/parts/files/test/browser/textFileService.test.ts new file mode 100644 index 00000000000..7d4fc142162 --- /dev/null +++ b/src/vs/workbench/parts/files/test/browser/textFileService.test.ts @@ -0,0 +1,222 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import * as assert from 'assert'; +import URI from 'vs/base/common/uri'; +import paths = require('vs/base/common/paths'); +import {ILifecycleService, ShutdownEvent} from 'vs/platform/lifecycle/common/lifecycle'; +import {workbenchInstantiationService, TestLifecycleService, TestTextFileService} from 'vs/test/utils/servicesTestUtils'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; +import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; +import {ITextFileService} from 'vs/workbench/parts/files/common/files'; +import {ConfirmResult} from 'vs/workbench/common/editor'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; +import {TextFileEditorModelManager} from 'vs/workbench/parts/files/common/editors/textFileEditorModelManager'; + +function toResource(path) { + return URI.file(paths.join('C:\\', path)); +} + +class ServiceAccessor { + constructor( + @ILifecycleService public lifecycleService: TestLifecycleService, + @ITextFileService public textFileService: TestTextFileService, + @IUntitledEditorService public untitledEditorService: IUntitledEditorService + ) { + } +} + +class ShutdownEventImpl implements ShutdownEvent { + + public value: boolean | TPromise; + + veto(value: boolean | TPromise): void { + this.value = value; + } +} + +suite('Files - TextFileService', () => { + + let instantiationService: TestInstantiationService; + let model: TextFileEditorModel; + let accessor: ServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + model = instantiationService.createInstance(TextFileEditorModel, toResource('/path/file.txt'), 'utf8'); + (accessor.textFileService.models).add(model.getResource(), model); + }); + + teardown(() => { + model.dispose(); + (accessor.textFileService.models).clear(); + accessor.untitledEditorService.revertAll(); + }); + + test('confirm onWillShutdown - no veto', function () { + const event = new ShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + assert.ok(!event.value); + }); + + test('confirm onWillShutdown - veto if user cancels', function (done) { + const service = accessor.textFileService; + service.setConfirmResult(ConfirmResult.CANCEL); + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.equal(service.getDirty().length, 1); + + const event = new ShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + assert.ok(event.value); + + done(); + }); + }); + + test('confirm onWillShutdown - no veto if user does not want to save', function (done) { + const service = accessor.textFileService; + service.setConfirmResult(ConfirmResult.DONT_SAVE); + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.equal(service.getDirty().length, 1); + + const event = new ShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + assert.ok(!event.value); + + done(); + }); + }); + + test('confirm onWillShutdown - save', function (done) { + const service = accessor.textFileService; + service.setConfirmResult(ConfirmResult.SAVE); + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.equal(service.getDirty().length, 1); + + const event = new ShutdownEventImpl(); + accessor.lifecycleService.fireWillShutdown(event); + + return (>event.value).then(veto => { + assert.ok(!veto); + assert.ok(!model.isDirty()); + + done(); + }); + }); + }); + + test('isDirty/getDirty - files and untitled', function (done) { + const service = accessor.textFileService; + model.load().then(() => { + assert.ok(!service.isDirty(model.getResource())); + model.textEditorModel.setValue('foo'); + + assert.ok(service.isDirty(model.getResource())); + assert.equal(service.getDirty().length, 1); + assert.equal(service.getDirty([model.getResource()])[0].toString(), model.getResource().toString()); + + const untitled = accessor.untitledEditorService.createOrGet(); + return untitled.resolve().then((model: UntitledEditorModel) => { + assert.ok(!service.isDirty(untitled.getResource())); + assert.equal(service.getDirty().length, 1); + model.setValue('changed'); + + assert.ok(service.isDirty(untitled.getResource())); + assert.equal(service.getDirty().length, 2); + assert.equal(service.getDirty([untitled.getResource()])[0].toString(), untitled.getResource().toString()); + + done(); + }); + }); + }); + + test('save - file', function (done) { + const service = accessor.textFileService; + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.ok(service.isDirty(model.getResource())); + + return service.save(model.getResource()).then(res => { + assert.ok(res); + assert.ok(!service.isDirty(model.getResource())); + + done(); + }); + }); + }); + + test('saveAll - file', function (done) { + const service = accessor.textFileService; + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.ok(service.isDirty(model.getResource())); + + return service.saveAll([model.getResource()]).then(res => { + assert.ok(res); + assert.ok(!service.isDirty(model.getResource())); + assert.equal(res.results.length, 1); + assert.equal(res.results[0].source.toString(), model.getResource().toString()); + + done(); + }); + }); + }); + + test('saveAs - file', function (done) { + const service = accessor.textFileService; + service.setPromptPath(model.getResource().fsPath); + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.ok(service.isDirty(model.getResource())); + + return service.saveAs(model.getResource()).then(res => { + assert.equal(res.toString(), model.getResource().toString()); + assert.ok(!service.isDirty(model.getResource())); + + done(); + }); + }); + }); + + test('revert - file', function (done) { + const service = accessor.textFileService; + service.setPromptPath(model.getResource().fsPath); + + model.load().then(() => { + model.textEditorModel.setValue('foo'); + + assert.ok(service.isDirty(model.getResource())); + + return service.revert(model.getResource()).then(res => { + assert.ok(res); + assert.ok(!service.isDirty(model.getResource())); + + done(); + }); + }); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/parts/git/browser/gitEditorContributions.ts b/src/vs/workbench/parts/git/browser/gitEditorContributions.ts index 8f96515ffc8..5dbd1fb6dba 100644 --- a/src/vs/workbench/parts/git/browser/gitEditorContributions.ts +++ b/src/vs/workbench/parts/git/browser/gitEditorContributions.ts @@ -5,6 +5,7 @@ 'use strict'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { IModel, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane, IEditorContribution, TrackedRangeStickiness } from 'vs/editor/common/editorCommon'; import { IGitService, ModelEvents, StatusType } from 'vs/workbench/parts/git/common/git'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; @@ -70,6 +71,7 @@ class MergeDecoratorBoundToModel extends Disposable { } } +@editorContribution export class MergeDecorator extends Disposable implements IEditorContribution { static ID = 'vs.git.editor.merge.decorator'; diff --git a/src/vs/workbench/parts/git/browser/gitServices.ts b/src/vs/workbench/parts/git/browser/gitServices.ts index 0dd68257327..3b8ab655e2e 100644 --- a/src/vs/workbench/parts/git/browser/gitServices.ts +++ b/src/vs/workbench/parts/git/browser/gitServices.ts @@ -479,15 +479,15 @@ export class GitService extends EventEmitter messageService.show(severity.Warning, { message: localize('updateGit', "You seem to have git {0} installed. Code works best with git >=2.0.0.", version), actions: [ - CloseAction, + new Action('downloadLatest', localize('download', "Download"), '', true, () => { + shell.openExternal('https://git-scm.com/'); + return null; + }), new Action('neverShowAgain', localize('neverShowAgain', "Don't show again"), null, true, () => { storageService.store(IgnoreOldGitStorageKey, true, StorageScope.GLOBAL); return null; }), - new Action('downloadLatest', localize('download', "Download"), '', true, () => { - shell.openExternal('https://git-scm.com/'); - return null; - }) + CloseAction ] }); } @@ -792,7 +792,7 @@ export class GitService extends EventEmitter error = createError( localize('checkNativeConsole', "There was an issue running a git operation. Please review the output or use a console to check the state of your repository."), - { actions: [showOutputAction, cancelAction] } + { actions: [cancelAction, showOutputAction] } ); (error).gitErrorCode = gitErrorCode; diff --git a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts index 9e5ad93a71c..bf04acda152 100644 --- a/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts +++ b/src/vs/workbench/parts/git/browser/gitWorkbenchContributions.ts @@ -24,11 +24,10 @@ import wbar = require('vs/workbench/common/actionRegistry'); import gitoutput = require('vs/workbench/parts/git/browser/gitOutput'); import output = require('vs/workbench/parts/output/common/output'); import {SyncActionDescriptor} from 'vs/platform/actions/common/actions'; -import {EditorBrowserRegistry} from 'vs/editor/browser/editorBrowserExtensions'; import confregistry = require('vs/platform/configuration/common/configurationRegistry'); import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import quickopen = require('vs/workbench/browser/quickopen'); -import editorcontrib = require('vs/workbench/parts/git/browser/gitEditorContributions'); +import 'vs/workbench/parts/git/browser/gitEditorContributions'; import {IActivityService, ProgressBadge, NumberBadge} from 'vs/workbench/services/activity/common/activityService'; import {IEventService} from 'vs/platform/event/common/event'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; @@ -498,9 +497,6 @@ export function registerContributions(): void { nls.localize('view', "View") ); - // Register MergeDecorator - EditorBrowserRegistry.registerEditorContribution(editorcontrib.MergeDecorator); - // Register StatusUpdater (platform.Registry.as(ext.Extensions.Workbench)).registerWorkbenchContribution( StatusUpdater diff --git a/src/vs/workbench/parts/git/browser/views/changes/changesView.css b/src/vs/workbench/parts/git/browser/views/changes/changesView.css index 4fe3736c87e..bd5892f7cc4 100644 --- a/src/vs/workbench/parts/git/browser/views/changes/changesView.css +++ b/src/vs/workbench/parts/git/browser/views/changes/changesView.css @@ -57,9 +57,8 @@ display: none; } -.git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content:after, .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content:before { - border: none; + background: none; } .git-viewlet > .changes-view > .status-view > .monaco-tree .monaco-tree-row .status-group { diff --git a/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts b/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts index b8394b93303..6131e2e0a0b 100644 --- a/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts +++ b/src/vs/workbench/parts/git/browser/views/empty/emptyView.ts @@ -15,14 +15,12 @@ import WinJS = require('vs/base/common/winjs.base'); import Builder = require('vs/base/browser/builder'); import Actions = require('vs/base/common/actions'); import InputBox = require('vs/base/browser/ui/inputbox/inputBox'); -import git = require('vs/workbench/parts/git/common/git'); import GitView = require('vs/workbench/parts/git/browser/views/view'); import GitActions = require('vs/workbench/parts/git/browser/gitActions'); import {IFileService} from 'vs/platform/files/common/files'; import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; import {IMessageService} from 'vs/platform/message/common/message'; - -import IGitService = git.IGitService; +import { IGitService } from 'vs/workbench/parts/git/common/git'; var $ = Builder.$; @@ -102,13 +100,8 @@ export class EmptyView extends EventEmitter.EventEmitter implements GitView.IVie DOM.EventHelper.stop(e); this.disableUI(); - - this.actionRunner.run(this.initAction).done(() => { - this.enableUI(); - }); + this.actionRunner.run(this.initAction).done(() => this.enableUI()); }); - - this.toDispose.push(this.gitService.addListener2(git.ServiceEvents.OPERATION, () => this.onGitOperation())); } private disableUI(): void { @@ -124,7 +117,7 @@ export class EmptyView extends EventEmitter.EventEmitter implements GitView.IVie } private enableUI(): void { - if (this.gitService.getRunningOperations().length > 0){ + if (this.gitService.getRunningOperations().length > 0) { return; } @@ -164,14 +157,6 @@ export class EmptyView extends EventEmitter.EventEmitter implements GitView.IVie // Events - private onGitOperation(): void { - if (this.gitService.getRunningOperations().length > 0) { - this.disableUI(); - } else { - this.enableUI(); - } - } - public dispose(): void { if (this.$el) { this.$el.dispose(); diff --git a/src/vs/workbench/parts/git/common/gitIpc.ts b/src/vs/workbench/parts/git/common/gitIpc.ts index 1d22f08aafa..625716cb033 100644 --- a/src/vs/workbench/parts/git/common/gitIpc.ts +++ b/src/vs/workbench/parts/git/common/gitIpc.ts @@ -232,15 +232,15 @@ export class GitChannelClient implements IRawGitService { } export interface IAskpassChannel extends IChannel { - call(command: 'askpass', id: string, host: string, gitCommand: string): TPromise; - call(command: string, ...args: any[]): TPromise; + call(command: 'askpass', args: [string, string, string]): TPromise; + call(command: string, args: any[]): TPromise; } export class AskpassChannel implements IAskpassChannel { constructor(private service: IAskpassService) { } - call(command: string, ...args: any[]): TPromise { + call(command: string, args: [string, string, string]): TPromise { switch (command) { case 'askpass': return this.service.askpass(args[0], args[1], args[2]); } @@ -252,6 +252,6 @@ export class AskpassChannelClient implements IAskpassService { constructor(private channel: IAskpassChannel) { } askpass(id: string, host: string, command: string): TPromise { - return this.channel.call('askpass', id, host, command); + return this.channel.call('askpass', [id, host, command]); } } \ No newline at end of file diff --git a/src/vs/workbench/parts/lib/node/configVariables.ts b/src/vs/workbench/parts/lib/node/configVariables.ts index 2891d7cf7f4..08e7269815d 100644 --- a/src/vs/workbench/parts/lib/node/configVariables.ts +++ b/src/vs/workbench/parts/lib/node/configVariables.ts @@ -25,6 +25,7 @@ export class ConfigVariables extends SystemVariables { } protected resolveString(value: string): string { + const originalValue = value; value = super.resolveString(value); let regexp = /\$\{config\.(.*?)\}/g; @@ -32,9 +33,9 @@ export class ConfigVariables extends SystemVariables { let config = this.configurationService.getConfiguration(); let newValue = new Function('_', 'try {return _.' + name + ';} catch (ex) { return "";}')(config); if (Types.isString(newValue)) { - return newValue; - } - else { + // Prevent infinite recursion and also support nested references (or tokens) + return newValue === originalValue ? '' : this.resolveString(newValue); + } else { return this.resolve(newValue) + ''; } }); diff --git a/src/vs/workbench/parts/lib/test/node/configVariables.test.ts b/src/vs/workbench/parts/lib/test/node/configVariables.test.ts index 7fc3fdc30a2..6f43b81d8bc 100644 --- a/src/vs/workbench/parts/lib/test/node/configVariables.test.ts +++ b/src/vs/workbench/parts/lib/test/node/configVariables.test.ts @@ -46,6 +46,46 @@ suite('ConfigVariables tests', () => { let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, TestEnvironmentService, URI.parse('file:///VSCode/workspaceLocation')); assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); + test('ConfigVariables: substitute nested configs', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo ${workspaceRoot} ${config.terminal.integrated.fontFamily}' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, TestEnvironmentService, URI.parse('file:///VSCode/workspaceLocation')); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo \\VSCode\\workspaceLocation bar bar xyz'); + } else { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo /VSCode/workspaceLocation bar bar xyz'); + } + }); + test('ConfigVariables: substitute accidental self referenced configs', () => { + let configurationService: IConfigurationService; + configurationService = new MockConfigurationService({ + editor: { + fontFamily: 'foo ${workspaceRoot} ${config.terminal.integrated.fontFamily} ${config.editor.fontFamily}' + }, + terminal: { + integrated: { + fontFamily: 'bar' + } + } + }); + + let systemVariables: ConfigVariables = new ConfigVariables(configurationService, null, null, TestEnvironmentService, URI.parse('file:///VSCode/workspaceLocation')); + if (Platform.isWindows) { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo \\VSCode\\workspaceLocation bar bar xyz'); + } else { + assert.strictEqual(systemVariables.resolve('abc ${config.editor.fontFamily} ${config.terminal.integrated.fontFamily} xyz'), 'abc foo /VSCode/workspaceLocation bar bar xyz'); + } + }); test('SystemVariables: substitute one env variable', () => { let configurationService: IConfigurationService; configurationService = new MockConfigurationService({ diff --git a/src/vs/workbench/parts/markers/browser/media/markers.css b/src/vs/workbench/parts/markers/browser/media/markers.css index 3a1288114de..491e4ac3590 100644 --- a/src/vs/workbench/parts/markers/browser/media/markers.css +++ b/src/vs/workbench/parts/markers/browser/media/markers.css @@ -4,7 +4,9 @@ *--------------------------------------------------------------------------------------------*/ .monaco-action-bar .action-item.markers-panel-action-filter { - width: 400px; + max-width: 400px; + min-width: 100px; + flex: 1; cursor: default; margin: 4px 10px 0 0; } diff --git a/src/vs/workbench/parts/nps/electron-browser/nps.contribution.ts b/src/vs/workbench/parts/nps/electron-browser/nps.contribution.ts index 60b54f1b647..a095697fa9b 100644 --- a/src/vs/workbench/parts/nps/electron-browser/nps.contribution.ts +++ b/src/vs/workbench/parts/nps/electron-browser/nps.contribution.ts @@ -85,10 +85,9 @@ class NPSContribution implements IWorkbenchContribution { return TPromise.as(null); }); - const actions = [takeSurveyAction, remindMeLaterAction, neverAgainAction]; + const actions = [neverAgainAction, remindMeLaterAction, takeSurveyAction ]; - // TODO@Ben need this setTimeout due to #9769 - setTimeout(() => messageService.show(Severity.Info, { message, actions })); + messageService.show(Severity.Info, { message, actions }); } getId(): string { diff --git a/src/vs/workbench/parts/output/browser/output.contribution.ts b/src/vs/workbench/parts/output/browser/output.contribution.ts index 39afff72d60..02801061fd5 100644 --- a/src/vs/workbench/parts/output/browser/output.contribution.ts +++ b/src/vs/workbench/parts/output/browser/output.contribution.ts @@ -14,7 +14,7 @@ import {KeybindingsRegistry} from 'vs/platform/keybinding/common/keybindingsRegi import {registerSingleton} from 'vs/platform/instantiation/common/extensions'; import {IWorkbenchActionRegistry, Extensions as ActionExtensions} from 'vs/workbench/common/actionRegistry'; import {OutputService} from 'vs/workbench/parts/output/browser/outputServices'; -import {ToggleOutputAction} from 'vs/workbench/parts/output/browser/outputActions'; +import {ToggleOutputAction, ClearOutputAction} from 'vs/workbench/parts/output/browser/outputActions'; import {OUTPUT_MIME, OUTPUT_MODE_ID, OUTPUT_PANEL_ID, IOutputService} from 'vs/workbench/parts/output/common/output'; import panel = require('vs/workbench/browser/panel'); import {EditorContextKeys} from 'vs/editor/common/editorCommon'; @@ -25,13 +25,11 @@ import {ContextKeyExpr} from 'vs/platform/contextkey/common/contextkey'; registerSingleton(IOutputService, OutputService); // Register Output Mode -ModesRegistry.registerCompatMode({ +ModesRegistry.registerLanguage({ id: OUTPUT_MODE_ID, extensions: [], aliases: [null], - mimetypes: [OUTPUT_MIME], - moduleId: 'vs/workbench/parts/output/common/outputMode', - ctorName: 'OutputMode' + mimetypes: [OUTPUT_MIME] }); // Register Output Panel @@ -52,6 +50,8 @@ actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleOutputActi } }), 'View: Toggle Output', nls.localize('viewCategory', "View")); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL), + 'View: Clear Output', nls.localize('viewCategory', "View")); interface IActionDescriptor { id: string; diff --git a/src/vs/workbench/parts/output/browser/outputActions.ts b/src/vs/workbench/parts/output/browser/outputActions.ts index 7e4ea033b9d..e78b22885f5 100644 --- a/src/vs/workbench/parts/output/browser/outputActions.ts +++ b/src/vs/workbench/parts/output/browser/outputActions.ts @@ -41,11 +41,15 @@ export class ToggleOutputAction extends Action { export class ClearOutputAction extends Action { + public static ID = 'workbench.output.action.clearOutput'; + public static LABEL = nls.localize('clearOutput', "Clear Output"); + constructor( + id: string, label: string, @IOutputService private outputService: IOutputService, @IPanelService private panelService: IPanelService ) { - super('workbench.output.action.clearOutput', nls.localize('clearOutput', "Clear Output"), 'output-action clear-output'); + super(id, label, 'output-action clear-output'); } public run(): TPromise { diff --git a/src/vs/workbench/parts/output/browser/outputEditorInput.ts b/src/vs/workbench/parts/output/browser/outputEditorInput.ts index 4fc14281c6a..c8be13ab129 100644 --- a/src/vs/workbench/parts/output/browser/outputEditorInput.ts +++ b/src/vs/workbench/parts/output/browser/outputEditorInput.ts @@ -77,7 +77,7 @@ export class OutputEditorInput extends StringEditorInput { this.bufferedOutput = ''; const panel = this.panelService.getActivePanel(); - (panel).revealLastLine(); + (panel).revealLastLine(true); } private onOutputReceived(e: IOutputEvent): void { diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index 599d6f3d63b..04b47236682 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -24,6 +24,7 @@ import {SwitchOutputAction, SwitchOutputActionItem, ClearOutputAction} from 'vs/ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IThemeService} from 'vs/workbench/services/themes/common/themeService'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; export class OutputPanel extends StringEditor { @@ -40,10 +41,11 @@ export class OutputPanel extends StringEditor { @IEventService eventService: IEventService, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IThemeService themeService: IThemeService, - @IOutputService private outputService: IOutputService + @IOutputService private outputService: IOutputService, + @IUntitledEditorService untitledEditorService: IUntitledEditorService ) { super(telemetryService, instantiationService, contextService, storageService, - messageService, configurationService, eventService, editorService, themeService); + messageService, configurationService, eventService, editorService, themeService, untitledEditorService); this.toDispose = []; } @@ -55,7 +57,7 @@ export class OutputPanel extends StringEditor { if (!this.actions) { this.actions = [ this.instantiationService.createInstance(SwitchOutputAction), - this.instantiationService.createInstance(ClearOutputAction) + this.instantiationService.createInstance(ClearOutputAction, ClearOutputAction.ID, ClearOutputAction.LABEL) ]; this.actions.forEach(a => { diff --git a/src/vs/workbench/parts/output/browser/outputServices.ts b/src/vs/workbench/parts/output/browser/outputServices.ts index 2bc297f9335..370d2d82212 100644 --- a/src/vs/workbench/parts/output/browser/outputServices.ts +++ b/src/vs/workbench/parts/output/browser/outputServices.ts @@ -17,6 +17,9 @@ import {IOutputEvent, IOutputChannel, IOutputService, Extensions, OUTPUT_PANEL_I import {OutputEditorInput} from 'vs/workbench/parts/output/browser/outputEditorInput'; import {OutputPanel} from 'vs/workbench/parts/output/browser/outputPanel'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {OutputLinkProvider} from 'vs/workbench/parts/output/common/outputLinkProvider'; const OUTPUT_ACTIVE_CHANNEL_KEY = 'output.activechannel'; @@ -31,12 +34,16 @@ export class OutputService implements IOutputService { private _onOutputChannel: Emitter; private _onActiveOutputChannel: Emitter; + private _outputLinkDetector: OutputLinkProvider; + constructor( @IStorageService private storageService: IStorageService, @IInstantiationService private instantiationService: IInstantiationService, @IEventService private eventService: IEventService, @ILifecycleService private lifecycleService: ILifecycleService, - @IPanelService private panelService: IPanelService + @IPanelService private panelService: IPanelService, + @IWorkspaceContextService contextService:IWorkspaceContextService, + @IModelService modelService: IModelService ) { this._onOutput = new Emitter(); this._onOutputChannel = new Emitter(); @@ -46,6 +53,8 @@ export class OutputService implements IOutputService { const channels = Registry.as(Extensions.OutputChannels).getChannels(); this.activeChannelId = this.storageService.get(OUTPUT_ACTIVE_CHANNEL_KEY, StorageScope.WORKSPACE, channels && channels.length > 0 ? channels[0].id : null); + + this._outputLinkDetector = new OutputLinkProvider(contextService, modelService); } public get onOutput(): Event { diff --git a/src/vs/workbench/parts/output/common/outputWorker.ts b/src/vs/workbench/parts/output/common/outputLinkComputer.ts similarity index 79% rename from src/vs/workbench/parts/output/common/outputWorker.ts rename to src/vs/workbench/parts/output/common/outputLinkComputer.ts index 87e1d6bdff5..c427d45511a 100644 --- a/src/vs/workbench/parts/output/common/outputWorker.ts +++ b/src/vs/workbench/parts/output/common/outputLinkComputer.ts @@ -4,63 +4,66 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import {IMirrorModel, IWorkerContext} from 'vs/editor/common/services/editorSimpleWorker'; +import {ILink} from 'vs/editor/common/modes'; import {TPromise} from 'vs/base/common/winjs.base'; -import {IResourceService} from 'vs/editor/common/services/resourceService'; import URI from 'vs/base/common/uri'; +import paths = require('vs/base/common/paths'); import strings = require('vs/base/common/strings'); import arrays = require('vs/base/common/arrays'); -import paths = require('vs/base/common/paths'); -import {ILink} from 'vs/editor/common/modes'; import {Range} from 'vs/editor/common/core/range'; +export interface ICreateData { + workspaceResourceUri: string; +} + export interface IResourceCreator { toResource: (workspaceRelativePath: string) => URI; } -/** - * A base class of text editor worker that helps with detecting links in the text that point to files in the workspace. - */ -export class OutputWorker { +export class OutputLinkComputer { + + private _ctx:IWorkerContext; + private _patterns: RegExp[]; private _workspaceResource: URI; - private patterns: RegExp[]; - private resourceService:IResourceService; - private _modeId: string; - constructor( - modeId: string, - @IResourceService resourceService: IResourceService - ) { - this._modeId = modeId; - this.resourceService = resourceService; - this._workspaceResource = null; - this.patterns = []; + constructor(ctx:IWorkerContext, createData:ICreateData) { + this._ctx = ctx; + this._workspaceResource = URI.parse(createData.workspaceResourceUri); + this._patterns = OutputLinkComputer.createPatterns(this._workspaceResource); } - public configure(workspaceResource: URI): TPromise { - this._workspaceResource = workspaceResource; - this.patterns = OutputWorker.createPatterns(this._workspaceResource); - return TPromise.as(void 0); + private _getModel(uri:string): IMirrorModel { + let models = this._ctx.getMirrorModels(); + for (let i = 0; i < models.length; i++) { + let model = models[i]; + if (model.uri.toString() === uri) { + return model; + } + } + return null; } - public provideLinks(resource: URI): TPromise { - let links: ILink[] = []; - if (!this.patterns.length) { - return TPromise.as(links); + public computeLinks(uri:string): TPromise { + let model = this._getModel(uri); + if (!model) { + return; } - let model = this.resourceService.get(resource); + let links: ILink[] = []; let resourceCreator: IResourceCreator = { toResource: (workspaceRelativePath: string): URI => { - if (typeof workspaceRelativePath === 'string' && this._workspaceResource) { + if (typeof workspaceRelativePath === 'string') { return URI.file(paths.join(this._workspaceResource.fsPath, workspaceRelativePath)); } return null; } }; - for (let i = 1, lineCount = model.getLineCount(); i <= lineCount; i++) { - links.push(...OutputWorker.detectLinks(model.getLineContent(i), i, this.patterns, resourceCreator)); + let lines = model.getValue().split(/\r\n|\r|\n/); + for (let i = 0, len = lines.length; i < len; i++) { + links.push(...OutputLinkComputer.detectLinks(lines[i], i + 1, this._patterns, resourceCreator)); } return TPromise.as(links); @@ -156,4 +159,8 @@ export class OutputWorker { return links; } -} \ No newline at end of file +} + +export function create(ctx:IWorkerContext, createData:ICreateData): OutputLinkComputer { + return new OutputLinkComputer(ctx, createData); +} diff --git a/src/vs/workbench/parts/output/common/outputLinkProvider.ts b/src/vs/workbench/parts/output/common/outputLinkProvider.ts new file mode 100644 index 00000000000..a15437c5bfe --- /dev/null +++ b/src/vs/workbench/parts/output/common/outputLinkProvider.ts @@ -0,0 +1,76 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import {TPromise} from 'vs/base/common/winjs.base'; +import URI from 'vs/base/common/uri'; +import {RunOnceScheduler, wireCancellationToken} from 'vs/base/common/async'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {LinkProviderRegistry, ILink} from 'vs/editor/common/modes'; +import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {OUTPUT_MODE_ID} from 'vs/workbench/parts/output/common/output'; +import {MonacoWebWorker, createWebWorker} from 'vs/editor/common/services/webWorker'; +import {ICreateData, OutputLinkComputer} from 'vs/workbench/parts/output/common/outputLinkComputer'; + +export class OutputLinkProvider { + + private static DISPOSE_WORKER_TIME = 3 * 60 * 1000; // dispose worker after 3 minutes of inactivity + + private _modelService: IModelService; + private _workspaceResource: URI; + + private _worker: MonacoWebWorker; + private _disposeWorker: RunOnceScheduler; + + constructor( + contextService:IWorkspaceContextService, + modelService: IModelService + ) { + let workspace = contextService.getWorkspace(); + + // Does not do anything unless there is a workspace... + if (workspace) { + this._modelService = modelService; + + this._workspaceResource = workspace.resource; + + LinkProviderRegistry.register(OUTPUT_MODE_ID, { + provideLinks: (model, token): Thenable => { + return wireCancellationToken(token, this._provideLinks(model.uri)); + } + }); + + this._worker = null; + this._disposeWorker = new RunOnceScheduler(() => { + if (this._worker) { + this._worker.dispose(); + this._worker = null; + } + }, OutputLinkProvider.DISPOSE_WORKER_TIME); + } + } + + private _getOrCreateWorker(): MonacoWebWorker { + this._disposeWorker.schedule(); + if (!this._worker) { + let createData:ICreateData = { + workspaceResourceUri: this._workspaceResource.toString() + }; + this._worker = createWebWorker(this._modelService, { + moduleId: 'vs/workbench/parts/output/common/outputLinkComputer', + createData: createData, + label: 'outputLinkComputer' + }); + } + return this._worker; + } + + private _provideLinks(modelUri:URI): TPromise { + return this._getOrCreateWorker().withSyncedResources([modelUri]).then((linkComputer) => { + return linkComputer.computeLinks(modelUri.toString()); + }); + } +} diff --git a/src/vs/workbench/parts/output/common/outputMode.ts b/src/vs/workbench/parts/output/common/outputMode.ts deleted file mode 100644 index 8a5c6668319..00000000000 --- a/src/vs/workbench/parts/output/common/outputMode.ts +++ /dev/null @@ -1,58 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -'use strict'; - -import {IModeDescriptor} from 'vs/editor/common/modes'; -import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation'; -import {OutputWorker} from 'vs/workbench/parts/output/common/outputWorker'; -import {TPromise} from 'vs/base/common/winjs.base'; -import URI from 'vs/base/common/uri'; -import * as modes from 'vs/editor/common/modes'; -import {CompatMode, ModeWorkerManager} from 'vs/editor/common/modes/abstractMode'; -import {wireCancellationToken} from 'vs/base/common/async'; -import {ICompatWorkerService, CompatWorkerAttr} from 'vs/editor/common/services/compatWorkerService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; - -export class OutputMode extends CompatMode { - - private _modeWorkerManager: ModeWorkerManager; - - constructor( - descriptor: IModeDescriptor, - @IInstantiationService instantiationService: IInstantiationService, - @ICompatWorkerService compatWorkerService: ICompatWorkerService, - @IWorkspaceContextService contextService:IWorkspaceContextService - ) { - super(descriptor.id, compatWorkerService); - this._modeWorkerManager = new ModeWorkerManager(descriptor, 'vs/workbench/parts/output/common/outputWorker', 'OutputWorker', null, instantiationService); - - modes.LinkProviderRegistry.register(this.getId(), { - provideLinks: (model, token): Thenable => { - return wireCancellationToken(token, this._provideLinks(model.uri)); - } - }); - - if (compatWorkerService.isInMainThread) { - let workspace = contextService.getWorkspace(); - if (workspace) { - this._configure(workspace.resource); - } - } - } - - private _worker(runner: (worker: OutputWorker) => TPromise): TPromise { - return this._modeWorkerManager.worker(runner); - } - - static $_configure = CompatWorkerAttr(OutputMode, OutputMode.prototype._configure); - private _configure(workspaceResource: URI): TPromise { - return this._worker((w) => w.configure(workspaceResource)); - } - - static $_provideLinks = CompatWorkerAttr(OutputMode, OutputMode.prototype._provideLinks); - private _provideLinks(resource: URI): TPromise { - return this._worker((w) => w.provideLinks(resource)); - } -} \ No newline at end of file diff --git a/src/vs/workbench/parts/output/test/outputWorker.test.ts b/src/vs/workbench/parts/output/test/outputLinkProvider.test.ts similarity index 80% rename from src/vs/workbench/parts/output/test/outputWorker.test.ts rename to src/vs/workbench/parts/output/test/outputLinkProvider.test.ts index 364c04daae8..a4ba217bae5 100644 --- a/src/vs/workbench/parts/output/test/outputWorker.test.ts +++ b/src/vs/workbench/parts/output/test/outputLinkProvider.test.ts @@ -8,7 +8,7 @@ import * as assert from 'assert'; import URI from 'vs/base/common/uri'; import {isMacintosh, isLinux} from 'vs/base/common/platform'; -import {OutputWorker} from 'vs/workbench/parts/output/common/outputWorker'; +import {OutputLinkComputer} from 'vs/workbench/parts/output/common/outputLinkComputer'; import {TestContextService} from 'vs/test/utils/servicesTestUtils'; function toOSPath(p: string): string { @@ -22,32 +22,32 @@ function toOSPath(p: string): string { suite('Workbench - OutputWorker', () => { test('OutputWorker - Link detection', function () { - let patternsSlash = OutputWorker.createPatterns( + let patternsSlash = OutputLinkComputer.createPatterns( URI.file('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala') ); - let patternsBackSlash = OutputWorker.createPatterns( + let patternsBackSlash = OutputLinkComputer.createPatterns( URI.file('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala') ); let contextService = new TestContextService(); let line = toOSPath('Foo bar'); - let result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + let result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 0); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 0); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 84); line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts in'); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -55,14 +55,14 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 88); line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336 in'); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336'); assert.equal(result[0].range.startColumn, 5); @@ -70,26 +70,26 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 90); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 90); line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9 in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); assert.equal(result[0].range.endColumn, 90); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -97,7 +97,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts>dir in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -105,7 +105,7 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:336:9] in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#336,9'); assert.equal(result[0].range.startColumn, 5); @@ -113,19 +113,19 @@ suite('Workbench - OutputWorker', () => { // Example: at [C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts] in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 90); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -133,26 +133,26 @@ suite('Workbench - OutputWorker', () => { // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js on line 8, column 13 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on line 8, column 13'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts on LINE 8, COLUMN 13'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 101); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8,13'); assert.equal(result[0].range.startColumn, 1); @@ -160,7 +160,7 @@ suite('Workbench - OutputWorker', () => { // Example: C:\Users\someone\AppData\Local\Temp\_monacodata_9888\workspaces\express\server.js:line 8 line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts:line 8'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#8'); assert.equal(result[0].range.startColumn, 1); @@ -168,13 +168,13 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts)'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 94); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 15); @@ -182,13 +182,13 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278)'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 98); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278'); assert.equal(result[0].range.startColumn, 15); @@ -196,26 +196,26 @@ suite('Workbench - OutputWorker', () => { // Example: at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34) line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 101); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 101); line = toOSPath(' at File.put (C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Game.ts:278:34)'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); assert.equal(result[0].range.endColumn, 101); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString() + '#278,34'); assert.equal(result[0].range.startColumn, 15); @@ -223,13 +223,13 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 102); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -237,13 +237,13 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 103); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -251,26 +251,26 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts(45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -278,26 +278,26 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/lib/something/Features.ts (45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -305,13 +305,13 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 102); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -319,13 +319,13 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 103); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45'); assert.equal(result[0].range.startColumn, 1); @@ -333,26 +333,26 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts(45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts(45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 105); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -360,26 +360,26 @@ suite('Workbench - OutputWorker', () => { // Example: C:/Users/someone/AppData/Local/Temp/_monacodata_9888/workspaces/mankala/Features.ts (45,18): error line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); line = toOSPath('C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\lib\\something\\Features.ts (45,18): error'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); assert.equal(result[0].range.endColumn, 106); - result = OutputWorker.detectLinks(line, 1, patternsBackSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsBackSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/lib/something/Features.ts').toString() + '#45,18'); assert.equal(result[0].range.startColumn, 1); @@ -387,7 +387,7 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts. in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 5); @@ -395,17 +395,17 @@ suite('Workbench - OutputWorker', () => { // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); // Example: at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ line = toOSPath(' at C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game\\ in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); // Example: at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" line = toOSPath(' at "C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts" in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 6); @@ -413,7 +413,7 @@ suite('Workbench - OutputWorker', () => { // Example: at 'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts' line = toOSPath(' at \'C:\\Users\\someone\\AppData\\Local\\Temp\\_monacodata_9888\\workspaces\\mankala\\Game.ts\' in'); - result = OutputWorker.detectLinks(line, 1, patternsSlash, contextService); + result = OutputLinkComputer.detectLinks(line, 1, patternsSlash, contextService); assert.equal(result.length, 1); assert.equal(result[0].url, contextService.toResource('/Game.ts').toString()); assert.equal(result[0].range.startColumn, 6); diff --git a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts index 168e2b276c7..1f6afb90e41 100644 --- a/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/commandsHandler.ts @@ -12,7 +12,7 @@ import arrays = require('vs/base/common/arrays'); import types = require('vs/base/common/types'); import {language, LANGUAGE_DEFAULT} from 'vs/base/common/platform'; import {IAction, Action} from 'vs/base/common/actions'; -import {toErrorMessage} from 'vs/base/common/errors'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {Mode, IEntryRunContext, IAutoFocus} from 'vs/base/parts/quickopen/common/quickOpen'; import {QuickOpenEntryGroup, IHighlight, QuickOpenModel} from 'vs/base/parts/quickopen/browser/quickOpenModel'; import {SyncActionDescriptor, ExecuteCommandAction, IMenuService} from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts b/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts index e4a7285491b..9f58e3c277c 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/gotoLineHandler.ts @@ -161,13 +161,13 @@ class GotoLineEntry extends EditorQuickOpenEntry { } interface IEditorLineDecoration { - lineHighlightId: string; + rangeHighlightId: string; lineDecorationId: string; position: Position; } export class GotoLineHandler extends QuickOpenHandler { - private lineHighlightDecorationId: IEditorLineDecoration; + private rangeHighlightDecorationId: IEditorLineDecoration; private lastKnownEditorViewState: IEditorViewState; constructor( @IWorkbenchEditorService private editorService: IWorkbenchEditorService) { @@ -200,18 +200,18 @@ export class GotoLineHandler extends QuickOpenHandler { editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { let deleteDecorations: string[] = []; - if (this.lineHighlightDecorationId) { - deleteDecorations.push(this.lineHighlightDecorationId.lineDecorationId); - deleteDecorations.push(this.lineHighlightDecorationId.lineHighlightId); - this.lineHighlightDecorationId = null; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + this.rangeHighlightDecorationId = null; } let newDecorations: IModelDeltaDecoration[] = [ - // lineHighlight at index 0 + // rangeHighlight at index 0 { range: range, options: { - className: 'lineHighlight', + className: 'rangeHighlight', isWholeLine: true } }, @@ -230,11 +230,11 @@ export class GotoLineHandler extends QuickOpenHandler { ]; let decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - let lineHighlightId = decorations[0]; + let rangeHighlightId = decorations[0]; let lineDecorationId = decorations[1]; - this.lineHighlightDecorationId = { - lineHighlightId: lineHighlightId, + this.rangeHighlightDecorationId = { + rangeHighlightId: rangeHighlightId, lineDecorationId: lineDecorationId, position: position }; @@ -242,20 +242,20 @@ export class GotoLineHandler extends QuickOpenHandler { } public clearDecorations(): void { - if (this.lineHighlightDecorationId) { + if (this.rangeHighlightDecorationId) { this.editorService.getVisibleEditors().forEach((editor) => { - if (editor.position === this.lineHighlightDecorationId.position) { + if (editor.position === this.rangeHighlightDecorationId.position) { let editorControl = editor.getControl(); editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { changeAccessor.deltaDecorations([ - this.lineHighlightDecorationId.lineDecorationId, - this.lineHighlightDecorationId.lineHighlightId + this.rangeHighlightDecorationId.lineDecorationId, + this.rangeHighlightDecorationId.rangeHighlightId ], []); }); } }); - this.lineHighlightDecorationId = null; + this.rangeHighlightDecorationId = null; } } diff --git a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts index 7b610c49e16..76bb6e93b2c 100644 --- a/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/gotoSymbolHandler.ts @@ -363,14 +363,14 @@ interface Outline { } interface IEditorLineDecoration { - lineHighlightId: string; + rangeHighlightId: string; lineDecorationId: string; position: Position; } export class GotoSymbolHandler extends QuickOpenHandler { private outlineToModelCache: { [modelId: string]: OutlineModel; }; - private lineHighlightDecorationId: IEditorLineDecoration; + private rangeHighlightDecorationId: IEditorLineDecoration; private lastKnownEditorViewState: IEditorViewState; private activeOutlineRequest: TPromise; @@ -509,19 +509,19 @@ export class GotoSymbolHandler extends QuickOpenHandler { editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { let deleteDecorations: string[] = []; - if (this.lineHighlightDecorationId) { - deleteDecorations.push(this.lineHighlightDecorationId.lineDecorationId); - deleteDecorations.push(this.lineHighlightDecorationId.lineHighlightId); - this.lineHighlightDecorationId = null; + if (this.rangeHighlightDecorationId) { + deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId); + deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId); + this.rangeHighlightDecorationId = null; } let newDecorations: IModelDeltaDecoration[] = [ - // lineHighlight at index 0 + // rangeHighlight at index 0 { range: fullRange, options: { - className: 'lineHighlight', + className: 'rangeHighlight', isWholeLine: true } }, @@ -541,11 +541,11 @@ export class GotoSymbolHandler extends QuickOpenHandler { ]; let decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations); - let lineHighlightId = decorations[0]; + let rangeHighlightId = decorations[0]; let lineDecorationId = decorations[1]; - this.lineHighlightDecorationId = { - lineHighlightId: lineHighlightId, + this.rangeHighlightDecorationId = { + rangeHighlightId: rangeHighlightId, lineDecorationId: lineDecorationId, position: position }; @@ -553,20 +553,20 @@ export class GotoSymbolHandler extends QuickOpenHandler { } public clearDecorations(): void { - if (this.lineHighlightDecorationId) { + if (this.rangeHighlightDecorationId) { this.editorService.getVisibleEditors().forEach((editor) => { - if (editor.position === this.lineHighlightDecorationId.position) { + if (editor.position === this.rangeHighlightDecorationId.position) { let editorControl = editor.getControl(); editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => { changeAccessor.deltaDecorations([ - this.lineHighlightDecorationId.lineDecorationId, - this.lineHighlightDecorationId.lineHighlightId + this.rangeHighlightDecorationId.lineDecorationId, + this.rangeHighlightDecorationId.rangeHighlightId ], []); }); } }); - this.lineHighlightDecorationId = null; + this.rangeHighlightDecorationId = null; } } diff --git a/src/vs/workbench/parts/quickopen/browser/helpHandler.ts b/src/vs/workbench/parts/quickopen/browser/helpHandler.ts index aff5568b277..4d28dca949c 100644 --- a/src/vs/workbench/parts/quickopen/browser/helpHandler.ts +++ b/src/vs/workbench/parts/quickopen/browser/helpHandler.ts @@ -127,9 +127,9 @@ export class HelpHandler extends QuickOpenHandler { let registry = (Registry.as(Extensions.Quickopen)); let handlerDescriptors = registry.getQuickOpenHandlers(); - let defaultHandlers = registry.getDefaultQuickOpenHandlers(); - if (defaultHandlers.length > 0) { - handlerDescriptors.push(...defaultHandlers); + let defaultHandler = registry.getDefaultQuickOpenHandler(); + if (defaultHandler) { + handlerDescriptors.push(defaultHandler); } let workbenchScoped: HelpEntry[] = []; diff --git a/src/vs/workbench/parts/quickopen/browser/media/Constant_16x.svg b/src/vs/workbench/parts/quickopen/browser/media/Constant_16x.svg new file mode 100644 index 00000000000..ed2a1751005 --- /dev/null +++ b/src/vs/workbench/parts/quickopen/browser/media/Constant_16x.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/quickopen/browser/media/Constant_16x_inverse.svg b/src/vs/workbench/parts/quickopen/browser/media/Constant_16x_inverse.svg new file mode 100644 index 00000000000..173e427f964 --- /dev/null +++ b/src/vs/workbench/parts/quickopen/browser/media/Constant_16x_inverse.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/vs/workbench/parts/quickopen/browser/media/gotoSymbolHandler.css b/src/vs/workbench/parts/quickopen/browser/media/gotoSymbolHandler.css index b24077d3084..4ed4285b7d1 100644 --- a/src/vs/workbench/parts/quickopen/browser/media/gotoSymbolHandler.css +++ b/src/vs/workbench/parts/quickopen/browser/media/gotoSymbolHandler.css @@ -3,6 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +.monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constant { + background-image: url('Constant_16x.svg'); + background-repeat: no-repeat; + background-position: 0 -2px; +} + +.vs-dark .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constant, +.hc-black .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constant { + background-image: url('Constant_16x_inverse.svg'); +} + .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.method, .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.function, .monaco-workbench .quick-open-widget .quick-open-tree .quick-open-entry .quick-open-entry-icon.constructor, diff --git a/src/vs/workbench/parts/search/browser/media/searchviewlet.css b/src/vs/workbench/parts/search/browser/media/searchviewlet.css index 9b110adfa7b..b13d8063217 100644 --- a/src/vs/workbench/parts/search/browser/media/searchviewlet.css +++ b/src/vs/workbench/parts/search/browser/media/searchviewlet.css @@ -283,7 +283,7 @@ } .vs-dark .monaco-workbench .search-viewlet .query-details .file-types .controls > .custom-checkbox.pattern, -.hc-black .monaco-workbench .search-viewlet .query-details .file-types .controls > .custom-checkbox.pattern:before { +.hc-black .monaco-workbench .search-viewlet .query-details .file-types .controls > .custom-checkbox.pattern { background: url('pattern-dark.svg') center center no-repeat; } diff --git a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts index 1ac72ad0941..e10ac405da0 100644 --- a/src/vs/workbench/parts/search/browser/openAnythingHandler.ts +++ b/src/vs/workbench/parts/search/browser/openAnythingHandler.ts @@ -12,15 +12,13 @@ import nls = require('vs/nls'); import {ThrottledDelayer} from 'vs/base/common/async'; import types = require('vs/base/common/types'); import {isWindows} from 'vs/base/common/platform'; -import scorer = require('vs/base/common/scorer'); import paths = require('vs/base/common/paths'); -import labels = require('vs/base/common/labels'); import strings = require('vs/base/common/strings'); import {IRange} from 'vs/editor/common/editorCommon'; import {IAutoFocus} from 'vs/base/parts/quickopen/common/quickOpen'; import {QuickOpenEntry, QuickOpenModel} from 'vs/base/parts/quickopen/browser/quickOpenModel'; import {QuickOpenHandler} from 'vs/workbench/browser/quickopen'; -import {FileEntry, OpenFileHandler} from 'vs/workbench/parts/search/browser/openFileHandler'; +import {FileEntry, OpenFileHandler, FileQuickOpenModel} from 'vs/workbench/parts/search/browser/openFileHandler'; /* tslint:disable:no-unused-variable */ import * as openSymbolHandler from 'vs/workbench/parts/search/browser/openSymbolHandler'; /* tslint:enable:no-unused-variable */ @@ -30,6 +28,7 @@ import {ISearchStats, ICachedSearchStats, IUncachedSearchStats} from 'vs/platfor import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {IWorkbenchSearchConfiguration} from 'vs/workbench/parts/search/common/search'; const objects_assign: (destination: T, source: U) => T & U = objects.assign; @@ -85,22 +84,21 @@ interface ITelemetryData { export import OpenSymbolHandler = openSymbolHandler.OpenSymbolHandler; export class OpenAnythingHandler extends QuickOpenHandler { + private static LINE_COLON_PATTERN = /[#|:|\(](\d*)([#|:|,](\d*))?\)?$/; - private static SYMBOL_SEARCH_INITIAL_TIMEOUT = 500; // Ignore symbol search after a timeout to not block search results - private static SYMBOL_SEARCH_INITIAL_TIMEOUT_WHEN_FILE_SEARCH_CACHED = 300; - private static SYMBOL_SEARCH_SUBSEQUENT_TIMEOUT = 100; - private static SEARCH_DELAY = 300; // This delay accommodates for the user typing a word and then stops typing to start searching + private static FILE_SEARCH_DELAY = 300; + private static SYMBOL_SEARCH_DELAY = 500; // go easier on those symbols! private static MAX_DISPLAYED_RESULTS = 512; private openSymbolHandler: OpenSymbolHandler; private openFileHandler: OpenFileHandler; - private symbolResultsToSearchCache: { [searchValue: string]: QuickOpenEntry[]; }; - private delayer: ThrottledDelayer; + private searchDelayer: ThrottledDelayer; private pendingSearch: TPromise; private isClosed: boolean; private scorerCache: { [key: string]: number }; + private includeSymbols: boolean; constructor( @IMessageService private messageService: IMessageService, @@ -111,98 +109,75 @@ export class OpenAnythingHandler extends QuickOpenHandler { ) { super(); - // Instantiate delegate handlers + this.scorerCache = Object.create(null); + this.searchDelayer = new ThrottledDelayer(OpenAnythingHandler.FILE_SEARCH_DELAY); + this.openSymbolHandler = instantiationService.createInstance(OpenSymbolHandler); this.openFileHandler = instantiationService.createInstance(OpenFileHandler); + this.updateHandlers(this.configurationService.getConfiguration()); + + this.registerListeners(); + } + + private registerListeners(): void { + this.configurationService.onDidUpdateConfiguration(e => this.updateHandlers(e.config)); + } + + private updateHandlers(configuration: IWorkbenchSearchConfiguration): void { + this.includeSymbols = configuration && configuration.search && configuration.search.quickOpen && configuration.search.quickOpen.includeSymbols; + + // Files + this.openFileHandler.setOptions({ + useIcons: this.includeSymbols // only need icons for file results if we mix with symbol results + }); + + // Symbols this.openSymbolHandler.setOptions({ skipDelay: true, // we have our own delay skipLocalSymbols: true, // we only want global symbols skipSorting: true // we sort combined with file results }); - - this.symbolResultsToSearchCache = Object.create(null); - this.scorerCache = Object.create(null); - this.delayer = new ThrottledDelayer(OpenAnythingHandler.SEARCH_DELAY); } public getResults(searchValue: string): TPromise { const timerEvent = this.telemetryService.timedPublicLog('openAnything'); const startTime = timerEvent.startTime ? timerEvent.startTime.getTime() : Date.now(); // startTime is undefined when telemetry is disabled - searchValue = searchValue.replace(/ /g, ''); // get rid of all whitespace - // Help Windows users to search for paths when using slash - if (isWindows) { - searchValue = searchValue.replace(/\//g, '\\'); - } - - // Cancel any pending search this.cancelPendingSearch(); + this.isClosed = false; // Treat this call as the handler being in use - // Treat this call as the handler being in use - this.isClosed = false; - - // Respond directly to empty search - if (!searchValue) { - return TPromise.as(new QuickOpenModel()); + // Massage search value + searchValue = searchValue.replace(/ /g, ''); // get rid of all whitespace + if (isWindows) { + searchValue = searchValue.replace(/\//g, '\\'); // Help Windows users to search for paths when using slash } - // Find a suitable range from the pattern looking for ":" and "#" - let searchWithRange = this.extractRange(searchValue); + const searchWithRange = this.extractRange(searchValue); // Find a suitable range from the pattern looking for ":" and "#" if (searchWithRange) { searchValue = searchWithRange.search; // ignore range portion in query } - // Check Cache first - let cachedSymbolResults = this.getSymbolResultsFromCache(searchValue, !!searchWithRange); + if (!searchValue) { + return TPromise.as(new QuickOpenModel()); // Respond directly to empty search + } // The throttler needs a factory for its promises - let promiseFactory = () => { - let receivedFileResults = false; - let searchStats: ISearchStats; + const promiseFactory = () => { + const resultPromises: TPromise[] = []; - // Symbol Results (unless a range is specified) - let resultPromises: TPromise[] = []; - if (cachedSymbolResults) { - resultPromises.push(TPromise.as(new QuickOpenModel(cachedSymbolResults))); - } else if (!searchWithRange) { - let symbolSearchTimeoutPromiseFn: (timeout: number) => TPromise = (timeout) => { - return TPromise.timeout(timeout).then(() => { + // File Results + resultPromises.push(this.openFileHandler.getResults(searchValue, OpenAnythingHandler.MAX_DISPLAYED_RESULTS)); - // As long as the file search query did not return, push out the symbol timeout - // so that the symbol search has a chance to return results at least as long as - // the file search did not return. - if (!receivedFileResults) { - return symbolSearchTimeoutPromiseFn(OpenAnythingHandler.SYMBOL_SEARCH_SUBSEQUENT_TIMEOUT); - } - - // Empty result since timeout was reached and file results are in - return TPromise.as(new QuickOpenModel()); - }); - }; - - let lookupPromise = this.openSymbolHandler.getResults(searchValue); - let timeoutPromise = symbolSearchTimeoutPromiseFn(this.openFileHandler.isCacheLoaded ? OpenAnythingHandler.SYMBOL_SEARCH_INITIAL_TIMEOUT_WHEN_FILE_SEARCH_CACHED : OpenAnythingHandler.SYMBOL_SEARCH_INITIAL_TIMEOUT); - - // Timeout lookup after N seconds to not block file search results - resultPromises.push(TPromise.any([lookupPromise, timeoutPromise]).then((result) => { - return result.value; - })); + // Symbol Results (unless disabled or a range or absolute path is specified) + if (this.includeSymbols && !searchWithRange && !paths.isAbsolute(searchValue)) { + resultPromises.push(this.openSymbolHandler.getResults(searchValue)); } else { resultPromises.push(TPromise.as(new QuickOpenModel())); // We need this empty promise because we are using the throttler below! } - // File Results - resultPromises.push(this.openFileHandler.getResultsWithStats(searchValue, OpenAnythingHandler.MAX_DISPLAYED_RESULTS).then(([results, stats]) => { - receivedFileResults = true; - searchStats = stats; - - return results; - })); - // Join and sort unified - this.pendingSearch = TPromise.join(resultPromises).then((results: QuickOpenModel[]) => { - const unsortedResultTime = Date.now(); + this.pendingSearch = TPromise.join(resultPromises).then(results => { this.pendingSearch = null; // If the quick open widget has been closed meanwhile, ignore the result @@ -210,39 +185,43 @@ export class OpenAnythingHandler extends QuickOpenHandler { return TPromise.as(new QuickOpenModel()); } - // Combine symbol results and file results - const symbolResults = results[0].entries; - let result = [...symbolResults, ...results[1].entries]; - - // // Cache for fast lookup - this.symbolResultsToSearchCache[searchValue] = symbolResults; + // Combine file results and symbol results (if any) + const mergedResults = [...results[0].entries, ...results[1].entries]; // Sort + const unsortedResultTime = Date.now(); const normalizedSearchValue = strings.stripWildcards(searchValue).toLowerCase(); const compare = (elementA: QuickOpenEntry, elementB: QuickOpenEntry) => QuickOpenEntry.compareByScore(elementA, elementB, searchValue, normalizedSearchValue, this.scorerCache); - const viewResults = arrays.top(result, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); + const viewResults = arrays.top(mergedResults, compare, OpenAnythingHandler.MAX_DISPLAYED_RESULTS); const sortedResultTime = Date.now(); // Apply range and highlights to file entries viewResults.forEach(entry => { if (entry instanceof FileEntry) { entry.setRange(searchWithRange ? searchWithRange.range : null); + const {labelHighlights, descriptionHighlights} = QuickOpenEntry.highlight(entry, searchValue, true /* fuzzy highlight */); entry.setHighlights(labelHighlights, descriptionHighlights); } }); + let fileSearchStats: ISearchStats; + if (results[0] instanceof FileQuickOpenModel) { + fileSearchStats = (results[0]).stats; + } else if (results[1] instanceof FileQuickOpenModel) { + fileSearchStats = (results[1]).stats; + } + timerEvent.data = this.createTimerEventData(startTime, { searchLength: searchValue.length, unsortedResultTime, sortedResultTime, - resultCount: result.length, - symbols: { - fromCache: !!cachedSymbolResults - }, - files: searchStats + resultCount: mergedResults.length, + symbols: { fromCache: false }, + files: fileSearchStats }); timerEvent.stop(); + return TPromise.as(new QuickOpenModel(viewResults)); }, (error: Error) => { this.pendingSearch = null; @@ -253,16 +232,28 @@ export class OpenAnythingHandler extends QuickOpenHandler { }; // Trigger through delayer to prevent accumulation while the user is typing (except when expecting results to come from cache) - return this.openFileHandler.isCacheLoaded ? promiseFactory() : this.delayer.trigger(promiseFactory); + return this.hasShortResponseTime() ? promiseFactory() : this.searchDelayer.trigger(promiseFactory, this.includeSymbols ? OpenAnythingHandler.SYMBOL_SEARCH_DELAY : OpenAnythingHandler.FILE_SEARCH_DELAY); + } + + public hasShortResponseTime(): boolean { + if (!this.includeSymbols) { + return this.openFileHandler.hasShortResponseTime(); + } + + return this.openFileHandler.hasShortResponseTime() && this.openSymbolHandler.hasShortResponseTime(); } private extractRange(value: string): ISearchWithRange { + if (!value) { + return null; + } + let range: IRange = null; // Find Line/Column number from search value using RegExp - let patternMatch = OpenAnythingHandler.LINE_COLON_PATTERN.exec(value); + const patternMatch = OpenAnythingHandler.LINE_COLON_PATTERN.exec(value); if (patternMatch && patternMatch.length > 1) { - let startLineNumber = parseInt(patternMatch[1], 10); + const startLineNumber = parseInt(patternMatch[1], 10); // Line Number if (types.isNumber(startLineNumber)) { @@ -275,7 +266,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { // Column Number if (patternMatch.length > 3) { - let startColumn = parseInt(patternMatch[3], 10); + const startColumn = parseInt(patternMatch[3], 10); if (types.isNumber(startColumn)) { range.startColumn = startColumn; range.endColumn = startColumn; @@ -304,55 +295,8 @@ export class OpenAnythingHandler extends QuickOpenHandler { return null; } - private getSymbolResultsFromCache(searchValue: string, hasRange: boolean): QuickOpenEntry[] { - if (paths.isAbsolute(searchValue)) { - return null; // bypass cache if user looks up an absolute path where matching goes directly on disk - } - - // Find cache entries by prefix of search value - let cachedEntries: QuickOpenEntry[]; - for (let previousSearch in this.symbolResultsToSearchCache) { - - // If we narrow down, we might be able to reuse the cached results - if (searchValue.indexOf(previousSearch) === 0) { - if (searchValue.indexOf(paths.nativeSep) >= 0 && previousSearch.indexOf(paths.nativeSep) < 0) { - continue; // since a path character widens the search for potential more matches, require it in previous search too - } - - cachedEntries = this.symbolResultsToSearchCache[previousSearch]; - break; - } - } - - if (!cachedEntries) { - return null; - } - - if (hasRange) { - return []; - } - - // Pattern match on results and adjust highlights - let results: QuickOpenEntry[] = []; - const normalizedSearchValueLowercase = strings.stripWildcards(searchValue).toLowerCase(); - for (let i = 0; i < cachedEntries.length; i++) { - let entry = cachedEntries[i]; - - // Check if this entry is a match for the search value - const resource = entry.getResource(); // can be null for symbol results! - let targetToMatch = resource ? labels.getPathLabel(resource, this.contextService) : entry.getLabel(); - if (!scorer.matches(targetToMatch, normalizedSearchValueLowercase)) { - continue; - } - - results.push(entry); - } - - return results; - } - public getGroupLabel(): string { - return nls.localize('fileAndTypeResults', "file and symbol results"); + return this.includeSymbols ? nls.localize('fileAndTypeResults', "file and symbol results") : nls.localize('fileResults', "file results"); } public getAutoFocus(searchValue: string): IAutoFocus { @@ -373,7 +317,6 @@ export class OpenAnythingHandler extends QuickOpenHandler { this.cancelPendingSearch(); // Clear Cache - this.symbolResultsToSearchCache = Object.create(null); this.scorerCache = Object.create(null); // Propagate @@ -402,6 +345,7 @@ export class OpenAnythingHandler extends QuickOpenHandler { private createFileEventData(startTime: number, stats: ISearchStats) { const cached = stats as ICachedSearchStats; const uncached = stats as IUncachedSearchStats; + return objects_assign({ fromCache: stats.fromCache, unsortedResultDuration: stats.unsortedResultTime && stats.unsortedResultTime - startTime, diff --git a/src/vs/workbench/parts/search/browser/openFileHandler.ts b/src/vs/workbench/parts/search/browser/openFileHandler.ts index 698437ba487..a505e8b70e2 100644 --- a/src/vs/workbench/parts/search/browser/openFileHandler.ts +++ b/src/vs/workbench/parts/search/browser/openFileHandler.ts @@ -26,16 +26,21 @@ import {IInstantiationService} from 'vs/platform/instantiation/common/instantiat import {IQueryOptions, ISearchService, ISearchStats, ISearchQuery} from 'vs/platform/search/common/search'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +export class FileQuickOpenModel extends QuickOpenModel { + + constructor(entries: QuickOpenEntry[], public stats?: ISearchStats) { + super(entries); + } +} + export class FileEntry extends EditorQuickOpenEntry { - private name: string; - private description: string; - private resource: URI; private range: IRange; constructor( - name: string, - description: string, - resource: URI, + private resource: URI, + private name: string, + private description: string, + private icon: string, @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IInstantiationService private instantiationService: IInstantiationService, @IConfigurationService private configurationService: IConfigurationService, @@ -61,7 +66,7 @@ export class FileEntry extends EditorQuickOpenEntry { } public getIcon(): string { - return 'file'; + return this.icon; } public getResource(): URI { @@ -72,8 +77,12 @@ export class FileEntry extends EditorQuickOpenEntry { this.range = range; } + public isFile(): boolean { + return true; // TODO@Ben debt with editor history merging + } + public getInput(): IResourceInput | EditorInput { - let input: IResourceInput = { + const input: IResourceInput = { resource: this.resource, options: { pinned: !this.configurationService.getConfiguration().workbench.editor.enablePreviewFromQuickOpen @@ -88,8 +97,12 @@ export class FileEntry extends EditorQuickOpenEntry { } } -export class OpenFileHandler extends QuickOpenHandler { +export interface IOpenFileOptions { + useIcons: boolean; +} +export class OpenFileHandler extends QuickOpenHandler { + private options: IOpenFileOptions; private queryBuilder: QueryBuilder; private cacheState: CacheState; @@ -104,52 +117,54 @@ export class OpenFileHandler extends QuickOpenHandler { this.queryBuilder = this.instantiationService.createInstance(QueryBuilder); } - public getResults(searchValue: string): TPromise { - return this.getResultsWithStats(searchValue) - .then(result => result[0]); + public setOptions(options: IOpenFileOptions) { + this.options = options; } - public getResultsWithStats(searchValue: string, maxSortedResults?: number): TPromise<[QuickOpenModel, ISearchStats]> { + public getResults(searchValue: string, maxSortedResults?: number): TPromise { searchValue = searchValue.trim(); - let promise: TPromise<[QuickOpenEntry[], ISearchStats]>; // Respond directly to empty search if (!searchValue) { - promise = TPromise.as(<[QuickOpenEntry[], ISearchStats]>[[], undefined]); - } else { - promise = this.doFindResults(searchValue, this.cacheState.cacheKey, maxSortedResults); + return TPromise.as(new FileQuickOpenModel([])); } - return promise.then(result => [new QuickOpenModel(result[0]), result[1]]); + // Do find results + return this.doFindResults(searchValue, this.cacheState.cacheKey, maxSortedResults); } - private doFindResults(searchValue: string, cacheKey?: string, maxSortedResults?: number): TPromise<[QuickOpenEntry[], ISearchStats]> { + private doFindResults(searchValue: string, cacheKey?: string, maxSortedResults?: number): TPromise { const query: IQueryOptions = { folderResources: this.contextService.getWorkspace() ? [this.contextService.getWorkspace().resource] : [], extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService), filePattern: searchValue, cacheKey: cacheKey }; + if (typeof maxSortedResults === 'number') { query.maxResults = maxSortedResults; query.sortByScore = true; } return this.searchService.search(this.queryBuilder.file(query)).then((complete) => { - let results: QuickOpenEntry[] = []; + const results: QuickOpenEntry[] = []; for (let i = 0; i < complete.results.length; i++) { - let fileMatch = complete.results[i]; + const fileMatch = complete.results[i]; - let label = paths.basename(fileMatch.resource.fsPath); - let description = labels.getPathLabel(paths.dirname(fileMatch.resource.fsPath), this.contextService); + const label = paths.basename(fileMatch.resource.fsPath); + const description = labels.getPathLabel(paths.dirname(fileMatch.resource.fsPath), this.contextService); - results.push(this.instantiationService.createInstance(FileEntry, label, description, fileMatch.resource)); + results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, (this.options && this.options.useIcons) ? 'file' : null)); } - return [results, complete.stats]; + return new FileQuickOpenModel(results, complete.stats); }); } + public hasShortResponseTime(): boolean { + return this.isCacheLoaded; + } + public onOpen(): void { this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.search(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState); this.cacheState.load(); @@ -158,14 +173,16 @@ export class OpenFileHandler extends QuickOpenHandler { private cacheQuery(cacheKey: string): ISearchQuery { const options: IQueryOptions = { folderResources: this.contextService.getWorkspace() ? [this.contextService.getWorkspace().resource] : [], - extraFileResources: [], + extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService), filePattern: '', cacheKey: cacheKey, maxResults: 0, sortByScore: true }; + const query = this.queryBuilder.file(options); this.searchService.extendQuery(query); + return query; } @@ -193,7 +210,7 @@ class CacheState { private promise: TPromise; - constructor (private cacheQuery: (cacheKey: string) => ISearchQuery, private doLoad: (query: ISearchQuery) => TPromise, private doDispose: (cacheKey: string) => TPromise, private previous: CacheState) { + constructor(private cacheQuery: (cacheKey: string) => ISearchQuery, private doLoad: (query: ISearchQuery) => TPromise, private doDispose: (cacheKey: string) => TPromise, private previous: CacheState) { this.query = cacheQuery(this._cacheKey); if (this.previous) { const current = objects.assign({}, this.query, { cacheKey: null }); @@ -222,17 +239,17 @@ class CacheState { this.previous = null; } }, err => { - console.error(errors.toErrorMessage(err)); + errors.onUnexpectedError(err); }); } public dispose(): void { - this.promise.then(null, () => {}) + this.promise.then(null, () => { }) .then(() => { this._isLoaded = false; return this.doDispose(this._cacheKey); }).then(null, err => { - console.error(errors.toErrorMessage(err)); + errors.onUnexpectedError(err); }); if (this.previous) { this.previous.dispose(); diff --git a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts index eb4fe70d18a..2c2a6aa2189 100644 --- a/src/vs/workbench/parts/search/browser/openSymbolHandler.ts +++ b/src/vs/workbench/parts/search/browser/openSymbolHandler.ts @@ -143,12 +143,13 @@ export class OpenSymbolHandler extends QuickOpenHandler { public getResults(searchValue: string): TPromise { searchValue = searchValue.trim(); - let promise: TPromise; - // Respond directly to empty search if (!searchValue) { - promise = TPromise.as([]); - } else if (!this.options.skipDelay) { + return TPromise.as(new QuickOpenModel([])); + } + + let promise: TPromise; + if (!this.options.skipDelay) { promise = this.delayer.trigger(() => this.doGetResults(searchValue)); // Run search with delay as needed } else { promise = this.doGetResults(searchValue); @@ -179,7 +180,6 @@ export class OpenSymbolHandler extends QuickOpenHandler { // Convert to Entries for (let element of types) { - if (this.options.skipLocalSymbols && !!element.containerName) { continue; // ignore local symbols if we are told so } @@ -207,4 +207,4 @@ export class OpenSymbolHandler extends QuickOpenHandler { autoFocusPrefixMatch: searchValue.trim() }; } -} +} \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/search.contribution.ts b/src/vs/workbench/parts/search/browser/search.contribution.ts index f1e9dbf1866..2905189ffd0 100644 --- a/src/vs/workbench/parts/search/browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/browser/search.contribution.ts @@ -139,7 +139,7 @@ actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActio 'vs/workbench/parts/search/browser/openAnythingHandler', 'OpenAnythingHandler', '', - nls.localize('openAnythingHandlerDescription', "Open Files and Global Symbols by Name") + nls.localize('openAnythingHandlerDescription', "Open Files by Name") ) ); @@ -195,6 +195,11 @@ configurationRegistry.registerConfiguration({ } ] } + }, + 'search.quickOpen.includeSymbols': { + 'type': 'boolean', + 'description': nls.localize('search.quickOpen.includeSymbols', "Configure to include results from a global symbol search in the file results for Quick Open."), + 'default': false } } }); \ No newline at end of file diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 97a1a620c63..8d03c9bebdd 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -25,7 +25,6 @@ import {ITree} from 'vs/base/parts/tree/browser/tree'; import {Tree} from 'vs/base/parts/tree/browser/treeImpl'; import {Scope} from 'vs/workbench/common/memento'; import {OpenGlobalSettingsAction} from 'vs/workbench/browser/actions/openSettings'; -import {UntitledEditorEvent, EventType as WorkbenchEventType} from 'vs/workbench/common/events'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {getOutOfWorkspaceEditorResources} from 'vs/workbench/common/editor'; import {FileChangeType, FileChangesEvent, EventType as FileEventType} from 'vs/platform/files/common/files'; @@ -55,6 +54,7 @@ import { SearchWidget } from 'vs/workbench/parts/search/browser/searchWidget'; import { RefreshAction, CollapseAllAction, ClearSearchResultsAction, ConfigureGlobalExclusionsAction } from 'vs/workbench/parts/search/browser/searchActions'; import { IReplaceService } from 'vs/workbench/parts/search/common/replace'; import Severity from 'vs/base/common/severity'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; export class SearchViewlet extends Viewlet { @@ -102,7 +102,8 @@ export class SearchViewlet extends Viewlet { @ISearchService private searchService: ISearchService, @IContextKeyService private contextKeyService: IContextKeyService, @IKeybindingService private keybindingService: IKeybindingService, - @IReplaceService private replaceService: IReplaceService + @IReplaceService private replaceService: IReplaceService, + @IUntitledEditorService private untitledEditorService: IUntitledEditorService ) { super(VIEWLET_ID, telemetryService); @@ -114,7 +115,7 @@ export class SearchViewlet extends Viewlet { this.viewletSettings = this.getMemento(storageService, Scope.WORKSPACE); this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_CHANGES, (e) => this.onFilesChanged(e))); - this.toUnbind.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_SAVED, (e) => this.onUntitledFileSaved(e))); + this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e))); this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config))); } @@ -931,7 +932,7 @@ export class SearchViewlet extends Viewlet { } if (match) { let range= match.range(); - if (this.viewModel.isReplaceActive()) { + if (this.viewModel.isReplaceActive() && !!this.viewModel.replaceString) { let replaceString= match.replaceString; return { startLineNumber: range.startLineNumber, @@ -945,15 +946,18 @@ export class SearchViewlet extends Viewlet { return void 0; } - private onUntitledFileSaved(e: UntitledEditorEvent): void { + private onUntitledDidChangeDirty(resource: URI): void { if (!this.viewModel) { return; } - let matches = this.viewModel.searchResult.matches(); - for (let i = 0, len = matches.length; i < len; i++) { - if (e.resource.toString() === matches[i].resource().toString()) { - this.viewModel.searchResult.remove(matches[i]); + // remove search results from this resource as it got disposed + if (!this.untitledEditorService.isDirty(resource)) { + let matches = this.viewModel.searchResult.matches(); + for (let i = 0, len = matches.length; i < len; i++) { + if (resource.toString() === matches[i].resource().toString()) { + this.viewModel.searchResult.remove(matches[i]); + } } } } diff --git a/src/vs/workbench/parts/search/common/search.ts b/src/vs/workbench/parts/search/common/search.ts index 585137639df..54a57865fbc 100644 --- a/src/vs/workbench/parts/search/common/search.ts +++ b/src/vs/workbench/parts/search/common/search.ts @@ -11,6 +11,8 @@ import {IDisposable} from 'vs/base/common/lifecycle'; import {CommonEditorRegistry} from 'vs/editor/common/editorCommonExtensions'; import {IRange} from 'vs/editor/common/editorCommon'; import URI from 'vs/base/common/uri'; +import {ISearchConfiguration} from 'vs/platform/search/common/search'; +import glob = require('vs/base/common/glob'); /** * Interface used to navigate to types by value. @@ -78,3 +80,12 @@ CommonEditorRegistry.registerLanguageCommand('_executeWorkspaceSymbolProvider', } return getWorkspaceSymbols(query); }); + +export interface IWorkbenchSearchConfiguration extends ISearchConfiguration { + search: { + quickOpen: { + includeSymbols: boolean; + }, + exclude: glob.IExpression; + }; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/search/common/searchModel.ts b/src/vs/workbench/parts/search/common/searchModel.ts index c4643b63e6d..645d009f518 100644 --- a/src/vs/workbench/parts/search/common/searchModel.ts +++ b/src/vs/workbench/parts/search/common/searchModel.ts @@ -69,7 +69,14 @@ export class Match { } public get replaceString(): string { - return this.parent().parent().searchModel.replacePattern.getReplaceString(this.getMatchString()); + let searchModel = this.parent().parent().searchModel; + let matchString = this.getMatchString(); + let replaceString = searchModel.replacePattern.getReplaceString(matchString); + // If match string is not matching then regex pattern has a lookahead expression + if (replaceString === null) { + replaceString = searchModel.replacePattern.getReplaceString(matchString + this._lineText.substring(this._range.endColumn - 1)); + } + return replaceString; } public getMatchString(): string { diff --git a/src/vs/workbench/parts/search/test/common/searchModel.test.ts b/src/vs/workbench/parts/search/test/common/searchModel.test.ts index b1b0dbb0d18..4f7ae86428e 100644 --- a/src/vs/workbench/parts/search/test/common/searchModel.test.ts +++ b/src/vs/workbench/parts/search/test/common/searchModel.test.ts @@ -233,6 +233,34 @@ suite('SearchModel', () => { assert.ok(target.calledOnce); }); + test('getReplaceString returns proper replace string for regExpressions', function () { + let results= [aRawMatch('file://c:/1', aLineMatch('preview 1', 1, [[1, 3], [4, 7]]))]; + instantiationService.stub(ISearchService, 'search', PPromise.as({results: results})); + + let testObject: SearchModel= instantiationService.createInstance(SearchModel); + testObject.search({ contentPattern: { pattern: 're' }, type: 1 }); + testObject.replaceString = 'hello'; + let match = testObject.searchResult.matches()[0].matches()[0]; + assert.equal('hello', match.replaceString); + + testObject.search({ contentPattern: { pattern: 're', isRegExp: true }, type: 1 }); + match = testObject.searchResult.matches()[0].matches()[0]; + assert.equal('hello', match.replaceString); + + testObject.search({ contentPattern: { pattern: 're(?:vi)', isRegExp: true }, type: 1 }); + match = testObject.searchResult.matches()[0].matches()[0]; + assert.equal('hello', match.replaceString); + + testObject.search({ contentPattern: { pattern: 'r(e)(?:vi)', isRegExp: true }, type: 1 }); + match = testObject.searchResult.matches()[0].matches()[0]; + assert.equal('hello', match.replaceString); + + testObject.search({ contentPattern: { pattern: 'r(e)(?:vi)', isRegExp: true }, type: 1 }); + testObject.replaceString = 'hello$1'; + match = testObject.searchResult.matches()[0].matches()[0]; + assert.equal('helloe', match.replaceString); + }); + function aRawMatch(resource: string, ...lineMatches: ILineMatch[]): IFileMatch { return { resource: URI.parse(resource), lineMatches }; } diff --git a/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts b/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts index 8cc9cf3edfe..1a752622516 100644 --- a/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts +++ b/src/vs/workbench/parts/snippets/electron-browser/snippetsTracker.ts @@ -5,6 +5,7 @@ 'use strict'; +import {localize} from 'vs/nls'; import workbenchExt = require('vs/workbench/common/contributions'); import paths = require('vs/base/common/paths'); import async = require('vs/base/common/async'); @@ -72,7 +73,7 @@ export class SnippetsTracker implements workbenchExt.IWorkbenchContribution { return winjs.TPromise.join(snippetFiles.map(snippetFile => { var modeId = snippetFile.replace(/\.json$/, '').toLowerCase(); var snippetPath = paths.join(this.snippetFolder, snippetFile); - return readAndRegisterSnippets(modeId, snippetPath); + return readAndRegisterSnippets(modeId, snippetPath, localize('userSnippet', "User Snippet")); })); }); } diff --git a/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts b/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts index eec72ca120a..75448ff21b6 100644 --- a/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/languageServiceTaskSystem.ts @@ -10,7 +10,7 @@ import { TerminateResponse} from 'vs/base/common/processes'; import { IMode } from 'vs/editor/common/modes'; import { EventEmitter } from 'vs/base/common/eventEmitter'; -import { ITaskSystem, ITaskSummary, TaskDescription, TelemetryEvent, Triggers, TaskConfiguration, ITaskRunResult } from 'vs/workbench/parts/tasks/common/taskSystem'; +import { ITaskSystem, ITaskSummary, TaskDescription, TelemetryEvent, Triggers, TaskConfiguration, ITaskExecuteResult, TaskExecuteKind } from 'vs/workbench/parts/tasks/common/taskSystem'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IModeService } from 'vs/editor/common/services/modeService'; @@ -33,30 +33,30 @@ export class LanguageServiceTaskSystem extends EventEmitter implements ITaskSyst this.modeService = modeService; } - public build(): ITaskRunResult { + public build(): ITaskExecuteResult { return this.processMode((mode) => { return null; }, 'build', Triggers.shortcut); } - public rebuild(): ITaskRunResult { + public rebuild(): ITaskExecuteResult { return this.processMode((mode) => { return null; }, 'rebuild', Triggers.shortcut); } - public clean(): ITaskRunResult { + public clean(): ITaskExecuteResult { return this.processMode((mode) => { return null; }, 'clean', Triggers.shortcut); } - public runTest(): ITaskRunResult { - return { promise: TPromise.wrapError('Not implemented yet.') }; + public runTest(): ITaskExecuteResult { + return { kind: TaskExecuteKind.Started, promise: TPromise.wrapError('Not implemented yet.') }; } - public run(taskIdentifier:string): ITaskRunResult { - return { promise: TPromise.wrapError('Not implemented yet.') }; + public run(taskIdentifier:string): ITaskExecuteResult { + return { kind: TaskExecuteKind.Started, promise: TPromise.wrapError('Not implemented yet.') }; } public isActive(): TPromise { @@ -84,13 +84,13 @@ export class LanguageServiceTaskSystem extends EventEmitter implements ITaskSyst return TPromise.as(result); } - private processMode(fn: (mode: IMode) => Promise, taskName: string, trigger: string): ITaskRunResult { + private processMode(fn: (mode: IMode) => Promise, taskName: string, trigger: string): ITaskExecuteResult { let telemetryEvent: TelemetryEvent = { trigger: trigger, command: 'languageService', success: true }; - return { promise: Promise.join(this.configuration.modes.map((mode) => { + return { kind: TaskExecuteKind.Started, started: {}, promise: Promise.join(this.configuration.modes.map((mode) => { return this.modeService.getOrCreateMode(mode); })).then((modes: IMode[]) => { let promises: Promise[] = []; @@ -104,7 +104,7 @@ export class LanguageServiceTaskSystem extends EventEmitter implements ITaskSyst }).then((value) => { this.telemetryService.publicLog(LanguageServiceTaskSystem.TelemetryEventName, telemetryEvent); return value; - },(err) => { + }, (err) => { telemetryEvent.success = false; this.telemetryService.publicLog(LanguageServiceTaskSystem.TelemetryEventName, telemetryEvent); return Promise.wrapError(err); diff --git a/src/vs/workbench/parts/tasks/common/taskSystem.ts b/src/vs/workbench/parts/tasks/common/taskSystem.ts index 2a944ce3336..1656dd2d681 100644 --- a/src/vs/workbench/parts/tasks/common/taskSystem.ts +++ b/src/vs/workbench/parts/tasks/common/taskSystem.ts @@ -188,9 +188,21 @@ export interface ITaskSummary { exitCode?: number; } -export interface ITaskRunResult { - restartOnFileChanges?: string; +export enum TaskExecuteKind { + Started = 1, + Active = 2 +} + +export interface ITaskExecuteResult { + kind: TaskExecuteKind; promise: TPromise; + started?: { + restartOnFileChanges?: string; + }; + active?: { + same: boolean; + watching: boolean; + }; } export namespace TaskSystemEvents { @@ -210,11 +222,11 @@ export interface TaskEvent { } export interface ITaskSystem extends IEventEmitter { - build(): ITaskRunResult; - rebuild(): ITaskRunResult; - clean(): ITaskRunResult; - runTest(): ITaskRunResult; - run(taskIdentifier: string): ITaskRunResult; + build(): ITaskExecuteResult; + rebuild(): ITaskExecuteResult; + clean(): ITaskExecuteResult; + runTest(): ITaskExecuteResult; + run(taskIdentifier: string): ITaskExecuteResult; isActive(): TPromise; isActiveSync(): boolean; canAutoTerminate(): boolean; diff --git a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts index 151b443ff23..62d4e47de95 100644 --- a/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts +++ b/src/vs/workbench/parts/tasks/electron-browser/task.contribution.ts @@ -60,7 +60,7 @@ import { ConfigVariables } from 'vs/workbench/parts/lib/node/configVariables'; import { ITextFileService, EventType } from 'vs/workbench/parts/files/common/files'; import { IOutputService, IOutputChannelRegistry, Extensions as OutputExt, IOutputChannel } from 'vs/workbench/parts/output/common/output'; -import { ITaskSystem, ITaskSummary, ITaskRunResult, TaskError, TaskErrors, TaskConfiguration, TaskDescription, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem'; +import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskConfiguration, TaskDescription, TaskSystemEvents } from 'vs/workbench/parts/tasks/common/taskSystem'; import { ITaskService, TaskServiceEvents } from 'vs/workbench/parts/tasks/common/taskService'; import { templates as taskTemplates } from 'vs/workbench/parts/tasks/common/taskTemplates'; @@ -169,10 +169,7 @@ class CleanAction extends AbstractTaskAction { } } -class ConfigureTaskRunnerAction extends Action { - - public static ID = 'workbench.action.tasks.configureTaskRunner'; - public static TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task Runner"); +abstract class OpenTaskConfigurationAction extends Action { private configurationService: IConfigurationService; private fileService: IFileService; @@ -273,6 +270,35 @@ class ConfigureTaskRunnerAction extends Action { } } +class ConfigureTaskRunnerAction extends OpenTaskConfigurationAction { + public static ID = 'workbench.action.tasks.configureTaskRunner'; + public static TEXT = nls.localize('ConfigureTaskRunnerAction.label', "Configure Task Runner"); + + constructor(id: string, label: string, @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, + @IWorkspaceContextService contextService: IWorkspaceContextService, @IOutputService outputService: IOutputService, + @IMessageService messageService: IMessageService, @IQuickOpenService quickOpenService: IQuickOpenService, + @IEnvironmentService environmentService: IEnvironmentService) { + super(id, label, configurationService, editorService, fileService, contextService, + outputService, messageService, quickOpenService, environmentService); + } + +} + +class ConfigureBuildTaskAction extends OpenTaskConfigurationAction { + public static ID = 'workbench.action.tasks.configureBuildTask'; + public static TEXT = nls.localize('ConfigureBuildTaskAction.label', "Configure Build Task"); + + constructor(id: string, label: string, @IConfigurationService configurationService: IConfigurationService, + @IWorkbenchEditorService editorService: IWorkbenchEditorService, @IFileService fileService: IFileService, + @IWorkspaceContextService contextService: IWorkspaceContextService, @IOutputService outputService: IOutputService, + @IMessageService messageService: IMessageService, @IQuickOpenService quickOpenService: IQuickOpenService, + @IEnvironmentService environmentService: IEnvironmentService) { + super(id, label, configurationService, editorService, fileService, contextService, + outputService, messageService, quickOpenService, environmentService); + } +} + class CloseMessageAction extends Action { public static ID = 'workbench.action.build.closeMessage'; @@ -478,11 +504,17 @@ class StatusBarItem implements IStatusbarItem { })); callOnDispose.push(this.taskService.addListener2(TaskServiceEvents.Inactive, (data:TaskServiceEventData) => { - this.activeCount--; - if (this.activeCount === 0) { - $(progress).hide(); - clearInterval(this.intervalToken); - this.intervalToken = null; + // Since the exiting of the sub process is communicated async we can't order inactive and terminate events. + // So try to treat them accordingly. + if (this.activeCount > 0) { + this.activeCount--; + if (this.activeCount === 0) { + $(progress).hide(); + if (this.intervalToken) { + clearInterval(this.intervalToken); + this.intervalToken = null; + } + } } })); @@ -512,28 +544,33 @@ interface TaskServiceEventData { } class NullTaskSystem extends EventEmitter implements ITaskSystem { - public build(): ITaskRunResult { + public build(): ITaskExecuteResult { return { + kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } - public rebuild(): ITaskRunResult { + public rebuild(): ITaskExecuteResult { return { + kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } - public clean(): ITaskRunResult { + public clean(): ITaskExecuteResult { return { + kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } - public runTest(): ITaskRunResult { + public runTest(): ITaskExecuteResult { return { + kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } - public run(taskIdentifier: string): ITaskRunResult { + public run(taskIdentifier: string): ITaskExecuteResult { return { + kind: TaskExecuteKind.Started, promise: TPromise.as({}) }; } @@ -754,6 +791,12 @@ class TaskService extends EventEmitter implements ITaskService { this.outputService, this.messageService, this.quickOpenService, this.environmentService); } + private configureBuildTask(): Action { + return new ConfigureBuildTaskAction(ConfigureBuildTaskAction.ID, ConfigureBuildTaskAction.TEXT, + this.configurationService, this.editorService, this.fileService, this.contextService, + this.outputService, this.messageService, this.quickOpenService, this.environmentService); + } + public build(): TPromise { return this.executeTarget(taskSystem => taskSystem.build()); } @@ -774,43 +817,50 @@ class TaskService extends EventEmitter implements ITaskService { return this.executeTarget(taskSystem => taskSystem.run(taskIdentifier)); } - private executeTarget(fn: (taskSystem: ITaskSystem) => ITaskRunResult): TPromise { + private executeTarget(fn: (taskSystem: ITaskSystem) => ITaskExecuteResult): TPromise { return this.textFileService.saveAll().then((value) => { // make sure all dirty files are saved return this.configurationService.reloadConfiguration().then(() => { // make sure configuration is up to date return this.taskSystemPromise. then((taskSystem) => { - return taskSystem.isActive().then((active) => { - if (!active) { - return fn(taskSystem); + let executeResult = fn(taskSystem); + if (executeResult.kind === TaskExecuteKind.Active) { + let active = executeResult.active; + if (active.same && active.watching) { + this.messageService.show(Severity.Info, nls.localize('TaskSystem.activeSame', 'The task is already active and in watch mode.')); } else { throw new TaskError(Severity.Warning, nls.localize('TaskSystem.active', 'There is an active running task right now. Terminate it first before executing another task.'), TaskErrors.RunningTask); } - }); - }). - then((runResult: ITaskRunResult) => { - if (runResult.restartOnFileChanges) { - let pattern = runResult.restartOnFileChanges; - this.fileChangesListener = this.eventService.addListener2(FileEventType.FILE_CHANGES, (event: FileChangesEvent) => { - let needsRestart = event.changes.some((change) => { - return (change.type === FileChangeType.ADDED || change.type === FileChangeType.DELETED) && !!match(pattern, change.resource.fsPath); - }); - if (needsRestart) { - this.terminate().done(() => { - // We need to give the child process a change to stop. - setTimeout(() => { - this.executeTarget(fn); - }, 2000); - }); - } - }); } - return runResult.promise.then((value) => { - if (this.clearTaskSystemPromise) { - this._taskSystemPromise = null; - this.clearTaskSystemPromise = false; + return executeResult; + }). + then((executeResult: ITaskExecuteResult) => { + if (executeResult.kind === TaskExecuteKind.Started) { + if (executeResult.started.restartOnFileChanges) { + let pattern = executeResult.started.restartOnFileChanges; + this.fileChangesListener = this.eventService.addListener2(FileEventType.FILE_CHANGES, (event: FileChangesEvent) => { + let needsRestart = event.changes.some((change) => { + return (change.type === FileChangeType.ADDED || change.type === FileChangeType.DELETED) && !!match(pattern, change.resource.fsPath); + }); + if (needsRestart) { + this.terminate().done(() => { + // We need to give the child process a change to stop. + setTimeout(() => { + this.executeTarget(fn); + }, 2000); + }); + } + }); } - return value; - }); + return executeResult.promise.then((value) => { + if (this.clearTaskSystemPromise) { + this._taskSystemPromise = null; + this.clearTaskSystemPromise = false; + } + return value; + }); + } else { + return executeResult.promise; + } }, (err: any) => { this.handleError(err); }); @@ -878,6 +928,14 @@ class TaskService extends EventEmitter implements ITaskService { return false; // Nothing to do here } + private getConfigureAction(code: TaskErrors): Action { + switch(code) { + case TaskErrors.NoBuildTask: + return this.configureBuildTask(); + default: + return this.configureAction(); + } + } private handleError(err:any):void { let showOutput = true; if (err instanceof TaskError) { @@ -887,10 +945,10 @@ class TaskService extends EventEmitter implements ITaskService { if (needsConfig || needsTerminate) { let closeAction = new CloseMessageAction(); let action = needsConfig - ? this.configureAction() + ? this.getConfigureAction(buildError.code) : new TerminateAction(TerminateAction.ID, TerminateAction.TEXT, this, this.telemetryService, this.messageService, this.contextService); - closeAction.closeFunction = this.messageService.show(buildError.severity, { message: buildError.message, actions: [closeAction, action ] }); + closeAction.closeFunction = this.messageService.show(buildError.severity, { message: buildError.message, actions: [action, closeAction ] }); } else { this.messageService.show(buildError.severity, buildError.message); } diff --git a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts index 9470a929c30..39704103f19 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerDetector.ts @@ -21,6 +21,7 @@ import * as FileConfig from './processRunnerConfiguration'; let build: string = 'build'; let test: string = 'test'; +let defaultValue: string = 'default'; interface TaskInfo { index: number; @@ -341,12 +342,15 @@ export class ProcessRunnerDetector { private testBuild(taskInfo: TaskInfo, taskName: string, index: number):void { if (taskName === build) { + taskInfo.index = index; + taskInfo.exact = 4; + } else if ((Strings.startsWith(taskName, build) || Strings.endsWith(taskName, build)) && taskInfo.exact < 4) { taskInfo.index = index; taskInfo.exact = 3; - } else if ((Strings.startsWith(taskName, build) || Strings.endsWith(taskName, build)) && taskInfo.exact < 3) { + } else if (taskName.indexOf(build) !== -1 && taskInfo.exact < 3) { taskInfo.index = index; taskInfo.exact = 2; - } else if (taskName.indexOf(build) !== -1 && taskInfo.exact < 2) { + } else if (taskName === defaultValue && taskInfo.exact < 2) { taskInfo.index = index; taskInfo.exact = 1; } diff --git a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts index 87e2fc2a1a4..87d11fa122c 100644 --- a/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts +++ b/src/vs/workbench/parts/tasks/node/processRunnerSystem.ts @@ -27,7 +27,7 @@ import { ProblemMatcher } from 'vs/platform/markers/common/problemMatcher'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { StartStopProblemCollector, WatchingProblemCollector, ProblemCollectorEvents } from 'vs/workbench/parts/tasks/common/problemCollectors'; -import { ITaskSystem, ITaskSummary, ITaskRunResult, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, CommandOptions, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem'; +import { ITaskSystem, ITaskSummary, ITaskExecuteResult, TaskExecuteKind, TaskError, TaskErrors, TaskRunnerConfiguration, TaskDescription, CommandOptions, ShowOutput, TelemetryEvent, Triggers, TaskSystemEvents, TaskEvent, TaskType } from 'vs/workbench/parts/tasks/common/taskSystem'; import * as FileConfig from './processRunnerConfiguration'; import {IDisposable, dispose} from 'vs/base/common/lifecycle'; @@ -52,6 +52,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { private errorsShown: boolean; private childProcess: LineProcess; private activeTaskIdentifier: string; + private activeTaskPromise: TPromise; constructor(fileConfig:FileConfig.ExternalTaskRunnerConfiguration, variables:ISystemVariables, markerService:IMarkerService, modelService: IModelService, telemetryService: ITelemetryService, outputService:IOutputService, outputChannelId:string, clearOutput: boolean = true) { super(); @@ -66,6 +67,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { this.defaultTestTaskIdentifier = null; this.childProcess = null; this.activeTaskIdentifier = null; + this.activeTaskPromise = null; this.outputChannel = this.outputService.getChannel(outputChannelId); if (clearOutput) { @@ -84,29 +86,41 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { } - public build(): ITaskRunResult { + public build(): ITaskExecuteResult { + if (this.activeTaskIdentifier) { + let task = this.configuration.tasks[this.activeTaskIdentifier]; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultBuildTaskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + } if (!this.defaultBuildTaskIdentifier) { - throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noBuildTask', 'No build task configured.'), TaskErrors.NoBuildTask); + throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noBuildTask', 'No task is marked as a build task in the tasks.json. Mark a task with \'isBuildCommand\'.'), TaskErrors.NoBuildTask); } return this.executeTask(this.defaultBuildTaskIdentifier, Triggers.shortcut); } - public rebuild(): ITaskRunResult { + public rebuild(): ITaskExecuteResult { throw new Error('Task - Rebuild: not implemented yet'); } - public clean(): ITaskRunResult { + public clean(): ITaskExecuteResult { throw new Error('Task - Clean: not implemented yet'); } - public runTest(): ITaskRunResult { + public runTest(): ITaskExecuteResult { + if (this.activeTaskIdentifier) { + let task = this.configuration.tasks[this.activeTaskIdentifier]; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === this.defaultTestTaskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + } if (!this.defaultTestTaskIdentifier) { throw new TaskError(Severity.Info, nls.localize('TaskRunnerSystem.noTestTask', 'No test task configured.'), TaskErrors.NoTestTask); } return this.executeTask(this.defaultTestTaskIdentifier, Triggers.shortcut); } - public run(taskIdentifier: string): ITaskRunResult { + public run(taskIdentifier: string): ITaskExecuteResult { + if (this.activeTaskIdentifier) { + let task = this.configuration.tasks[this.activeTaskIdentifier]; + return { kind: TaskExecuteKind.Active, active: { same: this.activeTaskIdentifier === taskIdentifier, watching: task.isWatching }, promise: this.activeTaskPromise }; + } return this.executeTask(taskIdentifier); } @@ -148,7 +162,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { return TPromise.as(result); } - private executeTask(taskIdentifier: string, trigger: string = Triggers.command): ITaskRunResult { + private executeTask(taskIdentifier: string, trigger: string = Triggers.command): ITaskExecuteResult { if (this.validationStatus.isFatal()) { throw new TaskError(Severity.Error, nls.localize('TaskRunnerSystem.fatalError', 'The provided task configuration has validation errors. See tasks output log for details.'), TaskErrors.ConfigValidationError); } @@ -188,7 +202,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { } } - private doExecuteTask(task: TaskDescription, telemetryEvent: TelemetryEvent): ITaskRunResult { + private doExecuteTask(task: TaskDescription, telemetryEvent: TelemetryEvent): ITaskExecuteResult { let taskSummary: ITaskSummary = {}; let configuration = this.configuration; if (!this.validationStatus.isOK() && !this.errorsShown) { @@ -240,7 +254,7 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { watchingProblemMatcher.aboutToStart(); let delayer:Async.Delayer = null; this.activeTaskIdentifier = task.id; - let promise = this.childProcess.start().then((success): ITaskSummary => { + this.activeTaskPromise = this.childProcess.start().then((success): ITaskSummary => { this.childProcessEnded(); watchingProblemMatcher.dispose(); toUnbind = dispose(toUnbind); @@ -281,14 +295,16 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { delayer = null; }); }); - let result: ITaskRunResult = (task).tscWatch ? { restartOnFileChanges: '**/*.ts', promise } : { promise }; + let result: ITaskExecuteResult = (task).tscWatch + ? { kind: TaskExecuteKind.Started, started: { restartOnFileChanges: '**/*.ts'} , promise: this.activeTaskPromise } + : { kind: TaskExecuteKind.Started, started: {}, promise: this.activeTaskPromise }; return result; } else { let event: TaskEvent = { taskId: task.id, taskName: task.name, type: TaskType.SingleRun }; this.emit(TaskSystemEvents.Active, event ); let startStopProblemMatcher = new StartStopProblemCollector(this.resolveMatchers(task.problemMatchers), this.markerService, this.modelService); this.activeTaskIdentifier = task.id; - let promise = this.childProcess.start().then((success): ITaskSummary => { + this.activeTaskPromise = this.childProcess.start().then((success): ITaskSummary => { this.childProcessEnded(); startStopProblemMatcher.done(); startStopProblemMatcher.dispose(); @@ -309,13 +325,14 @@ export class ProcessRunnerSystem extends EventEmitter implements ITaskSystem { this.outputChannel.append(line + '\n'); startStopProblemMatcher.processLine(line); }); - return { promise }; + return { kind: TaskExecuteKind.Started, started: {}, promise: this.activeTaskPromise }; } } private childProcessEnded(): void { this.childProcess = null; this.activeTaskIdentifier = null; + this.activeTaskPromise = null; } private handleError(task: TaskDescription, error: ErrorData): Promise { diff --git a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts index b98a68d7cb6..b618cf3403b 100644 --- a/src/vs/workbench/parts/tasks/test/node/configuration.test.ts +++ b/src/vs/workbench/parts/tasks/test/node/configuration.test.ts @@ -6,6 +6,7 @@ import * as assert from 'assert'; import Severity from 'vs/base/common/severity'; +import * as UUID from 'vs/base/common/uuid'; import * as Platform from 'vs/base/common/platform'; import { ProblemMatcher, FileLocationKind, ProblemPattern, ApplyToKind } from 'vs/platform/markers/common/problemMatcher'; @@ -117,11 +118,13 @@ class TaskBuilder { class ProblemMatcherBuilder { + public static DEFAULT_UUID = UUID.generateUuid(); + public result: ProblemMatcher; constructor(public parent: TaskBuilder) { this.result = { - owner: 'external', + owner: ProblemMatcherBuilder.DEFAULT_UUID, applyTo: ApplyToKind.allDocuments, severity: undefined, fileLocation: FileLocationKind.Relative, @@ -957,7 +960,15 @@ suite('Tasks Configuration parsing tests', () => { } function assertProblemMatcher(actual: ProblemMatcher, expected: ProblemMatcher) { - assert.strictEqual(actual.owner, expected.owner); + if (expected.owner === ProblemMatcherBuilder.DEFAULT_UUID) { + try { + UUID.parse(actual.owner); + } catch (err) { + assert.fail(actual.owner, 'Owner must be a UUID'); + } + } else { + assert.strictEqual(actual.owner, expected.owner); + } assert.strictEqual(actual.applyTo, expected.applyTo); assert.strictEqual(actual.severity, expected.severity); assert.strictEqual(actual.fileLocation, expected.fileLocation); diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css index d4696394a5f..132831e836b 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -38,4 +38,9 @@ .monaco-workbench .terminal-action.new { background: url('new.svg') center center no-repeat; } /* Dark theme / HC theme */ .vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-inverse.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-inverse.svg') center center no-repeat; } \ No newline at end of file +.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-inverse.svg') center center no-repeat; } + +.vs-dark .monaco-workbench.mac .panel.integrated-terminal .xterm-rows, +.hc-black .monaco-workbench.mac .panel.integrated-terminal .xterm-rows { + cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; +} \ No newline at end of file diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts index ba630349a1a..8f075de45d7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.contribution.ts @@ -64,17 +64,13 @@ configurationRegistry.registerConfiguration({ 'description': nls.localize('terminal.integrated.fontFamily', "Controls the font family of the terminal, this defaults to editor.fontFamily's value."), 'type': 'string' }, - 'terminal.integrated.fontWeight': { - 'description': nls.localize('terminal.integrated.fontWeight', "Controls the font weight of the terminal, this defaults to editor.fontWeight's value."), - 'type': 'string' - }, 'terminal.integrated.fontLigatures': { 'description': nls.localize('terminal.integrated.fontLigatures', "Controls whether font ligatures are enabled in the terminal."), 'type': 'boolean', 'default': false }, 'terminal.integrated.fontSize': { - 'description': nls.localize('terminal.integrated.fontSize', "Controls the font size of the terminal, this defaults to editor.fontSize's value."), + 'description': nls.localize('terminal.integrated.fontSize', "Controls the font size in pixels of the terminal, this defaults to editor.fontSize's value."), 'type': 'number', 'default': 0 }, @@ -94,7 +90,7 @@ configurationRegistry.registerConfiguration({ 'default': platform.isMacintosh }, 'terminal.integrated.commandsToSkipShell': { - 'description': nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch quick open."), + 'description': nls.localize('terminal.integrated.commandsToSkipShell', "A set of command IDs whose keybindings will not be sent to the shell and instead always be handled by Code. This allows the use of keybindings that would normally be consumed by the shell to act the same as when the terminal is not focused, for example ctrl+p to launch Quick Open."), 'type': 'array', 'items': { 'type': 'string' @@ -130,35 +126,36 @@ registerSingleton(ITerminalService, TerminalService); )); // On mac cmd+` is reserved to cycle between windows, that's why the keybindings use WinCtrl +const category = nls.localize('terminalCategory', "Terminal"); let actionRegistry = Registry.as(ActionExtensions.WorkbenchActions); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), KillTerminalAction.LABEL); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(KillTerminalAction, KillTerminalAction.ID, KillTerminalAction.LABEL), 'Terminal: Kill the Active Terminal Instance', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CopyTerminalSelectionAction, CopyTerminalSelectionAction.ID, CopyTerminalSelectionAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C, // Don't apply to Mac since cmd+c works mac: { primary: null } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), CopyTerminalSelectionAction.LABEL); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Copy Selection', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(CreateNewTerminalAction, CreateNewTerminalAction.ID, CreateNewTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyMod.Shift | KeyCode.US_BACKTICK } -}), CreateNewTerminalAction.LABEL); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalAction, FocusTerminalAction.ID, FocusTerminalAction.LABEL), FocusTerminalAction.LABEL); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), KillTerminalAction.LABEL); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), KillTerminalAction.LABEL); +}), 'Terminal: Create New Integrated Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusTerminalAction, FocusTerminalAction.ID, FocusTerminalAction.LABEL), 'Terminal: Focus Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextTerminalAction, FocusNextTerminalAction.ID, FocusNextTerminalAction.LABEL), 'Terminal: Focus Next Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousTerminalAction, FocusPreviousTerminalAction.ID, FocusPreviousTerminalAction.LABEL), 'Terminal: Focus Previous Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(TerminalPasteAction, TerminalPasteAction.ID, TerminalPasteAction.LABEL, { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_V, // Don't apply to Mac since cmd+v works mac: { primary: null } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), CopyTerminalSelectionAction.LABEL); -actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), RunSelectedTextInTerminalAction.LABEL); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Paste into Active Terminal', category); +actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(RunSelectedTextInTerminalAction, RunSelectedTextInTerminalAction.ID, RunSelectedTextInTerminalAction.LABEL), 'Terminal: Run Selected Text In Active Terminal', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ToggleTerminalAction, ToggleTerminalAction.ID, ToggleTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.US_BACKTICK, mac: { primary: KeyMod.WinCtrl | KeyCode.US_BACKTICK } -}), 'View: ' + ToggleTerminalAction.LABEL, nls.localize('viewCategory', "View")); +}), 'View: Toggle Integrated Terminal', nls.localize('viewCategory', "View")); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollDownTerminalAction, ScrollDownTerminalAction.ID, ScrollDownTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.DownArrow, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.DownArrow } -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), ScrollDownTerminalAction.LABEL); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Down', category); actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ScrollUpTerminalAction, ScrollUpTerminalAction.ID, ScrollUpTerminalAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.UpArrow, linux: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.UpArrow }, -}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), ScrollUpTerminalAction.LABEL); +}, KEYBINDING_CONTEXT_TERMINAL_FOCUS), 'Terminal: Scroll Up', category); diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts index 68e37e2f519..4ea36de6357 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminal.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminal.ts @@ -43,7 +43,6 @@ export interface ITerminalConfiguration { }, cursorBlinking: boolean, fontFamily: string, - fontWeight: string, fontLigatures: boolean, fontSize: number, lineHeight: number, @@ -88,5 +87,6 @@ export interface ITerminalService { export interface ITerminalPanel { closeTerminalById(terminalId: number): TPromise; + focus(): void; sendTextToActiveTerminal(text: string, addNewLine: boolean): void; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts index e93a1522c7b..ee8a601ff1e 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalActions.ts @@ -29,7 +29,7 @@ export class ToggleTerminalAction extends Action { export class KillTerminalAction extends Action { public static ID = 'workbench.action.terminal.kill'; - public static LABEL = nls.localize('workbench.action.terminal.kill', "Terminal: Kill the Active Terminal Instance"); + public static LABEL = nls.localize('workbench.action.terminal.kill', "Kill the Active Terminal Instance"); public static PANEL_LABEL = nls.localize('workbench.action.terminal.kill.short', "Kill Terminal"); constructor( @@ -52,7 +52,7 @@ export class KillTerminalAction extends Action { export class CopyTerminalSelectionAction extends Action { public static ID = 'workbench.action.terminal.copySelection'; - public static LABEL = nls.localize('workbench.action.terminal.copySelection', "Terminal: Copy Selection"); + public static LABEL = nls.localize('workbench.action.terminal.copySelection', "Copy Selection"); constructor( id: string, label: string, @@ -69,7 +69,7 @@ export class CopyTerminalSelectionAction extends Action { export class CreateNewTerminalAction extends Action { public static ID = 'workbench.action.terminal.new'; - public static LABEL = nls.localize('workbench.action.terminal.new', "Terminal: Create New Integrated Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.new', "Create New Integrated Terminal"); public static PANEL_LABEL = nls.localize('workbench.action.terminal.new.short', "New Terminal"); constructor( @@ -88,7 +88,7 @@ export class CreateNewTerminalAction extends Action { export class FocusTerminalAction extends Action { public static ID = 'workbench.action.terminal.focus'; - public static LABEL = nls.localize('workbench.action.terminal.focus', "Terminal: Focus Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.focus', "Focus Terminal"); constructor( id: string, label: string, @@ -105,7 +105,7 @@ export class FocusTerminalAction extends Action { export class FocusNextTerminalAction extends Action { public static ID = 'workbench.action.terminal.focusNext'; - public static LABEL = nls.localize('workbench.action.terminal.focusNext', "Terminal: Focus Next Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.focusNext', "Focus Next Terminal"); constructor( id: string, label: string, @@ -122,7 +122,7 @@ export class FocusNextTerminalAction extends Action { export class FocusPreviousTerminalAction extends Action { public static ID = 'workbench.action.terminal.focusPrevious'; - public static LABEL = nls.localize('workbench.action.terminal.focusPrevious', "Terminal: Focus Previous Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.focusPrevious', "Focus Previous Terminal"); constructor( id: string, label: string, @@ -138,7 +138,7 @@ export class FocusPreviousTerminalAction extends Action { export class TerminalPasteAction extends Action { public static ID = 'workbench.action.terminal.paste'; - public static LABEL = nls.localize('workbench.action.terminal.paste', "Terminal: Paste into Active Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.paste', "Paste into Active Terminal"); constructor( id: string, label: string, @@ -155,7 +155,7 @@ export class TerminalPasteAction extends Action { export class RunSelectedTextInTerminalAction extends Action { public static ID = 'workbench.action.terminal.runSelectedText'; - public static LABEL = nls.localize('workbench.action.terminal.runSelectedText', "Terminal: Run Selected Text In Active Terminal"); + public static LABEL = nls.localize('workbench.action.terminal.runSelectedText', "Run Selected Text In Active Terminal"); constructor( id: string, label: string, @@ -172,7 +172,7 @@ export class RunSelectedTextInTerminalAction extends Action { export class SwitchTerminalInstanceAction extends Action { public static ID = 'workbench.action.terminal.switchTerminalInstance'; - public static LABEL = nls.localize('workbench.action.terminal.switchTerminalInstance', "Terminal: Switch Terminal Instance"); + public static LABEL = nls.localize('workbench.action.terminal.switchTerminalInstance', "Switch Terminal Instance"); constructor( id: string, label: string, @@ -210,7 +210,7 @@ export class SwitchTerminalInstanceActionItem extends SelectActionItem { export class ScrollDownTerminalAction extends Action { public static ID = 'workbench.action.terminal.scrollDown'; - public static LABEL = nls.localize('workbench.action.terminal.scrollDown', "Terminal: Scroll Down"); + public static LABEL = nls.localize('workbench.action.terminal.scrollDown', "Scroll Down"); constructor( id: string, label: string, @@ -227,7 +227,7 @@ export class ScrollDownTerminalAction extends Action { export class ScrollUpTerminalAction extends Action { public static ID = 'workbench.action.terminal.scrollUp'; - public static LABEL = nls.localize('workbench.action.terminal.scrollUp', "Terminal: Scroll Up"); + public static LABEL = nls.localize('workbench.action.terminal.scrollUp', "Scroll Up"); constructor( id: string, label: string, diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts index c3faaa8fa3b..605ce1d809c 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalConfigHelper.ts @@ -71,7 +71,6 @@ const DEFAULT_ANSI_COLORS = { export interface ITerminalFont { fontFamily: string; - fontWeight: string; fontSize: string; lineHeight: number; charWidth: number; @@ -100,7 +99,7 @@ export class TerminalConfigHelper { return DEFAULT_ANSI_COLORS[baseThemeId]; } - private measureFont(fontFamily: string, fontWeight: string,fontSize: number, lineHeight: number): ITerminalFont { + private measureFont(fontFamily: string, fontSize: number, lineHeight: number): ITerminalFont { // Create charMeasureElement if it hasn't been created or if it was orphaned by its parent if (!this.charMeasureElement || !this.charMeasureElement.parentElement) { this.charMeasureElement = this.panelContainer.div().getHTMLElement(); @@ -108,7 +107,6 @@ export class TerminalConfigHelper { let style = this.charMeasureElement.style; style.display = 'block'; style.fontFamily = fontFamily; - style.fontWeight = fontWeight; style.fontSize = fontSize + 'px'; style.height = Math.floor(lineHeight * fontSize) + 'px'; this.charMeasureElement.innerText = 'X'; @@ -118,7 +116,6 @@ export class TerminalConfigHelper { let charHeight = Math.ceil(rect.height); return { fontFamily, - fontWeight, fontSize: fontSize + 'px', lineHeight, charWidth, @@ -127,7 +124,7 @@ export class TerminalConfigHelper { } /** - * Gets the font information based on the terminal.integrated.fontFamily, terminal.integrated.fontWeight + * Gets the font information based on the terminal.integrated.fontFamily * terminal.integrated.fontSize, terminal.integrated.lineHeight configuration properties */ public getFont(): ITerminalFont { @@ -135,14 +132,13 @@ export class TerminalConfigHelper { let editorConfig = this.configurationService.getConfiguration(); let fontFamily = terminalConfig.fontFamily || editorConfig.editor.fontFamily; - let fontWeight = terminalConfig.fontWeight || editorConfig.editor.fontWeight; let fontSize = this.toInteger(terminalConfig.fontSize, 0) || editorConfig.editor.fontSize; if (fontSize <= 0) { fontSize = DefaultConfig.editor.fontSize; } let lineHeight = this.toInteger(terminalConfig.lineHeight, DEFAULT_LINE_HEIGHT); - return this.measureFont(fontFamily, fontWeight, fontSize, lineHeight <= 0 ? DEFAULT_LINE_HEIGHT : lineHeight); + return this.measureFont(fontFamily, fontSize, lineHeight <= 0 ? DEFAULT_LINE_HEIGHT : lineHeight); } public getFontLigaturesEnabled(): boolean { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts index 9327986397f..3828eaceb91 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalPanel.ts @@ -330,7 +330,6 @@ export class TerminalPanel extends Panel implements ITerminalPanel { if (!this.font || this.fontsDiffer(this.font, newFont)) { this.fontStyleElement.innerHTML = '.monaco-workbench .panel.integrated-terminal .xterm {' + `font-family: ${newFont.fontFamily};` + - `font-weight: ${newFont.fontWeight};` + `font-size: ${newFont.fontSize};` + `line-height: ${newFont.lineHeight};` + '}'; @@ -344,7 +343,6 @@ export class TerminalPanel extends Panel implements ITerminalPanel { return a.charHeight !== b.charHeight || a.charWidth !== b.charWidth || a.fontFamily !== b.fontFamily || - a.fontWeight !== b.fontWeight || a.fontSize !== b.fontSize || a.lineHeight !== b.lineHeight; } diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 73b79b5332e..11cdcefdaa0 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -69,7 +69,6 @@ export class TerminalService implements ITerminalService { return this.show(false).then((terminalPanel) => { this.activeTerminalIndex = index; terminalPanel.setActiveTerminal(this.activeTerminalIndex); - terminalPanel.focus(); this._onActiveInstanceChanged.fire(); }); } @@ -188,6 +187,7 @@ export class TerminalService implements ITerminalService { // to be stored for when createNew is called from TerminalPanel.create. This has to work // like this as TerminalPanel.setVisible must create a terminal if there is none due to how // the TerminalPanel is restored on launch if it was open previously. + if (processCount === 0 && !name) { name = this.nextTerminalName; this.nextTerminalName = undefined; @@ -196,6 +196,12 @@ export class TerminalService implements ITerminalService { } return this.focus().then((terminalPanel) => { + // If the terminal panel has not been initialized yet skip this, the terminal will be + // created via a call from TerminalPanel.setVisible + if (terminalPanel === null) { + return; + } + // Only create a new process if none have been created since toggling the terminal // panel. This happens when createNew is called when the panel is either empty or no yet // created. @@ -289,7 +295,7 @@ export class TerminalService implements ITerminalService { let locale = this.configHelper.isSetLocaleVariables() ? platform.locale : undefined; let env = TerminalService.createTerminalEnv(process.env, this.configHelper.getShell(), this.contextService.getWorkspace(), locale); let terminalProcess = { - title: name, + title: name ? name : '', process: cp.fork('./terminalProcess', [], { env: env, cwd: URI.parse(path.dirname(require.toUrl('./terminalProcess'))).fsPath @@ -303,7 +309,7 @@ export class TerminalService implements ITerminalService { // Only listen for process title changes when a name is not provided terminalProcess.process.on('message', (message) => { if (message.type === 'title') { - terminalProcess.title = message.content; + terminalProcess.title = message.content ? message.content : ''; this._onInstanceTitleChanged.fire(); } }); diff --git a/src/vs/workbench/parts/terminal/test/terminalConfigHelper.test.ts b/src/vs/workbench/parts/terminal/test/terminalConfigHelper.test.ts index 69a4898b418..91e2eb51dc2 100644 --- a/src/vs/workbench/parts/terminal/test/terminalConfigHelper.test.ts +++ b/src/vs/workbench/parts/terminal/test/terminalConfigHelper.test.ts @@ -61,40 +61,6 @@ suite('Workbench - TerminalConfigHelper', () => { assert.equal(configHelper.getFont().fontFamily, 'foo', 'editor.fontFamily should be the fallback when terminal.integrated.fontFamily not set'); }); - test('TerminalConfigHelper - getFont fontWeight', function () { - let configurationService: IConfigurationService; - let configHelper: TerminalConfigHelper; - - configurationService = new MockConfigurationService({ - editor: { - fontFamily: 'foo', - fontWeight: 'lighter' - }, - terminal: { - integrated: { - fontFamily: 'bar', - fontWeight: 'bold' - } - } - }); - configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture); - assert.equal(configHelper.getFont().fontWeight, 'bold', 'terminal.integrated.fontWeight should be selected over editor.fontWeight'); - - configurationService = new MockConfigurationService({ - editor: { - fontFamily: 'foo', - fontWeight: 'lighter' - }, - terminal: { - integrated: { - fontFamily: 0 - } - } - }); - configHelper = new TerminalConfigHelper(Platform.Linux, configurationService, fixture); - assert.equal(configHelper.getFont().fontWeight, 'lighter', 'editor.fontWeight should be the fallback when terminal.integrated.fontWeight not set'); - }); - test('TerminalConfigHelper - getFont fontSize', function () { let configurationService: IConfigurationService; let configHelper: TerminalConfigHelper; diff --git a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts index de5aa1674d9..f4bdc21f7fa 100644 --- a/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts +++ b/src/vs/workbench/parts/themes/electron-browser/themes.contribution.ts @@ -42,11 +42,16 @@ class SelectColorThemeAction extends Action { const currentThemeId = this.themeService.getColorTheme(); const currentTheme = themes.filter(theme => theme.id === currentThemeId)[0]; +const pickInMarketPlace = findInMarketplacePick(this.viewletService, 'category:themes'); + const picks: IPickOpenEntry[] = themes .map(theme => ({ id: theme.id, label: theme.label, description: theme.description })) .sort((t1, t2) => t1.label.localeCompare(t2.label)); const selectTheme = (theme, broadcast) => { + if (theme === pickInMarketPlace) { + theme = currentTheme; + } this.themeService.setColorTheme(theme.id, broadcast) .done(null, err => this.messageService.show(Severity.Info, localize('problemChangingTheme', "Problem loading theme: {0}", err.message))); }; @@ -56,22 +61,7 @@ class SelectColorThemeAction extends Action { const delayer = new Delayer(100); if (this.extensionGalleryService.isEnabled()) { - const run = () => { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('category:themes'); - viewlet.focus(); - }); - }; - - picks.push({ - id: 'themes.findmore', - label: localize('findMore', "Find more in the Marketplace..."), - separator: { border: true }, - alwaysShow: true, - run - }); + picks.push(pickInMarketPlace); } return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex }}) @@ -106,6 +96,8 @@ class SelectIconThemeAction extends Action { const currentThemeId = this.themeService.getFileIconTheme(); const currentTheme = themes.filter(theme => theme.id === currentThemeId)[0]; + const pickInMarketPlace = findInMarketplacePick(this.viewletService, 'tag:icon-theme'); + const picks: IPickOpenEntry[] = themes .map(theme => ({ id: theme.id, label: theme.label, description: theme.description })) .sort((t1, t2) => t1.label.localeCompare(t2.label)); @@ -113,6 +105,9 @@ class SelectIconThemeAction extends Action { picks.splice(0, 0, { id: '', label: localize('noIconThemeLabel', 'None'), description: localize('noIconThemeDesc', 'Disable file icons') }); const selectTheme = (theme, broadcast) => { + if (theme === pickInMarketPlace) { + theme = currentTheme; + } this.themeService.setFileIconTheme(theme && theme.id, broadcast) .done(null, err => this.messageService.show(Severity.Info, localize('problemChangingIconTheme', "Problem loading icon theme: {0}", err.message))); }; @@ -121,24 +116,10 @@ class SelectIconThemeAction extends Action { const autoFocusIndex = firstIndex(picks, p => p.id === currentThemeId); const delayer = new Delayer(100); - /* if (this.extensionGalleryService.isEnabled()) { - const run = () => { - return this.viewletService.openViewlet(VIEWLET_ID, true) - .then(viewlet => viewlet as IExtensionsViewlet) - .then(viewlet => { - viewlet.search('category:themes', true); // define our own category - viewlet.focus(); - }); - }; - picks.push({ - id: 'themes.findmoreiconthemes', - label: localize('findMoreIconThemes', "Find more in the Marketplace..."), - separator: { border: true }, - alwaysShow: true, - run - }); - }*/ + if (this.extensionGalleryService.isEnabled()) { + picks.push(pickInMarketPlace); + } return this.quickOpenService.pick(picks, { placeHolder, autoFocus: { autoFocusIndex }}) .then( @@ -150,6 +131,19 @@ class SelectIconThemeAction extends Action { } } +function findInMarketplacePick(viewletService: IViewletService, query: string) { + return { + id: 'themes.findmore', + label: localize('findMore', "Find more in the Marketplace..."), + separator: { border: true }, + alwaysShow: true, + run: () => viewletService.openViewlet(VIEWLET_ID, true).then(viewlet => { + (viewlet).search(query); + viewlet.focus(); + }) + }; +} + const category = localize('preferences', "Preferences"); const colorThemeDescriptor = new SyncActionDescriptor(SelectColorThemeAction, SelectColorThemeAction.ID, SelectColorThemeAction.LABEL); diff --git a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts index a8ef7ea585e..e23f56134c6 100644 --- a/src/vs/workbench/parts/update/electron-browser/update.contribution.ts +++ b/src/vs/workbench/parts/update/electron-browser/update.contribution.ts @@ -45,8 +45,8 @@ export class UpdateContribution implements IWorkbenchContribution { messageService.show(Severity.Info, { message: nls.localize('releaseNotes', "Welcome to {0} v{1}! Would you like to read the Release Notes?", product.nameLong, pkg.version), actions: [ - CloseAction, - ShowReleaseNotesAction(product.releaseNotesUrl, true) + ShowReleaseNotesAction(product.releaseNotesUrl, true), + CloseAction ] }); }, 0); @@ -58,8 +58,8 @@ export class UpdateContribution implements IWorkbenchContribution { messageService.show(Severity.Info, { message: nls.localize('licenseChanged', "Our license terms have changed, please go through them.", product.nameLong, pkg.version), actions: [ - CloseAction, - LinkAction('update.showLicense', nls.localize('license', "Read License"), product.licenseUrl) + LinkAction('update.showLicense', nls.localize('license', "Read License"), product.licenseUrl), + CloseAction ] }); }, 0); @@ -73,16 +73,16 @@ export class UpdateContribution implements IWorkbenchContribution { messageService.show(Severity.Info, { message: nls.localize('insiderBuilds', "Insider builds are becoming daily builds!", product.nameLong, pkg.version), actions: [ - CloseAction, - new Action('update.neverAgain', nls.localize('neverShowAgain', "Never Show Again"), '', true, () => { - storageService.store(UpdateContribution.INSIDER_KEY, false, StorageScope.GLOBAL); - return TPromise.as(null); - }), new Action('update.insiderBuilds', nls.localize('readmore', "Read More"), '', true, () => { shell.openExternal('http://go.microsoft.com/fwlink/?LinkID=798816'); storageService.store(UpdateContribution.INSIDER_KEY, false, StorageScope.GLOBAL); return TPromise.as(null); - }) + }), + new Action('update.neverAgain', nls.localize('neverShowAgain', "Never Show Again"), '', true, () => { + storageService.store(UpdateContribution.INSIDER_KEY, false, StorageScope.GLOBAL); + return TPromise.as(null); + }), + CloseAction, ] }); }, 0); diff --git a/src/vs/workbench/services/configuration/common/configuration.ts b/src/vs/workbench/services/configuration/common/configuration.ts index 07fd4c8b53e..196059dc78b 100644 --- a/src/vs/workbench/services/configuration/common/configuration.ts +++ b/src/vs/workbench/services/configuration/common/configuration.ts @@ -10,9 +10,9 @@ export const CONFIG_DEFAULT_NAME = 'settings'; export const WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME = '.vscode'; export const WORKSPACE_CONFIG_DEFAULT_PATH = '.vscode/settings.json'; -export const IWorkbenchConfigurationService = createDecorator('configurationService'); +export const IWorkspaceConfigurationService = createDecorator('configurationService'); -export interface IWorkbenchConfigurationService extends IConfigurationService { +export interface IWorkspaceConfigurationService extends IConfigurationService { /** * Returns iff the workspace has configuration or not. @@ -22,9 +22,9 @@ export interface IWorkbenchConfigurationService extends IConfigurationService { /** * Override for the IConfigurationService#lookup() method that adds information about workspace settings. */ - lookup(key: string): IWorkbenchConfigurationValue; + lookup(key: string): IWorkspaceConfigurationValue; } -export interface IWorkbenchConfigurationValue extends IConfigurationValue { +export interface IWorkspaceConfigurationValue extends IConfigurationValue { workspace: T; } \ No newline at end of file diff --git a/src/vs/workbench/services/configuration/node/configurationService.ts b/src/vs/workbench/services/configuration/node/configurationService.ts index 26dea2a9262..0ec373f9c78 100644 --- a/src/vs/workbench/services/configuration/node/configurationService.ts +++ b/src/vs/workbench/services/configuration/node/configurationService.ts @@ -21,7 +21,7 @@ import errors = require('vs/base/common/errors'); import {IConfigFile, consolidate, newConfigFile} from 'vs/workbench/services/configuration/common/model'; import {IConfigurationServiceEvent, getConfigurationValue} from 'vs/platform/configuration/common/configuration'; import {ConfigurationService as BaseConfigurationService} from 'vs/platform/configuration/node/configurationService'; -import {IWorkbenchConfigurationService, IWorkbenchConfigurationValue, CONFIG_DEFAULT_NAME, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME} from 'vs/workbench/services/configuration/common/configuration'; +import {IWorkspaceConfigurationService, IWorkspaceConfigurationValue, CONFIG_DEFAULT_NAME, WORKSPACE_CONFIG_FOLDER_DEFAULT_NAME} from 'vs/workbench/services/configuration/common/configuration'; import {EventType as FileEventType, FileChangeType, FileChangesEvent} from 'vs/platform/files/common/files'; import Event, {Emitter} from 'vs/base/common/event'; @@ -39,7 +39,7 @@ interface IContent { /** * Wraps around the basic configuration service and adds knowledge about workspace settings. */ -export class WorkspaceConfigurationService implements IWorkbenchConfigurationService, IDisposable { +export class WorkspaceConfigurationService implements IWorkspaceConfigurationService, IDisposable { public _serviceBrand: any; @@ -113,7 +113,7 @@ export class WorkspaceConfigurationService implements IWorkbenchConfigurationSer return section ? this.cachedConfig[section] : this.cachedConfig; } - public lookup(key: string): IWorkbenchConfigurationValue { + public lookup(key: string): IWorkspaceConfigurationValue { const configurationValue = this.baseConfigurationService.lookup(key); return { diff --git a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts index 617f0240313..4891bda547a 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationEditingService.test.ts @@ -12,10 +12,9 @@ import fs = require('fs'); import * as json from 'vs/base/common/json'; import {TPromise} from 'vs/base/common/winjs.base'; import {Registry} from 'vs/platform/platform'; -import {ParsedArgs} from 'vs/code/node/argv'; +import {ParsedArgs, parseArgs} from 'vs/platform/environment/node/argv'; import {WorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {EnvironmentService} from 'vs/platform/environment/node/environmentService'; -import {parseArgs} from 'vs/code/node/argv'; import extfs = require('vs/base/node/extfs'); import {TestEventService, TestEditorService} from 'vs/test/utils/servicesTestUtils'; import uuid = require('vs/base/common/uuid'); diff --git a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts index 6b386af47d3..88b6bed4a9e 100644 --- a/src/vs/workbench/services/configuration/test/node/configurationService.test.ts +++ b/src/vs/workbench/services/configuration/test/node/configurationService.test.ts @@ -11,10 +11,10 @@ import path = require('path'); import fs = require('fs'); import {TPromise} from 'vs/base/common/winjs.base'; import {Registry} from 'vs/platform/platform'; -import {ParsedArgs} from 'vs/code/node/argv'; +import {ParsedArgs} from 'vs/platform/environment/node/argv'; import {WorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {EnvironmentService} from 'vs/platform/environment/node/environmentService'; -import {parseArgs} from 'vs/code/node/argv'; +import {parseArgs} from 'vs/platform/environment/node/argv'; import extfs = require('vs/base/node/extfs'); import {TestEventService} from 'vs/test/utils/servicesTestUtils'; import uuid = require('vs/base/common/uuid'); @@ -185,24 +185,6 @@ suite('WorkspaceConfigurationService - Node', () => { }); }); - test('global change triggers event', (done: () => void) => { - createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { - return createService(workspaceDir, globalSettingsFile).then(service => { - service.onDidUpdateConfiguration(event => { - const config = service.getConfiguration<{ testworkbench: { editor: { icons: boolean } } }>(); - assert.equal(config.testworkbench.editor.icons, true); - assert.equal(event.config.testworkbench.editor.icons, true); - - service.dispose(); - - cleanUp(done); - }); - - fs.writeFileSync(globalSettingsFile, '{ "testworkbench.editor.icons": true }'); - }); - }); - }); - test('workspace change triggers event', (done: () => void) => { createWorkspace((workspaceDir, globalSettingsFile, cleanUp) => { const workspaceContextService = new WorkspaceContextService({ resource: URI.file(workspaceDir) }); diff --git a/src/vs/workbench/services/files/electron-browser/fileService.ts b/src/vs/workbench/services/files/electron-browser/fileService.ts index bf722511185..15cb35d152e 100644 --- a/src/vs/workbench/services/files/electron-browser/fileService.ts +++ b/src/vs/workbench/services/files/electron-browser/fileService.ts @@ -6,21 +6,25 @@ import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; -import {IDisposable} from 'vs/base/common/lifecycle'; +import {IDisposable, dispose} from 'vs/base/common/lifecycle'; import paths = require('vs/base/common/paths'); import encoding = require('vs/base/node/encoding'); import errors = require('vs/base/common/errors'); import strings = require('vs/base/common/strings'); import uri from 'vs/base/common/uri'; import timer = require('vs/base/common/timer'); +import {asFileEditorInput} from 'vs/workbench/common/editor'; import {IFileService, IFilesConfiguration, IResolveFileOptions, IFileStat, IContent, IStreamContent, IImportResult, IResolveContentOptions, IUpdateContentOptions} from 'vs/platform/files/common/files'; import {FileService as NodeFileService, IFileServiceOptions, IEncodingOverride} from 'vs/workbench/services/files/node/fileService'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {IEventService} from 'vs/platform/event/common/event'; import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {Action} from 'vs/base/common/actions'; +import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IMessageService, IMessageWithAction, Severity} from 'vs/platform/message/common/message'; import {IEnvironmentService} from 'vs/platform/environment/common/environment'; +import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; +import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; import {shell} from 'electron'; @@ -33,19 +37,26 @@ export class FileService implements IFileService { private raw: IFileService; - private configurationChangeListenerUnbind: IDisposable; + private toUnbind: IDisposable[]; + private activeOutOfWorkspaceWatchers: { [resource: string]: boolean; }; constructor( @IConfigurationService private configurationService: IConfigurationService, @IEventService private eventService: IEventService, @IWorkspaceContextService private contextService: IWorkspaceContextService, + @IWorkbenchEditorService private editorService: IWorkbenchEditorService, @IEnvironmentService private environmentService: IEnvironmentService, + @IEditorGroupService private editorGroupService: IEditorGroupService, + @ILifecycleService private lifecycleService: ILifecycleService, @IMessageService private messageService: IMessageService ) { + this.toUnbind = []; + this.activeOutOfWorkspaceWatchers = Object.create(null); + const configuration = this.configurationService.getConfiguration(); // adjust encodings - let encodingOverride: IEncodingOverride[] = []; + const encodingOverride: IEncodingOverride[] = []; encodingOverride.push({ resource: uri.file(environmentService.appSettingsHome), encoding: encoding.UTF8 }); if (this.contextService.getWorkspace()) { encodingOverride.push({ resource: uri.file(paths.join(this.contextService.getWorkspace().resource.fsPath, '.vscode')), encoding: encoding.UTF8 }); @@ -57,7 +68,7 @@ export class FileService implements IFileService { } // build config - let fileServiceConfig: IFileServiceOptions = { + const fileServiceConfig: IFileServiceOptions = { errorLogger: (msg: string) => this.onFileServiceError(msg), encoding: configuration.files && configuration.files.encoding, encodingOverride: encodingOverride, @@ -66,7 +77,7 @@ export class FileService implements IFileService { }; // create service - let workspace = this.contextService.getWorkspace(); + const workspace = this.contextService.getWorkspace(); this.raw = new NodeFileService(workspace ? workspace.resource.fsPath : void 0, fileServiceConfig, this.eventService); // Listeners @@ -93,8 +104,44 @@ export class FileService implements IFileService { private registerListeners(): void { - // Config Changes - this.configurationChangeListenerUnbind = this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config)); + // Config changes + this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationChange(e.config))); + + // Editor changing + this.toUnbind.push(this.editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); + + // Lifecycle + this.lifecycleService.onShutdown(this.dispose, this); + } + + private onEditorsChanged(): void { + this.handleOutOfWorkspaceWatchers(); + } + + private handleOutOfWorkspaceWatchers(): void { + const visibleOutOfWorkspaceResources = this.editorService.getVisibleEditors().map(editor => { + return asFileEditorInput(editor.input, true); + }).filter(input => { + return !!input && !this.contextService.isInsideWorkspace(input.getResource()); + }).map(input => { + return input.getResource().toString(); + }); + + // Handle no longer visible out of workspace resources + Object.keys(this.activeOutOfWorkspaceWatchers).forEach(watchedResource => { + if (visibleOutOfWorkspaceResources.indexOf(watchedResource) < 0) { + this.unwatchFileChanges(watchedResource); + delete this.activeOutOfWorkspaceWatchers[watchedResource]; + } + }); + + // Handle newly visible out of workspace resources + visibleOutOfWorkspaceResources.forEach(resourceToWatch => { + if (!this.activeOutOfWorkspaceWatchers[resourceToWatch]) { + this.watchFileChanges(uri.parse(resourceToWatch)); + this.activeOutOfWorkspaceWatchers[resourceToWatch] = true; + } + }); } private onConfigurationChange(configuration: IFilesConfiguration): void { @@ -114,8 +161,8 @@ export class FileService implements IFileService { } public resolveContent(resource: uri, options?: IResolveContentOptions): TPromise { - let contentId = resource.toString(); - let timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Load {0}', contentId)); + const contentId = resource.toString(); + const timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Load {0}', contentId)); return this.raw.resolveContent(resource, options).then((result) => { timerEvent.stop(); @@ -125,8 +172,8 @@ export class FileService implements IFileService { } public resolveStreamContent(resource: uri, options?: IResolveContentOptions): TPromise { - let contentId = resource.toString(); - let timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Load {0}', contentId)); + const contentId = resource.toString(); + const timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Load {0}', contentId)); return this.raw.resolveStreamContent(resource, options).then((result) => { timerEvent.stop(); @@ -140,7 +187,7 @@ export class FileService implements IFileService { } public updateContent(resource: uri, value: string, options?: IUpdateContentOptions): TPromise { - let timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Save {0}', resource.toString())); + const timerEvent = timer.start(timer.Topic.WORKBENCH, strings.format('Save {0}', resource.toString())); return this.raw.updateContent(resource, value, options).then((result) => { timerEvent.stop(); @@ -182,14 +229,14 @@ export class FileService implements IFileService { } private doMoveItemToTrash(resource: uri): TPromise { - let workspace = this.contextService.getWorkspace(); + const workspace = this.contextService.getWorkspace(); if (!workspace) { return TPromise.wrapError('Need a workspace to use this'); } - let absolutePath = resource.fsPath; + const absolutePath = resource.fsPath; - let result = shell.moveItemToTrash(absolutePath); + const result = shell.moveItemToTrash(absolutePath); if (!result) { return TPromise.wrapError(new Error(nls.localize('trashFailed', "Failed to move '{0}' to the trash", paths.basename(absolutePath)))); } @@ -230,12 +277,13 @@ export class FileService implements IFileService { } public dispose(): void { + this.toUnbind = dispose(this.toUnbind); - // Listeners - if (this.configurationChangeListenerUnbind) { - this.configurationChangeListenerUnbind.dispose(); - this.configurationChangeListenerUnbind = null; + // Dispose watchers if any + for (const key in this.activeOutOfWorkspaceWatchers) { + this.unwatchFileChanges(key); } + this.activeOutOfWorkspaceWatchers = Object.create(null); // Dispose service this.raw.dispose(); diff --git a/src/vs/workbench/services/files/node/fileService.ts b/src/vs/workbench/services/files/node/fileService.ts index 3c08e83af0c..f49b9b0b109 100644 --- a/src/vs/workbench/services/files/node/fileService.ts +++ b/src/vs/workbench/services/files/node/fileService.ts @@ -148,7 +148,7 @@ export class FileService implements IFileService { } private doResolveContent(resource: uri, options: IResolveContentOptions, contentResolver: (resource: uri, etag?: string, enc?: string) => TPromise): TPromise { - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); // Guard early against attempts to resolve an invalid file path if (resource.scheme !== 'file' || !resource.fsPath) { @@ -160,7 +160,7 @@ export class FileService implements IFileService { // 1.) detect mimes return nfcall(mime.detectMimesFromFile, absolutePath).then((detected: mime.IMimeAndEncoding): TPromise => { - let isText = detected.mimes.indexOf(baseMime.MIME_BINARY) === -1; + const isText = detected.mimes.indexOf(baseMime.MIME_BINARY) === -1; // Return error early if client only accepts text and this is not text if (options && options.acceptTextOnly && !isText) { @@ -230,9 +230,9 @@ export class FileService implements IFileService { } public resolveContents(resources: uri[]): TPromise { - let limiter = new Limiter(FileService.MAX_DEGREE_OF_PARALLEL_FS_OPS); + const limiter = new Limiter(FileService.MAX_DEGREE_OF_PARALLEL_FS_OPS); - let contentPromises = []>[]; + const contentPromises = []>[]; resources.forEach(resource => { contentPromises.push(limiter.queue(() => this.resolveFileContent(resource).then(content => content, error => TPromise.as(null /* ignore errors gracefully */)))); }); @@ -243,7 +243,7 @@ export class FileService implements IFileService { } public updateContent(resource: uri, value: string, options: IUpdateContentOptions = Object.create(null)): TPromise { - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); // 1.) check file return this.checkFile(absolutePath, options).then(exists => { @@ -256,7 +256,7 @@ export class FileService implements IFileService { // 2.) create parents as needed return createParentsPromise.then(() => { - let encodingToWrite = this.getEncoding(resource, options.encoding); + const encodingToWrite = this.getEncoding(resource, options.encoding); let addBomPromise: TPromise = TPromise.as(false); // UTF_16 BE and LE as well as UTF_8 with BOM always have a BOM @@ -284,7 +284,7 @@ export class FileService implements IFileService { // Otherwise use encoding lib else { - let encoded = encoding.encode(value, encodingToWrite, { addBOM: addBom }); + const encoded = encoding.encode(value, encodingToWrite, { addBOM: addBom }); writeFilePromise = pfs.writeFileAndFlush(absolutePath, encoded); } @@ -306,7 +306,7 @@ export class FileService implements IFileService { public createFolder(resource: uri): TPromise { // 1.) create folder - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); return pfs.mkdirp(absolutePath).then(() => { // 2.) resolve @@ -315,7 +315,7 @@ export class FileService implements IFileService { } public rename(resource: uri, newName: string): TPromise { - let newPath = paths.join(paths.dirname(resource.fsPath), newName); + const newPath = paths.join(paths.dirname(resource.fsPath), newName); return this.moveFile(resource, uri.file(newPath)); } @@ -329,8 +329,8 @@ export class FileService implements IFileService { } private moveOrCopyFile(source: uri, target: uri, keepCopy: boolean, overwrite: boolean): TPromise { - let sourcePath = this.toAbsolutePath(source); - let targetPath = this.toAbsolutePath(target); + const sourcePath = this.toAbsolutePath(source); + const targetPath = this.toAbsolutePath(target); // 1.) move / copy return this.doMoveOrCopyFile(sourcePath, targetPath, keepCopy, overwrite).then(() => { @@ -344,8 +344,8 @@ export class FileService implements IFileService { // 1.) check if target exists return pfs.exists(targetPath).then(exists => { - let isCaseRename = sourcePath.toLowerCase() === targetPath.toLowerCase(); - let isSameFile = sourcePath === targetPath; + const isCaseRename = sourcePath.toLowerCase() === targetPath.toLowerCase(); + const isSameFile = sourcePath === targetPath; // Return early with conflict if target exists and we are not told to overwrite if (exists && !isCaseRename && !overwrite) { @@ -383,9 +383,9 @@ export class FileService implements IFileService { } public importFile(source: uri, targetFolder: uri): TPromise { - let sourcePath = this.toAbsolutePath(source); - let targetResource = uri.file(paths.join(targetFolder.fsPath, paths.basename(source.fsPath))); - let targetPath = this.toAbsolutePath(targetResource); + const sourcePath = this.toAbsolutePath(source); + const targetResource = uri.file(paths.join(targetFolder.fsPath, paths.basename(source.fsPath))); + const targetPath = this.toAbsolutePath(targetResource); // 1.) resolve return pfs.stat(sourcePath).then(stat => { @@ -403,7 +403,7 @@ export class FileService implements IFileService { } public del(resource: uri): TPromise { - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); return nfcall(extfs.del, absolutePath, this.tmpPath); } @@ -429,7 +429,7 @@ export class FileService implements IFileService { } private toStatResolver(resource: uri): TPromise { - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); return pfs.stat(absolutePath).then(stat => { return new StatResolver(resource, stat.isDirectory(), stat.mtime.getTime(), stat.size, this.options.verboseLogging); @@ -437,7 +437,7 @@ export class FileService implements IFileService { } private resolveFileStreamContent(resource: uri, etag?: string, enc?: string): TPromise { - let absolutePath = this.toAbsolutePath(resource); + const absolutePath = this.toAbsolutePath(resource); return this.resolve(resource).then((model): TPromise => { @@ -455,11 +455,11 @@ export class FileService implements IFileService { }); } - let fileEncoding = this.getEncoding(model.resource, enc); + const fileEncoding = this.getEncoding(model.resource, enc); const reader = fs.createReadStream(absolutePath).pipe(encoding.decodeStream(fileEncoding)); // decode takes care of stripping any BOMs from the file content - let content: IStreamContent = model; + const content: IStreamContent = model; content.value = reader; content.encoding = fileEncoding; // make sure to store the encoding in the model to restore it later when writing @@ -471,7 +471,7 @@ export class FileService implements IFileService { return this.resolveFileStreamContent(resource, etag, enc).then((streamContent) => { return new TPromise((c, e) => { let done = false; - let chunks: string[] = []; + const chunks: string[] = []; streamContent.value.on('data', buf => { chunks.push(buf); @@ -485,7 +485,7 @@ export class FileService implements IFileService { }); streamContent.value.on('end', () => { - let content: IContent = streamContent; + const content: IContent = streamContent; content.value = chunks.join(''); if (!done) { @@ -500,7 +500,7 @@ export class FileService implements IFileService { private getEncoding(resource: uri, preferredEncoding?: string): string { let fileEncoding: string; - let override = this.getEncodingOverride(resource); + const override = this.getEncodingOverride(resource); if (override) { fileEncoding = override; } else if (preferredEncoding) { @@ -519,7 +519,7 @@ export class FileService implements IFileService { private getEncodingOverride(resource: uri): string { if (resource && this.options.encodingOverride && this.options.encodingOverride.length) { for (let i = 0; i < this.options.encodingOverride.length; i++) { - let override = this.options.encodingOverride[i]; + const override = this.options.encodingOverride[i]; // check if the resource is a child of the resource with override and use // the provided encoding in that case @@ -553,7 +553,7 @@ export class FileService implements IFileService { } let mode = stat.mode; - let readonly = !(mode & 128); + const readonly = !(mode & 128); // Throw if file is readonly and we are not instructed to overwrite if (readonly && !options.overwriteReadonly) { @@ -579,7 +579,7 @@ export class FileService implements IFileService { public watchFileChanges(resource: uri): void { assert.ok(resource && resource.scheme === 'file', 'Invalid resource for watching: ' + resource); - let fsPath = resource.fsPath; + const fsPath = resource.fsPath; // Create or get watcher for provided path let watcher = this.activeFileChangesWatchers[resource.toString()]; @@ -607,11 +607,11 @@ export class FileService implements IFileService { // handle emit through delayer to accommodate for bulk changes this.fileChangesWatchDelayer.trigger(() => { - let buffer = this.undeliveredRawFileChangesEvents; + const buffer = this.undeliveredRawFileChangesEvents; this.undeliveredRawFileChangesEvents = []; // Normalize - let normalizedEvents = normalize(buffer); + const normalizedEvents = normalize(buffer); // Emit this.eventEmitter.emit(EventType.FILE_CHANGES, toFileChangesEvent(normalizedEvents)); @@ -625,9 +625,9 @@ export class FileService implements IFileService { public unwatchFileChanges(resource: uri): void; public unwatchFileChanges(path: string): void; public unwatchFileChanges(arg1: any): void { - let resource = (typeof arg1 === 'string') ? uri.parse(arg1) : arg1; + const resource = (typeof arg1 === 'string') ? uri.parse(arg1) : arg1; - let watcher = this.activeFileChangesWatchers[resource.toString()]; + const watcher = this.activeFileChangesWatchers[resource.toString()]; if (watcher) { watcher.close(); delete this.activeFileChangesWatchers[resource.toString()]; @@ -641,7 +641,7 @@ export class FileService implements IFileService { } for (let key in this.activeFileChangesWatchers) { - let watcher = this.activeFileChangesWatchers[key]; + const watcher = this.activeFileChangesWatchers[key]; watcher.close(); } this.activeFileChangesWatchers = Object.create(null); @@ -675,7 +675,7 @@ export class StatResolver { public resolve(options: IResolveFileOptions): TPromise { // General Data - let fileStat: IFileStat = { + const fileStat: IFileStat = { resource: this.resource, isDirectory: this.isDirectory, hasChildren: undefined, @@ -729,9 +729,9 @@ export class StatResolver { // for each file in the folder flow.parallel(files, (file: string, clb: (error: Error, children: IFileStat) => void) => { - let fileResource = uri.file(paths.resolve(absolutePath, file)); + const fileResource = uri.file(paths.resolve(absolutePath, file)); let fileStat: fs.Stats; - let $this = this; + const $this = this; flow.sequence( function onError(error: Error): void { @@ -759,7 +759,7 @@ export class StatResolver { }, function resolve(childCount: number): void { - let childStat: IFileStat = { + const childStat: IFileStat = { resource: fileResource, isDirectory: fileStat.isDirectory(), hasChildren: childCount > 0, diff --git a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts index ddaad3e6634..55fc34a0001 100644 --- a/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/unix/chokidarWatcherService.ts @@ -27,7 +27,7 @@ export class ChokidarWatcherService implements IWatcherService { private spamWarningLogged:boolean; public watch(request: IWatcherRequest): TPromise { - let watcherOpts: chokidar.IOptions = { + const watcherOpts: chokidar.IOptions = { ignoreInitial: true, ignorePermissionErrors: true, followSymlinks: true, // this is the default of chokidar and supports file events through symlinks @@ -36,7 +36,7 @@ export class ChokidarWatcherService implements IWatcherService { binaryInterval: 1000 }; - let chokidarWatcher = chokidar.watch(request.basePath, watcherOpts); + const chokidarWatcher = chokidar.watch(request.basePath, watcherOpts); // Detect if for some reason the native watcher library fails to load if (process.platform === 'darwin' && !chokidarWatcher.options.useFsEvents) { @@ -44,7 +44,7 @@ export class ChokidarWatcherService implements IWatcherService { } let undeliveredFileEvents: watcher.IRawFileChange[] = []; - let fileEventDelayer = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY); + const fileEventDelayer = new ThrottledDelayer(ChokidarWatcherService.FS_EVENT_DELAY); return new TPromise((c, e, p) => { chokidarWatcher.on('all', (type: string, path: string) => { @@ -86,7 +86,7 @@ export class ChokidarWatcherService implements IWatcherService { } // Check for spam - let now = Date.now(); + const now = Date.now(); if (undeliveredFileEvents.length === 0) { this.spamWarningLogged = false; this.spamCheckStartTime = now; @@ -100,11 +100,11 @@ export class ChokidarWatcherService implements IWatcherService { // Delay and send buffer fileEventDelayer.trigger(() => { - let events = undeliveredFileEvents; + const events = undeliveredFileEvents; undeliveredFileEvents = []; // Broadcast to clients normalized - let res = watcher.normalize(events); + const res = watcher.normalize(events); p(res); // Logging diff --git a/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts index 08ddaf75048..e0c7b3cce70 100644 --- a/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts +++ b/src/vs/workbench/services/files/node/watcher/win32/csharpWatcherService.ts @@ -31,25 +31,25 @@ export class OutOfProcessWin32FolderWatcher { } private startWatcher(): void { - let args = [this.watchedFolder]; + const args = [this.watchedFolder]; if (this.verboseLogging) { args.push('-verbose'); } this.handle = cp.spawn(uri.parse(require.toUrl('vs/workbench/services/files/node/watcher/win32/CodeHelper.exe')).fsPath, args); - let stdoutLineDecoder = new decoder.LineDecoder(); + const stdoutLineDecoder = new decoder.LineDecoder(); // Events over stdout this.handle.stdout.on('data', (data: NodeBuffer) => { // Collect raw events from output - let rawEvents: IRawFileChange[] = []; + const rawEvents: IRawFileChange[] = []; stdoutLineDecoder.write(data).forEach((line) => { - let eventParts = line.split('|'); + const eventParts = line.split('|'); if (eventParts.length === 2) { - let changeType = Number(eventParts[0]); - let absolutePath = eventParts[1]; + const changeType = Number(eventParts[0]); + const absolutePath = eventParts[1]; // File Change Event (0 Changed, 1 Created, 2 Deleted) if (changeType >= 0 && changeType < 3) { diff --git a/src/vs/workbench/services/history/browser/history.ts b/src/vs/workbench/services/history/browser/history.ts index 9fafe92ad3c..c6f89c674d2 100644 --- a/src/vs/workbench/services/history/browser/history.ts +++ b/src/vs/workbench/services/history/browser/history.ts @@ -14,7 +14,7 @@ import {IEditor as IBaseEditor} from 'vs/platform/editor/common/editor'; import {EditorInput, IGroupEvent, IEditorRegistry, Extensions} from 'vs/workbench/common/editor'; import {BaseTextEditor} from 'vs/workbench/browser/parts/editor/textEditor'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import {IHistoryService} from 'vs/workbench/services/history/common/history'; +import {IRecentlyClosedEditor, IHistoryService} from 'vs/workbench/services/history/common/history'; import {Selection} from 'vs/editor/common/core/selection'; import {IEditorInput, ITextEditorOptions} from 'vs/platform/editor/common/editor'; import {IEventService} from 'vs/platform/event/common/event'; @@ -228,7 +228,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic private currentFileEditorState: EditorState; private history: IEditorInput[]; - private recentlyClosed: IEditorInput[]; + private recentlyClosed: IRecentlyClosedEditor[]; private loaded: boolean; private registry: IEditorRegistry; @@ -268,7 +268,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic // Remove all inputs matching and add as last recently closed this.removeFromRecentlyClosed(editor); - this.recentlyClosed.push(editor); + this.recentlyClosed.push({ editor, index: event.index }); // Bounding if (this.recentlyClosed.length > HistoryService.MAX_RECENTLY_CLOSED_EDITORS) { @@ -283,7 +283,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic } } - public popLastClosedEditor(): IEditorInput { + public popLastClosedEditor(): IRecentlyClosedEditor { this.ensureLoaded(); return this.recentlyClosed.pop(); @@ -531,14 +531,14 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic let restored = false; this.recentlyClosed.forEach((e, i) => { - if (e.matches(input)) { + if (e.editor.matches(input)) { if (!restored) { restoredInput = this.restoreInput(input); restored = true; } if (restoredInput) { - this.recentlyClosed[i] = restoredInput; + this.recentlyClosed[i].editor = restoredInput; } else { this.stack.splice(i, 1); } @@ -573,7 +573,7 @@ export class HistoryService extends BaseHistoryService implements IHistoryServic private removeFromRecentlyClosed(input: IEditorInput): void { this.recentlyClosed.forEach((e, i) => { - if (e.matches(input)) { + if (e.editor.matches(input)) { this.recentlyClosed.splice(i, 1); } }); diff --git a/src/vs/workbench/services/history/common/history.ts b/src/vs/workbench/services/history/common/history.ts index 6ccfd7f1566..072da8a1379 100644 --- a/src/vs/workbench/services/history/common/history.ts +++ b/src/vs/workbench/services/history/common/history.ts @@ -9,6 +9,11 @@ import {IEditorInput} from 'vs/platform/editor/common/editor'; export const IHistoryService = createDecorator('historyService'); +export class IRecentlyClosedEditor { + editor: IEditorInput; + index: number; +} + export interface IHistoryService { _serviceBrand: ServiceIdentifier; @@ -16,7 +21,7 @@ export interface IHistoryService { /** * Removes and returns the last closed editor if any. */ - popLastClosedEditor(): IEditorInput; + popLastClosedEditor(): IRecentlyClosedEditor; /** * Navigate forwards in history. diff --git a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts index 4fc5808c708..68ff9196890 100644 --- a/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/electron-browser/lifecycleService.ts @@ -6,7 +6,7 @@ import {TPromise} from 'vs/base/common/winjs.base'; import Severity from 'vs/base/common/severity'; -import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {ILifecycleService, ShutdownEvent} from 'vs/platform/lifecycle/common/lifecycle'; import {IMessageService} from 'vs/platform/message/common/message'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; @@ -81,7 +81,7 @@ export class LifecycleService implements ILifecycleService { } }, err => { // error, treated like a veto, done - this.messageService.show(Severity.Error, errors.toErrorMessage(err)); + this.messageService.show(Severity.Error, toErrorMessage(err)); lazyValue = true; })); } diff --git a/src/vs/workbench/services/message/browser/messageService.ts b/src/vs/workbench/services/message/browser/messageService.ts index 43954eb7c51..dfa6643cbbb 100644 --- a/src/vs/workbench/services/message/browser/messageService.ts +++ b/src/vs/workbench/services/message/browser/messageService.ts @@ -5,9 +5,9 @@ 'use strict'; import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import types = require('vs/base/common/types'); -import {MessageList, Severity as BaseSeverity} from 'vs/base/browser/ui/messagelist/messageList'; -import {Identifiers} from 'vs/workbench/common/constants'; +import {MessageList, Severity as BaseSeverity} from 'vs/workbench/services/message/browser/messagelist/messageList'; import {IDisposable} from 'vs/base/common/lifecycle'; import {IMessageService, IMessageWithAction, IConfirmation, Severity} from 'vs/platform/message/common/message'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; @@ -30,9 +30,10 @@ export class WorkbenchMessageService implements IMessageService { private messageBuffer: IBufferedMessage[]; constructor( + container: HTMLElement, private telemetryService: ITelemetryService ) { - this.handler = new MessageList(Identifiers.WORKBENCH_CONTAINER, telemetryService); + this.handler = new MessageList(container, telemetryService); this.messageBuffer = []; this.canShowMessages = true; @@ -116,7 +117,7 @@ export class WorkbenchMessageService implements IMessageService { // Show in Console if (sev === Severity.Error) { - console.error(errors.toErrorMessage(message, true)); + console.error(toErrorMessage(message, true)); } // Show in Global Handler diff --git a/src/vs/base/browser/ui/messagelist/messageList.css b/src/vs/workbench/services/message/browser/messagelist/messageList.css similarity index 100% rename from src/vs/base/browser/ui/messagelist/messageList.css rename to src/vs/workbench/services/message/browser/messagelist/messageList.css diff --git a/src/vs/base/browser/ui/messagelist/messageList.ts b/src/vs/workbench/services/message/browser/messagelist/messageList.ts similarity index 87% rename from src/vs/base/browser/ui/messagelist/messageList.ts rename to src/vs/workbench/services/message/browser/messagelist/messageList.ts index 48e21c24b29..2ec9067bd48 100644 --- a/src/vs/base/browser/ui/messagelist/messageList.ts +++ b/src/vs/workbench/services/message/browser/messagelist/messageList.ts @@ -8,9 +8,9 @@ import 'vs/css!./messageList'; import nls = require('vs/nls'); import {TPromise} from 'vs/base/common/winjs.base'; -import {Builder, withElementById, $} from 'vs/base/browser/builder'; +import {Builder, $} from 'vs/base/browser/builder'; import DOM = require('vs/base/browser/dom'); -import errors = require('vs/base/common/errors'); +import {toErrorMessage} from 'vs/base/common/errorMessage'; import aria = require('vs/base/browser/ui/aria/aria'); import types = require('vs/base/common/types'); import Event, {Emitter} from 'vs/base/common/event'; @@ -59,17 +59,17 @@ export class MessageList { private messageListPurger: TPromise; private messageListContainer: Builder; - private containerElementId: string; + private container: HTMLElement; private options: IMessageListOptions; private usageLogger: IUsageLogger; private _onMessagesShowing: Emitter; private _onMessagesCleared: Emitter; - constructor(containerElementId: string, usageLogger?: IUsageLogger, options: IMessageListOptions = { purgeInterval: MessageList.DEFAULT_MESSAGE_PURGER_INTERVAL, maxMessages: MessageList.DEFAULT_MAX_MESSAGES, maxMessageLength: MessageList.DEFAULT_MAX_MESSAGE_LENGTH }) { + constructor(container: HTMLElement, usageLogger?: IUsageLogger, options: IMessageListOptions = { purgeInterval: MessageList.DEFAULT_MESSAGE_PURGER_INTERVAL, maxMessages: MessageList.DEFAULT_MAX_MESSAGES, maxMessageLength: MessageList.DEFAULT_MAX_MESSAGE_LENGTH }) { this.messages = []; this.messageListPurger = null; - this.containerElementId = containerElementId; + this.container = container; this.usageLogger = usageLogger; this.options = options; @@ -92,14 +92,14 @@ export class MessageList { public showMessage(severity: Severity, message: IMessageWithAction): () => void; public showMessage(severity: Severity, message: any): () => void { if (Array.isArray(message)) { - let closeFns: Function[] = []; + const closeFns: Function[] = []; message.forEach((msg: any) => closeFns.push(this.showMessage(severity, msg))); return () => closeFns.forEach((fn) => fn()); } // Return only if we are unable to extract a message text - let messageText = this.getMessageText(message); + const messageText = this.getMessageText(message); if (!messageText || typeof messageText !== 'string') { return () => {/* empty */ }; } @@ -114,7 +114,7 @@ export class MessageList { } if (message instanceof Error) { - return errors.toErrorMessage(message, false); + return toErrorMessage(message, false); } if ((message).message) { @@ -162,10 +162,7 @@ export class MessageList { } private renderMessages(animate: boolean, delta: number): void { - let container = withElementById(this.containerElementId); - if (!container) { - return; // Cannot build container for messages yet, return - } + const container = $(this.container); // Lazily create, otherwise clear old if (!this.messageListContainer) { @@ -182,7 +179,7 @@ export class MessageList { // Render Messages as List Items $(this.messageListContainer).ul({ 'class': 'message-list' }, (ul: Builder) => { - let messages = this.prepareMessages(); + const messages = this.prepareMessages(); if (messages.length > 0) { this._onMessagesShowing.fire(); } else { @@ -207,14 +204,14 @@ export class MessageList { container.li({ class: 'message-list-entry message-list-entry-with-action' }, (li) => { // Actions (if none provided, add one default action to hide message) - let messageActions = this.getMessageActions(message); + const messageActions = this.getMessageActions(message); li.div({ class: 'actions-container' }, (actionContainer) => { - for (let i = messageActions.length - 1; i >= 0; i--) { - let action = messageActions[i]; + for (let i = 0; i < messageActions.length; i++) { + const action = messageActions[i]; actionContainer.div({ class: 'message-action' }, (div) => { div.a({ class: 'action-button', tabindex: '0', role: 'button' }).text(action.label).on([DOM.EventType.CLICK, DOM.EventType.KEY_DOWN], (e) => { if (e instanceof KeyboardEvent) { - let event = new StandardKeyboardEvent(e); + const event = new StandardKeyboardEvent(e); if (!event.equals(CommonKeybindings.ENTER) && !event.equals(CommonKeybindings.SPACE)) { return; // Only handle Enter/Escape for keyboard access } @@ -241,17 +238,17 @@ export class MessageList { }); // Text - let text = message.text.substr(0, this.options.maxMessageLength); + const text = message.text.substr(0, this.options.maxMessageLength); li.div({ class: 'message-left-side' }, (div) => { div.addClass('message-overflow-ellipsis'); // Severity indicator - let sev = message.severity; - let label = (sev === Severity.Error) ? nls.localize('error', "Error") : (sev === Severity.Warning) ? nls.localize('warning', "Warn") : nls.localize('info', "Info"); + const sev = message.severity; + const label = (sev === Severity.Error) ? nls.localize('error', "Error") : (sev === Severity.Warning) ? nls.localize('warning', "Warn") : nls.localize('info', "Info"); $().span({ class: 'message-left-side severity ' + ((sev === Severity.Error) ? 'app-error' : (sev === Severity.Warning) ? 'app-warning' : 'app-info'), text: label }).appendTo(div); // Error message - let messageContentElement: HTMLElement = htmlRenderer.renderHtml({ + const messageContentElement: HTMLElement = htmlRenderer.renderHtml({ tagName: 'span', className: 'message-left-side', formattedText: text @@ -282,12 +279,12 @@ export class MessageList { private prepareMessages(): IMessageEntry[] { // Aggregate Messages by text to reduce their count - let messages: IMessageEntry[] = []; - let handledMessages: { [message: string]: number; } = {}; + const messages: IMessageEntry[] = []; + const handledMessages: { [message: string]: number; } = {}; let offset = 0; for (let i = 0; i < this.messages.length; i++) { - let message = this.messages[i]; + const message = this.messages[i]; if (types.isUndefinedOrNull(handledMessages[message.text])) { message.count = 1; messages.push(message); @@ -335,7 +332,7 @@ export class MessageList { let messageFound = false; for (let i = 0; i < this.messages.length; i++) { - let message = this.messages[i]; + const message = this.messages[i]; let hide = false; // Hide specific message @@ -373,7 +370,7 @@ export class MessageList { let counter = 0; for (let i = 0; i < this.messages.length; i++) { - let message = this.messages[i]; + const message = this.messages[i]; // Only purge infos and warnings and only if they are not providing actions if (message.severity !== Severity.Error && !message.actions) { diff --git a/src/vs/workbench/services/message/electron-browser/messageService.ts b/src/vs/workbench/services/message/electron-browser/messageService.ts index c19ca0c3df8..63475c4089d 100644 --- a/src/vs/workbench/services/message/electron-browser/messageService.ts +++ b/src/vs/workbench/services/message/electron-browser/messageService.ts @@ -16,10 +16,11 @@ import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; export class MessageService extends WorkbenchMessageService { constructor( + container: HTMLElement, @IWindowService private windowService: IWindowService, @ITelemetryService telemetryService: ITelemetryService ) { - super(telemetryService); + super(container, telemetryService); } public confirm(confirmation: IConfirmation): boolean { diff --git a/src/vs/workbench/services/part/common/partService.ts b/src/vs/workbench/services/part/common/partService.ts index 9b2a11072fd..d87d8e14370 100644 --- a/src/vs/workbench/services/part/common/partService.ts +++ b/src/vs/workbench/services/part/common/partService.ts @@ -100,4 +100,9 @@ export interface IPartService { * Removes a class from the workbench part. */ removeClass(clazz: string): void; + + /** + * Returns the identifier of the element that contains the workbench. + */ + getWorkbenchElementId(): string; } \ No newline at end of file diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index cea84b6e6c7..521199149ba 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -7,6 +7,7 @@ import * as childProcess from 'child_process'; import {StringDecoder} from 'string_decoder'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import fs = require('fs'); import paths = require('path'); import {Readable} from "stream"; @@ -129,7 +130,8 @@ export class FileWalker { if (platform.isMacintosh) { this.traversal = Traversal.MacFind; traverse = this.macFindTraversal; - } else if (platform.isWindows) { + // Disable 'dir' for now (#11181, #11179, #11183, #11182). + } else if (false && platform.isWindows) { this.traversal = Traversal.WindowsDir; traverse = this.windowsDirTraversal; } else if (platform.isLinux) { @@ -151,7 +153,9 @@ export class FileWalker { rootFolderDone(err); } else { // fallback - this.errors.push(String(err)); + const errorMessage = toErrorMessage(err); + console.error(errorMessage); + this.errors.push(errorMessage); this.nodeJSTraversal(rootFolder, onResult, rootFolderDone); } } else { @@ -181,6 +185,11 @@ export class FileWalker { relativeFiles.pop(); } + if (relativeFiles.length && relativeFiles[0].indexOf('\n') !== -1) { + done(new Error('Splitting up files failed')); + return; + } + this.matchFiles(rootFolder, relativeFiles, onResult); done(); @@ -188,7 +197,7 @@ export class FileWalker { } private windowsDirTraversal(rootFolder: string, onResult: (result: IRawFileMatch) => void, done: (err?: Error) => void): void { - const cmd = childProcess.spawn('cmd', ['/U', '/c', 'dir', '/s', '/b', '/a-d'], { cwd: rootFolder }); + const cmd = childProcess.spawn('cmd', ['/U', '/c', 'dir', '/s', '/b', '/a-d', rootFolder]); this.readStdout(cmd, 'ucs2', (err: Error, stdout?: string) => { if (err) { done(err); @@ -203,6 +212,11 @@ export class FileWalker { relativeFiles.pop(); } + if (relativeFiles.length && relativeFiles[0].indexOf('\n') !== -1) { + done(new Error('Splitting up files failed')); + return; + } + this.matchFiles(rootFolder, relativeFiles, onResult); done(); @@ -225,6 +239,11 @@ export class FileWalker { relativeFiles.pop(); } + if (relativeFiles.length && relativeFiles[0].indexOf('\n') !== -1) { + done(new Error('Splitting up files failed')); + return; + } + this.matchFiles(rootFolder, relativeFiles, onResult); done(); diff --git a/src/vs/workbench/services/search/node/textSearch.ts b/src/vs/workbench/services/search/node/textSearch.ts index c2b203f5278..b1901ab44f5 100644 --- a/src/vs/workbench/services/search/node/textSearch.ts +++ b/src/vs/workbench/services/search/node/textSearch.ts @@ -45,7 +45,7 @@ export class Engine implements ISearchEngine { this.rootFolders = config.rootFolders; this.extraFiles = config.extraFiles; this.walker = walker; - this.contentPattern = strings.createRegExp(config.contentPattern.pattern, config.contentPattern.isRegExp, config.contentPattern.isCaseSensitive, config.contentPattern.isWordMatch, true); + this.contentPattern = strings.createRegExp(config.contentPattern.pattern, config.contentPattern.isRegExp, {matchCase: config.contentPattern.isCaseSensitive, wholeWord: config.contentPattern.isWordMatch, multiline: false, global: true}); this.isCanceled = false; this.limitReached = false; this.maxResults = config.maxResults; diff --git a/src/vs/workbench/services/themes/common/themeService.ts b/src/vs/workbench/services/themes/common/themeService.ts index acd65a9b307..a783b1433c3 100644 --- a/src/vs/workbench/services/themes/common/themeService.ts +++ b/src/vs/workbench/services/themes/common/themeService.ts @@ -27,4 +27,19 @@ export interface IThemeData { label: string; description?: string; path: string; +} + +export interface IThemeDocument { + name: string; + include: string; + settings: IThemeSetting[]; +} + +export interface IThemeSetting { + name?: string; + scope?: string[]; + settings: IThemeSettingStyle[]; +} + +export interface IThemeSettingStyle { } \ No newline at end of file diff --git a/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts b/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts new file mode 100644 index 00000000000..5912bb9aeb5 --- /dev/null +++ b/src/vs/workbench/services/themes/electron-browser/stylesContributions.ts @@ -0,0 +1,335 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import {IThemeDocument, IThemeSetting, IThemeSettingStyle} from 'vs/workbench/services/themes/common/themeService'; +import {Color} from 'vs/base/common/color'; +import {getBaseThemeId, getSyntaxThemeId} from 'vs/platform/theme/common/themes'; + +interface ThemeGlobalSettings { + background?: string; + foreground?: string; + fontStyle?: string; + caret?: string; + invisibles?: string; + guide?: string; + + lineHighlight?: string; + rangeHighlight?: string; + + hoverHighlight?: string; + + selection?: string; + inactiveSelection?: string; + selectionHighlight?: string; + + findRangeHighlight?: string; + findMatchHighlight?: string; + currentFindMatchHighlight?: string; + + wordHighlight?: string; + wordHighlightStrong?: string; + + referenceHighlight?: string; + + activeLinkForeground?: string; +} + +class Theme { + + private selector: string; + private settings: IThemeSetting[]; + private globalSettings: ThemeGlobalSettings = null; + + constructor(private themeId: string, themeDocument: IThemeDocument) { + this.selector = `${getBaseThemeId(themeId)}.${getSyntaxThemeId(themeId)}`; + this.settings = themeDocument.settings; + let settings = this.settings[0]; + if (!settings.scope) { + this.globalSettings = settings.settings; + } + } + + public getSelector(): string { + return this.selector; + } + + public hasGlobalSettings(): boolean { + return !!this.globalSettings; + } + + public getGlobalSettings(): ThemeGlobalSettings { + return this.globalSettings; + } + + public getSettings(): IThemeSetting[] { + return this.settings; + } + +} + +abstract class StyleRules { + public abstract getCssRules(theme: Theme, cssRules: string[]): void; +} + +export class TokenStylesContribution { + + public contributeStyles(themeId: string, themeDocument: IThemeDocument, cssRules: string[]): void { + let theme = new Theme(themeId, themeDocument); + theme.getSettings().forEach((s: IThemeSetting, index, arr) => { + let scope: string | string[] = s.scope; + let settings = s.settings; + if (scope && settings) { + let rules = Array.isArray(scope) ? scope : scope.split(','); + let statements = this._settingsToStatements(settings); + rules.forEach(rule => { + rule = rule.trim().replace(/ /g, '.'); // until we have scope hierarchy in the editor dom: replace spaces with . + + cssRules.push(`.monaco-editor.${theme.getSelector()} .token.${rule} { ${statements} }`); + }); + } + }); + } + + private _settingsToStatements(settings: IThemeSettingStyle): string { + let statements: string[] = []; + + for (let settingName in settings) { + const value = settings[settingName]; + switch (settingName) { + case 'foreground': + let foreground = new Color(value); + statements.push(`color: ${foreground};`); + break; + case 'background': + // do not support background color for now, see bug 18924 + //let background = new Color(value); + //statements.push(`background-color: ${background};`); + break; + case 'fontStyle': + let segments = value.split(' '); + segments.forEach(s => { + switch (s) { + case 'italic': + statements.push(`font-style: italic;`); + break; + case 'bold': + statements.push(`font-weight: bold;`); + break; + case 'underline': + statements.push(`text-decoration: underline;`); + break; + } + }); + } + } + return statements.join(' '); + } +} + +export class EditorStylesContribution { + + public contributeStyles(themeId: string, themeDocument: IThemeDocument, cssRules: string[]) { + let editorStyleRules = [ + new EditorBackgroundStyleRules(), + new EditorForegroundStyleRules(), + new EditorCursorStyleRules(), + new EditorWhiteSpaceStyleRules(), + new EditorIndentGuidesStyleRules(), + new EditorLineHighlightStyleRules(), + new EditorSelectionStyleRules(), + new EditorWordHighlightStyleRules(), + new EditorFindStyleRules(), + new EditorReferenceSearchStyleRules(), + new EditorHoverHighlightStyleRules(), + new EditorLinkStyleRules() + ]; + let theme = new Theme(themeId, themeDocument); + if (theme.hasGlobalSettings()) { + editorStyleRules.forEach((editorStyleRule => { + editorStyleRule.getCssRules(theme, cssRules); + })); + } + } +} + +export class SearchViewStylesContribution { + + public contributeStyles(themeId: string, themeDocument: IThemeDocument, cssRules: string[]): void { + let theme = new Theme(themeId, themeDocument); + if (theme.hasGlobalSettings()) { + if (theme.getGlobalSettings().findMatchHighlight) { + let color = new Color(theme.getGlobalSettings().findMatchHighlight); + cssRules.push(`.${theme.getSelector()} .search-viewlet .findInFileMatch { background-color: ${color}; }`); + cssRules.push(`.${theme.getSelector()} .search-viewlet .highlight { background-color: ${color}; }`); + } + } + } +} + +abstract class EditorStyleRules extends StyleRules { + + protected addBackgroundColorRule(theme: Theme, selector: string, color: string | Color, rules: string[]): void { + if (color) { + color = color instanceof Color ? color : new Color(color); + rules.push(`.monaco-editor.${theme.getSelector()} ${selector} { background-color: ${color}; }`); + } + } + +} + +class EditorBackgroundStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + let themeSelector = theme.getSelector(); + if (theme.getGlobalSettings().background) { + let background = new Color(theme.getGlobalSettings().background); + this.addBackgroundColorRule(theme, '.monaco-editor-background', background, cssRules); + this.addBackgroundColorRule(theme, '.glyph-margin', background, cssRules); + cssRules.push(`.${themeSelector} .monaco-workbench .monaco-editor-background { background-color: ${background}; }`); + } + } +} + +class EditorForegroundStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + let themeSelector = theme.getSelector(); + if (theme.getGlobalSettings().foreground) { + let foreground = new Color(theme.getGlobalSettings().foreground); + cssRules.push(`.monaco-editor.${themeSelector} .token { color: ${foreground}; }`); + } + } +} + +class EditorHoverHighlightStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + this.addBackgroundColorRule(theme, '.hoverHighlight', theme.getGlobalSettings().hoverHighlight, cssRules); + } +} + +class EditorLinkStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + if (theme.getGlobalSettings().activeLinkForeground) { + cssRules.push(`.monaco-editor.${theme.getSelector()} .detected-link-active { color: ${new Color(theme.getGlobalSettings().activeLinkForeground)} !important; }`); + cssRules.push(`.monaco-editor.${theme.getSelector()} .goto-definition-link { color: ${new Color(theme.getGlobalSettings().activeLinkForeground)} !important; }`); + } + } +} + +class EditorSelectionStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + if (theme.getGlobalSettings().selection) { + this.addBackgroundColorRule(theme, '.focused .selected-text', theme.getGlobalSettings().selection, cssRules); + } + + if (theme.getGlobalSettings().inactiveSelection) { + this.addBackgroundColorRule(theme, '.selected-text', theme.getGlobalSettings().inactiveSelection, cssRules); + } else if (theme.getGlobalSettings().selection) { + let selection = new Color(theme.getGlobalSettings().selection); + this.addBackgroundColorRule(theme, '.selected-text', selection.transparent(0.5), cssRules); + } + + let selectionHighlightColor = this.getSelectionHighlightColor(theme); + if (selectionHighlightColor) { + this.addBackgroundColorRule(theme, '.focused .selectionHighlight', selectionHighlightColor, cssRules); + this.addBackgroundColorRule(theme, '.selectionHighlight', selectionHighlightColor.transparent(0.5), cssRules); + } + } + + private getSelectionHighlightColor(theme: Theme) { + if (theme.getGlobalSettings().selectionHighlight) { + return new Color(theme.getGlobalSettings().selectionHighlight); + } + + if (theme.getGlobalSettings().selection && theme.getGlobalSettings().background) { + let selection = new Color(theme.getGlobalSettings().selection); + let background = new Color(theme.getGlobalSettings().background); + return deriveLessProminentColor(selection, background); + } + + return null; + } +} + +class EditorWordHighlightStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + this.addBackgroundColorRule(theme, '.wordHighlight', theme.getGlobalSettings().wordHighlight, cssRules); + this.addBackgroundColorRule(theme, '.wordHighlightStrong', theme.getGlobalSettings().wordHighlightStrong, cssRules); + } +} + +class EditorFindStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + this.addBackgroundColorRule(theme, '.findMatch', theme.getGlobalSettings().findMatchHighlight, cssRules); + this.addBackgroundColorRule(theme, '.currentFindMatch', theme.getGlobalSettings().currentFindMatchHighlight, cssRules); + this.addBackgroundColorRule(theme, '.findScope', theme.getGlobalSettings().findRangeHighlight, cssRules); + } +} + +class EditorReferenceSearchStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + this.addBackgroundColorRule(theme, '.reference-zone-widget .ref-tree .referenceMatch', theme.getGlobalSettings().findMatchHighlight, cssRules); + this.addBackgroundColorRule(theme, '.reference-zone-widget .preview .reference-decoration', theme.getGlobalSettings().referenceHighlight, cssRules); + } +} + +class EditorLineHighlightStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + if (theme.getGlobalSettings().lineHighlight) { + cssRules.push(`.monaco-editor.${theme.getSelector()} .current-line { background-color: ${new Color(theme.getGlobalSettings().lineHighlight)}; border: none; }`); + } + this.addBackgroundColorRule(theme, '.rangeHighlight', theme.getGlobalSettings().rangeHighlight, cssRules); + } +} + +class EditorCursorStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + let themeSelector = theme.getSelector(); + if (theme.getGlobalSettings().caret) { + let caret = new Color(theme.getGlobalSettings().caret); + let oppositeCaret = caret.opposite(); + cssRules.push(`.monaco-editor.${themeSelector} .cursor { background-color: ${caret}; border-color: ${caret}; color: ${oppositeCaret}; }`); + } + } +} + +class EditorWhiteSpaceStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + let themeSelector = theme.getSelector(); + if (theme.getGlobalSettings().invisibles) { + let invisibles = new Color(theme.getGlobalSettings().invisibles); + cssRules.push(`.monaco-editor.${themeSelector} .token.whitespace { color: ${invisibles} !important; }`); + } + } +} + +class EditorIndentGuidesStyleRules extends EditorStyleRules { + public getCssRules(theme: Theme, cssRules: string[]): void { + let themeSelector = theme.getSelector(); + let color = this.getColor(theme.getGlobalSettings()); + if (color !== null) { + cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${color}; }`); + } + } + + private getColor(theme: ThemeGlobalSettings): Color { + if (theme.guide) { + return new Color(theme.guide); + } + if (theme.invisibles) { + return new Color(theme.invisibles); + } + return null; + } +} + +function deriveLessProminentColor(from: Color, backgroundColor: Color): Color { + let contrast = from.getContrast(backgroundColor); + if (contrast < 1.7 || contrast > 4.5) { + return null; + } + if (from.isDarkerThan(backgroundColor)) { + return Color.getLighterColor(from, backgroundColor, 0.4); + } + return Color.getDarkerColor(from, backgroundColor, 0.4); +} \ No newline at end of file diff --git a/src/vs/workbench/services/themes/electron-browser/themeService.ts b/src/vs/workbench/services/themes/electron-browser/themeService.ts index 37b4dd1ea02..35038feb248 100644 --- a/src/vs/workbench/services/themes/electron-browser/themeService.ts +++ b/src/vs/workbench/services/themes/electron-browser/themeService.ts @@ -11,8 +11,9 @@ import Json = require('vs/base/common/json'); import {IThemeExtensionPoint} from 'vs/platform/theme/common/themeExtensionPoint'; import {IExtensionService} from 'vs/platform/extensions/common/extensions'; import {ExtensionsRegistry, IExtensionMessageCollector} from 'vs/platform/extensions/common/extensionsRegistry'; -import {IThemeService, IThemeData} from 'vs/workbench/services/themes/common/themeService'; -import {getBaseThemeId, getSyntaxThemeId} from 'vs/platform/theme/common/themes'; +import {IThemeService, IThemeData, IThemeSetting, IThemeDocument} from 'vs/workbench/services/themes/common/themeService'; +import {TokenStylesContribution, EditorStylesContribution, SearchViewStylesContribution} from 'vs/workbench/services/themes/electron-browser/stylesContributions'; +import {getBaseThemeId} from 'vs/platform/theme/common/themes'; import {IWindowService} from 'vs/workbench/services/window/electron-browser/windowService'; import {IStorageService, StorageScope} from 'vs/platform/storage/common/storage'; import {ITelemetryService} from 'vs/platform/telemetry/common/telemetry'; @@ -100,29 +101,6 @@ let iconThemeExtPoint = ExtensionsRegistry.registerExtensionPoint { return pfs.readFile(fileSetPath).then(content => { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.toString(), errors); + let contentValue = Json.parse(content.toString(), errors); if (errors.length > 0) { return TPromise.wrapError(new Error(nls.localize('error.cannotparseicontheme', "Problems parsing file icons file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', ')))); } @@ -503,8 +481,10 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic qualifier = baseThemeClassName + ' ' + qualifier; } + let expanded = '.monaco-tree-row.expanded'; // workaround for #11453 + addSelector(`${qualifier} .folder-icon::before`, associations.folder); - addSelector(`${qualifier} .expanded .folder-icon::before`, associations.folderExpanded); + addSelector(`${qualifier} ${expanded} .folder-icon::before`, associations.folderExpanded); addSelector(`${qualifier} .file-icon::before`, associations.file); let folderNames = associations.folderNames; @@ -516,7 +496,13 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic let folderNamesExpanded = associations.folderNamesExpanded; if (folderNamesExpanded) { for (let folderName in folderNamesExpanded) { - addSelector(`${qualifier} .expanded .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNames[folderName]); + addSelector(`${qualifier} ${expanded} .${escapeCSS(folderName.toLowerCase())}-name-folder-icon.folder-icon::before`, folderNamesExpanded[folderName]); + } + } + let languageIds = associations.languageIds; + if (languageIds) { + for (let languageId in languageIds) { + addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); } } let fileExtensions = associations.fileExtensions; @@ -540,12 +526,6 @@ function _processIconThemeDocument(id: string, iconThemeDocumentPath: string, ic addSelector(`${qualifier} ${selectors.join('')}.file-icon::before`, fileNames[fileName]); } } - let languageIds = associations.languageIds; - if (languageIds) { - for (let languageId in languageIds) { - addSelector(`${qualifier} .${escapeCSS(languageId)}-lang-file-icon.file-icon::before`, languageIds[languageId]); - } - } } } collectSelectors(iconThemeDocument); @@ -621,11 +601,11 @@ function applyTheme(theme: IInternalThemeData, onApply: (theme:IInternalThemeDat }); } -function _loadThemeDocument(themePath: string) : TPromise { +function _loadThemeDocument(themePath: string) : TPromise { return pfs.readFile(themePath).then(content => { if (Paths.extname(themePath) === '.json') { let errors: Json.ParseError[] = []; - let contentValue = Json.parse(content.toString(), errors); + let contentValue = Json.parse(content.toString(), errors); if (errors.length > 0) { return TPromise.wrapError(new Error(nls.localize('error.cannotparsejson', "Problems parsing JSON theme file: {0}", errors.map(e => Json.getParseErrorMessage(e.error)).join(', ')))); } @@ -645,116 +625,19 @@ function _loadThemeDocument(themePath: string) : TPromise { }); } -function _processThemeObject(themeId: string, themeDocument: ThemeDocument): string { +function _processThemeObject(themeId: string, themeDocument: IThemeDocument): string { let cssRules: string[] = []; - - let themeSettings : ThemeSetting[] = themeDocument.settings; - let editorSettings : ThemeSettingStyle = { - background: void 0, - foreground: void 0, - caret: void 0, - invisibles: void 0, - guide: void 0, - lineHighlight: void 0, - selection: void 0 - }; - - let themeSelector = `${getBaseThemeId(themeId)}.${getSyntaxThemeId(themeId)}`; + let themeSettings : IThemeSetting[] = themeDocument.settings; if (Array.isArray(themeSettings)) { - themeSettings.forEach((s : ThemeSetting, index, arr) => { - if (index === 0 && !s.scope) { - editorSettings = s.settings; - } else { - let scope: string | string[] = s.scope; - let settings = s.settings; - if (scope && settings) { - let rules = Array.isArray(scope) ? scope : scope.split(','); - let statements = _settingsToStatements(settings); - rules.forEach(rule => { - rule = rule.trim().replace(/ /g, '.'); // until we have scope hierarchy in the editor dom: replace spaces with . - - cssRules.push(`.monaco-editor.${themeSelector} .token.${rule} { ${statements} }`); - }); - } - } - }); - } - - if (editorSettings.background) { - let background = new Color(editorSettings.background); - cssRules.push(`.monaco-editor.${themeSelector} .monaco-editor-background { background-color: ${background}; }`); - cssRules.push(`.monaco-editor.${themeSelector} .glyph-margin { background-color: ${background}; }`); - cssRules.push(`.${themeSelector} .monaco-workbench .monaco-editor-background { background-color: ${background}; }`); - } - if (editorSettings.foreground) { - let foreground = new Color(editorSettings.foreground); - cssRules.push(`.monaco-editor.${themeSelector} .token { color: ${foreground}; }`); - } - if (editorSettings.selection) { - let selection = new Color(editorSettings.selection); - cssRules.push(`.monaco-editor.${themeSelector} .focused .selected-text { background-color: ${selection}; }`); - cssRules.push(`.monaco-editor.${themeSelector} .selected-text { background-color: ${selection.transparent(0.5)}; }`); - } - if (editorSettings.lineHighlight) { - let lineHighlight = new Color(editorSettings.lineHighlight); - cssRules.push(`.monaco-editor.${themeSelector} .current-line { background-color: ${lineHighlight}; border:0; }`); - } - if (editorSettings.caret) { - let caret = new Color(editorSettings.caret); - let oppositeCaret = caret.opposite(); - cssRules.push(`.monaco-editor.${themeSelector} .cursor { background-color: ${caret}; border-color: ${caret}; color: ${oppositeCaret}; }`); - } - if (editorSettings.invisibles) { - let invisibles = new Color(editorSettings.invisibles); - cssRules.push(`.monaco-editor.${themeSelector} .token.whitespace { color: ${invisibles} !important; }`); - } - if (editorSettings.guide) { - let guide = new Color(editorSettings.guide); - cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${guide}; }`); - } else if (editorSettings.invisibles) { - let invisibles = new Color(editorSettings.invisibles); - cssRules.push(`.monaco-editor.${themeSelector} .lines-content .cigr { background: ${invisibles}; }`); + new TokenStylesContribution().contributeStyles(themeId, themeDocument, cssRules); + new EditorStylesContribution().contributeStyles(themeId, themeDocument, cssRules); + new SearchViewStylesContribution().contributeStyles(themeId, themeDocument, cssRules); } return cssRules.join('\n'); } -function _settingsToStatements(settings: ThemeSettingStyle): string { - let statements: string[] = []; - - for (let settingName in settings) { - const value = settings[settingName]; - switch (settingName) { - case 'foreground': - let foreground = new Color(value); - statements.push(`color: ${foreground};`); - break; - case 'background': - // do not support background color for now, see bug 18924 - //let background = new Color(value); - //statements.push(`background-color: ${background};`); - break; - case 'fontStyle': - let segments = value.split(' '); - segments.forEach(s => { - switch (s) { - case 'italic': - statements.push(`font-style: italic;`); - break; - case 'bold': - statements.push(`font-weight: bold;`); - break; - case 'underline': - statements.push(`text-decoration: underline;`); - break; - } - }); - } - } - return statements.join(' '); -} - let colorThemeRulesClassName = 'contributedColorTheme'; let iconThemeRulesClassName = 'contributedIconTheme'; @@ -771,60 +654,6 @@ function _applyRules(styleSheetContent: string, rulesClassName: string) { } } -interface RGBA { r: number; g: number; b: number; a: number; } - -class Color { - - private parsed: RGBA; - private str: string; - - constructor(arg: string | RGBA) { - if (typeof arg === 'string') { - this.parsed = Color.parse(arg); - } else { - this.parsed = arg; - } - this.str = null; - } - - private static parse(color: string): RGBA { - function parseHex(str: string) { - return parseInt('0x' + str); - } - - if (color.charAt(0) === '#' && color.length >= 7) { - let r = parseHex(color.substr(1, 2)); - let g = parseHex(color.substr(3, 2)); - let b = parseHex(color.substr(5, 2)); - let a = color.length === 9 ? parseHex(color.substr(7, 2)) / 0xff : 1; - return { r, g, b, a }; - } - return { r: 255, g: 0, b: 0, a: 1 }; - } - - public toString(): string { - if (!this.str) { - let p = this.parsed; - this.str = `rgba(${p.r}, ${p.g}, ${p.b}, ${+p.a.toFixed(2)})`; - } - return this.str; - } - - public transparent(factor: number): Color { - let p = this.parsed; - return new Color({ r: p.r, g: p.g, b: p.b, a: p.a * factor }); - } - - public opposite(): Color { - return new Color({ - r: 255 - this.parsed.r, - g: 255 - this.parsed.g, - b: 255 - this.parsed.b, - a : this.parsed.a - }); - } -} - const schemaId = 'vscode://schemas/icon-theme'; const schema: IJSONSchema = { type: 'object', @@ -901,6 +730,9 @@ const schema: IJSONSchema = { folderNames: { $ref: '#/definitions/folderNames' }, + folderNamesExpanded: { + $ref: '#/definitions/folderNamesExpanded' + }, fileExtensions: { $ref: '#/definitions/fileExtensions' }, @@ -1016,16 +848,14 @@ const schema: IJSONSchema = { $ref: '#/definitions/languageIds' }, light: { - $ref: '#/definitions/associations' + $ref: '#/definitions/associations', + description: nls.localize('schema.light', 'Optional associations for file icons in light color themes.') }, highContrast: { - $ref: '#/definitions/associations' + $ref: '#/definitions/associations', + description: nls.localize('schema.highContrast', 'Optional associations for file icons in high contrast color themes.') } - }, - required: [ - 'iconDefinitions', - 'file' - ] + } }; let schemaRegistry = Registry.as(JSONExtensions.JSONContribution); diff --git a/src/vs/workbench/services/thread/electron-browser/threadService.ts b/src/vs/workbench/services/thread/electron-browser/threadService.ts index 20bc1026c51..49883252fbc 100644 --- a/src/vs/workbench/services/thread/electron-browser/threadService.ts +++ b/src/vs/workbench/services/thread/electron-browser/threadService.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import {Action} from 'vs/base/common/actions'; -import {toErrorMessage} from 'vs/base/common/errors'; +import {toErrorMessage} from 'vs/base/common/errorMessage'; import {stringify} from 'vs/base/common/marshalling'; import * as objects from 'vs/base/common/objects'; import * as strings from 'vs/base/common/strings'; @@ -151,9 +151,7 @@ class ExtensionHostProcessManager { if (this.isExtensionDevelopmentHost && port) { this.windowService.broadcast({ channel: EXTENSION_ATTACH_BROADCAST_CHANNEL, - payload: { - port: port - } + payload: { port } }, this.environmentService.extensionDevelopmentPath /* target */); } diff --git a/src/vs/workbench/services/untitled/common/untitledEditorService.ts b/src/vs/workbench/services/untitled/common/untitledEditorService.ts index fe049315b4a..879952d4918 100644 --- a/src/vs/workbench/services/untitled/common/untitledEditorService.ts +++ b/src/vs/workbench/services/untitled/common/untitledEditorService.ts @@ -9,6 +9,7 @@ import {createDecorator, IInstantiationService} from 'vs/platform/instantiation/ import {EventType} from 'vs/base/common/events'; import arrays = require('vs/base/common/arrays'); import {UntitledEditorInput} from 'vs/workbench/common/editor/untitledEditorInput'; +import Event, {Emitter} from 'vs/base/common/event'; export const IUntitledEditorService = createDecorator('untitledEditorService'); @@ -16,6 +17,11 @@ export interface IUntitledEditorService { _serviceBrand: any; + /** + * Events for when untitled editors change (e.g. getting dirty, saved or reverted). + */ + onDidChangeDirty: Event; + /** * Returns the untitled editor input matching the provided resource. */ @@ -57,12 +63,20 @@ export interface IUntitledEditorService { } export class UntitledEditorService implements IUntitledEditorService { + public _serviceBrand: any; private static CACHE: { [resource: string]: UntitledEditorInput } = Object.create(null); private static KNOWN_ASSOCIATED_FILE_PATHS: { [resource: string]: boolean } = Object.create(null); + private _onDidChangeDirty: Emitter; + constructor(@IInstantiationService private instantiationService: IInstantiationService) { + this._onDidChangeDirty = new Emitter(); + } + + public get onDidChangeDirty(): Event { + return this._onDidChangeDirty.event; } public get(resource: URI): UntitledEditorInput { @@ -94,7 +108,7 @@ export class UntitledEditorService implements IUntitledEditorService { } public isDirty(resource: URI): boolean { - let input = this.get(resource); + const input = this.get(resource); return input && input.isDirty(); } @@ -137,12 +151,17 @@ export class UntitledEditorService implements IUntitledEditorService { } while (Object.keys(UntitledEditorService.CACHE).indexOf(resource.toString()) >= 0); } - let input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId); + const input = this.instantiationService.createInstance(UntitledEditorInput, resource, hasAssociatedFilePath, modeId); + + const listener = input.onDidChangeDirty(() => { + this._onDidChangeDirty.fire(resource); + }); // Remove from cache on dispose input.addOneTimeDisposableListener(EventType.DISPOSE, () => { delete UntitledEditorService.CACHE[input.getResource().toString()]; delete UntitledEditorService.KNOWN_ASSOCIATED_FILE_PATHS[input.getResource().toString()]; + listener.dispose(); }); // Add to cache diff --git a/src/vs/workbench/test/browser/parts/quickOpen/quickopen.perf.test.ts b/src/vs/workbench/test/browser/parts/quickOpen/quickopen.perf.test.ts index 92866933354..1d8a01ccf1a 100644 --- a/src/vs/workbench/test/browser/parts/quickOpen/quickopen.perf.test.ts +++ b/src/vs/workbench/test/browser/parts/quickOpen/quickopen.perf.test.ts @@ -7,9 +7,8 @@ import 'vs/workbench/parts/search/browser/search.contribution'; // load contributions import * as assert from 'assert'; -import {WorkspaceContextService} from 'vs/platform/workspace/common/workspace'; +import {WorkspaceContextService, IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; import {createSyncDescriptor} from 'vs/platform/instantiation/common/descriptors'; -import {ensureStaticPlatformServices, IEditorOverrideServices} from 'vs/editor/browser/standalone/standaloneServices'; import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService'; import {ISearchService} from 'vs/platform/search/common/search'; import {ITelemetryService, ITelemetryInfo} from 'vs/platform/telemetry/common/telemetry'; @@ -26,6 +25,11 @@ import {IEnvironmentService} from 'vs/platform/environment/common/environment'; import * as Timer from 'vs/base/common/timer'; import {TPromise} from 'vs/base/common/winjs.base'; import URI from 'vs/base/common/uri'; +import {InstantiationService} from 'vs/platform/instantiation/common/instantiationService'; +import {SimpleConfigurationService} from 'vs/editor/browser/standalone/simpleServices'; +import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; +import {ModelServiceImpl} from 'vs/editor/common/services/modelServiceImpl'; +import {IModelService} from 'vs/editor/common/services/modelService'; declare var __dirname: string; @@ -43,13 +47,12 @@ suite('QuickOpen performance', () => { const testWorkspacePath = testWorkspaceArg ? path.resolve(testWorkspaceArg) : __dirname; const telemetryService = new TestTelemetryService(); - const overrides: IEditorOverrideServices = { - contextService: new WorkspaceContextService({ resource: URI.file(testWorkspacePath) }), - telemetryService - }; - - const services = ensureStaticPlatformServices(overrides); - const instantiationService = services.instantiationService.createChild(new ServiceCollection( + const configurationService = new SimpleConfigurationService(); + const instantiationService = new InstantiationService(new ServiceCollection( + [ITelemetryService, telemetryService], + [IConfigurationService, new SimpleConfigurationService()], + [IModelService, new ModelServiceImpl(null, configurationService, null)], + [IWorkspaceContextService, new WorkspaceContextService({ resource: URI.file(testWorkspacePath) })], [IWorkbenchEditorService, new TestEditorService()], [IEditorGroupService, new TestEditorGroupService()], [IEnvironmentService, TestEnvironmentService], @@ -58,11 +61,11 @@ suite('QuickOpen performance', () => { )); const registry = Registry.as(Extensions.Quickopen); - const descriptors = registry.getDefaultQuickOpenHandlers(); - assert.strictEqual(descriptors.length, 1); + const descriptor = registry.getDefaultQuickOpenHandler(); + assert.ok(descriptor); function measure() { - return instantiationService.createInstance(descriptors[0]) + return instantiationService.createInstance(descriptor) .then((handler: QuickOpenHandler) => { handler.onOpen(); return handler.getResults('a').then(result => { @@ -76,7 +79,7 @@ suite('QuickOpen performance', () => { }).then(uncachedEvent => { return handler.getResults('ab').then(result => { const cachedEvent = popEvent(); - assert.ok(cachedEvent.data.symbols.fromCache, 'symbolsFromCache'); + assert.strictEqual(uncachedEvent.data.symbols.fromCache, false, 'symbols.fromCache'); assert.ok(cachedEvent.data.files.fromCache, 'filesFromCache'); handler.onClose(false); return [uncachedEvent, cachedEvent]; diff --git a/src/vs/workbench/test/browser/services.test.ts b/src/vs/workbench/test/browser/services.test.ts index f00471b6a3a..c942d30c79f 100644 --- a/src/vs/workbench/test/browser/services.test.ts +++ b/src/vs/workbench/test/browser/services.test.ts @@ -7,39 +7,25 @@ import * as assert from 'assert'; import {IAction, IActionItem} from 'vs/base/common/actions'; -import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; import {Promise, TPromise} from 'vs/base/common/winjs.base'; import paths = require('vs/base/common/paths'); import {IEditorControl} from 'vs/platform/editor/common/editor'; import URI from 'vs/base/common/uri'; -import {IModelService} from 'vs/editor/common/services/modelService'; -import {IModeService} from 'vs/editor/common/services/modeService'; -import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace'; -import {IStorageService} from 'vs/platform/storage/common/storage'; -import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; -import {ILifecycleService, NullLifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; -import {IFileService} from 'vs/platform/files/common/files'; -import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; -import PartService = require('vs/workbench/services/part/common/partService'); import {BaseEditor} from 'vs/workbench/browser/parts/editor/baseEditor'; import {EditorInput, EditorOptions, TextEditorOptions} from 'vs/workbench/common/editor'; import {StringEditorInput} from 'vs/workbench/common/editor/stringEditorInput'; import {StringEditorModel} from 'vs/workbench/common/editor/stringEditorModel'; import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; import {TextFileEditorModel} from 'vs/workbench/parts/files/common/editors/textFileEditorModel'; -import {ITextFileService} from 'vs/workbench/parts/files/common/files'; -import {createMockModelService, TestTextFileService, TestEventService, TestPartService, TestStorageService, TestConfigurationService, TestContextService, TestWorkspace, TestEditorService} from 'vs/test/utils/servicesTestUtils'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; import {Viewlet} from 'vs/workbench/browser/viewlet'; import {IPanel} from 'vs/workbench/common/panel'; -import {ITelemetryService, NullTelemetryService} from 'vs/platform/telemetry/common/telemetry'; -import {IUntitledEditorService, UntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; import {WorkbenchProgressService, ScopedService} from 'vs/workbench/services/progress/browser/progressService'; import {DelegatingWorkbenchEditorService, WorkbenchEditorService, IEditorPart} from 'vs/workbench/services/editor/browser/editorService'; import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService'; import {IPanelService} from 'vs/workbench/services/panel/common/panelService'; import {IViewlet} from 'vs/workbench/common/viewlet'; import {Position, Direction, IEditor} from 'vs/platform/editor/common/editor'; -import {IEventService} from 'vs/platform/event/common/event'; import {Emitter} from 'vs/base/common/event'; let activeViewlet: Viewlet = {}; @@ -284,73 +270,7 @@ class TestProgressBar { suite('Workbench UI Services', () => { test('WorkbenchEditorService', function () { - const TestFileService = { - resolveContent: function (resource) { - return TPromise.as({ - resource: resource, - value: 'Hello Html', - etag: 'index.txt', - mime: 'text/plain', - encoding: 'utf8', - mtime: new Date().getTime(), - name: paths.basename(resource.fsPath) - }); - }, - - resolveStreamContent: function (resource) { - return TPromise.as({ - resource: resource, - value: { - on: (event:string, callback:Function): void => { - if (event === 'data') { - callback('Hello Html'); - } - if (event === 'end') { - callback(); - } - } - }, - etag: 'index.txt', - mime: 'text/plain', - encoding: 'utf8', - mtime: new Date().getTime(), - name: paths.basename(resource.fsPath) - }); - }, - - updateContent: function (res) { - return TPromise.timeout(1).then(() => { - return { - resource: res, - etag: 'index.txt', - mime: 'text/plain', - encoding: 'utf8', - mtime: new Date().getTime(), - name: paths.basename(res.fsPath) - }; - }); - } - }; - - let editorService = new TestEditorService(function () { }); - let eventService = new TestEventService(); - let contextService = new TestContextService(TestWorkspace); - - let instantiationService= new TestInstantiationService(); - instantiationService.stub(IEventService, eventService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(ITelemetryService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IWorkbenchEditorService, editorService); - instantiationService.stub(PartService.IPartService, new TestPartService()); - instantiationService.stub(IModeService); - instantiationService.stub(IModelService, createMockModelService(instantiationService)); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IFileService, TestFileService); - - instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + let instantiationService = workbenchInstantiationService(); let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toResource('/something.js'), 'text/javascript', void 0); @@ -413,22 +333,7 @@ suite('Workbench UI Services', () => { }); test('DelegatingWorkbenchEditorService', function () { - let editorService = new TestEditorService(function () { }); - let contextService = new TestContextService(TestWorkspace); - let eventService = new TestEventService(); - let telemetryService = NullTelemetryService; - - let instantiationService = new TestInstantiationService(); - instantiationService.stub(IEventService, eventService); - instantiationService.stub(ITelemetryService, telemetryService); - instantiationService.stub(IStorageService, new TestStorageService()); - instantiationService.stub(IUntitledEditorService, instantiationService.createInstance(UntitledEditorService)); - instantiationService.stub(IWorkbenchEditorService, editorService); - instantiationService.stub(PartService.IPartService, new TestPartService()); - instantiationService.stub(ILifecycleService, NullLifecycleService); - instantiationService.stub(IWorkspaceContextService, contextService); - instantiationService.stub(IConfigurationService, new TestConfigurationService()); - instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); + let instantiationService = workbenchInstantiationService(); let activeInput: EditorInput = instantiationService.createInstance(FileEditorInput, toResource('/something.js'), 'text/javascript', void 0); let testEditorPart = new TestEditorPart(); diff --git a/src/vs/workbench/test/common/editor/editorStacksModel.test.ts b/src/vs/workbench/test/common/editor/editorStacksModel.test.ts index b21d2ace993..cbde0076e6c 100644 --- a/src/vs/workbench/test/common/editor/editorStacksModel.test.ts +++ b/src/vs/workbench/test/common/editor/editorStacksModel.test.ts @@ -6,11 +6,11 @@ 'use strict'; import * as assert from 'assert'; -import {EditorStacksModel, EditorGroup} from 'vs/workbench/common/editor/editorStacksModel'; +import {EditorStacksModel, EditorGroup, GroupEvent} from 'vs/workbench/common/editor/editorStacksModel'; import {EditorInput, IFileEditorInput, IEditorIdentifier, IEditorGroup, IStacksModelChangeEvent, IEditorRegistry, Extensions as EditorExtensions, IEditorInputFactory} from 'vs/workbench/common/editor'; import URI from 'vs/base/common/uri'; import {TestStorageService, TestConfigurationService, TestLifecycleService, TestContextService} from 'vs/test/utils/servicesTestUtils'; -import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; import {IConfigurationService} from 'vs/platform/configuration/common/configuration'; import {IStorageService} from 'vs/platform/storage/common/storage'; import {ILifecycleService} from 'vs/platform/lifecycle/common/lifecycle'; @@ -48,7 +48,7 @@ interface ModelEvents { interface GroupEvents { opened: EditorInput[]; activated: EditorInput[]; - closed: EditorInput[]; + closed: GroupEvent[]; pinned: EditorInput[]; unpinned: EditorInput[]; moved: EditorInput[]; @@ -87,7 +87,7 @@ function groupListener(group: EditorGroup): GroupEvents { }; group.onEditorOpened(e => groupEvents.opened.push(e)); - group.onEditorClosed(e => groupEvents.closed.push(e.editor)); + group.onEditorClosed(e => groupEvents.closed.push(e)); group.onEditorActivated(e => groupEvents.activated.push(e)); group.onEditorPinned(e => groupEvents.pinned.push(e)); group.onEditorUnpinned(e => groupEvents.unpinned.push(e)); @@ -494,7 +494,9 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[0], input1); + assert.equal(events.closed[0].editor, input1); + assert.equal(events.closed[0].index, 0); + assert.equal(events.closed[0].pinned, true); // Active && Preview const input2 = input(); @@ -514,13 +516,15 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[1], input2); + assert.equal(events.closed[1].editor, input2); + assert.equal(events.closed[1].index, 0); + assert.equal(events.closed[1].pinned, false); group.closeEditor(input2); assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[1], input2); + assert.equal(events.closed[1].editor, input2); // Nonactive && Pinned => gets active because its first editor const input3 = input(); @@ -540,7 +544,7 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[2], input3); + assert.equal(events.closed[2].editor, input3); assert.equal(events.opened[2], input3); assert.equal(events.activated[2], input3); @@ -549,7 +553,7 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[2], input3); + assert.equal(events.closed[2].editor, input3); // Nonactive && Preview => gets active because its first editor const input4 = input(); @@ -569,7 +573,7 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 0); assert.equal(group.getEditors(true).length, 0); assert.equal(group.activeEditor, void 0); - assert.equal(events.closed[3], input4); + assert.equal(events.closed[3].editor, input4); }); test('Stack - Multiple Editors - Pinned and Active', function () { @@ -725,8 +729,8 @@ suite('Editor Stacks Model', () => { assert.equal(events.opened[0], input1); assert.equal(events.opened[1], input2); assert.equal(events.opened[2], input3); - assert.equal(events.closed[0], input1); - assert.equal(events.closed[1], input2); + assert.equal(events.closed[0].editor, input1); + assert.equal(events.closed[1].editor, input2); const mru = group.getEditors(true); assert.equal(mru[0], input3); @@ -809,7 +813,7 @@ suite('Editor Stacks Model', () => { assert.equal(group.count, 2); // 2 previews got merged into one assert.equal(group.getEditors()[0], input2); assert.equal(group.getEditors()[1], input3); - assert.equal(events.closed[0], input1); + assert.equal(events.closed[0].editor, input1); assert.equal(group.count, 2); group.unpin(input3); @@ -817,7 +821,7 @@ suite('Editor Stacks Model', () => { assert.equal(group.activeEditor, input3); assert.equal(group.count, 1); // pinning replaced the preview assert.equal(group.getEditors()[0], input3); - assert.equal(events.closed[1], input2); + assert.equal(events.closed[1].editor, input2); assert.equal(group.count, 1); }); diff --git a/src/vs/workbench/test/common/editor/untitledEditor.test.ts b/src/vs/workbench/test/common/editor/untitledEditor.test.ts new file mode 100644 index 00000000000..3fd93eaea3e --- /dev/null +++ b/src/vs/workbench/test/common/editor/untitledEditor.test.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import URI from 'vs/base/common/uri'; +import * as assert from 'assert'; +import {join} from 'vs/base/common/paths'; +import {TestInstantiationService} from 'vs/test/utils/instantiationTestUtils'; +import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService'; +import {workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; +import {UntitledEditorModel} from 'vs/workbench/common/editor/untitledEditorModel'; + +class ServiceAccessor { + constructor(@IUntitledEditorService public untitledEditorService: IUntitledEditorService) { + } +} + +suite('Workbench - Untitled Editor', () => { + + let instantiationService: TestInstantiationService; + let accessor: ServiceAccessor; + + setup(() => { + instantiationService = workbenchInstantiationService(); + accessor = instantiationService.createInstance(ServiceAccessor); + }); + + teardown(() => { + accessor.untitledEditorService.revertAll(); + }); + + test('Untitled Editor Service', function (done) { + const service = accessor.untitledEditorService; + assert.equal(service.getAll().length, 0); + + const input1 = service.createOrGet(); + assert.equal(input1, service.createOrGet(input1.getResource())); + + const input2 = service.createOrGet(); + + // get() / getAll() + assert.equal(service.get(input1.getResource()), input1); + assert.equal(service.getAll().length, 2); + assert.equal(service.getAll([input1.getResource(), input2.getResource()]).length, 2); + + // revertAll() + service.revertAll([input1.getResource()]); + assert.ok(input1.isDisposed()); + assert.equal(service.getAll().length, 1); + + // dirty + input2.resolve().then((model: UntitledEditorModel) => { + assert.ok(!service.isDirty(input2.getResource())); + + const listener = service.onDidChangeDirty(resource => { + listener.dispose(); + + assert.equal(resource.toString(), input2.getResource().toString()); + + assert.ok(service.isDirty(input2.getResource())); + assert.equal(service.getDirty()[0].toString(), input2.getResource().toString()); + + service.revertAll(); + assert.equal(service.getAll().length, 0); + assert.ok(!input2.isDirty()); + assert.ok(!model.isDirty()); + + done(); + }); + + model.textEditorModel.setValue('foo bar'); + }); + }); + + test('Untitled with associated resource', function () { + const service = accessor.untitledEditorService; + const file = URI.file(join('C:\\', '/foo/file.txt')); + const untitled = service.createOrGet(file); + + assert.ok(service.hasAssociatedFilePath(untitled.getResource())); + + untitled.dispose(); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts index 168c41180da..c4b0d1dbaf3 100644 --- a/src/vs/workbench/test/node/api/extHostApiCommands.test.ts +++ b/src/vs/workbench/test/node/api/extHostApiCommands.test.ts @@ -23,6 +23,7 @@ import {ExtHostLanguageFeatures} from 'vs/workbench/api/node/extHostLanguageFeat import {MainThreadLanguageFeatures} from 'vs/workbench/api/node/mainThreadLanguageFeatures'; import {registerApiCommands} from 'vs/workbench/api/node/extHostApiCommands'; import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; import {MainThreadCommands} from 'vs/workbench/api/node/mainThreadCommands'; import {ExtHostDocuments} from 'vs/workbench/api/node/extHostDocuments'; import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConverters'; @@ -108,7 +109,7 @@ suite('ExtHostLanguageFeatureCommands', function() { const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); diff --git a/src/vs/workbench/test/node/api/extHostCommands.test.ts b/src/vs/workbench/test/node/api/extHostCommands.test.ts new file mode 100644 index 00000000000..ddc99181787 --- /dev/null +++ b/src/vs/workbench/test/node/api/extHostCommands.test.ts @@ -0,0 +1,59 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import {ExtHostCommands} from 'vs/workbench/api/node/extHostCommands'; +import {MainThreadCommandsShape} from 'vs/workbench/api/node/extHost.protocol'; +import {TPromise} from 'vs/base/common/winjs.base'; +import {CommandsRegistry} from 'vs/platform/commands/common/commands'; +import {OneGetThreadService} from './testThreadService'; + +suite('ExtHostCommands', function () { + + test('dispose calls unregister', function () { + + let lastUnregister: string; + + const shape = new class extends MainThreadCommandsShape { + $registerCommand(id: string): TPromise { + return; + } + $unregisterCommand(id: string): TPromise { + lastUnregister = id; + return; + } + }; + + const commands = new ExtHostCommands(OneGetThreadService(shape), undefined); + commands.registerCommand('foo', () => { }).dispose(); + assert.equal(lastUnregister, 'foo'); + assert.equal(CommandsRegistry.getCommand('foo'), undefined); + + }); + + test('dispose bubbles only once', function () { + + let unregisterCounter = 0; + + const shape = new class extends MainThreadCommandsShape { + $registerCommand(id: string): TPromise { + return; + } + $unregisterCommand(id: string): TPromise { + unregisterCounter += 1; + return; + } + }; + + const commands = new ExtHostCommands(OneGetThreadService(shape), undefined); + const reg = commands.registerCommand('foo', () => { }); + reg.dispose(); + reg.dispose(); + reg.dispose(); + assert.equal(unregisterCounter, 1); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts index f0dbfd633c3..698c83097ab 100644 --- a/src/vs/workbench/test/node/api/extHostConfiguration.test.ts +++ b/src/vs/workbench/test/node/api/extHostConfiguration.test.ts @@ -9,7 +9,7 @@ import * as assert from 'assert'; import {ExtHostConfiguration} from 'vs/workbench/api/node/extHostConfiguration'; import {MainThreadConfigurationShape} from 'vs/workbench/api/node/extHost.protocol'; import {TPromise} from 'vs/base/common/winjs.base'; -import {ConfigurationTarget, ConfigurationEditingErrorCode, IConfigurationEditingError} from 'vs/workbench/services/configuration/common/configurationEditing'; +import {ConfigurationTarget/*, ConfigurationEditingErrorCode, IConfigurationEditingError*/} from 'vs/workbench/services/configuration/common/configurationEditing'; suite('ExtHostConfiguration', function () { @@ -21,49 +21,49 @@ suite('ExtHostConfiguration', function () { } }; - function createExtHostConfiguration(data: any = {}, shape?: MainThreadConfigurationShape) { - if (!shape) { - shape = new class extends MainThreadConfigurationShape { }; - } - const result = new ExtHostConfiguration(shape); - result.$acceptConfigurationChanged(data); - return result; - } + // function createExtHostConfiguration(data: any = {}, shape?: MainThreadConfigurationShape) { + // if (!shape) { + // shape = new class extends MainThreadConfigurationShape { }; + // } + // const result = new ExtHostConfiguration(shape); + // result.$acceptConfigurationChanged(data); + // return result; + // } test('check illegal state', function () { assert.throws(() => new ExtHostConfiguration(new class extends MainThreadConfigurationShape { }).getConfiguration('foo')); }); - test('udate / section to key', function () { + // test('udate / section to key', function () { - const shape = new RecordingShape(); - const allConfig = createExtHostConfiguration({ foo: { bar: 1, far: 2 } }, shape); + // const shape = new RecordingShape(); + // const allConfig = createExtHostConfiguration({ foo: { bar: 1, far: 2 } }, shape); - let config = allConfig.getConfiguration('foo'); - config.update('bar', 42, true); + // let config = allConfig.getConfiguration('foo'); + // config.update('bar', 42, true); - assert.equal(shape.lastArgs[1], 'foo.bar'); - assert.equal(shape.lastArgs[2], 42); + // assert.equal(shape.lastArgs[1], 'foo.bar'); + // assert.equal(shape.lastArgs[2], 42); - config = allConfig.getConfiguration(''); - config.update('bar', 42, true); - assert.equal(shape.lastArgs[1], 'bar'); + // config = allConfig.getConfiguration(''); + // config.update('bar', 42, true); + // assert.equal(shape.lastArgs[1], 'bar'); - config.update('foo.bar', 42, true); - assert.equal(shape.lastArgs[1], 'foo.bar'); - }); + // config.update('foo.bar', 42, true); + // assert.equal(shape.lastArgs[1], 'foo.bar'); + // }); - test('update / error-state not OK', function () { + // test('update / error-state not OK', function () { - const shape = new class extends MainThreadConfigurationShape { - $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): TPromise { - return TPromise.wrapError({ code: ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, message: 'Unknown Key' }); // something !== OK - } - }; + // const shape = new class extends MainThreadConfigurationShape { + // $updateConfigurationOption(target: ConfigurationTarget, key: string, value: any): TPromise { + // return TPromise.wrapError({ code: ConfigurationEditingErrorCode.ERROR_UNKNOWN_KEY, message: 'Unknown Key' }); // something !== OK + // } + // }; - return createExtHostConfiguration({}, shape) - .getConfiguration('') - .update('', true, false) - .then(() => assert.ok(false), err => { /* expecting rejection */}); - }); + // return createExtHostConfiguration({}, shape) + // .getConfiguration('') + // .update('', true, false) + // .then(() => assert.ok(false), err => { /* expecting rejection */}); + // }); }); diff --git a/src/vs/workbench/test/node/api/extHostDiagnostics.test.ts b/src/vs/workbench/test/node/api/extHostDiagnostics.test.ts index 999f59de499..ec1c67b84ab 100644 --- a/src/vs/workbench/test/node/api/extHostDiagnostics.test.ts +++ b/src/vs/workbench/test/node/api/extHostDiagnostics.test.ts @@ -159,6 +159,35 @@ suite('ExtHostDiagnostics', () => { collection.dispose(); }); + test('diagnostics collection, set tuple overrides, #11547', function () { + + let lastEntries: [URI, IMarkerData[]][]; + let collection = new DiagnosticCollection('test', new class extends DiagnosticsShape { + $changeMany(owner: string, entries: [URI, IMarkerData[]][]): TPromise { + lastEntries = entries; + return super.$changeMany(owner, entries); + } + }); + let uri = URI.parse('sc:hightower'); + + collection.set([[uri, [new Diagnostic(new Range(0, 0, 1, 1), 'error')]]]); + assert.equal(collection.get(uri).length, 1); + assert.equal(collection.get(uri)[0].message, 'error'); + assert.equal(lastEntries.length, 1); + let [[, data1]] = lastEntries; + assert.equal(data1.length, 1); + assert.equal(data1[0].message, 'error'); + lastEntries = undefined; + + collection.set([[uri, [new Diagnostic(new Range(0, 0, 1, 1), 'warning')]]]); + assert.equal(collection.get(uri).length, 1); + assert.equal(collection.get(uri)[0].message, 'warning'); + assert.equal(lastEntries.length, 1); + let [[, data2]] = lastEntries; + assert.equal(data2.length, 1); + assert.equal(data2[0].message, 'warning'); + lastEntries = undefined; + }); test('diagnostic capping', function () { diff --git a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts index f4e2808920f..e22a9435875 100644 --- a/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts +++ b/src/vs/workbench/test/node/api/extHostLanguageFeatures.test.ts @@ -39,6 +39,7 @@ import {getLinks} from 'vs/editor/contrib/links/common/links'; import {asWinJsPromise} from 'vs/base/common/async'; import {MainContext, ExtHostContext} from 'vs/workbench/api/node/extHost.protocol'; import {ExtHostDiagnostics} from 'vs/workbench/api/node/extHostDiagnostics'; +import {ExtHostHeapService} from 'vs/workbench/api/node/extHostHeapService'; const defaultSelector = { scheme: 'far' }; const model: EditorCommon.IModel = EditorModel.createFromString( @@ -97,7 +98,7 @@ suite('ExtHostLanguageFeatures', function() { const diagnostics = new ExtHostDiagnostics(threadService); threadService.set(ExtHostContext.ExtHostDiagnostics, diagnostics); - extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, diagnostics); + extHost = new ExtHostLanguageFeatures(threadService, extHostDocuments, commands, new ExtHostHeapService(), diagnostics); threadService.set(ExtHostContext.ExtHostLanguageFeatures, extHost); mainThread = threadService.setTestInstance(MainContext.MainThreadLanguageFeatures, instantiationService.createInstance(MainThreadLanguageFeatures)); diff --git a/src/vs/workbench/test/node/api/extHostMessagerService.test.ts b/src/vs/workbench/test/node/api/extHostMessagerService.test.ts index 3cb6c67bf0f..70865650586 100644 --- a/src/vs/workbench/test/node/api/extHostMessagerService.test.ts +++ b/src/vs/workbench/test/node/api/extHostMessagerService.test.ts @@ -39,8 +39,8 @@ suite('ExtHostMessageService', function () { service.$showMessage(1, '', [{ title: 'a thing', isCloseAffordance: false, handle: 0 }]); assert.equal(actions.length, 2); let [first, second] = actions; - assert.equal(first.label, 'Close'); - assert.equal(second.label, 'a thing'); + assert.equal(first.label, 'a thing'); + assert.equal(second.label, 'Close'); // override close action service.$showMessage(1, '', [{ title: 'a thing', isCloseAffordance: true, handle: 0 }]); diff --git a/src/vs/workbench/test/node/api/extHostWorkspace.test.ts b/src/vs/workbench/test/node/api/extHostWorkspace.test.ts index d9aece2b3a4..1c146117c18 100644 --- a/src/vs/workbench/test/node/api/extHostWorkspace.test.ts +++ b/src/vs/workbench/test/node/api/extHostWorkspace.test.ts @@ -20,4 +20,16 @@ suite('ExtHostWorkspace', function () { 'm:/Apps/DartPubCache/hosted/pub.dartlang.org/convert-2.0.1/lib/src/hex.dart'); }); + + test('asRelativePath, same paths, #11402', function () { + const root = '/home/aeschli/workspaces/samples/docker'; + const input = '/home/aeschli/workspaces/samples/docker'; + const ws = new ExtHostWorkspace(new TestThreadService(), root); + + assert.equal(ws.getRelativePath(input), input); + + const input2 = '/home/aeschli/workspaces/samples/docker/a.file'; + assert.equal(ws.getRelativePath(input2), 'a.file'); + + }); }); diff --git a/src/vs/workbench/test/node/api/mainThreadCommands.test.ts b/src/vs/workbench/test/node/api/mainThreadCommands.test.ts new file mode 100644 index 00000000000..1e3bb45f7cd --- /dev/null +++ b/src/vs/workbench/test/node/api/mainThreadCommands.test.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import * as assert from 'assert'; +import {MainThreadCommands} from 'vs/workbench/api/node/mainThreadCommands'; +import {CommandsRegistry} from 'vs/platform/commands/common/commands'; +import {OneGetThreadService} from './testThreadService'; + +suite('MainThreadCommands', function () { + + test('dispose on unregister', function () { + + const commands = new MainThreadCommands(OneGetThreadService(null), undefined); + assert.equal(CommandsRegistry.getCommand('foo'), undefined); + + // register + commands.$registerCommand('foo'); + assert.ok(CommandsRegistry.getCommand('foo')); + + // unregister + commands.$unregisterCommand('foo'); + assert.equal(CommandsRegistry.getCommand('foo'), undefined); + }); +}); \ No newline at end of file diff --git a/src/vs/workbench/test/node/api/testThreadService.ts b/src/vs/workbench/test/node/api/testThreadService.ts index 1fa73af6c48..b626eb24dac 100644 --- a/src/vs/workbench/test/node/api/testThreadService.ts +++ b/src/vs/workbench/test/node/api/testThreadService.ts @@ -9,6 +9,18 @@ import {TPromise} from 'vs/base/common/winjs.base'; import {AbstractThreadService} from 'vs/workbench/services/thread/common/abstractThreadService'; import {IThreadService, ProxyIdentifier} from 'vs/workbench/services/thread/common/threadService'; +export function OneGetThreadService(thing: any): IThreadService { + return { + _serviceBrand: undefined, + get(): T { + return thing; + }, + set(): void { + throw new Error(); + } + }; +} + export class TestThreadService extends AbstractThreadService implements IThreadService { public _serviceBrand: any; diff --git a/src/vs/workbench/workbench.main.ts b/src/vs/workbench/workbench.main.ts index 101aa2f8f07..64b6960b8c9 100644 --- a/src/vs/workbench/workbench.main.ts +++ b/src/vs/workbench/workbench.main.ts @@ -10,6 +10,8 @@ import 'vs/base/common/strings'; import 'vs/base/common/errors'; // Editor +import 'vs/editor/contrib/accessibility/browser/accessibility'; +import 'vs/editor/contrib/defineKeybinding/browser/defineKeybinding'; import 'vs/editor/contrib/selectionClipboard/electron-browser/selectionClipboard'; import 'vs/editor/contrib/suggest/electron-browser/snippetCompletion'; import 'vs/editor/browser/editor.all'; diff --git a/tslint.json b/tslint.json index 4ea88cdbb9b..7c749e70f4c 100644 --- a/tslint.json +++ b/tslint.json @@ -1,13 +1,12 @@ { "rules": { "no-unused-expression": true, - "no-unreachable": true, "no-duplicate-variable": true, "no-duplicate-key": true, "no-unused-variable": true, "curly": true, "class-name": true, - "semicolon": true, + "semicolon": ["always"], "triple-equals": true, "no-unexternalized-strings": [ true,