├── .gitignore ├── Context.sublime-menu ├── Default (Linux).sublime-keymap ├── Default (OSX).sublime-keymap ├── Default (Windows).sublime-keymap ├── Default.sublime-keymap ├── Default.sublime-mousemap ├── ErrorList.YAML-tmLanguage ├── ErrorList.hidden-tmLanguage ├── FindRefs.YAML-tmLanguage ├── FindRefs.hidden-tmLanguage ├── FindRefs.hidden-tmTheme ├── FindRefs.sublime-settings ├── LICENSE ├── Main.sublime-menu ├── README.md ├── TypeScript.sublime-commands ├── TypeScript.sublime-settings ├── icons ├── aim.png ├── arrow-right.png ├── arrow-right2.png ├── arrow-right3.png ├── deleted_dual_arrow.png ├── rightArrow.png ├── tree.png └── white-right.png ├── main.py ├── messages.json ├── messages ├── 0.1.1.txt └── 0.1.5.txt ├── popup.html ├── screenshots ├── build_loose_file.gif ├── build_tsconfig.gif ├── def.gif ├── diagnostic.gif ├── errorlist.gif ├── find_ref.gif ├── format.gif ├── hover.gif ├── navigateToSymbol.gif ├── quickinfo.gif ├── refs.png ├── rename.gif ├── signature.gif └── toolbar.png ├── snippets ├── Constructor.sublime-snippet ├── class-{-}.sublime-snippet ├── do-while(-).sublime-snippet ├── for-()-{[]}.sublime-snippet ├── for-()-{}-(faster).sublime-snippet ├── for-()-{}.sublime-snippet ├── for-(in)-{}.sublime-snippet ├── function-(fun).sublime-snippet ├── get-()-{}.sublime-snippet ├── if-___-else.sublime-snippet ├── if.sublime-snippet ├── import-require.sublime-snippet ├── log.sublime-snippet ├── method-(fun).sublime-snippet ├── namespace.sublime-snippet ├── property.sublime-snippet ├── reference.sublime-snippet ├── return-FALSE.sublime-snippet ├── return-TRUE.sublime-snippet ├── return.sublime-snippet ├── set-()-{}.sublime-snippet ├── setTimeout.sublime-snippet ├── switch(-).sublime-snippet └── throw.sublime-snippet └── typescript ├── __init__.py ├── commands ├── __init__.py ├── base_command.py ├── browse.py ├── build.py ├── error_info.py ├── error_list.py ├── format.py ├── go_to_definition.py ├── go_to_type.py ├── nav_to.py ├── quick_info.py ├── references.py ├── rename.py ├── save.py ├── settings.py ├── show_doc.py └── signature.py ├── libs ├── __init__.py ├── client_manager.py ├── editor_client.py ├── git_helpers.py ├── global_vars.py ├── json_helpers.py ├── logger.py ├── lsp_client.py ├── lsp_helpers.py ├── node_client.py ├── os_helpers.py ├── panel_manager.py ├── popup_manager.py ├── reference.py ├── service_proxy.py ├── text_helpers.py ├── view_helpers.py └── work_scheduler.py └── listeners ├── __init__.py ├── completion.py ├── error_list.py ├── event_hub.py ├── format.py ├── idle.py ├── listeners.py ├── nav_to.py ├── quick_info_tool_tip.py ├── rename.py └── tooltip.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.cache 2 | .tmpbuf* 3 | .log* 4 | built/* 5 | ~*.docx 6 | src/service/*.js 7 | src/testsrc/*.js 8 | src/service/editorServices.d.ts 9 | libs/rpdb/ 10 | libs/ptvsd/ 11 | *.pyc 12 | TS.log 13 | *.pyproj 14 | *.sln 15 | ptvsd/* 16 | .vs/* 17 | sublime.py 18 | sublime_plugin.py 19 | .idea/* 20 | .settings/* 21 | tsserver/node_modules/* -------------------------------------------------------------------------------- /Context.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { "command": "browse_code", "caption": "Explore code at cursor" }, 3 | { "command": "typescript_find_references", "caption": "Find local references" } 4 | ] -------------------------------------------------------------------------------- /Default (Linux).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": [ "ctrl+v" ], 4 | "command": "typescript_paste_and_format", 5 | "context": [ 6 | { "key": "setting.typescript_auto_format", "operator": "equal", "operand": true }, 7 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 8 | ] 9 | } 10 | ] -------------------------------------------------------------------------------- /Default (OSX).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": [ "super+v" ], 4 | "command": "typescript_paste_and_format", 5 | "context": [ 6 | { "key": "setting.typescript_auto_format", "operator": "equal", "operand": true }, 7 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 8 | ] 9 | } 10 | ] -------------------------------------------------------------------------------- /Default (Windows).sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": [ "ctrl+v" ], 4 | "command": "typescript_paste_and_format", 5 | "context": [ 6 | { "key": "setting.typescript_auto_format", "operator": "equal", "operand": true }, 7 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" }, 8 | { "key": "setting.enable_typescript_language_service", "operator": "equal", "operand": true } 9 | ] 10 | } 11 | ] -------------------------------------------------------------------------------- /Default.sublime-keymap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "keys": [ "ctrl+t", "ctrl+d" ], 4 | "command": "typescript_go_to_definition", 5 | "context": [ 6 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 7 | ] 8 | }, 9 | { 10 | "keys": [ "f12" ], 11 | "command": "typescript_go_to_definition", 12 | "context": [ 13 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 14 | ] 15 | }, 16 | { 17 | "keys": [ "ctrl+t", "ctrl+f" ], 18 | "command": "typescript_format_selection", 19 | "context": [ 20 | { "key": "selection_empty", "operator": "equal", "operand": false, "match_all": true }, 21 | { "key": "num_selections", "operator": "equal", "operand": 1, "match_all": true }, 22 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 23 | ] 24 | }, 25 | { 26 | "keys": [ "ctrl+alt+r"], 27 | "command": "typescript_nav_to", 28 | "context": [ 29 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 30 | ] 31 | }, 32 | { 33 | "keys": [ "ctrl+t", "ctrl+f" ], 34 | "command": "typescript_format_document", 35 | "context": [ 36 | { "key": "selection_empty", "operator": "equal", "operand": true, "match_all": true }, 37 | { "key": "num_selections", "operator": "equal", "operand": 1, "match_all": true }, 38 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 39 | ] 40 | }, 41 | { 42 | "keys": [ "ctrl+t", "ctrl+m" ], 43 | "command": "typescript_rename", 44 | "context": [ 45 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 46 | ] 47 | }, 48 | { 49 | "keys": [ "ctrl+t", "ctrl+n" ], 50 | "command": "typescript_next_ref", 51 | "context": [ 52 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 53 | ] 54 | }, 55 | { 56 | "keys": [ "ctrl+t", "ctrl+n" ], 57 | "command": "typescript_next_ref", 58 | "context": [ 59 | { "key": "selector", "operator": "equal", "operand": "text.find-refs" } 60 | ] 61 | }, 62 | { 63 | "keys": [ "ctrl+t", "ctrl+p" ], 64 | "command": "typescript_prev_ref", 65 | "context": [ 66 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 67 | ] 68 | }, 69 | { 70 | "keys": [ "ctrl+t", "ctrl+p" ], 71 | "command": "typescript_prev_ref", 72 | "context": [ 73 | { "key": "selector", "operator": "equal", "operand": "text.find-refs" } 74 | ] 75 | }, 76 | { 77 | "keys": [ "ctrl+t", "ctrl+q" ], 78 | "command": "typescript_quick_info_doc", 79 | "context": [ 80 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 81 | ] 82 | }, 83 | { 84 | "keys": [ "ctrl+t", "ctrl+r" ], 85 | "command": "typescript_find_references", 86 | "context": [ 87 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 88 | ] 89 | }, 90 | { 91 | "keys": [ "ctrl+t", "ctrl+s" ], 92 | "command": "typescript_save", 93 | "context": [ 94 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 95 | ] 96 | }, 97 | { 98 | "keys": [ "ctrl+t", "ctrl+o" ], 99 | "command": "typescript_signature_panel", 100 | "context": [ 101 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 102 | ] 103 | }, 104 | { 105 | "keys": ["("], 106 | "command": "typescript_signature_popup", 107 | "context": [ 108 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" }, 109 | { "key": "paren_pressed"} 110 | ] 111 | }, 112 | { 113 | "keys": ["alt+down"], 114 | "command": "typescript_signature_popup", 115 | "args": {"move": "next"}, 116 | "context": [ 117 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" }, 118 | { "key": "is_popup_visible"} 119 | ] 120 | }, 121 | { 122 | "keys": ["alt+up"], 123 | "command": "typescript_signature_popup", 124 | "args": {"move": "prev"}, 125 | "context": [ 126 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" }, 127 | { "key": "is_popup_visible"} 128 | ] 129 | }, // In case when auto match is enabled, only format if not within {} 130 | { 131 | "keys": [ "alt+,"], 132 | "command": "typescript_signature_popup", 133 | "context": [ 134 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" }, 135 | { "key": "tooltip_supported", "operator": "equal", "operand": true} 136 | 137 | ] 138 | }, 139 | { 140 | "keys": [ "enter" ], 141 | "command": "typescript_go_to_ref", 142 | "context": [ 143 | { "key": "selector", "operator": "equal", "operand": "text.find-refs" } 144 | ] 145 | }, 146 | { 147 | "keys": [ "enter" ], 148 | "command": "typescript_go_to_error", 149 | "context": [ 150 | { "key": "selector", "operator": "equal", "operand": "text.error-list" } 151 | ] 152 | }, 153 | { 154 | "keys": [ "enter" ], 155 | "command": "typescript_auto_indent_on_enter_between_curly_brackets", 156 | "context": [ 157 | { "key": "setting.typescript_auto_indent", "operator": "equal", "operand": true }, 158 | { "key": "auto_complete_visible", "operator": "equal", "operand": false }, 159 | { "key": "selection_empty", "operator": "equal", "operand": true }, 160 | { "key": "preceding_text", "operator": "regex_contains", "operand": "\\{$" }, 161 | { "key": "following_text", "operator": "regex_contains", "operand": "^\\}" }, 162 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 163 | ] 164 | }, 165 | { 166 | "keys": [ "ctrl+;" ], 167 | "command": "typescript_format_line", 168 | "context": [ 169 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 170 | ] 171 | }, 172 | { 173 | "keys": [ "ctrl+shift+]" ], 174 | "command": "typescript_format_brackets", 175 | "context": [ 176 | { "key": "selector", "operator": "equal", "operand": "source.ts, source.tsx" } 177 | ] 178 | } 179 | ] 180 | -------------------------------------------------------------------------------- /Default.sublime-mousemap: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "button": "button1", 4 | "count": 2, 5 | "modifiers": ["ctrl"], 6 | "command": "typescript_go_to_ref", 7 | "press_command": "drag_select" 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /ErrorList.YAML-tmLanguage: -------------------------------------------------------------------------------- 1 | # [PackageDev] target_format: plist, ext: tmLanguage 2 | --- 3 | name: Error List 4 | scopeName: text.error-list 5 | uuid: 52410ea6-4de5-4b0e-9be7-12842e39a3a6 6 | 7 | patterns: 8 | - include: '#error-count' 9 | - include: '#filename' 10 | - include: '#message' 11 | 12 | repository: 13 | filename: 14 | match: ^([^ ].*:)$ 15 | captures: 16 | '1': {name: entity.name.filename.error-list} 17 | 18 | error-count: 19 | match: (?<=\[)(\d+\s*errors)(?=\]) 20 | captures: 21 | '1': {name: keyword.other.error-list} 22 | 23 | message: 24 | begin: \( 25 | end: \n 26 | patterns: 27 | - include: '#location' 28 | 29 | location: 30 | match: (?<=\()(\d+),\s*(\d+)(?=\)) 31 | captures: 32 | '1': {name: constant.numeric.location.error-list} 33 | '2': {name: constant.numeric.location.error-list} 34 | ... 35 | -------------------------------------------------------------------------------- /ErrorList.hidden-tmLanguage: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | Error List 7 | patterns 8 | 9 | 10 | include 11 | #error-count 12 | 13 | 14 | include 15 | #filename 16 | 17 | 18 | include 19 | #message 20 | 21 | 22 | repository 23 | 24 | error-count 25 | 26 | captures 27 | 28 | 1 29 | 30 | name 31 | keyword.other.error-list 32 | 33 | 34 | match 35 | (?<=\[)(\d+\s*errors)(?=\]) 36 | 37 | filename 38 | 39 | captures 40 | 41 | 1 42 | 43 | name 44 | entity.name.filename.error-list 45 | 46 | 47 | match 48 | ^([^ ].*:)$ 49 | 50 | location 51 | 52 | captures 53 | 54 | 1 55 | 56 | name 57 | constant.numeric.location.error-list 58 | 59 | 2 60 | 61 | name 62 | constant.numeric.location.error-list 63 | 64 | 65 | match 66 | (?<=\()(\d+),\s*(\d+)(?=\)) 67 | 68 | message 69 | 70 | begin 71 | \( 72 | end 73 | \n 74 | patterns 75 | 76 | 77 | include 78 | #location 79 | 80 | 81 | 82 | 83 | scopeName 84 | text.error-list 85 | uuid 86 | 52410ea6-4de5-4b0e-9be7-12842e39a3a6 87 | 88 | 89 | -------------------------------------------------------------------------------- /FindRefs.hidden-tmTheme: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | name 6 | TypeScript Find All References 7 | settings 8 | 9 | 10 | settings 11 | 12 | background 13 | #272822 14 | foreground 15 | #ffffff 16 | caret 17 | #FFFFFF 18 | lineHighlight 19 | #3E3D32 20 | 21 | 22 | 23 | scope 24 | constant.numeric.line-number.find-refs 25 | settings 26 | 27 | foreground 28 | #745FAC 29 | 30 | 31 | 32 | 33 | scope 34 | constant.numeric.line-number.match.find-refs 35 | settings 36 | 37 | foreground 38 | #AA81FF 39 | 40 | 41 | 42 | 43 | scope 44 | entity.name.filename.find-refs 45 | settings 46 | 47 | foreground 48 | #E6D762 49 | 50 | 51 | 52 | 53 | uuid 54 | edcc9b28-ac04-4b4a-8fa3-f4be3c0d3b02 55 | 56 | 57 | -------------------------------------------------------------------------------- /FindRefs.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "spell_check": false, 3 | "draw_indent_guides": false, 4 | "gutter": true, 5 | "line_numbers": false, 6 | "highlight_line": false, 7 | "hide_minimap": true, 8 | "rulers": [] 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Main.sublime-menu: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "goto", 4 | "children": [ 5 | { 6 | "command": "typescript_nav_to", 7 | "caption": "Navigate to TypeScript Symbol..." 8 | } 9 | ] 10 | }, 11 | { 12 | "id": "preferences", 13 | "children": [ 14 | { 15 | "id": "package-settings", 16 | "children": [ 17 | { 18 | "caption": "Sublime Lsp Connector", 19 | "children": [ 20 | { 21 | "command": "typescript_open_plugin_default_setting_file", 22 | "caption": "Plugin Settings – Default" 23 | }, 24 | { 25 | "command": "open_file", 26 | "args": { 27 | "file": "${packages}/User/SublimeLsp.sublime-settings" 28 | }, 29 | "caption": "Plugin Settings – User" 30 | } 31 | ] 32 | } 33 | ] 34 | } 35 | ] 36 | } 37 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LSP Connector for Sublime Text 2 | 3 | A [Language Server Protocol](https://github.com/Microsoft/language-server-protocol) connector for Sublime Text 3. 4 | 5 | *The project is in beta mode. Feedback or issue? Please email us at support@sourcegraph.com or [file an issue.](https://github.com/sourcegraph/sublime-lsp/issues)* 6 | 7 | ## Overview 8 | 9 | The [Language Server Protocol](https://github.com/Microsoft/language-server-protocol) is a specification that enables advanced language capabilities in an editor independent-way. Previously, to support a given language in a given editor, an editor extension writer would have to _both_ write the editor specific functionality _as well as_ language analysis capabilities for each language. This means that every editor and language had different levels of capability and reliability. 10 | 11 | With LSP, one editor extension can be written per editor, and one language server per language. [Sourcegraph's master plan](https://sourcegraph.com/plan) is to support editor and language server open source authors, [let us know](mailto:hi@sourcegraph.com) if you'd like to work on one! 12 | 13 | This plugin borrows heavily from the popular [Microsoft TypeScript Sublime Plugin](https://github.com/Microsoft/TypeScript-Sublime-Plugin). 14 | 15 | ## Operations supported 16 | 17 | This connector currently supports: 18 | * [hover](https://github.com/sourcegraph/sublime-lsp#hover) operations (Sublime build >=3124) 19 | * [goto definition](https://github.com/sourcegraph/sublime-lsp#go-to-definition) 20 | * [find all references](https://github.com/sourcegraph/sublime-lsp#find-all-references) 21 | * [error highlighting](https://github.com/sourcegraph/sublime-lsp#diagnostics) 22 | 23 | Autocomplete, semantic symbol-based search, formatting utilities will soon be supported. 24 | 25 | ## Languages supported 26 | 27 | This plugin has been developed for use with the [go-langserver](https://github.com/sourcegraph/go-langserver) language server. [Sourcegraph](https://sourcegraph.com) is currently developing JavaScript/TypeScript, Python, and PHP language servers, which will also work with this adapter. Finally, any language server implementing the [Language Server Protocol](https://langserver.org) can be connected to this plugin. 28 | 29 | ## Installation 30 | 31 | ### Connector installation 32 | 33 | Install the `sublime-lsp` connector for Sublime Text by cloning `sublime-lsp` repository into your Sublime Text 3 Packages folder: 34 | 35 | macOS: 36 | 37 | ```shell 38 | git clone git@github.com:sourcegraph/sublime-lsp.git ~/Library/Application\ Support/Sublime\ Text\ 3/Packages/sublime-lsp 39 | ``` 40 | 41 | Linux: 42 | 43 | ```shell 44 | git clone git@github.com:sourcegraph/sublime-lsp.git ~/.config/sublime-text-3/Packages/sublime-lsp 45 | ``` 46 | 47 | Windows: 48 | 49 | ```bat 50 | cd "%APPDATA%\Sublime Text 3\Packages" 51 | git clone https://github.com/sourcegraph/sublime-lsp 52 | ``` 53 | 54 | ### Go installation 55 | 56 | Install the `langserver-go` binary by running `go get -u github.com/sourcegraph/go-langserver/`. The `go-langserver` binary should now be available via your command line. 57 | 58 | Next, configure the LSP connector for the `langserver-go` binary. To change your Sourcegraph settings, open `SublimeLsp.sublime-settings` by clicking `Sublime Text > Preferences > Package Settings > Sublime Lsp Connector > Settings - User`. 59 | 60 | Add the following client descriptor into `clients` section 61 | 62 | ``` 63 | { 64 | ... 65 | "clients": [ 66 | { 67 | "binary": "go-langserver", 68 | "file_exts": ["go"], 69 | // the go binary must be in the path 70 | "path_additions": ["/usr/local/go/bin"], 71 | "env": { 72 | // GOPATH is a required argument, ~'s don't work 73 | "GOPATH": "", 74 | } 75 | } 76 | ] 77 | .... 78 | } 79 | ``` 80 | 81 | Finally, restart Sublime Text to start using the plugin. You may want to [disable Sublime's native tooltips](https://github.com/sourcegraph/lsp-sublime#remove-sublime-text-3-tooltips-and-goto-menu-items), as they are duplicative and interfere with this connector's tooltips. 82 | 83 | ### TypeScript/JavaScript installation 84 | 85 | Install the TypeScript/JavaScript LSP server the following way: 86 | 87 | ```shell 88 | export JSTS_DIR=... 89 | git clone https://github.com/sourcegraph/javascript-typescript-langserver $JSTS_DIR 90 | cd $JSTS_DIR 91 | npm install 92 | node_modules/.bin/tsc 93 | ``` 94 | 95 | Please make sure that `$JSTS_DIR/bin` is in `$PATH` 96 | 97 | Next, register TypeScript/JavaScript LSP client. To change your Sourcegraph settings, open `SublimeLsp.sublime-settings` by clicking `Sublime Text > Preferences > Package Settings > Sublime Lsp Connector > Settings - User`. 98 | 99 | Add the following client descriptor into `clients` section 100 | 101 | ``` 102 | { 103 | ... 104 | "clients": [ 105 | { 106 | "binary": "javascript-typescript-stdio", 107 | "file_exts": ["ts", "tsx", "js", "jsx"], 108 | "path_additions": ["/path/to/jsts/bin", "/path/to/node/binary/usually/usr/local/bin"], 109 | } 110 | ] 111 | ... 112 | } 113 | ``` 114 | 115 | Finally, restart Sublime Text to start using the plugin. You may want to [disable Sublime's native tooltips](https://github.com/sourcegraph/sublime-lsp#remove-sublime-text-3-tooltips-and-goto-menu-items), as they are duplicative and interfere with this connector's tooltips. 116 | 117 | 118 | ## Usage 119 | 120 | ### Hover 121 | 122 | As you navigate through Go files, when your cursor is on a symbol, you should see hover tooltips. You may have to [disable Sublime's native tooltips](https://github.com/sourcegraph/sublime-lsp#remove-sublime-text-3-tooltips-and-goto-menu-items). 123 | 124 | ![hover tooltips](screenshots/hover.gif) 125 | 126 | ### Goto definition 127 | 128 | [Execute](https://github.com/sourcegraph/sublime-lsp#open-the-command-window) the `Lsp: Goto definition` command, and Sublime will jump to the definition of a symbol in your workspace. 129 | 130 | ![goto def](screenshots/def.gif) 131 | 132 | ### Find all references 133 | 134 | [Execute](https://github.com/sourcegraph/sublime-lsp#open-the-command-window) the `Lsp: Find all references` command, and Sublime will open up a results pane with semantic references to the symbols within your project. 135 | 136 | ![find all references](screenshots/refs.png) 137 | 138 | ### Diagnostics 139 | 140 | As you type, the language server connector will receive diagnostics from the language server. If any errors are detected, the editor will display a tooltip when the offending text is clicked. 141 | 142 | ![find all references](screenshots/diagnostic.gif) 143 | 144 | ## Troubleshooting 145 | 146 | ### Make sure the langserver-go is installed 147 | Run `langserver-go -h` in your command line. You should see a help menu. 148 | 149 | ### Sublime Text 3 version check 150 | For hover tooltips to work, you'll need Sublime Text 3, Build 3124 (released in 9/2016). Navigate to `Sublime Text > About Sublime Text` to check the version. 151 | 152 | ### Remove Sublime Text 3 tooltips and Goto menu items 153 | If you are seeing two tooltips that flicker when you hover over symbols, you may have to disable Sublime Text 3 tooltips. Navigate to `Sublime Text > Preferences > Settings`, and add the following lines: 154 | 155 | ``` 156 | { 157 | ... 158 | "show_definitions": false, // recommended: removes default Sublime tooltips 159 | "index_files": false, // optional: removes default Sublime "Goto Definition" from right click 160 | ... 161 | } 162 | ``` 163 | 164 | ## Using Sublime with LSP Connector 165 | 166 | ### Open the command window 167 | 168 | To open Sublime's command window and access LSP connector operations, just execute the following key combination: 169 | * Linux/Windows ctrl+shift+p 170 | * Mac command+shift+p 171 | 172 | Any search items starting with `Language Server` is provided by the LSP connector. 173 | 174 | ![command bar access](screenshots/toolbar.png) 175 | 176 | ### Add right click menu options 177 | 178 | By default, the sublime-lsp connector includes two options to your right click menu. `Find local references` and `Explore code at cursor.` 179 | 180 | ### Change default keybindings 181 | 182 | TBD 183 | 184 | ## Support 185 | 186 | Found a bug, want to request a feature, or want to help Sourcegraph build the global graph of code? Send us an email at hi@sourcegraph.com. 187 | -------------------------------------------------------------------------------- /TypeScript.sublime-commands: -------------------------------------------------------------------------------- 1 | [ 2 | { "caption" : "Langserver: GoTo Definition", "command": "typescript_go_to_definition" }, 3 | { "caption" : "Langserver: Explore Code", "command": "browse_code" }, 4 | { "caption" : "Langserver: Quick Info Documentation", "command": "typescript_quick_info_doc" }, 5 | { "caption" : "Langserver: Overloads Panel", "command": "typescript_signature_panel" }, 6 | { "caption" : "Langserver: Save Tmp", "command": "typescript_save" }, 7 | { "caption" : "Langserver: Format Selection", "command": "typescript_format_selection" }, 8 | { "caption" : "Langserver: Format Document", "command": "typescript_format_document" }, 9 | { "caption" : "Langserver: Find References", "command": "typescript_find_references" }, 10 | { "caption" : "Langserver: Navigate To Symbol", "command": "typescript_nav_to" }, 11 | { "caption" : "Langserver: Rename", "command": "typescript_rename" }, 12 | { "caption" : "Langserver: Paste And Format", "command": "typescript_paste_and_format" }, 13 | { "caption" : "Langserver: Format Line", "command": "typescript_format_line" }, 14 | { "caption" : "Langserver: Format Block", "command": "typescript_format_brackets" }, 15 | { "caption" : "Langserver: Signature Info", "command": "typescript_signature_popup" }, 16 | { "caption" : "Langserver: Show Error List", "command": "typescript_project_error_list" } 17 | ] 18 | -------------------------------------------------------------------------------- /TypeScript.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | "auto_complete_triggers" : [ {"selector": "source.ts", "characters": "."} ], 3 | "use_tab_stops": false, 4 | "word_separators": "./\\()\"'-:,.;<>~!@#%^&*|+=[]{}`~?" 5 | } 6 | -------------------------------------------------------------------------------- /icons/aim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/aim.png -------------------------------------------------------------------------------- /icons/arrow-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/arrow-right.png -------------------------------------------------------------------------------- /icons/arrow-right2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/arrow-right2.png -------------------------------------------------------------------------------- /icons/arrow-right3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/arrow-right3.png -------------------------------------------------------------------------------- /icons/deleted_dual_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/deleted_dual_arrow.png -------------------------------------------------------------------------------- /icons/rightArrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/rightArrow.png -------------------------------------------------------------------------------- /icons/tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/tree.png -------------------------------------------------------------------------------- /icons/white-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/icons/white-right.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess 4 | 5 | if sys.version_info < (3, 0): 6 | from typescript.libs import * 7 | from typescript.libs.reference import * 8 | from typescript.libs.view_helpers import * 9 | from typescript.listeners import * 10 | from typescript.commands import * 11 | else: 12 | from .typescript.libs import * 13 | from .typescript.libs.reference import * 14 | from .typescript.libs.view_helpers import * 15 | from .typescript.listeners import * 16 | from .typescript.commands import * 17 | 18 | # Enable Python Tools for visual studio remote debugging 19 | try: 20 | from ptvsd import enable_attach 21 | 22 | enable_attach(secret=None) 23 | except ImportError: 24 | pass 25 | 26 | 27 | def _cleanup_011(): 28 | """Remove any old zipped package installed by 0.1.1 release""" 29 | this_file = os.path.abspath(__file__) 30 | 31 | # Is the current file running under installed packages or packages? 32 | offset = this_file.find(os.path.sep + 'Installed Packages' + os.path.sep) 33 | if offset == -1: 34 | offset = this_file.find(os.path.sep + 'Packages' + os.path.sep) 35 | 36 | if offset == -1: 37 | print('ERROR: Could not location parent packages folder') 38 | return 39 | 40 | # Move/delete old package if present 41 | old_package = os.path.join(this_file[:offset], 'Installed Packages', 'TypeScript.sublime-package') 42 | temp_name = os.path.join(this_file[:offset], 'Installed Packages', 'TypeScript.-old-sublime-package') 43 | if os.path.exists(old_package): 44 | # Rename first, in case delete fails due to file in use 45 | print('Detected outdated TypeScript plugin package. Removing ' + old_package) 46 | os.rename(old_package, temp_name) 47 | os.remove(temp_name) 48 | 49 | try: 50 | _cleanup_011() 51 | except: 52 | pass 53 | 54 | logger.log.warn('TypeScript plugin initialized.') 55 | 56 | 57 | def plugin_loaded(): 58 | """ 59 | Note: this is not always called on startup by Sublime, so we call it 60 | from on_activated or on_close if necessary. 61 | """ 62 | log.debug("plugin_loaded started") 63 | settings = sublime.load_settings('SublimeLsp.sublime-settings') 64 | global_vars._language_service_enabled = settings.get('enable_typescript_language_service', True) 65 | print ("lang_service_enabled: " + str(global_vars.get_language_service_enabled())) 66 | if not global_vars.get_language_service_enabled(): 67 | return 68 | 69 | cli.initialize() 70 | ref_view = get_ref_view(False) 71 | if ref_view: 72 | settings = ref_view.settings() 73 | ref_info_view = settings.get('refinfo') 74 | if ref_info_view: 75 | print("got refinfo from settings") 76 | ref_info = build_ref_info(ref_info_view) 77 | cli.update_ref_info(ref_info) 78 | ref_view.set_scratch(True) 79 | highlight_ids(ref_view, ref_info.get_ref_id()) 80 | cur_line = ref_info.get_ref_line() 81 | if cur_line: 82 | update_ref_line(ref_info, int(cur_line), ref_view) 83 | else: 84 | print("no current ref line") 85 | else: 86 | window = sublime.active_window() 87 | if window: 88 | window.focus_view(ref_view) 89 | window.run_command('close') 90 | else: 91 | print("ref view not found") 92 | log.debug("plugin_loaded ended") 93 | 94 | 95 | def plugin_unloaded(): 96 | """ 97 | Note: this unload is not always called on exit 98 | """ 99 | print('typescript plugin unloaded') 100 | ref_view = get_ref_view() 101 | if ref_view: 102 | ref_info = cli.get_ref_info() 103 | if ref_info: 104 | ref_view.settings().set('refinfo', ref_info.as_value()) 105 | for client in cli.client_manager.get_clients(): 106 | client.exit() 107 | -------------------------------------------------------------------------------- /messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "0.1.1": "messages/0.1.1.txt", 3 | "0.1.5": "messages/0.1.5.txt" 4 | } 5 | -------------------------------------------------------------------------------- /messages/0.1.1.txt: -------------------------------------------------------------------------------- 1 | Welcome to the TypeScript plugin for Sublime Text! 2 | 3 | If you were previously using the 'TypeScript' package from Eric Clemmons 4 | (https://github.com/ericclemmons/sublime-typescript) via Package Control, 5 | this has now been redirected to this 'TypeScript' package from Microsoft. 6 | 7 | Details about this package and its feature can be found on its GitHub 8 | repository at https://github.com/Microsoft/TypeScript-Sublime-Plugin. 9 | 10 | We hope you enjoy using this plugin, and look forward to hearing any feedback, 11 | bugs, or suggestions you have on the GitHub repo above. 12 | 13 | Thanks! 14 | -------------------------------------------------------------------------------- /messages/0.1.5.txt: -------------------------------------------------------------------------------- 1 | Welcome to the TypeScript plugin for Sublime Text! 2 | 3 | Details about this package and its feature can be found on its GitHub 4 | repository at https://github.com/Microsoft/TypeScript-Sublime-Plugin. 5 | 6 | v0.1.5 updates: 7 | - Fixed several issues with syntax highlighting 8 | - Fixed performance issues caused by quickinfo requests on large files 9 | - Fixed the bug where users were unable to type the '}' and ';' keys 10 | - Fixed wrong indentation between curly braces 11 | - Fixed issues with signature tooltip positioning 12 | - Fixed incorrect error spans in Sublime Text 2 13 | - Resolved confusion caused by certain snippet triggers in the completion list 14 | - Added build functionality for loose files and projects configured with tsconfig.json. TypeScript files can now be built via "Tools" -> "Build" in the menu 15 | - Updated the TypeScript language service to v1.5 (will ship with Visual Studio 2015 RTM) 16 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 10 |
${signature}
11 |
${description}
12 |
${activeParam}
13 |
${index}
-------------------------------------------------------------------------------- /screenshots/build_loose_file.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/build_loose_file.gif -------------------------------------------------------------------------------- /screenshots/build_tsconfig.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/build_tsconfig.gif -------------------------------------------------------------------------------- /screenshots/def.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/def.gif -------------------------------------------------------------------------------- /screenshots/diagnostic.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/diagnostic.gif -------------------------------------------------------------------------------- /screenshots/errorlist.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/errorlist.gif -------------------------------------------------------------------------------- /screenshots/find_ref.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/find_ref.gif -------------------------------------------------------------------------------- /screenshots/format.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/format.gif -------------------------------------------------------------------------------- /screenshots/hover.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/hover.gif -------------------------------------------------------------------------------- /screenshots/navigateToSymbol.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/navigateToSymbol.gif -------------------------------------------------------------------------------- /screenshots/quickinfo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/quickinfo.gif -------------------------------------------------------------------------------- /screenshots/refs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/refs.png -------------------------------------------------------------------------------- /screenshots/rename.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/rename.gif -------------------------------------------------------------------------------- /screenshots/signature.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/signature.gif -------------------------------------------------------------------------------- /screenshots/toolbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/screenshots/toolbar.png -------------------------------------------------------------------------------- /snippets/Constructor.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 6 | ctor 7 | source.ts, source.tsx 8 | constructor … 9 | -------------------------------------------------------------------------------- /snippets/class-{-}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 10 | class 11 | source.ts, source.tsx 12 | class … 13 | -------------------------------------------------------------------------------- /snippets/do-while(-).sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | do 6 | source.ts, source.tsx 7 | do … while … 8 | -------------------------------------------------------------------------------- /snippets/for-()-{[]}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | fori 6 | source.ts, source.tsx 7 | for (…) {…} 8 | 9 | -------------------------------------------------------------------------------- /snippets/for-()-{}-(faster).sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | = 0; ${20:i}--) { 3 | ${100:${1:Things}[${20:i}]}$0 4 | }]]> 5 | for 6 | source.ts, source.tsx 7 | for (…) {…} (Improved Native For-Loop) 8 | 9 | -------------------------------------------------------------------------------- /snippets/for-()-{}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | for 6 | source.ts, source.tsx 7 | for (…) {…} 8 | 9 | -------------------------------------------------------------------------------- /snippets/for-(in)-{}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | forobj 6 | source.ts, source.tsx 7 | for … in … loop 8 | 9 | -------------------------------------------------------------------------------- /snippets/function-(fun).sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | func 6 | source.ts, source.tsx 7 | Function 8 | -------------------------------------------------------------------------------- /snippets/get-()-{}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | getter 8 | source.ts, source.tsx 9 | get-property … 10 | -------------------------------------------------------------------------------- /snippets/if-___-else.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | ifelse 8 | source.ts, source.tsx 9 | if … else … 10 | -------------------------------------------------------------------------------- /snippets/if.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | if 6 | source.ts, source.tsx 7 | if … 8 | -------------------------------------------------------------------------------- /snippets/import-require.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | import 5 | source.ts, source.tsx 6 | imports a module … 7 | -------------------------------------------------------------------------------- /snippets/log.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | clog 5 | source.ts, source.tsx 6 | console log 7 | -------------------------------------------------------------------------------- /snippets/method-(fun).sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | meth 6 | source.ts, source.tsx 7 | class method 8 | -------------------------------------------------------------------------------- /snippets/namespace.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 5 | nam 6 | source.ts, source.tsx 7 | -------------------------------------------------------------------------------- /snippets/property.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 11 | prop 12 | source.ts, source.tsx 13 | full property … 14 | -------------------------------------------------------------------------------- /snippets/reference.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | $0]]> 4 | ref 5 | source.ts, source.tsx 6 | does a triple-slash reference 7 | -------------------------------------------------------------------------------- /snippets/return-FALSE.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | ret0 4 | source.ts, source.tsx 5 | return false 6 | -------------------------------------------------------------------------------- /snippets/return-TRUE.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | ret1 4 | source.ts, source.tsx 5 | return true 6 | -------------------------------------------------------------------------------- /snippets/return.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 3 | ret 4 | source.ts, source.tsx 5 | return statement 6 | -------------------------------------------------------------------------------- /snippets/set-()-{}.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 7 | setter 8 | source.ts, source.tsx 9 | set-property … 10 | -------------------------------------------------------------------------------- /snippets/setTimeout.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | {$0}${2:}, ${1:500});]]> 3 | tout 4 | source.ts, source.tsx 5 | setTimeout function 6 | -------------------------------------------------------------------------------- /snippets/switch(-).sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 11 | switch 12 | source.ts, source.tsx 13 | switch statement … 14 | -------------------------------------------------------------------------------- /snippets/throw.sublime-snippet: -------------------------------------------------------------------------------- 1 | 2 | 4 | throw 5 | source.ts, source.tsx 6 | Throw Exception 7 | -------------------------------------------------------------------------------- /typescript/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcegraph/sublime-lsp/a8f27ce7a7cfe51cb0a5e688069951b6c764b4c2/typescript/__init__.py -------------------------------------------------------------------------------- /typescript/commands/__init__.py: -------------------------------------------------------------------------------- 1 | from .error_info import TypescriptErrorInfo 2 | from .error_list import TypescriptProjectErrorList, TypescriptGoToError 3 | from .go_to_definition import TypescriptGoToDefinitionCommand 4 | from .go_to_type import TypescriptGoToTypeCommand 5 | from .nav_to import TypescriptNavToCommand 6 | from .quick_info import TypescriptQuickInfo, TypescriptQuickInfoDoc 7 | from .save import TypescriptSave 8 | from .show_doc import TypescriptShowDoc 9 | from .signature import TypescriptSignaturePanel, TypescriptSignaturePopup 10 | from .format import ( 11 | TypescriptFormatBrackets, 12 | TypescriptFormatDocument, 13 | TypescriptFormatLine, 14 | TypescriptFormatOnKey, 15 | TypescriptFormatSelection, 16 | TypescriptPasteAndFormat, 17 | TypescriptAutoIndentOnEnterBetweenCurlyBrackets 18 | ) 19 | from .references import ( 20 | TypescriptFindReferencesCommand, 21 | TypescriptGoToRefCommand, 22 | TypescriptNextRefCommand, 23 | TypescriptEmptyRefs, 24 | TypescriptPopulateRefs, 25 | TypescriptPrevRefCommand 26 | ) 27 | from .rename import ( 28 | TypescriptDelayedRenameFile, 29 | TypescriptFinishRenameCommand, 30 | TypescriptRenameCommand 31 | ) 32 | from .build import TypescriptBuildCommand 33 | from .settings import ( 34 | TypescriptOpenPluginDefaultSettingFile, 35 | TypescriptOpenTsDefaultSettingFile 36 | # TypescriptOpenTsreactDefaultSettingFile 37 | ) 38 | from .browse import BrowseCode 39 | 40 | __all__ = [ 41 | "TypescriptAutoIndentOnEnterBetweenCurlyBrackets", 42 | "TypescriptErrorInfo", 43 | "TypescriptProjectErrorList", 44 | "TypescriptGoToError", 45 | "TypescriptFormatBrackets", 46 | "TypescriptFormatDocument", 47 | "TypescriptFormatLine", 48 | "TypescriptFormatOnKey", 49 | "TypescriptFormatSelection", 50 | "TypescriptPasteAndFormat", 51 | "TypescriptGoToDefinitionCommand", 52 | "TypescriptGoToTypeCommand", 53 | "TypescriptGoToRefCommand", 54 | "TypescriptNavToCommand", 55 | "TypescriptQuickInfo", 56 | "TypescriptQuickInfoDoc", 57 | "TypescriptFindReferencesCommand", 58 | "TypescriptGoToDefinitionCommand", 59 | "TypescriptNextRefCommand", 60 | "TypescriptEmptyRefs", 61 | "TypescriptPopulateRefs", 62 | "TypescriptPrevRefCommand", 63 | "TypescriptDelayedRenameFile", 64 | "TypescriptFinishRenameCommand", 65 | "TypescriptRenameCommand", 66 | "TypescriptSave", 67 | "TypescriptShowDoc", 68 | "TypescriptSignaturePanel", 69 | "TypescriptSignaturePopup", 70 | "TypescriptBuildCommand", 71 | "TypescriptOpenPluginDefaultSettingFile", 72 | "TypescriptOpenTsDefaultSettingFile", 73 | # "TypescriptOpenTsreactDefaultSettingFile" 74 | "BrowseCode" 75 | ] 76 | -------------------------------------------------------------------------------- /typescript/commands/base_command.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | from ..libs.global_vars import get_language_service_enabled 3 | from ..libs.view_helpers import is_supported_ext, active_view 4 | 5 | 6 | class TypeScriptBaseTextCommand(sublime_plugin.TextCommand): 7 | def is_enabled(self): 8 | return is_supported_ext(self.view) and get_language_service_enabled() 9 | 10 | 11 | class TypeScriptBaseWindowCommand(sublime_plugin.WindowCommand): 12 | def is_enabled(self): 13 | return is_supported_ext(self.window.active_view()) and get_language_service_enabled() 14 | 15 | 16 | class TypeScriptBaseApplicationCommand(sublime_plugin.ApplicationCommand): 17 | def is_enabled(self): 18 | return is_supported_ext(active_view()) and get_language_service_enabled() 19 | -------------------------------------------------------------------------------- /typescript/commands/browse.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.reference import * 3 | from .base_command import TypeScriptBaseTextCommand 4 | from ..libs.editor_client import get_root_path 5 | from ..libs.git_helpers import get_url_struct 6 | import webbrowser 7 | 8 | 9 | class BrowseCode(TypeScriptBaseTextCommand): 10 | """Browse code on Sourcegraph.com""" 11 | def run(self, text): 12 | check_update_view(self.view) 13 | # TODO check that this file isn't dirty. Won't sync with line numbers if so. 14 | root_directory = get_root_path() 15 | file_path = sublime.active_window().extract_variables().get('file') 16 | git_url_struct = get_url_struct(root_directory, file_path) 17 | if git_url_struct is None: 18 | self.view.set_status("typescript_error", "Error assembling url for %s" % file_path) 19 | row, col = self.view.rowcol(self.view.sel()[0].begin()) 20 | sourcegraph_url = "https://sourcegraph.com/"+git_url_struct[0]+"@"+git_url_struct[1]+"/-/blob/"+git_url_struct[2]+"#L"+str(row+1) 21 | print(sourcegraph_url) 22 | webbrowser.open(sourcegraph_url) 23 | 24 | def is_visible(self): 25 | return True 26 | -------------------------------------------------------------------------------- /typescript/commands/build.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | from ..libs.global_vars import * 4 | from ..libs import cli 5 | 6 | 7 | class TypescriptBuildCommand(sublime_plugin.WindowCommand): 8 | build_parameters = "" 9 | 10 | def run(self): 11 | if get_node_path() is None: 12 | print("Cannot find node. Build cancelled.") 13 | return 14 | 15 | file_name = self.window.active_view().file_name() 16 | service = cli.get_service() 17 | if not service: 18 | return None 19 | project_info = service.project_info(file_name) 20 | if project_info["success"]: 21 | body = project_info["body"] 22 | if ("configFileName" in body) and body["configFileName"].endswith(".json"): 23 | tsconfig_dir = dirname(project_info["body"]["configFileName"]) 24 | self.window.run_command("exec", { 25 | "cmd": [get_node_path(), get_tsc_path(), "-p", tsconfig_dir], 26 | # regex to capture build result for displaying in the output panel 27 | "file_regex": "^(.+?)\\((\\d+),(\\d+)\\): (.+)$" 28 | }) 29 | else: 30 | sublime.active_window().show_input_panel( 31 | "Build parameters: ", 32 | TypescriptBuildCommand.build_parameters, # initial text 33 | lambda params: self.compile_inferred_project(file_name, params), 34 | None, # on change 35 | None # on cancel 36 | ) 37 | 38 | def compile_inferred_project(self, file_name, params=""): 39 | cmd = [get_node_path(), get_tsc_path(), file_name] 40 | print(cmd) 41 | if params != "": 42 | cmd.extend(params.split(' ')) 43 | self.window.run_command("exec", { 44 | "cmd": cmd, 45 | "file_regex": "^(.+?)\\((\\d+),(\\d+)\\): (.+)$" 46 | }) 47 | TypescriptBuildCommand.build_parameters = params 48 | -------------------------------------------------------------------------------- /typescript/commands/error_info.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | from ..libs.view_helpers import * 4 | from .base_command import TypeScriptBaseTextCommand 5 | 6 | 7 | class TypescriptErrorInfo(TypeScriptBaseTextCommand): 8 | """ 9 | Command called from event handlers to show error text in status line 10 | (or to erase error text from status line if no error text for location) 11 | """ 12 | def run(self, text): 13 | client_info = cli.get_or_add_file(self.view.file_name()) 14 | pt = self.view.sel()[0].begin() 15 | error_text = "" 16 | for (region, text) in client_info.errors['syntacticDiag']: 17 | if region.contains(pt): 18 | error_text = text 19 | break 20 | for (region, text) in client_info.errors['semanticDiag']: 21 | if region.contains(pt): 22 | error_text = text 23 | break 24 | 25 | if len(error_text) > 0: 26 | if PHANTOM_SUPPORT: 27 | template = '
{0}
' 28 | display_text = template.format(error_text) 29 | self.view.add_phantom("typescript_error", self.view.sel()[0], display_text, sublime.LAYOUT_BLOCK) 30 | self.view.set_status("typescript_error", error_text) -------------------------------------------------------------------------------- /typescript/commands/error_list.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import sublime_plugin 3 | 4 | from ..libs import cli, log, global_vars 5 | from ..libs.view_helpers import active_view, get_info, is_supported_ext, active_view 6 | from ..libs.panel_manager import get_panel_manager 7 | from ..listeners.error_list import start_timer 8 | from .base_command import TypeScriptBaseWindowCommand 9 | 10 | class TypescriptProjectErrorList(sublime_plugin.WindowCommand): 11 | 12 | def is_enabled(self): 13 | return is_supported_ext(active_view()) and not global_vars.IS_ST2 and global_vars.get_language_service_enabled() 14 | 15 | def run(self): 16 | panel_manager = get_panel_manager() 17 | panel_manager.add_panel("errorlist") 18 | 19 | if not cli.worker_client.started(): 20 | panel_manager.show_panel("errorlist", ["Starting worker for project error list..."]) 21 | # start worker process 22 | cli.worker_client.start() 23 | else: 24 | # The server is up already, so just show the panel without overwriting the content 25 | panel_manager.show_panel("errorlist") 26 | 27 | opened_views = [view for view in self.window.views() if view.file_name() is not None] 28 | for opened_view in opened_views: 29 | # load each opened file 30 | get_info(opened_view) 31 | 32 | # send the first error request 33 | start_timer() 34 | 35 | 36 | class TypescriptGoToError(sublime_plugin.TextCommand): 37 | 38 | def is_enabled(self): 39 | return not global_vars.IS_ST2 and global_vars.get_language_service_enabled() 40 | 41 | def run(self, text): 42 | print("TypeScriptGoToError") 43 | error_line, _ = self.view.rowcol(self.view.sel()[0].begin()) 44 | self.update_error_line(error_line) 45 | line_map = get_panel_manager().get_line_map("errorlist") 46 | if error_line in line_map: 47 | file_name, row, col = line_map[error_line] 48 | sublime.active_window().open_file( 49 | '{0}:{1}:{2}'.format(file_name, row or 0, col or 0), 50 | sublime.ENCODED_POSITION 51 | ) 52 | 53 | def update_error_line(self, line): 54 | self.view.erase_regions("cur_error") 55 | caret_pos = self.view.text_point(line, 0) 56 | # sublime 2 doesn't support custom icons 57 | icon = "Packages/" + global_vars.PLUGIN_NAME + "/icons/arrow-right3.png" 58 | self.view.add_regions( 59 | "cur_error", 60 | [sublime.Region(caret_pos, caret_pos + 1)], 61 | "keyword", 62 | icon, 63 | sublime.HIDDEN 64 | ) -------------------------------------------------------------------------------- /typescript/commands/format.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs import log 3 | from .base_command import TypeScriptBaseTextCommand 4 | 5 | 6 | class TypescriptFormatOnKey(TypeScriptBaseTextCommand): 7 | """ 8 | Format on ";", "}", or "\n"; called by typing these keys in a ts file 9 | in the case of "\n", this is only called when no completion dialogue is visible 10 | """ 11 | def run(self, text, key="", insert_key=True): 12 | log.debug("running TypescriptFormatOnKey") 13 | 14 | if 0 == len(key): 15 | return 16 | check_update_view(self.view) 17 | service = cli.get_service() 18 | if not service: 19 | return None 20 | format_response = service.format_on_key(self.view.file_name(), get_location_from_view(self.view), key) 21 | if format_response["success"]: 22 | # logger.log.debug(str(formatResp)) 23 | code_edits = format_response["body"] 24 | apply_formatting_changes(text, self.view, code_edits) 25 | 26 | 27 | class TypescriptFormatSelection(TypeScriptBaseTextCommand): 28 | """Command to format the current selection""" 29 | def run(self, text): 30 | log.debug("running TypescriptFormatSelection") 31 | r = self.view.sel()[0] 32 | format_range(text, self.view, r.begin(), r.end()) 33 | 34 | 35 | class TypescriptFormatDocument(TypeScriptBaseTextCommand): 36 | """Command to format the entire buffer""" 37 | def run(self, text): 38 | log.debug("running TypescriptFormatDocument") 39 | format_range(text, self.view, 0, self.view.size()) 40 | 41 | 42 | class TypescriptFormatLine(TypeScriptBaseTextCommand): 43 | """Command to format the current line""" 44 | def run(self, text): 45 | log.debug("running TypescriptFormatLine") 46 | line_region = self.view.line(self.view.sel()[0]) 47 | line_text = self.view.substr(line_region) 48 | if NON_BLANK_LINE_PATTERN.search(line_text): 49 | format_range(text, self.view, line_region.begin(), line_region.end()) 50 | else: 51 | position = self.view.sel()[0].begin() 52 | line, offset = self.view.rowcol(position) 53 | if line > 0: 54 | self.view.run_command('typescript_format_on_key', {"key": "\n", "insert_key": False}) 55 | 56 | 57 | class TypescriptFormatBrackets(TypeScriptBaseTextCommand): 58 | def run(self, text): 59 | log.debug("running TypescriptFormatBrackets") 60 | check_update_view(self.view) 61 | sel = self.view.sel() 62 | if len(sel) == 1: 63 | original_pos = sel[0].begin() 64 | bracket_char = self.view.substr(original_pos) 65 | if bracket_char != "}": 66 | self.view.run_command('move_to', {"to": "brackets"}) 67 | bracket_pos = self.view.sel()[0].begin() 68 | bracket_char = self.view.substr(bracket_pos) 69 | if bracket_char == "}": 70 | self.view.run_command('move', {"by": "characters", "forward": True}) 71 | self.view.run_command('typescript_format_on_key', {"key": "}", "insert_key": False}) 72 | self.view.run_command('move', {"by": "characters", "forward": True}) 73 | 74 | 75 | class TypescriptPasteAndFormat(TypeScriptBaseTextCommand): 76 | def is_enabled(self): 77 | return True 78 | 79 | def run(self, text): 80 | if is_supported_ext(self.view) and get_language_service_enabled(): 81 | self._run(text) 82 | else: 83 | # fall back to default paste command 84 | self.view.run_command('paste') 85 | 86 | def _run(self, text): 87 | log.debug("running TypescriptPasteAndFormat") 88 | view = self.view 89 | check_update_view(view) 90 | regions_before_paste = regions_to_static_regions(view.sel()) 91 | if IS_ST2: 92 | view.add_regions("apresPaste", copy_regions(view.sel()), "", "", sublime.HIDDEN) 93 | else: 94 | view.add_regions("apresPaste", copy_regions(view.sel()), flags=sublime.HIDDEN) 95 | view.run_command("paste") 96 | regions_after_paste = view.get_regions("apresPaste") 97 | view.erase_regions("apresPaste") 98 | 99 | for rb, ra in zip(regions_before_paste, regions_after_paste): 100 | line_start = view.line(rb.begin()).begin() 101 | line_end = view.line(ra.begin()).end() 102 | format_range(text, view, line_start, line_end) 103 | 104 | 105 | class TypescriptAutoIndentOnEnterBetweenCurlyBrackets(TypeScriptBaseTextCommand): 106 | """ 107 | Handle the case of hitting enter between {} to auto indent and format 108 | """ 109 | 110 | def run(self, text): 111 | log.debug("running TypescriptAutoIndentOnEnterBetweenCurlyBrackets") 112 | view = self.view 113 | view.run_command('typescript_format_on_key', {"key": "\n"}) 114 | loc = view.sel()[0].begin() 115 | row, offset = view.rowcol(loc) 116 | tab_size = view.settings().get('tab_size') 117 | brace_offset = offset 118 | ws = "" 119 | for i in range(tab_size): 120 | ws += ' ' 121 | ws += "\n" 122 | for i in range(brace_offset): 123 | ws += ' ' 124 | # insert the whitespace 125 | insert_text(view, text, loc, ws) 126 | set_caret_pos(view, loc + tab_size) -------------------------------------------------------------------------------- /typescript/commands/go_to_definition.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.reference import * 3 | from .base_command import TypeScriptBaseTextCommand 4 | 5 | 6 | class TypescriptGoToDefinitionCommand(TypeScriptBaseTextCommand): 7 | """Go to definition command""" 8 | def run(self, text): 9 | check_update_view(self.view) 10 | service = cli.get_service() 11 | if not service: 12 | return None 13 | definition_resp = service.definition(self.view.file_name(), get_location_from_view(self.view)) 14 | if definition_resp["success"]: 15 | code_span = definition_resp["body"][0] if len(definition_resp["body"]) > 0 else None 16 | if code_span: 17 | filename = code_span["file"] 18 | start_location = code_span["start"] 19 | sublime.active_window().open_file( 20 | '{0}:{1}:{2}'.format(filename, start_location["line"], start_location["offset"]), 21 | sublime.ENCODED_POSITION 22 | ) 23 | -------------------------------------------------------------------------------- /typescript/commands/go_to_type.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.reference import * 3 | from .base_command import TypeScriptBaseTextCommand 4 | 5 | 6 | class TypescriptGoToTypeCommand(TypeScriptBaseTextCommand): 7 | """Go to type command""" 8 | def run(self, text): 9 | check_update_view(self.view) 10 | service = cli.get_service() 11 | if not service: 12 | return None 13 | type_resp = service.type(self.view.file_name(), get_location_from_view(self.view)) 14 | if type_resp["success"]: 15 | items = type_resp["body"] 16 | if len(items) > 0: 17 | code_span = items[0] 18 | filename = code_span["file"] 19 | start_location = code_span["start"] 20 | sublime.active_window().open_file( 21 | '{0}:{1}:{2}'.format(filename, start_location["line"], start_location["offset"]), 22 | sublime.ENCODED_POSITION 23 | ) 24 | -------------------------------------------------------------------------------- /typescript/commands/nav_to.py: -------------------------------------------------------------------------------- 1 | from ..libs import * 2 | from ..libs.view_helpers import * 3 | from ..libs.reference import * 4 | from .base_command import TypeScriptBaseWindowCommand 5 | 6 | 7 | class TypescriptNavToCommand(TypeScriptBaseWindowCommand): 8 | nav_to_panel_started = False 9 | 10 | # indicate weather the insert_text command has finished pasting text into the textbox, 11 | # during which time the on_modified callback shouldn't run 12 | insert_text_finished = False 13 | input_text = "" 14 | 15 | @classmethod 16 | def reset(cls): 17 | cls.nav_to_panel_started = False 18 | cls.insert_text_finished = False 19 | 20 | def run(self, input_text=""): 21 | logger.log.debug("start running nav_to with text: %s" % input_text) 22 | 23 | TypescriptNavToCommand.reset() 24 | TypescriptNavToCommand.input_text = input_text 25 | TypescriptNavToCommand.nav_to_panel_started = True 26 | 27 | # Text used for querying is not always equal to the input text. This is because the quick 28 | # panel will disappear if an empty list is provided, and we want to avoid this. Therefore 29 | # when some input text that will result in empty results is given (for example, empty 30 | # string), we use alternative text to ensure the panel stay active 31 | query_text = "a" if input_text == "" else input_text 32 | service = cli.get_service() 33 | if not service: 34 | return None 35 | response_dict = service.nav_to(query_text, self.window.active_view().file_name()) 36 | if response_dict["success"]: 37 | items = response_dict["body"] 38 | self.items = items if len(items) != 0 else self.items 39 | 40 | self.window.show_quick_panel(self.format_nav_to_result(self.items), self.on_done) 41 | logger.log.debug("end running nav_to with text: %s" % input_text) 42 | 43 | def on_done(self, index): 44 | TypescriptNavToCommand.reset() 45 | 46 | if index >= 0: 47 | item = self.items[index] 48 | line, offset = item['start']['line'], item['start']['offset'] 49 | file_at_location = item['file'] + ":%s:%s" % (line, offset) 50 | self.window.open_file(file_at_location, sublime.ENCODED_POSITION) 51 | 52 | def format_nav_to_result(self, item_list): 53 | def get_description_str(item): 54 | name = item["name"] 55 | kind = item["kind"] 56 | container_kind = item["containerKind"] if "containerKind" in item else os.path.basename(item["file"]) + " (global)" 57 | container_name = item["containerName"] if "containerName" in item else "" 58 | description_str = "{0} in {1} {2}".format(kind, container_kind, container_name) 59 | return [name, description_str] 60 | 61 | return [get_description_str(item) for item in item_list] 62 | 63 | def on_highlight(self, index): 64 | pass 65 | -------------------------------------------------------------------------------- /typescript/commands/quick_info.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.text_helpers import escape_html 3 | from .base_command import TypeScriptBaseTextCommand 4 | 5 | 6 | class TypescriptQuickInfo(TypeScriptBaseTextCommand): 7 | """Command currently called only from event handlers""" 8 | 9 | def handle_quick_info(self, quick_info_resp_dict): 10 | if quick_info_resp_dict["success"]: 11 | info_str = quick_info_resp_dict["body"]["displayString"] 12 | doc_str = quick_info_resp_dict["body"]["documentation"] 13 | if not info_str and not doc_str: 14 | self.view.erase_status("typescript_info") 15 | return 16 | 17 | if doc_str: 18 | info_str += " (^T^Q for more)" 19 | self.view.set_status("typescript_info", info_str) 20 | else: 21 | self.view.erase_status("typescript_info") 22 | 23 | def run(self, text): 24 | check_update_view(self.view) 25 | word_at_sel = self.view.classify(self.view.sel()[0].begin()) 26 | if word_at_sel & SUBLIME_WORD_MASK: 27 | service = cli.get_service() 28 | if not service: 29 | return None 30 | service.quick_info(self.view.file_name(), get_location_from_view(self.view), self.handle_quick_info) 31 | else: 32 | self.view.erase_status("typescript_info") 33 | 34 | 35 | class TypescriptQuickInfoDoc(TypeScriptBaseTextCommand): 36 | """ 37 | Command to show the doc string associated with quick info; 38 | re-runs quick info in case info has changed 39 | """ 40 | 41 | def handle_quick_info(self, quick_info_resp_dict, display_point): 42 | if quick_info_resp_dict["success"]: 43 | info_str = quick_info_resp_dict["body"]["displayString"] 44 | status_info_str = info_str 45 | doc_str = quick_info_resp_dict["body"]["documentation"] 46 | 47 | if not info_str and not doc_str: 48 | return 49 | # process documentation 50 | if doc_str: 51 | if not TOOLTIP_SUPPORT: 52 | doc_panel = sublime.active_window().get_output_panel("doc") 53 | doc_panel.run_command( 54 | 'typescript_show_doc', 55 | {'infoStr': info_str, 'docStr': doc_str} 56 | ) 57 | doc_panel.settings().set('color_scheme', "Packages/Color Scheme - Default/Blackboard.tmTheme") 58 | sublime.active_window().run_command('show_panel', {'panel': 'output.doc'}) 59 | status_info_str = info_str + " (^T^Q for more)" 60 | self.view.set_status("typescript_info", status_info_str) 61 | 62 | # process display string 63 | if TOOLTIP_SUPPORT: 64 | html_info_str = escape_html(info_str) 65 | html_doc_str = escape_html(doc_str) 66 | html = "
" + html_info_str + "
" 67 | if len(doc_str) > 0: 68 | html += "
" + html_doc_str + "
" 69 | self.view.show_popup(html, flags=sublime.HIDE_ON_MOUSE_MOVE_AWAY, location=display_point, max_width=800) 70 | else: 71 | self.view.erase_status("typescript_info") 72 | 73 | def run(self, text, hover_point=None): 74 | check_update_view(self.view) 75 | display_point = self.view.sel()[0].begin() if hover_point is None else hover_point 76 | word_at_sel = self.view.classify(display_point) 77 | if word_at_sel & SUBLIME_WORD_MASK: 78 | service = cli.get_service() 79 | if not service: 80 | return None 81 | service.quick_info(self.view.file_name(), get_location_from_position(self.view, display_point), lambda response: self.handle_quick_info(response, display_point)) 82 | else: 83 | self.view.erase_status("typescript_info") 84 | -------------------------------------------------------------------------------- /typescript/commands/references.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | import sublime_plugin 4 | 5 | from ..libs import * 6 | from ..libs.view_helpers import * 7 | from ..libs.reference import * 8 | from ..libs import log 9 | from .base_command import TypeScriptBaseTextCommand 10 | from collections import defaultdict 11 | 12 | 13 | class TypescriptFindReferencesCommand(TypeScriptBaseTextCommand): 14 | """Find references command""" 15 | def handle_references(self, references_resp): 16 | if references_resp["success"]: 17 | pos = self.view.sel()[0].begin() 18 | cursor = self.view.rowcol(pos) 19 | line = str(cursor[0] + 1) 20 | t = threading.Thread(target=TypescriptFindReferencesCommand.__decorate, args=(self.view.file_name(), line, references_resp["body"])) 21 | t.daemon = True 22 | t.start() 23 | self.view.erase_status("typescript_populate_refs") 24 | 25 | def run(self, text): 26 | check_update_view(self.view) 27 | service = cli.get_service() 28 | if not service: 29 | return None 30 | ref_view = get_ref_view() 31 | ref_view.run_command('typescript_empty_refs') 32 | service.references(self.view.file_name(), get_location_from_view(self.view), self.handle_references) 33 | 34 | def is_visible(self): 35 | if not cli.client_manager.has_extension(sublime.active_window().extract_variables().get('file_extension')): 36 | return False 37 | if len(self.view.sel()) == 0: 38 | False 39 | # TODO(uforic) hide if text isn't clicked on, imitating Sublime Go to def behavior 40 | return True 41 | 42 | @staticmethod 43 | def __decorate(file_name, line, references): 44 | symbol = None 45 | file_entries = defaultdict(list) 46 | for i, entry in enumerate(references["refs"]): 47 | file_entries[entry["file"]].append(entry) 48 | for file_name in file_entries: 49 | get_lines_from_file(file_name, file_entries[file_name]) 50 | for entry in file_entries[file_name]: 51 | if symbol is None and entry["start"]["line"] == entry["end"]["line"]: 52 | symbol = entry["lineText"][entry["start"]["offset"]-1:entry["end"]["offset"]-1] 53 | if symbol is None: 54 | symbol = "?" 55 | references["symbolName"] = symbol 56 | args = {"line": line, "filename": file_name, "referencesRespBody": references} 57 | args_json_str = json_helpers.encode(args) 58 | ref_view = get_ref_view() 59 | ref_view.run_command('typescript_populate_refs', {"argsJson": args_json_str}) 60 | 61 | 62 | class TypescriptGoToRefCommand(sublime_plugin.TextCommand): 63 | """ 64 | If cursor is on reference line, go to (filename, line, offset) referenced by that line 65 | """ 66 | def is_enabled(self): 67 | return global_vars.get_language_service_enabled() 68 | 69 | def run(self, text): 70 | pos = self.view.sel()[0].begin() 71 | cursor = self.view.rowcol(pos) 72 | ref_info = cli.get_ref_info() 73 | mapping = ref_info.get_mapping(str(cursor[0])) 74 | if mapping: 75 | (filename, l, c, p, n) = mapping.as_tuple() 76 | update_ref_line(ref_info, cursor[0], self.view) 77 | sublime.active_window().open_file( 78 | '{0}:{1}:{2}'.format(filename, l + 1 or 0, c + 1 or 0), 79 | sublime.ENCODED_POSITION 80 | ) 81 | 82 | 83 | # TODO: generalize this to work for all types of references 84 | class TypescriptNextRefCommand(sublime_plugin.TextCommand): 85 | def is_enabled(self): 86 | return global_vars.get_language_service_enabled() 87 | 88 | def run(self, text): 89 | ref_view = get_ref_view() 90 | if ref_view: 91 | ref_info = cli.get_ref_info() 92 | line = ref_info.next_ref_line() 93 | pos = ref_view.text_point(int(line), 0) 94 | set_caret_pos(ref_view, pos) 95 | ref_view.run_command('typescript_go_to_ref') 96 | 97 | 98 | # TODO: generalize this to work for all types of references 99 | class TypescriptPrevRefCommand(sublime_plugin.TextCommand): 100 | """Go to previous reference in active references file""" 101 | def is_enabled(self): 102 | return global_vars.get_language_service_enabled() 103 | 104 | def run(self, text): 105 | ref_view = get_ref_view() 106 | if ref_view: 107 | ref_info = cli.get_ref_info() 108 | line = ref_info.prev_ref_line() 109 | pos = ref_view.text_point(int(line), 0) 110 | set_caret_pos(ref_view, pos) 111 | ref_view.run_command('typescript_go_to_ref') 112 | 113 | 114 | class TypescriptEmptyRefs(sublime_plugin.TextCommand): 115 | """ 116 | Helper command called by TypescriptFindReferences; put the references in the 117 | references buffer (such as build errors) 118 | """ 119 | 120 | def run(self, text): 121 | self.view.set_read_only(False) 122 | # erase the caret showing the last reference followed 123 | self.view.erase_regions("curref") 124 | # clear the references buffer 125 | self.view.erase(text, sublime.Region(0, self.view.size())) 126 | header = "Finding references..." 127 | self.view.insert(text, self.view.text_point(0,0), header) 128 | self.view.set_read_only(True) 129 | 130 | 131 | # TODO: generalize this to populate any type of references file 132 | class TypescriptPopulateRefs(sublime_plugin.TextCommand): 133 | """ 134 | Helper command called by TypescriptFindReferences; put the references in the 135 | references buffer (such as build errors) 136 | """ 137 | def is_enabled(self): 138 | return global_vars.get_language_service_enabled() 139 | 140 | def run(self, text, argsJson): 141 | args = json_helpers.decode(argsJson) 142 | file_name = args["filename"] 143 | line = args["line"] 144 | ref_display_string = args["referencesRespBody"]["symbolName"] 145 | ref_id = args["referencesRespBody"]["symbolName"] 146 | refs = args["referencesRespBody"]["refs"] 147 | 148 | file_count = 0 149 | match_count = 0 150 | self.view.set_read_only(False) 151 | # erase the caret showing the last reference followed 152 | self.view.erase_regions("curref") 153 | # clear the references buffer 154 | self.view.erase(text, sublime.Region(0, self.view.size())) 155 | self.view.set_syntax_file("Packages/" + PLUGIN_NAME + "/FindRefs.hidden-tmLanguage") 156 | header = "References to {0} \n\n".format(ref_display_string) 157 | self.view.insert(text, self.view.sel()[0].begin(), header) 158 | window = sublime.active_window() 159 | ref_info = None 160 | if len(refs) > 0: 161 | prev_file_name = "" 162 | prev_line = None 163 | for ref in refs: 164 | file_name = ref["file"] 165 | if prev_file_name != file_name: 166 | file_count += 1 167 | if prev_file_name != "": 168 | self.view.insert(text, self.view.sel()[0].begin(), "\n") 169 | self.view.insert(text, self.view.sel()[0].begin(), file_name + ":\n") 170 | prev_file_name = file_name 171 | start_location = ref["start"] 172 | (l, c) = extract_line_offset(start_location) 173 | pos = self.view.sel()[0].begin() 174 | cursor = self.view.rowcol(pos) 175 | line = str(cursor[0]) 176 | if not ref_info: 177 | ref_info = cli.init_ref_info(line, ref_id) 178 | ref_info.add_mapping(line, Ref(file_name, l, c, prev_line)) 179 | if prev_line: 180 | mapping = ref_info.get_mapping(prev_line) 181 | mapping.set_next_line(line) 182 | prev_line = line 183 | content = ref["lineText"] 184 | display_ref = " {0}: {1}\n".format(l + 1, content) 185 | match_count += 1 186 | self.view.insert(text, self.view.sel()[0].begin(), display_ref) 187 | ref_info.set_last_line(line) 188 | self.view.insert(text, self.view.sel()[0].begin(), 189 | "\n{0} matches in {1} file{2}\n".format(match_count, 190 | file_count, "" if (file_count == 1) else "s")) 191 | if match_count > 0: 192 | highlight_ids(self.view, ref_id) 193 | window.focus_view(self.view) 194 | set_caret_pos(self.view, self.view.text_point(2, 0)) 195 | # serialize the reference info into the settings 196 | self.view.settings().set('refinfo', ref_info.as_value()) 197 | self.view.set_read_only(True) 198 | 199 | 200 | def get_lines_from_file(fileName, entries): 201 | line_nos = defaultdict(list) 202 | for entry in entries: 203 | line_nos[entry["start"]["line"] - 1].append(entry) 204 | f = None 205 | try: 206 | f = open(fileName, encoding='utf-8', errors='ignore') 207 | count = 0 208 | for line in f: 209 | if line_nos.get(count): 210 | for entry in line_nos.get(count): 211 | entry["lineText"] = line.rstrip() 212 | count = count + 1 213 | 214 | except Exception as e: 215 | log.exception('Error fetching preview from %s' % (fileName)) 216 | finally: 217 | if f is not None: 218 | f.close() 219 | -------------------------------------------------------------------------------- /typescript/commands/rename.py: -------------------------------------------------------------------------------- 1 | from ..libs import * 2 | from ..libs.view_helpers import * 3 | from ..libs.reference import * 4 | from .base_command import TypeScriptBaseTextCommand 5 | 6 | 7 | class TypescriptRenameCommand(TypeScriptBaseTextCommand): 8 | """ 9 | Command to rename identifier 10 | """ 11 | def run(self, text): 12 | check_update_view(self.view) 13 | service = cli.get_service() 14 | if not service: 15 | return None 16 | rename_response = service.rename(self.view.file_name(), get_location_from_view(self.view)) 17 | if not rename_response['success']: 18 | return 19 | 20 | body = rename_response['body'] 21 | 22 | info = body['info'] 23 | if not info['canRename']: 24 | self.view.set_status('typescript_error', info['localizedErrorMessage']) 25 | return 26 | 27 | display_name = info['fullDisplayName'] 28 | outer_locations = body['locs'] 29 | 30 | def on_done(new_name): 31 | args = {'newName': new_name, 'outerLocs': outer_locations} 32 | args_json_str = json_helpers.encode(args) 33 | self.view.run_command('typescript_finish_rename', {'args_json': args_json_str}) 34 | 35 | if len(outer_locations) > 0: 36 | sublime.active_window().show_input_panel( 37 | 'New name for {0}: '.format(display_name), 38 | info['displayName'], # initial text 39 | on_done, 40 | None, # on_change 41 | None # on_cancel 42 | ) 43 | 44 | 45 | class TypescriptFinishRenameCommand(TypeScriptBaseTextCommand): 46 | """ 47 | Called from on_done handler in finish_rename command 48 | on_done is called by input panel for new name 49 | """ 50 | def run(self, text, args_json=""): 51 | args = json_helpers.decode(args_json) 52 | new_name = args["newName"] 53 | outer_locations = args["outerLocs"] 54 | if len(outer_locations) > 0: 55 | for outerLoc in outer_locations: 56 | file = outerLoc["file"] 57 | inner_locations = outerLoc["locs"] 58 | rename_view = active_window().find_open_file(file) 59 | if not rename_view: 60 | # File not loaded but on disk 61 | client_info = cli.get_or_add_file(file) 62 | client_info.rename_on_load = {"locs": inner_locations, "name": new_name} 63 | active_window().open_file(file) 64 | elif rename_view != self.view: 65 | # File opened but not current one 66 | rename_view.run_command('typescript_delayed_rename_file', 67 | {"locs_name": {"locs": inner_locations, "name": new_name}}) 68 | else: 69 | for inner_location in inner_locations: 70 | start_line, start_offset = extract_line_offset(inner_location["start"]) 71 | end_line, end_offset = extract_line_offset(inner_location["end"]) 72 | apply_edit(text, self.view, start_line, start_offset, end_line, 73 | end_offset, new_text=new_name) 74 | 75 | 76 | class TypescriptDelayedRenameFile(TypeScriptBaseTextCommand): 77 | """Rename in 'on_load' method""" 78 | def run(self, text, locs_name=None): 79 | if locs_name['locs'] and (len(locs_name['name']) > 0): 80 | locs = locs_name['locs'] 81 | name = locs_name['name'] 82 | for inner_location in locs: 83 | start_line, start_offset = extract_line_offset(inner_location['start']) 84 | end_line, end_offset = extract_line_offset(inner_location['end']) 85 | apply_edit(text, self.view, start_line, start_offset, end_line, 86 | end_offset, new_text=name) 87 | -------------------------------------------------------------------------------- /typescript/commands/save.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from .base_command import TypeScriptBaseTextCommand 3 | 4 | 5 | class TypescriptSave(TypeScriptBaseTextCommand): 6 | """Save file command 7 | 8 | For debugging, send command to server to save server buffer in temp file 9 | TODO: safe temp file name on Windows 10 | """ 11 | def run(self, text): 12 | service = cli.get_service() 13 | if not service: 14 | return None 15 | service.save_to(self.view.file_name(), "/tmp/curstate") 16 | -------------------------------------------------------------------------------- /typescript/commands/settings.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | from ..libs.global_vars import * 4 | from ..libs import cli, logger 5 | import os 6 | 7 | class TypescriptOpenPluginDefaultSettingFile(sublime_plugin.WindowCommand): 8 | def run(self): 9 | default_plugin_setting_path = os.path.join(PLUGIN_DIR, "SublimeLsp.sublime-settings") 10 | sublime.active_window().open_file(default_plugin_setting_path) 11 | 12 | class TypescriptOpenTsDefaultSettingFile(sublime_plugin.WindowCommand): 13 | def run(self): 14 | default_ts_setting_path = os.path.join(PLUGIN_DIR, "SublimeLsp.sublime-settings") 15 | sublime.active_window().open_file(default_ts_setting_path) 16 | 17 | # class TypescriptOpenTsreactDefaultSettingFile(sublime_plugin.WindowCommand): 18 | # def run(self): 19 | # default_tsreact_setting_path = os.path.join(PLUGIN_DIR, "TypeScriptReact.sublime-settings") 20 | # sublime.active_window().open_file(default_tsreact_setting_path) -------------------------------------------------------------------------------- /typescript/commands/show_doc.py: -------------------------------------------------------------------------------- 1 | from .base_command import TypeScriptBaseTextCommand 2 | 3 | 4 | class TypescriptShowDoc(TypeScriptBaseTextCommand): 5 | def run(self, text, info_str="", doc_str=""): 6 | self.view.insert(text, self.view.sel()[0].begin(), info_str + "\n\n") 7 | self.view.insert(text, self.view.sel()[0].begin(), doc_str) -------------------------------------------------------------------------------- /typescript/commands/signature.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | from ..libs import * 4 | from ..libs.view_helpers import * 5 | from ..libs.reference import * 6 | from .base_command import TypeScriptBaseTextCommand 7 | 8 | 9 | class TypescriptSignaturePanel(TypeScriptBaseTextCommand): 10 | def run(self, text): 11 | logger.log.debug('TypeScript signature panel triggered') 12 | self.results = [] 13 | self.snippets = [] 14 | service = cli.get_service() 15 | if not service: 16 | return None 17 | service.signature_help( 18 | self.view.file_name(), 19 | get_location_from_view(self.view), '', 20 | self.on_results 21 | ) 22 | if self.results: 23 | self.view.window().show_quick_panel(self.results, self.on_selected) 24 | 25 | def on_results(self, response_dict): 26 | if not response_dict["success"] or not response_dict["body"]: 27 | return 28 | 29 | def get_text_from_parts(display_parts): 30 | result = "" 31 | if display_parts: 32 | for part in display_parts: 33 | result += part["text"] 34 | return result 35 | 36 | for signature in response_dict["body"]["items"]: 37 | signature_text = get_text_from_parts(signature["prefixDisplayParts"]) 38 | snippet_text = "" 39 | param_id_x = 1 40 | 41 | if signature["parameters"]: 42 | for param in signature["parameters"]: 43 | if param_id_x > 1: 44 | signature_text += ", " 45 | snippet_text += ", " 46 | 47 | param_text = "" 48 | param_text += get_text_from_parts(param["displayParts"]) 49 | signature_text += param_text 50 | snippet_text += "${" + str(param_id_x) + ":" + param_text + "}" 51 | param_id_x += 1 52 | 53 | signature_text += get_text_from_parts(signature["suffixDisplayParts"]) 54 | self.results.append(signature_text) 55 | self.snippets.append(snippet_text) 56 | 57 | def on_selected(self, index): 58 | if index == -1: 59 | return 60 | 61 | self.view.run_command('insert_snippet', {"contents": self.snippets[index]}) 62 | 63 | 64 | class TypescriptSignaturePopup(sublime_plugin.TextCommand): 65 | def is_enabled(self): 66 | return TOOLTIP_SUPPORT and is_supported_ext(self.view) and get_language_service_enabled() 67 | 68 | def run(self, edit, move=None): 69 | log.debug('In run for signature popup with move: {0}'.format(move if move else 'None')) 70 | if not TOOLTIP_SUPPORT: 71 | return 72 | 73 | popup_manager = get_popup_manager() 74 | if move is None: 75 | popup_manager.queue_signature_popup(self.view) 76 | elif move == 'prev': 77 | popup_manager.move_prev() 78 | elif move == 'next': 79 | popup_manager.move_next() 80 | else: 81 | raise ValueError('Unknown arg: ' + move) 82 | 83 | -------------------------------------------------------------------------------- /typescript/libs/__init__.py: -------------------------------------------------------------------------------- 1 | # from .node_client import NodeCommClient, ServerClient, WorkerClient 2 | from .lsp_client import LspCommClient, ServerClient, WorkerClient 3 | from .popup_manager import PopupManager 4 | from .service_proxy import ServiceProxy 5 | from .editor_client import cli, EditorClient 6 | from .popup_manager import get_popup_manager 7 | from .logger import log 8 | from .panel_manager import get_panel_manager 9 | from . import logger 10 | from . import global_vars 11 | __all__ = [ 12 | 'cli', 13 | 'EditorClient', 14 | 'logger', 15 | 'log', 16 | 'get_popup_manager', 17 | # 'NodeCommClient', 18 | 'LspCommClient', 19 | 'ServerClient', 20 | 'WorkerClient', 21 | 'json_helpers', 22 | 'PopupManager', 23 | 'ServiceProxy', 24 | 'work_scheduler', 25 | 'global_vars', 26 | 'get_panel_manager' 27 | ] -------------------------------------------------------------------------------- /typescript/libs/client_manager.py: -------------------------------------------------------------------------------- 1 | from .lsp_client import ServerClient, WorkerClient 2 | from .service_proxy import ServiceProxy 3 | from .logger import log 4 | 5 | import json 6 | import threading 7 | try: 8 | from Queue import Queue 9 | except ImportError: 10 | from queue import Queue # python 3.x 11 | 12 | 13 | class LspClientManager(): 14 | 15 | def __init__(self): 16 | self.client_mapping = {} 17 | self.extension_mapping = {} 18 | self.response_queue = Queue() 19 | self.lock = threading.Lock() 20 | 21 | def register_extensions(self, file_exts, binary_name, args, env): 22 | for file_ext in file_exts: 23 | log.debug('Registered binary {0} for extension {1}'.format(binary_name, file_ext)) 24 | self.extension_mapping[file_ext] = (binary_name, args if args else [], env) 25 | 26 | def get_client(self, file_ext, root_path): 27 | log.debug('Looking for client for extension {0} in {1}'.format(file_ext, root_path)) 28 | if file_ext not in self.extension_mapping: 29 | log.debug('Extension {0} is not supported yet'.format(file_ext)) 30 | return None 31 | # return FileExtensionNotRegistered(file_ext) 32 | binary_name, args, env = self.extension_mapping[file_ext] 33 | key = (root_path, binary_name) 34 | if key in self.client_mapping: 35 | log.debug('Using existing client') 36 | return self.client_mapping[key] 37 | try: 38 | log.debug('Instantiating new client using binary {0} for extension {1} in {2}'.format(binary_name, file_ext, root_path)) 39 | node_client = ServerClient(binary_name, args, env, root_path) 40 | worker_client = WorkerClient(binary_name, args, env, root_path) 41 | service = ServiceProxy(worker_client, node_client) 42 | self.client_mapping[key] = service 43 | return service 44 | except Exception as err: 45 | # Deal with process init failure 46 | return None 47 | # return ProcessFailsToStart( 48 | # file_ext, binary_name, args, env) 49 | 50 | def has_extension(self, ext): 51 | return self.extension_mapping.get(ext) is not None 52 | 53 | def get_clients(self): 54 | return self.client_mapping.values() 55 | 56 | 57 | class ClientManagerError(): 58 | pass 59 | 60 | 61 | class FileExtensionNotRegistered(ClientManagerError): 62 | 63 | def __init__(self, file_ext): 64 | self.file_ext = file_ext 65 | self.message = "File extension %s doesn't have a registered LSP binary." % ( 66 | self.file_ext) 67 | 68 | 69 | class ProcessFailsToStart(ClientManagerError): 70 | 71 | def __init__(self, file_ext, binary_name, args, env): 72 | self.file_ext = file_ext 73 | self.message = "Failed to start LSP client %s registered for extension %s with args %s and env %s" % ( 74 | file_ext, binary_name, args, env) 75 | -------------------------------------------------------------------------------- /typescript/libs/editor_client.py: -------------------------------------------------------------------------------- 1 | from .reference import RefInfo 2 | from .lsp_client import ServerClient, WorkerClient 3 | from .client_manager import LspClientManager 4 | # from .node_client import ServerClient, WorkerClient 5 | from .service_proxy import ServiceProxy 6 | from .global_vars import * 7 | from . import global_vars 8 | import copy 9 | 10 | class ClientFileInfo: 11 | """per-file, globally-accessible information""" 12 | 13 | def __init__(self, filename): 14 | self.filename = filename 15 | self.pending_changes = False 16 | self.change_count = 0 17 | self.errors = { 18 | 'syntacticDiag': [], 19 | 'semanticDiag': [], 20 | } 21 | self.rename_on_load = None 22 | 23 | 24 | def get_root_path(): 25 | # hack, because the Sublime API is broken and .get('folder') returns the 26 | # top folder always 27 | file_path = sublime.active_window().extract_variables().get('file_path') 28 | if not file_path: 29 | return None 30 | 31 | found_folder = sublime.active_window().extract_variables().get('folder') 32 | chars_matching = -1 33 | for folder in sublime.active_window().folders(): 34 | if file_path.startswith(folder) and len(folder) > chars_matching: 35 | found_folder = folder 36 | chars_matching = len(found_folder) 37 | return found_folder 38 | 39 | 40 | class EditorClient: 41 | """A singleton class holding information for the entire application that must be accessible globally""" 42 | 43 | def __init__(self): 44 | self.file_map = {} 45 | self.ref_info = None 46 | self.seq_to_tempfile_name = {} 47 | self.available_tempfile_list = [] 48 | self.tmpseq = 0 49 | # self.node_client = None 50 | # self.worker_client = None 51 | self.initialized = False 52 | 53 | self.tab_size = 4 54 | self.indent_size = self.tab_size 55 | self.translate_tab_to_spaces = False 56 | self.ts_auto_format_enabled = True 57 | self.ts_auto_indent_enabled = True 58 | self.auto_match_enabled = True 59 | self.client_manager = LspClientManager() 60 | 61 | def go_specific_hack(self, env): 62 | gopaths = env["GOPATH"].split(os.pathsep) 63 | for gopath in gopaths: 64 | env["PATH"] = os.path.join(gopath, "bin")+os.pathsep+env["PATH"] 65 | 66 | def initialize_client_manager(self, settings): 67 | # initialize client manager 68 | clients = settings.get("clients") 69 | if clients: 70 | for client in clients: 71 | env = copy.deepcopy(os.environ) 72 | path_additions = client.get("path_additions") 73 | if path_additions: 74 | env["PATH"] = os.pathsep.join(path_additions)+os.pathsep+env["PATH"] 75 | additional_env = client.get("env") 76 | if additional_env: 77 | env.update(additional_env) 78 | if "go" in client["file_exts"]: 79 | self.go_specific_hack(env) 80 | self.client_manager.register_extensions( 81 | client["file_exts"], 82 | client["binary"], 83 | client.get("args"), 84 | env) 85 | 86 | 87 | def get_service(self): 88 | file_ext = sublime.active_window().extract_variables().get('file_extension') 89 | root_path = get_root_path() 90 | return self.client_manager.get_client(file_ext, root_path) 91 | 92 | def initialize(self): 93 | """ 94 | Sublime_api methods can only be executed in plugin_loaded, and they will 95 | return None if executed during import time. Therefore the cli needs to be 96 | initialized during loading time 97 | """ 98 | 99 | # retrieve the path to tsserver.js 100 | # first see if user set the path to the file 101 | settings = sublime.load_settings("SublimeLsp.sublime-settings") 102 | self.initialize_client_manager(settings) 103 | # tsdk_location = settings.get("typescript_tsdk") 104 | # if tsdk_location: 105 | # proc_file = os.path.join(tsdk_location, "tsserver.js") 106 | # global_vars._tsc_path = os.path.join(tsdk_location, "tsc.js") 107 | # else: 108 | # # otherwise, get tsserver.js from package directory 109 | # proc_file = os.path.join(PLUGIN_DIR, "tsserver", "tsserver.js") 110 | # global_vars._tsc_path = os.path.join(PLUGIN_DIR, "tsserver", "tsc.js") 111 | # print("Path of tsserver.js: " + proc_file) 112 | # print("Path of tsc.js: " + get_tsc_path()) 113 | 114 | # self.node_client = ServerClient(proc_file) 115 | # self.worker_client = WorkerClient(proc_file) 116 | # self.service = ServiceProxy(self.worker_client, self.node_client) 117 | 118 | # load formatting settings and set callbacks for setting changes 119 | for setting_name in [ 120 | 'tab_size', 121 | 'indent_size', 122 | 'translate_tabs_to_spaces', 123 | 'typescript_auto_format', 124 | 'typescript_auto_indent', 125 | 'auto_match_enabled' 126 | ]: 127 | settings.add_on_change(setting_name, self.load_format_settings) 128 | self.load_format_settings() 129 | 130 | self.initialized = True 131 | 132 | def load_format_settings(self): 133 | settings = sublime.load_settings('SublimeLsp.sublime-settings') 134 | self.tab_size = settings.get('tab_size', 4) 135 | self.indent_size = settings.get('indent_size', self.tab_size) 136 | self.translate_tab_to_spaces = settings.get('translate_tabs_to_spaces', False) 137 | self.ts_auto_format_enabled = settings.get("typescript_auto_format") 138 | self.ts_auto_indent_enabled = settings.get("typescript_auto_indent") 139 | self.auto_match_enabled = settings.get("auto_match_enabled") 140 | self.set_features() 141 | 142 | def set_features(self): 143 | host_info = "Sublime Text version " + str(sublime.version()) 144 | # Preferences Settings 145 | format_options = { 146 | "tabSize": self.tab_size, 147 | "indentSize": self.indent_size, 148 | "convertTabsToSpaces": self.translate_tab_to_spaces 149 | } 150 | service = self.get_service() 151 | if not service: 152 | return None 153 | service.configure(host_info, None, format_options) 154 | 155 | # ref info is for Find References view 156 | # TODO: generalize this so that there can be multiple 157 | # for example, one for Find References and one for build errors 158 | def dispose_ref_info(self): 159 | self.ref_info = None 160 | 161 | def init_ref_info(self, first_line, ref_id): 162 | self.ref_info = RefInfo(first_line, ref_id) 163 | return self.ref_info 164 | 165 | def update_ref_info(self, ref_info): 166 | self.ref_info = ref_info 167 | 168 | def get_ref_info(self): 169 | return self.ref_info 170 | 171 | def get_or_add_file(self, filename): 172 | """Get or add per-file information that must be globally accessible """ 173 | if os.name == "nt" and filename: 174 | filename = filename.replace('/', '\\') 175 | if filename not in self.file_map: 176 | client_info = ClientFileInfo(filename) 177 | self.file_map[filename] = client_info 178 | else: 179 | client_info = self.file_map[filename] 180 | return client_info 181 | 182 | def has_errors(self, filename): 183 | client_info = self.get_or_add_file(filename) 184 | return len(client_info.errors['syntacticDiag']) > 0 or len(client_info.errors['semanticDiag']) > 0 185 | 186 | # The globally accessible instance 187 | cli = EditorClient() 188 | -------------------------------------------------------------------------------- /typescript/libs/git_helpers.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | import re 3 | import os 4 | import sys 5 | import logging 6 | 7 | 8 | def get_url_struct(cwd, file_path): 9 | git_url = clean_git_url(get_git_url(cwd)) 10 | commit_hash = get_git_commit_hash(cwd) 11 | path = file_path.replace(cwd, "") 12 | path = "/".join(path.split(os.sep)) 13 | path = path.lstrip(os.sep) 14 | if git_url is None or commit_hash is None: 15 | return None 16 | return (git_url, commit_hash, path) 17 | 18 | 19 | def get_git_commit_hash(cwd): 20 | args = ["git", "rev-parse", "HEAD"] 21 | return run_command_sync(args, cwd) 22 | 23 | 24 | def get_git_url(cwd): 25 | args = ["git", "config", "--get", "remote.origin.url"] 26 | return run_command_sync(args, cwd) 27 | 28 | 29 | GIT_PATTERN = re.compile("git\@|.git|https:\/\/") 30 | SSH_PATTERN = re.compile("com:") 31 | 32 | 33 | def clean_git_url(git_url): 34 | # TODO there are more cases to cover than this 35 | if git_url is None: 36 | return None 37 | git_url = GIT_PATTERN.sub("", git_url, 10) 38 | return SSH_PATTERN.sub("com/", git_url, 10) 39 | 40 | 41 | def run_command_sync(args, cwd): 42 | try: 43 | output = subprocess.check_output(args, cwd=cwd) 44 | if sys.version_info >= (3, 0): 45 | return str(output, "utf-8").strip() 46 | return output.strip() 47 | except Exception as e: 48 | logging.debug("Error running %s in working directory %s" % (args, cwd)) 49 | return None 50 | 51 | # function getGitStatus(data) { 52 | # const parentDirectory = path.resolve(data.filename, "..").split(" ").join("\\ "); 53 | # const basename = path.basename(data.filename); 54 | 55 | # // if file has changed, return because annotations won't correlate 56 | # if (data.is_dirty) { 57 | # return null; 58 | # } 59 | # const fileChanged = this.hasFileChanged(parentDirectory, basename); 60 | # if (fileChanged) { 61 | # return null; 62 | # } 63 | 64 | # let gitCommitID = this.getGitCommitHash(parentDirectory); 65 | 66 | # const mainGitRepo = this.getTopLevelGitDirectory(parentDirectory); 67 | 68 | # let gitWebURI = this.getGitUrl(parentDirectory); 69 | # gitWebURI = this.cleanGitUrl(gitWebURI); 70 | 71 | # const relativeGitRepo = parentDirectory.split(mainGitRepo).join(""); 72 | # const relativeFilePath = data.filename.split(`${mainGitRepo}/`).join(""); 73 | 74 | # return {gitWebUri: gitWebURI, relativeGitRepo: relativeGitRepo, relativeFilePath: relativeFilePath, gitCommitId: gitCommitID}; 75 | 76 | # } -------------------------------------------------------------------------------- /typescript/libs/global_vars.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | import logging 4 | import sublime 5 | from os.path import dirname 6 | 7 | # determine if the host is sublime text 2 8 | IS_ST2 = int(sublime.version()) < 3000 9 | 10 | # Get the directory path to this file; 11 | # Note: there are several ways this plugin can be installed 12 | # 1. from the package control -> the absolute path of __file__ is real and contains plugin name 13 | # 2. git clone directly to the sublime packages folder -> the absolute path of __file__ is real and contains plugin name 14 | # 3. git clone to somewhere else, and link to the sublime packages folder -> the absolute path is real in Sublime 3, 15 | # but is somewhere else in Sublime 2;and therefore in Sublime 2 there is no direct way to obtain the plugin name 16 | if not IS_ST2: 17 | PLUGIN_DIR = dirname(dirname(dirname(os.path.abspath(__file__)))) 18 | else: 19 | _sublime_packages_dir = sublime.packages_path() 20 | _cur_file_abspath = os.path.abspath(__file__) 21 | if _sublime_packages_dir not in _cur_file_abspath: 22 | # The plugin is installed as a link 23 | for p in os.listdir(_sublime_packages_dir): 24 | link_path = _sublime_packages_dir + os.sep + p 25 | if os.path.realpath(link_path) in _cur_file_abspath: 26 | PLUGIN_DIR = link_path 27 | break 28 | else: 29 | PLUGIN_DIR = dirname(dirname(dirname(os.path.abspath(__file__)))) 30 | PLUGIN_NAME = os.path.basename(PLUGIN_DIR) 31 | 32 | # The node path will be initialized in the node_client.py module 33 | _node_path = None 34 | def get_node_path(): 35 | return _node_path 36 | 37 | # The tsc.js path will be initialized in the editor_client.py module 38 | _tsc_path = None 39 | def get_tsc_path(): 40 | return _tsc_path 41 | 42 | # only Sublime Text 3 build after 3072 support tooltip 43 | TOOLTIP_SUPPORT = int(sublime.version()) >= 3072 44 | 45 | PHANTOM_SUPPORT = int(sublime.version()) >= 3118 46 | 47 | # detect if quick info is available for symbol 48 | SUBLIME_WORD_MASK = 515 49 | 50 | # set logging levels 51 | LOG_FILE_LEVEL = logging.DEBUG 52 | LOG_CONSOLE_LEVEL = logging.DEBUG 53 | 54 | NON_BLANK_LINE_PATTERN = re.compile("[\S]+") 55 | VALID_COMPLETION_ID_PATTERN = re.compile("[a-zA-Z_$\.][\w$\.]*\Z") 56 | 57 | # idle time length in millisecond 58 | IDLE_TIME_LENGTH = 200 59 | 60 | _language_service_enabled = True 61 | def get_language_service_enabled(): 62 | return _language_service_enabled -------------------------------------------------------------------------------- /typescript/libs/json_helpers.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | 4 | class ObjectJSONEncoder(json.JSONEncoder): 5 | def default(self, obj): 6 | if isinstance(obj, object): 7 | # filter out properties with None value 8 | return dict((key, value) for (key, value) in obj.__dict__.items() if not value is None) 9 | return json.JSONEncoder.default(self, obj) 10 | 11 | 12 | def encode(obj): 13 | json_str = json.dumps(obj, cls=ObjectJSONEncoder) 14 | return json_str 15 | 16 | 17 | def decode(json_str): 18 | return json.loads(json_str) -------------------------------------------------------------------------------- /typescript/libs/logger.py: -------------------------------------------------------------------------------- 1 | """ 2 | Exposes logging and debugging operations. 3 | 4 | Use the 'debug', 'info', 'warning', 'error', or 'critial' methods on the 'log' 5 | object to send messages to the stderr (which appear in the console in Sublime). 6 | 7 | A log file is also created in the plugin folder for messages at the level set 8 | by the properties below. 9 | """ 10 | 11 | import logging 12 | from os import path 13 | from .global_vars import LOG_CONSOLE_LEVEL, LOG_FILE_LEVEL 14 | 15 | # The default path to the log file created for diagnostic output 16 | _pluginRoot = path.dirname(path.dirname(path.abspath(__file__))) 17 | filePath = path.join(_pluginRoot, 'TS.log') 18 | 19 | log = logging.getLogger('TS') 20 | log.setLevel(logging.DEBUG) 21 | 22 | _logFormat = logging.Formatter('%(asctime)s: %(thread)d: %(levelname)s: %(message)s') 23 | 24 | logFile = logging.FileHandler(filePath, mode='w') 25 | logFile.setLevel(logging.DEBUG) 26 | logFile.setFormatter(_logFormat) 27 | log.addHandler(logFile) 28 | 29 | console = logging.StreamHandler() 30 | console.setLevel(logging.DEBUG) 31 | console.setFormatter(_logFormat) 32 | log.addHandler(console) 33 | 34 | log.info('Logging configured to log to file: {0}'.format(filePath)) 35 | 36 | 37 | def view_debug(view, message): 38 | filename = view.file_name() 39 | view_name = view.name() 40 | name = view_name if filename is None else filename 41 | log.debug(message + ": " + name) 42 | 43 | 44 | logFile.setLevel(LOG_FILE_LEVEL) 45 | console.setLevel(LOG_CONSOLE_LEVEL) 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /typescript/libs/lsp_helpers.py: -------------------------------------------------------------------------------- 1 | from . import json_helpers 2 | import sublime 3 | import json 4 | import sys 5 | 6 | if int(sublime.version()) < 3000: 7 | import urllib 8 | from urlparse import urljoin 9 | else: 10 | import urllib.request as urllib 11 | from urllib.parse import urljoin 12 | 13 | def init_message(root_path, process_id): 14 | cmd = { 15 | "id": 0, 16 | "method": "initialize", 17 | "jsonrpc": "2.0", 18 | "params": { 19 | "processId": process_id, 20 | "rootPath": root_path, 21 | "capabilities": {}, 22 | } 23 | } 24 | return json_helpers.encode(cmd) 25 | 26 | 27 | def convert_cmd(str_cmd): 28 | old_cmd = json_helpers.decode(str_cmd) 29 | 30 | # shortcut if it is hand-jammed init message 31 | if old_cmd.get("method"): 32 | return old_cmd 33 | 34 | args = old_cmd.get("arguments") 35 | command = old_cmd["command"] 36 | new_cmd = { 37 | "id": old_cmd["seq"], 38 | "jsonrpc": "2.0" 39 | } 40 | if command == "quickinfo" or command == "definition": 41 | new_cmd["method"] = "textDocument/hover" if command == "quickinfo" else "textDocument/definition" 42 | new_cmd["params"] = { 43 | "position": convert_position_to_lsp(args), 44 | "textDocument": { 45 | "uri": filename_to_uri(args["file"]) 46 | } 47 | } 48 | return new_cmd 49 | elif command == "change": 50 | new_cmd["method"] = "textDocument/didChange" 51 | new_cmd["params"] = { 52 | "textDocument": { 53 | "uri": filename_to_uri(args["file"]), 54 | "version": 1 55 | }, 56 | "contentChanges": convert_change_to_lsp(args) 57 | } 58 | return new_cmd 59 | elif command == "open": 60 | new_cmd["method"] = "textDocument/didOpen" 61 | new_cmd["params"] = { 62 | "textDocument": { 63 | "uri": filename_to_uri(args["file"]), 64 | "languageId": "go", 65 | "version": 0, 66 | "text": args["text"] 67 | }, 68 | } 69 | return new_cmd 70 | elif command == "references": 71 | new_cmd["method"] = "textDocument/references" 72 | new_cmd["params"] = { 73 | "position": convert_position_to_lsp(args), 74 | "textDocument": { 75 | "uri": filename_to_uri(args["file"]) 76 | }, 77 | "context": { 78 | "includeDeclaration": False, 79 | } 80 | } 81 | return new_cmd 82 | return None 83 | 84 | 85 | def to_lsp_method(method): 86 | if method == "quickinfo": 87 | return "textDocument/hover" 88 | if method == "definition": 89 | return "textDocument/definition" 90 | 91 | 92 | def convert_position_to_lsp(args): 93 | return { 94 | "line": args["line"] - 1, 95 | "character": args["offset"] - 1 96 | } 97 | 98 | 99 | def convert_range_to_lsp(args): 100 | return { 101 | "start": { 102 | "line": args["line"] - 1, 103 | "character": args["offset"] - 1 104 | }, 105 | "end": { 106 | "line": args["endLine"] - 1, 107 | "character": args["endOffset"] - 1 108 | } 109 | } 110 | 111 | 112 | def convert_position_from_lsp(args): 113 | if args is None: 114 | return None 115 | return { 116 | "line": args["line"] + 1, 117 | "offset": args["character"] + 1 118 | } 119 | 120 | 121 | def convert_change_to_lsp(args): 122 | return [ 123 | { 124 | # "range": convert_range_to_lsp(args), 125 | # "rangeLength": 5, 126 | "text": args["insertString"] 127 | } 128 | ] 129 | 130 | 131 | def convert_filename_to_lsp(args, version=None): 132 | return_val = { 133 | "uri": filename_to_uri(args["file"]) 134 | } 135 | if version: 136 | return_val["version"] = version 137 | return return_val 138 | 139 | 140 | def filename_to_uri(filename): 141 | return urljoin('file:', urllib.pathname2url(filename)) 142 | 143 | def convert_lsp_to_filename(uri): 144 | uri = uri[len("file://"):] 145 | if sys.platform == "win32": 146 | uri = uri.lstrip("/") 147 | return uri 148 | 149 | def format_request(request): 150 | """Converts the request into json and adds the Content-Length header""" 151 | content = json.dumps(request, indent=2) 152 | content_length = len(content) 153 | 154 | result = "Content-Length: {}\r\n\r\n{}".format(content_length, content) 155 | return result 156 | 157 | 158 | def convert_other(msg): 159 | if not msg.get("params"): 160 | return None 161 | params = msg["params"] 162 | if params.get("diagnostics"): 163 | diags = [] 164 | for diag in params.get("diagnostics"): 165 | diags.append( 166 | { 167 | "text": diag["message"], 168 | "start": convert_position_from_lsp(diag["range"]["start"]), 169 | "end": convert_position_from_lsp(diag["range"]["end"]), 170 | }) 171 | return { 172 | "event": "syntaxDiag", 173 | "type": "event", 174 | "seq": 0, 175 | "body": { 176 | "file": convert_lsp_to_filename(params["uri"]), 177 | "diagnostics": diags 178 | } 179 | } 180 | return None 181 | 182 | 183 | def convert_hover(result): 184 | if type(result) is dict: 185 | return result 186 | return { 187 | "value": result, 188 | "language": "markdown" 189 | } 190 | 191 | def convert_response(request_type, response): 192 | if response.get("id") == 0: 193 | return None 194 | success = response.get("result") is not None 195 | if not success: 196 | return None 197 | if not response.get("result"): 198 | return None 199 | 200 | if request_type == "textDocument/hover": 201 | result = response["result"] 202 | range = result["range"] 203 | return { 204 | "seq": 0, 205 | "request_seq": response["id"], 206 | "success": success, 207 | "command": "quickinfo", 208 | "body": { 209 | "displayString": convert_hover(result["contents"][0])["value"] if result["contents"] is not None and len(result["contents"]) > 0 else None, 210 | "start": convert_position_from_lsp(range["start"] if range else None), 211 | "kind": "alias", 212 | "end": convert_position_from_lsp(range["end"] if range else None), 213 | "kindModifiers": "", 214 | "documentation": convert_hover(result["contents"][1])["value"] if result["contents"] is not None and len(result["contents"]) > 1 else None, 215 | }, 216 | "type": "response" 217 | } 218 | elif request_type == "textDocument/definition": 219 | first_result = response["result"][0] 220 | return { 221 | "seq": 0, 222 | "request_seq": response["id"], 223 | "success": success, 224 | "command": "definition", 225 | "body": [{ 226 | "start": convert_position_from_lsp(first_result["range"]["start"]), 227 | "end": convert_position_from_lsp(first_result["range"]["end"]), 228 | "file": convert_lsp_to_filename(first_result["uri"]), 229 | "kindModifiers": "", 230 | "documentation": "" 231 | }], 232 | "type": "response" 233 | } 234 | elif request_type == "textDocument/references": 235 | referencesRespBody = { 236 | "refs": [] 237 | } 238 | 239 | for entry in response["result"]: 240 | file = convert_lsp_to_filename(entry["uri"]) 241 | referencesRespBody["refs"].append({ 242 | "end": convert_position_from_lsp(entry["range"]["end"]), 243 | "start": convert_position_from_lsp(entry["range"]["start"]), 244 | "isDefinition": False, 245 | "isWriteAccess": True, 246 | "file": file 247 | }) 248 | return { 249 | "seq": 0, 250 | "request_seq": response["id"], 251 | "success": success, 252 | "command": "references", 253 | "body": referencesRespBody, 254 | "type": "response" 255 | } 256 | 257 | return None 258 | 259 | 260 | # file_name = args["filename"] 261 | # line = args["line"] 262 | # ref_display_string = args["referencesRespBody"]["symbolDisplayString"] 263 | # ref_id = args["referencesRespBody"]["symbolName"] 264 | # refs = args["referencesRespBody"]["refs"] 265 | 266 | -------------------------------------------------------------------------------- /typescript/libs/os_helpers.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | # From shutil.which introduced in Python 3.3 5 | def which(cmd, mode=os.F_OK | os.X_OK, path=None): 6 | """Given a command, mode, and a PATH string, return the path which 7 | conforms to the given mode on the PATH, or None if there is no such 8 | file. 9 | 10 | `mode` defaults to os.F_OK | os.X_OK. `path` defaults to the result 11 | of os.environ.get("PATH"), or can be overridden with a custom search 12 | path. 13 | 14 | """ 15 | # Check that a given file can be accessed with the correct mode. 16 | # Additionally check that `file` is not a directory, as on Windows 17 | # directories pass the os.access check. 18 | def _access_check(fn, mode): 19 | return (os.path.exists(fn) and os.access(fn, mode) 20 | and not os.path.isdir(fn)) 21 | 22 | # If we're given a path with a directory part, look it up directly rather 23 | # than referring to PATH directories. This includes checking relative to the 24 | # current directory, e.g. ./script 25 | if os.path.dirname(cmd): 26 | if _access_check(cmd, mode): 27 | return cmd 28 | return None 29 | 30 | if path is None: 31 | path = os.environ.get("PATH", os.defpath) 32 | if not path: 33 | return None 34 | path = path.split(os.pathsep) 35 | 36 | if sys.platform == "win32": 37 | # The current directory takes precedence on Windows. 38 | if not os.curdir in path: 39 | path.insert(0, os.curdir) 40 | 41 | # PATHEXT is necessary to check on Windows. 42 | pathext = os.environ.get("PATHEXT", "").split(os.pathsep) 43 | # See if the given file matches any of the expected path extensions. 44 | # This will allow us to short circuit when given "python.exe". 45 | # If it does match, only test that one, otherwise we have to try 46 | # others. 47 | if any(cmd.lower().endswith(ext.lower()) for ext in pathext): 48 | files = [cmd] 49 | else: 50 | files = [cmd + ext for ext in pathext] 51 | else: 52 | # On other platforms you don't have things like PATHEXT to tell you 53 | # what file suffixes are executable, so just pass on cmd as-is. 54 | files = [cmd] 55 | 56 | seen = set() 57 | for dir in path: 58 | normdir = os.path.normcase(dir) 59 | if not normdir in seen: 60 | seen.add(normdir) 61 | for thefile in files: 62 | name = os.path.join(dir, thefile) 63 | if _access_check(name, mode): 64 | return name 65 | return None -------------------------------------------------------------------------------- /typescript/libs/panel_manager.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | from .global_vars import IS_ST2, PLUGIN_NAME 3 | 4 | class PanelManager: 5 | 6 | def __init__(self): 7 | self.panels = dict() 8 | self.panel_line_maps = dict() 9 | 10 | def get_panel(self, panel_name): 11 | if panel_name not in self.panels: 12 | self.add_panel(panel_name) 13 | return self.panels[panel_name] 14 | 15 | def set_line_map(self, panel_name, map): 16 | if panel_name in self.panels: 17 | self.panel_line_maps[panel_name] = map 18 | 19 | def get_line_map(self, panel_name): 20 | if panel_name in self.panel_line_maps: 21 | return self.panel_line_maps[panel_name] 22 | 23 | def add_panel(self, panel_name): 24 | if panel_name not in self.panels: 25 | if IS_ST2: 26 | self.panels[panel_name] = sublime.active_window().get_output_panel(panel_name) 27 | else: 28 | self.panels[panel_name] = sublime.active_window().create_output_panel(panel_name) 29 | settings = self.panels[panel_name].settings() 30 | settings.set("auto_indent", False) 31 | settings.set("draw_white_space", "none") 32 | settings.set("line_numbers", False) 33 | if panel_name == "errorlist": 34 | self.panels[panel_name].set_syntax_file("Packages/" + PLUGIN_NAME + "/ErrorList.hidden-tmLanguage") 35 | else: 36 | self.panels[panel_name].set_syntax_file("Packages/Text/Plain Text.tmLanguage") 37 | 38 | def is_panel_active(self, panel_name): 39 | return panel_name in self.panels and self.panels[panel_name].window() is not None 40 | 41 | def show_panel(self, panel_name, initial_content_lines=None): 42 | if initial_content_lines is not None: 43 | self.write_lines_to_panel(panel_name, initial_content_lines) 44 | sublime.active_window().run_command("show_panel", {"panel": "output." + panel_name}) 45 | 46 | def write_lines_to_panel(self, panel_name, lines): 47 | # check if actual changes happen to unncessasary refreshing 48 | # which cound be annoying if the user chose to fold some text 49 | # and it gets unfolded every time the panel refreshes 50 | panel = self.panels[panel_name] 51 | original_countent = panel.substr(sublime.Region(0, panel.size())) 52 | new_countent = "\n".join(lines) 53 | if original_countent != new_countent: 54 | panel.set_read_only(False) 55 | panel.run_command("select_all") 56 | panel.run_command("right_delete") 57 | panel.run_command("insert", {"characters": new_countent}) 58 | self.panels[panel_name].set_read_only(True) 59 | 60 | def hide_panel(self): 61 | sublime.active_window().run_command("hide_panel") 62 | 63 | _panel_manager = None 64 | 65 | def get_panel_manager(): 66 | global _panel_manager 67 | if _panel_manager is None: 68 | _panel_manager = PanelManager() 69 | return _panel_manager -------------------------------------------------------------------------------- /typescript/libs/reference.py: -------------------------------------------------------------------------------- 1 | from .global_vars import * 2 | 3 | 4 | class Ref: 5 | """Reference item in the 'Find All Reference' view 6 | 7 | A reference to a source file, line, offset; next and prev refer to the 8 | next and previous reference in a view containing references 9 | """ 10 | 11 | def __init__(self, filename, line, offset, prev_line): 12 | self.filename = filename 13 | self.line = line 14 | self.offset = offset 15 | self.next_line = None 16 | self.prev_line = prev_line 17 | 18 | def set_next_line(self, n): 19 | self.next_line = n 20 | 21 | def as_tuple(self): 22 | return self.filename, self.line, self.offset, self.prev_line, self.next_line 23 | 24 | 25 | class RefInfo: 26 | """Maps (line in view containing references) to (filename, line, offset) referenced""" 27 | 28 | def __init__(self, first_line, ref_id): 29 | self.ref_map = {} 30 | self.current_ref_line = None 31 | self.first_line = first_line 32 | self.last_line = None 33 | self.ref_id = ref_id 34 | 35 | def set_last_line(self, last_line): 36 | self.last_line = last_line 37 | 38 | def add_mapping(self, line, target): 39 | self.ref_map[line] = target 40 | 41 | def contains_mapping(self, line): 42 | return line in self.ref_map 43 | 44 | def get_mapping(self, line): 45 | if line in self.ref_map: 46 | return self.ref_map[line] 47 | 48 | def get_current_mapping(self): 49 | if self.current_ref_line: 50 | return self.get_mapping(self.current_ref_line) 51 | 52 | def set_ref_line(self, line): 53 | self.current_ref_line = line 54 | 55 | def get_ref_line(self): 56 | return self.current_ref_line 57 | 58 | def get_ref_id(self): 59 | return self.ref_id 60 | 61 | def next_ref_line(self): 62 | current_mapping = self.get_current_mapping() 63 | if (not self.current_ref_line) or (not current_mapping): 64 | self.current_ref_line = self.first_line 65 | else: 66 | (filename, l, c, p, n) = current_mapping.as_tuple() 67 | if n: 68 | self.current_ref_line = n 69 | else: 70 | self.current_ref_line = self.first_line 71 | return self.current_ref_line 72 | 73 | def prev_ref_line(self): 74 | current_mapping = self.get_current_mapping() 75 | if (not self.current_ref_line) or (not current_mapping): 76 | self.current_ref_line = self.last_line 77 | else: 78 | (filename, l, c, p, n) = current_mapping.as_tuple() 79 | if p: 80 | self.current_ref_line = p 81 | else: 82 | self.current_ref_line = self.last_line 83 | 84 | return self.current_ref_line 85 | 86 | def as_value(self): 87 | vmap = {} 88 | keys = self.ref_map.keys() 89 | for key in keys: 90 | vmap[key] = self.ref_map[key].as_tuple() 91 | return vmap, self.current_ref_line, self.first_line, self.last_line, self.ref_id 92 | 93 | 94 | def build_ref(ref_tuple): 95 | """Build a Ref from a serialized Ref""" 96 | (filename, line, offset, prev_line, next_line) = ref_tuple 97 | ref = Ref(filename, line, offset, prev_line) 98 | ref.set_next_line(next_line) 99 | return ref 100 | 101 | 102 | def build_ref_info(ref_info_tuple): 103 | """Build a RefInfo object from a serialized RefInfo""" 104 | (dict, current_line, first_line, last_line, ref_id) = ref_info_tuple 105 | ref_info = RefInfo(first_line, ref_id) 106 | ref_info.set_ref_line(current_line) 107 | ref_info.set_last_line(last_line) 108 | for key in dict.keys(): 109 | ref_info.add_mapping(key, build_ref(dict[key])) 110 | return ref_info 111 | 112 | 113 | def highlight_ids(view, ref_id): 114 | """Highlight all occurrences of ref_id in view""" 115 | id_regions = view.find_all("(?<=\W)" + ref_id + "(?=\W)") 116 | if id_regions and (len(id_regions) > 0): 117 | if IS_ST2: 118 | view.add_regions("refid", id_regions, "constant.numeric", "", sublime.DRAW_OUTLINED) 119 | else: 120 | view.add_regions("refid", id_regions, "constant.numeric", 121 | flags=sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_SOLID_UNDERLINE) 122 | 123 | 124 | def update_ref_line(ref_info, cur_line, view): 125 | """Update the given line in reference view 126 | 127 | Update the gutter icon 128 | """ 129 | # Todo: make sure the description is right 130 | 131 | view.erase_regions("curref") 132 | caret_pos = view.text_point(cur_line, 0) 133 | # sublime 2 doesn't support custom icons 134 | icon = "Packages/" + PLUGIN_NAME + "/icons/arrow-right3.png" if not IS_ST2 else "" 135 | view.add_regions( 136 | "curref", 137 | [sublime.Region(caret_pos, caret_pos + 1)], 138 | "keyword", 139 | icon, 140 | sublime.HIDDEN 141 | ) 142 | -------------------------------------------------------------------------------- /typescript/libs/service_proxy.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from . import json_helpers 4 | from .global_vars import IS_ST2 5 | from .node_client import CommClient 6 | from .text_helpers import Location 7 | 8 | 9 | class ServiceProxy: 10 | def __init__(self, worker_client=CommClient(), server_client=CommClient()): 11 | self.__comm = server_client 12 | self.__worker_comm = worker_client 13 | self.seq = 1 14 | 15 | def increase_seq(self): 16 | temp = self.seq 17 | self.seq += 1 18 | return temp 19 | 20 | def exit(self): 21 | req_dict = self.create_req_dict("exit") 22 | json_str = json_helpers.encode(req_dict) 23 | self.__comm.postCmd(json_str) 24 | if self.__worker_comm.started(): 25 | self.__worker_comm.postCmd(json_str) 26 | 27 | def stop_worker(self): 28 | req_dict = self.create_req_dict("exit") 29 | json_str = json_helpers.encode(req_dict) 30 | if self.__worker_comm.started(): 31 | self.__worker_comm.postCmd(json_str) 32 | 33 | def configure(self, host_info="Sublime Text", file=None, format_options=None): 34 | args = {"hostInfo": host_info, "formatOptions": format_options, "file": file} 35 | req_dict = self.create_req_dict("configure", args) 36 | json_str = json_helpers.encode(req_dict) 37 | response_dict = self.__comm.postCmd(json_str) 38 | if self.__worker_comm.started(): 39 | self.__worker_comm.postCmd(json_str) 40 | 41 | def change(self, path, begin_location=Location(1, 1), end_location=Location(1, 1), insertString=""): 42 | args = { 43 | "file": path, 44 | # "line": begin_location.line, 45 | # "offset": begin_location.offset, 46 | # "endLine": end_location.line, 47 | # "endOffset": end_location.offset, 48 | "insertString": insertString 49 | } 50 | req_dict = self.create_req_dict("change", args) 51 | json_str = json_helpers.encode(req_dict) 52 | self.__comm.postCmd(json_str) 53 | if self.__worker_comm.started(): 54 | self.__worker_comm.postCmd(json_str) 55 | 56 | def completions(self, path, location=Location(1, 1), prefix="", on_completed=None): 57 | args = {"file": path, "line": location.line, "offset": location.offset, "prefix": prefix} 58 | req_dict = self.create_req_dict("completions", args) 59 | json_str = json_helpers.encode(req_dict) 60 | self.__comm.sendCmd( 61 | json_str, 62 | lambda response_dict: None if on_completed is None else on_completed(response_dict), 63 | req_dict["seq"] 64 | ) 65 | 66 | def async_completions(self, path, location=Location(1, 1), prefix="", on_completed=None): 67 | args = {"file": path, "line": location.line, "offset": location.offset, "prefix": prefix} 68 | req_dict = self.create_req_dict("completions", args) 69 | json_str = json_helpers.encode(req_dict) 70 | self.__comm.sendCmdAsync(json_str, on_completed, req_dict["seq"]) 71 | 72 | def signature_help(self, path, location=Location(1, 1), prefix="", on_completed=None): 73 | args = {"file": path, "line": location.line, "offset": location.offset, "prefix": prefix} 74 | req_dict = self.create_req_dict("signatureHelp", args) 75 | json_str = json_helpers.encode(req_dict) 76 | self.__comm.sendCmd( 77 | json_str, 78 | lambda response_dict: None if on_completed is None else on_completed(response_dict), 79 | req_dict["seq"] 80 | ) 81 | 82 | def async_signature_help(self, path, location=Location(1, 1), prefix="", on_completed=None): 83 | args = {"file": path, "line": location.line, "offset": location.offset, "prefix": prefix} 84 | req_dict = self.create_req_dict("signatureHelp", args) 85 | json_str = json_helpers.encode(req_dict) 86 | self.__comm.sendCmdAsync(json_str, on_completed, req_dict["seq"]) 87 | 88 | def definition(self, path, location=Location(1, 1)): 89 | args = {"file": path, "line": location.line, "offset": location.offset} 90 | req_dict = self.create_req_dict("definition", args) 91 | json_str = json_helpers.encode(req_dict) 92 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 93 | return response_dict 94 | 95 | def format(self, path, begin_location=Location(1, 1), end_location=Location(1, 1)): 96 | args = { 97 | "file": path, 98 | "line": begin_location.line, 99 | "offset": begin_location.offset, 100 | "endLine": end_location.line, 101 | "endOffset": end_location.offset 102 | } 103 | req_dict = self.create_req_dict("format", args) 104 | json_str = json_helpers.encode(req_dict) 105 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 106 | if self.__worker_comm.started(): 107 | self.__worker_comm.sendCmdSync(json_str, req_dict["seq"]) 108 | return response_dict 109 | 110 | def format_on_key(self, path, location=Location(1, 1), key=""): 111 | args = {"file": path, "line": location.line, "offset": location.offset, "key": key} 112 | req_dict = self.create_req_dict("formatonkey", args) 113 | json_str = json_helpers.encode(req_dict) 114 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 115 | if self.__worker_comm.started(): 116 | self.__worker_comm.sendCmdSync(json_str, req_dict["seq"]) 117 | return response_dict 118 | 119 | def open(self, path, contents): 120 | args = {"file": path, "text": contents} 121 | req_dict = self.create_req_dict("open", args) 122 | json_str = json_helpers.encode(req_dict) 123 | self.__comm.postCmd(json_str) 124 | if self.__worker_comm.started(): 125 | self.__worker_comm.postCmd(json_str) 126 | 127 | def open_on_worker(self, path, contents): 128 | args = {"file": path, "text": contents} 129 | req_dict = self.create_req_dict("open", args) 130 | json_str = json_helpers.encode(req_dict) 131 | if self.__worker_comm.started(): 132 | self.__worker_comm.postCmd(json_str) 133 | 134 | def close(self, path): 135 | args = {"file": path} 136 | req_dict = self.create_req_dict("close", args) 137 | json_str = json_helpers.encode(req_dict) 138 | self.__comm.postCmd(json_str) 139 | if self.__worker_comm.started(): 140 | self.__worker_comm.postCmd(json_str) 141 | 142 | def references(self, path, location=Location(1, 1), on_completed=None): 143 | args = {"file": path, "line": location.line, "offset": location.offset} 144 | req_dict = self.create_req_dict("references", args) 145 | json_str = json_helpers.encode(req_dict) 146 | callback = on_completed or (lambda: None) 147 | if not IS_ST2: 148 | self.__comm.sendCmdAsync( 149 | json_str, 150 | callback, 151 | req_dict["seq"] 152 | ) 153 | else: 154 | self.__comm.sendCmd( 155 | json_str, 156 | callback, 157 | req_dict["seq"] 158 | ) 159 | 160 | def reload(self, path, alternate_path): 161 | args = {"file": path, "tmpfile": alternate_path} 162 | req_dict = self.create_req_dict("reload", args) 163 | json_str = json_helpers.encode(req_dict) 164 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 165 | if self.__worker_comm.started(): 166 | self.__worker_comm.sendCmdSync(json_str, req_dict["seq"]) 167 | return response_dict 168 | 169 | def reload_on_worker(self, path, alternate_path): 170 | args = {"file": path, "tmpfile": alternate_path} 171 | req_dict = self.create_req_dict("reload", args) 172 | json_str = json_helpers.encode(req_dict) 173 | if self.__worker_comm.started(): 174 | response_dict = self.__worker_comm.sendCmdSync(json_str, req_dict["seq"]) 175 | return response_dict 176 | 177 | def reload_async(self, path, alternate_path, on_completed): 178 | args = {"file": path, "tmpfile": alternate_path} 179 | req_dict = self.create_req_dict("reload", args) 180 | json_str = json_helpers.encode(req_dict) 181 | self.__comm.sendCmdAsync(json_str, on_completed, req_dict["seq"]) 182 | if self.__worker_comm.started(): 183 | self.__worker_comm.sendCmdAsync(json_str, None, req_dict["seq"]) 184 | 185 | def reload_async_on_worker(self, path, alternate_path, on_completed): 186 | args = {"file": path, "tmpfile": alternate_path} 187 | req_dict = self.create_req_dict("reload", args) 188 | json_str = json_helpers.encode(req_dict) 189 | if self.__worker_comm.started(): 190 | self.__worker_comm.sendCmdAsync(json_str, None, req_dict["seq"]) 191 | 192 | def rename(self, path, location=Location(1, 1)): 193 | args = {"file": path, "line": location.line, "offset": location.offset} 194 | req_dict = self.create_req_dict("rename", args) 195 | json_str = json_helpers.encode(req_dict) 196 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 197 | if self.__worker_comm.started(): 198 | self.__worker_comm.sendCmdSync(json_str, req_dict["seq"]) 199 | return response_dict 200 | 201 | def request_get_err(self, delay=0, pathList=[]): 202 | args = {"files": pathList, "delay": delay} 203 | req_dict = self.create_req_dict("geterr", args) 204 | json_str = json_helpers.encode(req_dict) 205 | self.__comm.postCmd(json_str) 206 | 207 | def request_get_err_for_project(self, delay=0, path=""): 208 | args = {"file": path, "delay": delay} 209 | req_dict = self.create_req_dict("geterrForProject", args) 210 | json_str = json_helpers.encode(req_dict) 211 | if self.__worker_comm.started(): 212 | self.__worker_comm.postCmd(json_str) 213 | 214 | def type(self, path, location=Location(1, 1)): 215 | args = {"file": path, "line": location.line, "offset": location.offset} 216 | req_dict = self.create_req_dict("type", args) 217 | json_str = json_helpers.encode(req_dict) 218 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 219 | return response_dict 220 | 221 | def quick_info(self, path, location=Location(1, 1), on_completed=None): 222 | args = {"file": path, "line": location.line, "offset": location.offset} 223 | req_dict = self.create_req_dict("quickinfo", args) 224 | json_str = json_helpers.encode(req_dict) 225 | callback = on_completed or (lambda: None) 226 | if not IS_ST2: 227 | self.__comm.sendCmdAsync( 228 | json_str, 229 | callback, 230 | req_dict["seq"] 231 | ) 232 | else: 233 | self.__comm.sendCmd( 234 | json_str, 235 | callback, 236 | req_dict["seq"] 237 | ) 238 | 239 | def get_event(self): 240 | event_json_str = self.__comm.getEvent() 241 | return json_helpers.decode(event_json_str) if event_json_str is not None else None 242 | 243 | def get_event_from_worker(self): 244 | event_json_str = self.__worker_comm.getEvent() 245 | return json_helpers.decode(event_json_str) if event_json_str is not None else None 246 | 247 | def save_to(self, path, alternatePath): 248 | args = {"file": path, "tmpfile": alternatePath} 249 | req_dict = self.create_req_dict("saveto", args) 250 | json_str = json_helpers.encode(req_dict) 251 | self.__comm.postCmd(json_str) 252 | 253 | def nav_to(self, search_text, file_name): 254 | args = {"searchValue": search_text, "file": file_name, "maxResultCount": 20} 255 | req_dict = self.create_req_dict("navto", args) 256 | json_str = json_helpers.encode(req_dict) 257 | response_dict = self.__comm.sendCmdSync(json_str, req_dict["seq"]) 258 | return response_dict 259 | 260 | def project_info(self, file_name, need_file_name_list=False): 261 | args = {"file": file_name, "needFileNameList": need_file_name_list} 262 | req_dict = self.create_req_dict("projectInfo", args) 263 | json_str = json_helpers.encode(req_dict) 264 | return self.__comm.sendCmdSync(json_str, req_dict["seq"]) 265 | 266 | def async_document_highlights(self, path, location, on_completed=None): 267 | args = {"line": location.line, "offset": location.offset, "file": path, "filesToSearch": [path]} 268 | req_dict = self.create_req_dict("documentHighlights", args) 269 | json_str = json_helpers.encode(req_dict) 270 | self.__comm.sendCmdAsync(json_str, on_completed, req_dict["seq"]) 271 | 272 | def add_event_handler(self, event_name, cb): 273 | self.__comm.add_event_handler(event_name, cb) 274 | 275 | def add_event_handler_for_worker(self, event_name, cb): 276 | self.__worker_comm.add_event_handler(event_name, cb) 277 | 278 | def create_req_dict(self, command_name, args=None): 279 | req_dict = { 280 | "command": command_name, 281 | "seq": self.increase_seq(), 282 | "type": "request" 283 | } 284 | if args: 285 | req_dict["arguments"] = args 286 | return req_dict 287 | -------------------------------------------------------------------------------- /typescript/libs/text_helpers.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from .global_vars import * 4 | 5 | 6 | class Location: 7 | """Object containing line and offset (one-based) of file location 8 | 9 | Location is a server protocol. Both line and offset are 1-based. 10 | """ 11 | 12 | def __init__(self, line, offset): 13 | self.line = line 14 | self.offset = offset 15 | 16 | def to_dict(self): 17 | return {"line": self.line, "offset": self.offset} 18 | 19 | 20 | class StaticRegion: 21 | """Region that will not change as buffer is modified""" 22 | 23 | def __init__(self, a, b): 24 | self.a = a 25 | self.b = b 26 | 27 | def to_region(self): 28 | return sublime.Region(self.a, self.b) 29 | 30 | def begin(self): 31 | return self.a 32 | 33 | def empty(self): 34 | return self.a == self.b 35 | 36 | 37 | def copy_region(r): 38 | """Copy a region (this is needed because the original region may change)""" 39 | return sublime.Region(r.begin(), r.end()) 40 | 41 | 42 | def copy_regions(regions): 43 | """Copy a list of regions""" 44 | return [copy_region(r) for r in regions] 45 | 46 | 47 | def region_to_static_region(r): 48 | """Copy a region into a static region""" 49 | return StaticRegion(r.begin(), r.end()) 50 | 51 | 52 | def static_regions_to_regions(static_regions): 53 | """Convert a list of static regions to ordinary regions""" 54 | return [sr.to_region() for sr in static_regions] 55 | 56 | 57 | def regions_to_static_regions(regions): 58 | """Copy a list of regions into a list of static regions""" 59 | return [region_to_static_region(r) for r in regions] 60 | 61 | 62 | def decrease_empty_regions(empty_regions, amount): 63 | """ 64 | From a list of empty regions, make a list of regions whose begin() value is 65 | one before the begin() value of the corresponding input (for left_delete) 66 | """ 67 | return [sublime.Region(r.begin() - amount, r.end() - amount) for r in empty_regions] 68 | 69 | 70 | def decrease_locs_to_regions(locs, amount): 71 | """Move the given locations by amount, and then return the corresponding regions""" 72 | return [sublime.Region(loc - amount, loc - amount) for loc in locs] 73 | 74 | 75 | def extract_line_offset(line_offset): 76 | """ 77 | Destructure line and offset tuple from LineOffset object 78 | convert 1-based line, offset to zero-based line, offset 79 | ``lineOffset`` LineOffset object 80 | """ 81 | if isinstance(line_offset, dict): 82 | line = line_offset["line"] - 1 83 | offset = line_offset["offset"] - 1 84 | else: 85 | line = line_offset.line - 1 86 | offset = line_offset.offset - 1 87 | return line, offset 88 | 89 | 90 | def escape_html(raw_string): 91 | """Escape html content 92 | 93 | Note: only use for short strings 94 | """ 95 | return raw_string.replace('&', '&').replace('<', '<').replace('>', ">") 96 | 97 | 98 | def left_expand_empty_region(regions, number=1): 99 | """Expand region list one to left for backspace change info""" 100 | result = [] 101 | for region in regions: 102 | if region.empty(): 103 | result.append(sublime.Region(region.begin() - number, region.end())) 104 | else: 105 | result.append(region) 106 | return result 107 | 108 | 109 | def right_expand_empty_region(regions): 110 | """Expand region list one to right for delete key change info""" 111 | result = [] 112 | for region in regions: 113 | if region.empty(): 114 | result.append(sublime.Region(region.begin(), region.end() + 1)) 115 | else: 116 | result.append(region) 117 | return result 118 | 119 | 120 | def build_replace_regions(empty_regions_a, empty_regions_b): 121 | """ 122 | Given two list of cursor locations, connect each pair of locations for form 123 | a list of regions, used for replacement later 124 | """ 125 | rr = [] 126 | for i in range(len(empty_regions_a)): 127 | rr.append(sublime.Region(empty_regions_a[i].begin(), empty_regions_b[i].begin())) 128 | return rr 129 | -------------------------------------------------------------------------------- /typescript/libs/work_scheduler.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | import threading 3 | import time 4 | 5 | from .logger import log 6 | 7 | 8 | class WorkScheduler(): 9 | 10 | """ Manages the scheduling of high frequency work-items 11 | 12 | The editor is expected to be fast and responsive. Frequently performing 13 | slow or blocking work on the UI thread is undesirable. This is a challenge 14 | for a feature such as signature help, where the requested data can be high 15 | cost to retrieve, and the data needs to be retrieved frequently as the user 16 | edits within the call. This class is largely designed to aid this scenario. 17 | 18 | The work scheduler adds value by throttling high volumes of requests to 19 | avoid over-updating, and by managing the transfer of work across threads. 20 | 21 | This class makes the assumption only one popup will be active at a time, 22 | with the latest set of signature data received. It provides a design where 23 | only one request may be sent but awaiting a response at a time, and that 24 | only one request may be waiting to send at a time - these are refered to as 25 | the 'current', and 'queued' requests below. 26 | 27 | A current request has been sent, but may not have received a response yet. 28 | If the popup is canceled before a response is received, the callback will 29 | be ignored. 30 | 31 | A queued request has not been started yet, so can be updated or canceled at 32 | any time. Once it becomes 'due', either it becomes the current request and 33 | executed, or if there is still a current running, it gets requeued. 34 | 35 | To avoid race conditions, the following design & usage requirements should 36 | generally be adhered to: 37 | - All queued requests are scheduled with sublime.set_timeout, which 38 | schedules work for the UI thread, and requests are sent on this thread. 39 | Once it begins, it may chose to run the work on another thread however. 40 | - The final 'done' callback can happen on any thread, but will also use 41 | set_timeout to move the completion work and UI updating to the UI thread 42 | 43 | This design has the following benefits: 44 | - Most tracking state is only touched by the UI thread, thus reducing the 45 | need for complex locks or queues to avoid race conditions. 46 | - Most actual Python processing happens on the one thread (assuming work 47 | offloaded is mostly I/O bound work). Due to the GIL, this is usually 48 | the most efficient exeuction model for non-blocked work. 49 | 50 | 51 | ## Example use of queue_request 52 | 53 | # Set some locals to capture in the worker functions provided 54 | _view = self.view 55 | _file = self.filename 56 | _loc = self.view.location 57 | 58 | # Define a function to do the request and notify on completion 59 | def get_signature_data(on_done): 60 | cli.request_signature_help(_file, _loc, on_done) 61 | 62 | # Define a function to handle the completion response 63 | def do_display(signature_data): 64 | popup_text = get_sig_popup(signature_data) 65 | _view.show_popup(popup_text) 66 | 67 | # Schedule the request 68 | queue_request(get_signature_data, do_display) 69 | """ 70 | 71 | def __init__(self): 72 | self.lock = threading.Lock() 73 | 74 | # Set to the callback to be executed on the next schedule execution 75 | self.next_job = None 76 | # Set to the time the last job started execution 77 | self.last_time = 0 78 | # Set to the amount of time the last job took to execute 79 | self.last_cost = 0 80 | # Set to True if a timer is already pending 81 | self.timer_set = False 82 | # Set to True if a job is currently executing 83 | self.job_running = False 84 | # Set to True if the outstanding work has been canceled 85 | self.canceled = False 86 | 87 | def queue_request(self, worker, handler): 88 | log.debug('In queue_request for work scheduler') 89 | 90 | # Use nested functions to close over the worker and handler parameters 91 | 92 | def work_done(results): 93 | """ Called when the scheduled work item is complete 94 | 95 | This function does some bookkeeping before calling the completion 96 | handler provided when the job was queued. 97 | """ 98 | log.debug('In work_done for work scheduler') 99 | end_time = time.time() 100 | canceled = False 101 | with self.lock: 102 | self.last_cost = end_time - self.last_time 103 | self.job_running = False 104 | canceled = self.canceled 105 | log.debug('Work took {0:d}ms'.format(int(self.last_cost * 1000))) 106 | if not canceled: 107 | # Post the response to the handler on the main thread 108 | sublime.set_timeout(lambda: handler(results), 0) 109 | 110 | def do_work(): 111 | """ Called to execute the worker callback provided 112 | 113 | This function closes over the worker callback provided, and is 114 | stored in the slot for the queued work item (self.next_job). 115 | """ 116 | log.debug('In do_work for work scheduler') 117 | start_time = time.time() 118 | canceled = False 119 | with self.lock: 120 | self.last_time = start_time 121 | canceled = self.canceled 122 | if canceled: 123 | self.job_running = False 124 | if not canceled: 125 | worker(work_done) 126 | 127 | def on_scheduled(): 128 | """ This function is called by the scheduler when the timeout fires 129 | 130 | This pulls the queued work-item from self.next_job, which is an 131 | instance of 'do_work' above, and executes it. 132 | """ 133 | log.debug('In on_scheduled for work scheduler') 134 | job = None 135 | job_running = False 136 | with self.lock: 137 | if self.job_running: 138 | job_running = True 139 | else: 140 | # Get the job to run if not canceled, and reset timer state 141 | if not self.canceled: 142 | job = self.next_job 143 | if job: 144 | # There will be a job running when this function exits 145 | self.job_running = True 146 | self.timer_set = False 147 | self.next_job = None 148 | if job_running: 149 | # Defer 50ms until current job completes. 150 | log.debug('Timer elapsed while prior job running. Deferring') 151 | sublime.set_timeout(on_scheduled, 50) 152 | else: 153 | if job: 154 | job() 155 | 156 | # When to set the timer for next. 157 | delta_ms = 0 158 | job_scheduled = False 159 | curr_time = time.time() 160 | 161 | with self.lock: 162 | # Ensure queued job is this job and state is not canceled 163 | self.next_job = do_work 164 | self.canceled = False 165 | job_scheduled = self.timer_set 166 | if not self.timer_set: 167 | # How long to defer execution. Use last cost as basis 168 | if self.last_cost: 169 | min_delay = self.last_cost * 3 170 | next_time = self.last_time + min_delay 171 | delta_ms = int((next_time - curr_time) * 1000) 172 | else: 173 | delta_ms = 33 174 | self.timer_set = True # Will be before this function returns 175 | 176 | if not job_scheduled: 177 | # Ensure no less that 33ms, and no more than 500ms 178 | delta_ms = max(33, delta_ms) 179 | delta_ms = min(500, delta_ms) 180 | # Run whatever is the 'next_job' when scheduler is due 181 | log.debug('Scheduling job for {0}ms'.format(delta_ms)) 182 | sublime.set_timeout(on_scheduled, delta_ms) 183 | else: 184 | log.debug('Job already scheduled') 185 | 186 | def cancel(self): 187 | log.debug('In cancel for work scheduler') 188 | with self.lock: 189 | self.canceled = True 190 | self.next_job = None 191 | self.last_time = 0 192 | self.last_cost = 0 193 | self.timer_set = False 194 | 195 | 196 | _default_scheduler = WorkScheduler() 197 | 198 | 199 | def work_scheduler(): 200 | return _default_scheduler 201 | -------------------------------------------------------------------------------- /typescript/listeners/__init__.py: -------------------------------------------------------------------------------- 1 | from .listeners import TypeScriptEventListener 2 | from .completion import CompletionEventListener 3 | from .format import FormatEventListener 4 | from .idle import IdleListener 5 | from .nav_to import NavToEventListener 6 | from .rename import RenameEventListener 7 | from .tooltip import TooltipEventListener 8 | from .quick_info_tool_tip import QuickInfoToolTipEventListener 9 | -------------------------------------------------------------------------------- /typescript/listeners/completion.py: -------------------------------------------------------------------------------- 1 | import sublime 2 | 3 | from ..libs.view_helpers import * 4 | from ..libs.text_helpers import * 5 | from .event_hub import EventHub 6 | 7 | 8 | class CompletionEventListener: 9 | def __init__(self): 10 | self.completions_ready = False 11 | self.completions_loc = None 12 | # Used to check if the completion is out of date 13 | self.completion_request_seq = None 14 | self.completion_request_prefix = None 15 | self.completion_request_loc = None 16 | self.if_completion_request_member = False 17 | self.pending_completions = [] 18 | self.modified = False 19 | 20 | def on_activated_with_info(self, view, info): 21 | info.last_completion_loc = None 22 | # save cursor in case we need to read what was inserted 23 | info.prev_sel = regions_to_static_regions(view.sel()) 24 | 25 | def on_text_command_with_info(self, view, command_name, args, info): 26 | if command_name in ["commit_completion", "insert_best_completion"]: 27 | # for finished completion, remember current cursor and set 28 | # a region that will be moved by the inserted text 29 | info.completion_sel = copy_regions(view.sel()) 30 | view.add_regions( 31 | "apresComp", 32 | copy_regions(view.sel()), 33 | flags=sublime.HIDDEN 34 | ) 35 | 36 | def on_modified_with_info(self, view, info): 37 | self.modified = True 38 | 39 | def on_selection_modified_with_info(self, view, info): 40 | if self.modified: 41 | # backspace past start of completion 42 | if info.last_completion_loc and info.last_completion_loc > view.sel()[0].begin(): 43 | view.run_command('hide_auto_complete') 44 | self.modified = False 45 | 46 | def on_post_text_command_with_info(self, view, command_name, args, info): 47 | if not info.change_sent and info.modified: 48 | # file is modified but on_text_command and on_modified did not 49 | # handle it 50 | # handle insertion of string from completion menu, so that 51 | # it is fast to type completedName1.completedName2 (avoid a lag 52 | # when completedName1 is committed) 53 | if command_name in ["commit_completion", "insert_best_completion"] and \ 54 | len(view.sel()) == 1 and \ 55 | not info.client_info.pending_changes: 56 | # get saved region that was pushed forward by insertion of 57 | # the completion 58 | apres_comp_region = view.get_regions("apresComp") 59 | # note: assuming sublime makes all regions empty for 60 | # completion -- which the doc claims is true, 61 | # the insertion string is from region saved in 62 | # on_query_completion to region pushed forward by 63 | # completion insertion 64 | insertion_string = view.substr( 65 | sublime.Region(info.completion_prefix_sel[0].begin(), apres_comp_region[0].begin())) 66 | send_replace_changes_for_regions( 67 | view, 68 | build_replace_regions( 69 | info.completion_prefix_sel, 70 | info.completion_sel 71 | ), 72 | insertion_string) 73 | view.erase_regions("apresComp") 74 | info.last_completion_loc = None 75 | 76 | def on_query_completions(self, view, prefix, locations): 77 | """ 78 | Note: synchronous for now; can change to async by adding hide/show from the handler 79 | """ 80 | service = cli.get_service() 81 | if not service: 82 | return None 83 | info = get_info(view) 84 | if info: 85 | info.completion_prefix_sel = decrease_locs_to_regions(locations, len(prefix)) 86 | if not IS_ST2: 87 | view.add_regions("apresComp", decrease_locs_to_regions(locations, 0), flags=sublime.HIDDEN) 88 | 89 | if (not self.completions_ready) or IS_ST2: 90 | location = get_location_from_position(view, locations[0]) 91 | check_update_view(view) 92 | if IS_ST2: 93 | # Send synchronous request for Sublime Text 2 94 | service.completions(view.file_name(), location, prefix, self.handle_completion_info) 95 | else: 96 | # Send asynchronous request for Sublime Text 3 97 | # 'locations' is an array because of multiple cursor support 98 | self.completion_request_loc = locations[0] 99 | self.completion_request_prefix = prefix 100 | self.completion_request_seq = service.seq 101 | if locations[0] > 0: 102 | prev_char = view.substr(sublime.Region(locations[0] - 1, locations[0] - 1)) 103 | self.if_completion_request_member = (prev_char == ".") 104 | else: 105 | self.if_completion_request_member = False 106 | service.async_completions(view.file_name(), location, prefix, self.handle_completion_info) 107 | 108 | completions = self.pending_completions 109 | info.last_completion_loc = locations[0] 110 | self.pending_completions = [] 111 | self.completions_ready = False 112 | return completions, sublime.INHIBIT_WORD_COMPLETIONS | sublime.INHIBIT_EXPLICIT_COMPLETIONS 113 | 114 | def handle_completion_info(self, completions_resp): 115 | """Helper callback when completion info received from server""" 116 | self.pending_completions = [] 117 | if not IS_ST2: 118 | view = active_view() 119 | loc = view.sel()[0].begin() 120 | prefix_length = len(self.completion_request_prefix) 121 | # Get the current content from the starting location to the cursor location 122 | cur_str = view.substr(sublime.Region(self.completion_request_loc - prefix_length, loc)) 123 | if not cur_str.startswith(self.completion_request_prefix): 124 | # Changed content indicates outdated completion 125 | return 126 | if "." in cur_str: 127 | if not self.if_completion_request_member: 128 | print(cur_str + " includes a dot but not req mem") 129 | return 130 | if len(cur_str) > 0 and not VALID_COMPLETION_ID_PATTERN.match(cur_str): 131 | return 132 | 133 | if completions_resp["success"] and (completions_resp["request_seq"] == self.completion_request_seq or IS_ST2): 134 | completions = [] 135 | raw_completions = completions_resp["body"] 136 | if raw_completions: 137 | for raw_completion in raw_completions: 138 | name = raw_completion["name"] 139 | completion = (name + "\t" + raw_completion["kind"], name.replace("$", "\\$")) 140 | completions.append(completion) 141 | self.pending_completions = completions 142 | if not IS_ST2: 143 | self.completions_ready = True 144 | active_view().run_command('hide_auto_complete') 145 | self.run_auto_complete() 146 | 147 | def run_auto_complete(self): 148 | active_view().run_command("auto_complete", { 149 | 'disable_auto_insert': True, 150 | 'api_completions_only': False, 151 | 'next_completion_if_showing': False, 152 | 'auto_complete_commit_on_tab': True, 153 | }) 154 | 155 | listener = CompletionEventListener() 156 | EventHub.subscribe("on_query_completions", listener.on_query_completions) 157 | EventHub.subscribe("on_activated_with_info", listener.on_activated_with_info) 158 | EventHub.subscribe("on_text_command_with_info", listener.on_text_command_with_info) 159 | EventHub.subscribe("on_modified_with_info", listener.on_modified_with_info) 160 | EventHub.subscribe("on_selection_modified_with_info", listener.on_selection_modified_with_info) 161 | EventHub.subscribe("on_post_text_command_with_info", listener.on_post_text_command_with_info) -------------------------------------------------------------------------------- /typescript/listeners/error_list.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.text_helpers import * 3 | from ..libs import get_panel_manager, log 4 | from .event_hub import EventHub 5 | 6 | 7 | class ProjectErrorListener: 8 | 9 | def __init__(self): 10 | self.just_changed_focus = False 11 | self.modified = False 12 | self.pending_timeout = 0 13 | self.pending_update_error_list_panel = 0 14 | self.errors = dict() 15 | self.event_handler_added = False 16 | 17 | def is_error_list_panel_active(self): 18 | return get_panel_manager().is_panel_active("errorlist") 19 | 20 | def on_activated_with_info(self, view, info): 21 | # Only starts the timer when the error list panel is active 22 | if self.is_error_list_panel_active(): 23 | self.set_request_error_timer(50) 24 | self.just_changed_focus = True 25 | 26 | def post_on_modified(self, view): 27 | if not is_special_view(view) and self.is_error_list_panel_active(): 28 | self.modified = True 29 | self.set_request_error_timer(150) 30 | log.debug("error list timer started") 31 | 32 | def set_request_error_timer(self, ms): 33 | """Set timer to go off when file not being modified""" 34 | self.pending_timeout += 1 35 | sublime.set_timeout(self.handle_time_out, ms) 36 | 37 | def handle_time_out(self): 38 | self.pending_timeout -= 1 39 | if self.pending_timeout == 0: 40 | self.on_idle() 41 | 42 | def on_idle(self): 43 | view = active_view() 44 | info = get_info(view) 45 | if info: 46 | log.debug("asking for project errors") 47 | if self.is_error_list_panel_active(): 48 | self.request_errors(view, info, 100) 49 | 50 | def load_error(self, json_dict): 51 | log.debug(json_dict) 52 | if json_dict["type"] != "event": 53 | return 54 | 55 | error_type = json_dict["event"] 56 | if error_type not in ["syntaxDiag", "semanticDiag"]: 57 | return 58 | 59 | body = json_dict["body"] 60 | if body is not None: 61 | file = body["file"] 62 | if file not in self.errors: 63 | self.errors[file] = {"syntaxDiag": [], "semanticDiag": []} 64 | self.errors[file][error_type] = [] 65 | diags = body["diagnostics"] 66 | for diag in diags: 67 | message = " ({0}, {1}) {2}".format( 68 | diag["start"]["line"], 69 | diag["start"]["offset"], 70 | diag["text"] 71 | ) 72 | self.errors[file][error_type].append(message) 73 | self.set_update_error_list_panel_timer(100) 74 | 75 | def set_update_error_list_panel_timer(self, ms): 76 | self.pending_update_error_list_panel += 1 77 | sublime.set_timeout(self.handle_update_error_list_panel, ms) 78 | 79 | def handle_update_error_list_panel(self): 80 | self.pending_update_error_list_panel -= 1 81 | if self.pending_update_error_list_panel == 0: 82 | self.update_error_list_panel() 83 | 84 | def update_error_list_panel(self): 85 | log.debug("update error list panel") 86 | output_lines = [] 87 | output_line_map = dict() 88 | 89 | for file in self.errors: 90 | start_line_number = len(output_lines) 91 | error_count = len(self.errors[file]["syntaxDiag"]) + len(self.errors[file]["semanticDiag"]) 92 | if error_count > 0: 93 | output_lines.append("{0} [{1} errors]".format(file, error_count)) 94 | output_lines.extend(self.errors[file]["syntaxDiag"] + self.errors[file]["semanticDiag"]) 95 | for cur_line_number in range(start_line_number, len(output_lines)): 96 | matches = re.findall("(?:\((\d+), (\d+)\))", output_lines[cur_line_number]) 97 | if len(matches) > 0: 98 | row, col = matches[0] 99 | output_line_map[cur_line_number] = (file, row, col) 100 | 101 | if len(output_lines) == 0: 102 | output_lines = ["No error found in this project."] 103 | # remove the gutter icon 104 | get_panel_manager().get_panel("errorlist").erase_regions("cur_error") 105 | 106 | get_panel_manager().write_lines_to_panel("errorlist", output_lines) 107 | get_panel_manager().set_line_map("errorlist", output_line_map) 108 | 109 | def request_errors(self, view, info, error_delay): 110 | if not self.event_handler_added: 111 | cli.service.add_event_handler_for_worker("syntaxDiag", self.load_error) 112 | cli.service.add_event_handler_for_worker("semanticDiag", self.load_error) 113 | self.event_handler_added = True 114 | 115 | if info and self.is_error_list_panel_active() and ( 116 | self.just_changed_focus or 117 | self.modified 118 | ): 119 | self.just_changed_focus = False 120 | self.modified = False 121 | cli.service.request_get_err_for_project(error_delay, view.file_name()) 122 | 123 | listener = ProjectErrorListener() 124 | 125 | def start_timer(): 126 | global listener 127 | listener.just_changed_focus = True 128 | listener.set_request_error_timer(50) 129 | 130 | EventHub.subscribe("on_activated_with_info", listener.on_activated_with_info) 131 | EventHub.subscribe("post_on_modified", listener.post_on_modified) -------------------------------------------------------------------------------- /typescript/listeners/event_hub.py: -------------------------------------------------------------------------------- 1 | from ..libs import global_vars 2 | 3 | class EventHub: 4 | listener_dict = dict() 5 | 6 | @classmethod 7 | def subscribe(cls, key, listener): 8 | if key in cls.listener_dict.keys(): 9 | cls.listener_dict[key].append(listener) 10 | else: 11 | cls.listener_dict[key] = [listener] 12 | 13 | @classmethod 14 | def run_listeners(cls, key, *args): 15 | if not global_vars.get_language_service_enabled(): 16 | return 17 | 18 | if key in cls.listener_dict.keys(): 19 | for handler in cls.listener_dict[key]: 20 | handler(*args) 21 | 22 | @classmethod 23 | def run_listener_with_return(cls, key, *args): 24 | if not global_vars.get_language_service_enabled(): 25 | return None 26 | 27 | """Return the first non-None result, otherwise return None""" 28 | if key in cls.listener_dict.keys(): 29 | res = cls.listener_dict[key][0](*args) 30 | if res is not None: 31 | return res 32 | return None 33 | -------------------------------------------------------------------------------- /typescript/listeners/format.py: -------------------------------------------------------------------------------- 1 | from .event_hub import EventHub 2 | from ..libs.view_helpers import * 3 | from ..libs.logger import log 4 | from ..libs import cli 5 | 6 | class FormatEventListener: 7 | def on_post_text_command_with_info(self, view, command_name, args, info): 8 | if command_name in \ 9 | ["typescript_format_on_key", 10 | "typescript_format_document", 11 | "typescript_format_selection", 12 | "typescript_format_line", 13 | "typescript_paste_and_format"]: 14 | print("handled changes for " + command_name) 15 | 16 | def on_modified_with_info(self, view, info): 17 | log.debug("Format on key") 18 | 19 | if ( 20 | is_supported_ext(view) and 21 | cli.ts_auto_format_enabled and 22 | info.prev_sel and 23 | len(info.prev_sel) == 1 and 24 | info.prev_sel[0].empty() 25 | ): 26 | last_command, args, repeat_times = view.command_history(0) 27 | redo_command = view.command_history(1)[0] 28 | log.debug("last_command:{0}, args:{1}".format(last_command, args)) 29 | log.debug("redo_command:{0}".format(redo_command)) 30 | if redo_command != "" and redo_command is not None: 31 | # in an undo session, avoid running format_on_key. For 32 | # a non-undo session in ST3, the redo_command is an empty 33 | # string; in ST2, the redo_command is None 34 | return 35 | 36 | if last_command == "insert": 37 | pos = info.prev_sel[0].begin() 38 | if ";" in args["characters"]: 39 | view.run_command("typescript_format_on_key", {"key": ";"}) 40 | if "}" in args["characters"]: 41 | if cli.auto_match_enabled: 42 | prev_char = view.substr(pos - 1) 43 | post_char = view.substr(pos + 1) 44 | log.debug("prev_char: {0}, post_char: {1}".format(prev_char, post_char)) 45 | if prev_char != "{" and post_char != "}": 46 | view.run_command("typescript_format_on_key", {"key": "}"}) 47 | else: 48 | view.run_command("typescript_format_on_key", {"key": "}"}) 49 | if "\n" in args["characters"]: 50 | if cli.ts_auto_indent_enabled and view.score_selector(pos, "meta.scope.between-tag-pair") > 0: 51 | view.run_command("typescript_format_on_key", {"key": "\n"}) 52 | 53 | listener = FormatEventListener() 54 | EventHub.subscribe("on_post_text_command_with_info", listener.on_post_text_command_with_info) 55 | EventHub.subscribe("on_modified_with_info", listener.on_modified_with_info) -------------------------------------------------------------------------------- /typescript/listeners/idle.py: -------------------------------------------------------------------------------- 1 | from ..libs.view_helpers import * 2 | from ..libs.text_helpers import * 3 | from ..libs import log 4 | from .event_hub import EventHub 5 | 6 | class TimeoutScheduler: 7 | """ 8 | If there are multiple timeouts set for the same function, only call the 9 | function in the last timeout in chronological order. 10 | """ 11 | def __init__(self, handler): 12 | self.timeout_count = 0 # number of pending timeouts 13 | self.handler = handler 14 | 15 | def reset_timeout(self, mstime): 16 | self.timeout_count += 1 17 | sublime.set_timeout(self._check_timeout_count, mstime) 18 | 19 | def _check_timeout_count(self): 20 | self.timeout_count -= 1 21 | if self.timeout_count == 0: 22 | self.handler() 23 | 24 | class IdleListener: 25 | def __init__(self): 26 | self.just_changed_focus = False 27 | self.modified = False # Flag to check if buffer has just been modified 28 | self.event_handler_added = False 29 | self.occurrences_event_handler_added = False 30 | 31 | self.on_idle_timeout_scheduler = TimeoutScheduler(self.on_idle) 32 | self.on_selection_idle_timeout_scheduler = TimeoutScheduler(self.on_selection_idle) 33 | 34 | # LISTENERS 35 | 36 | def on_activated_with_info(self, view, info): 37 | # Set modified and selection idle timers, so we can read diagnostics 38 | # and update status line 39 | self.on_idle_timeout_scheduler.reset_timeout(IDLE_TIME_LENGTH) 40 | self.on_selection_idle_timeout_scheduler.reset_timeout(IDLE_TIME_LENGTH) 41 | self.just_changed_focus = True 42 | 43 | def post_on_modified(self, view): 44 | if not is_special_view(view): 45 | self.modified = True 46 | self.on_idle_timeout_scheduler.reset_timeout(100) 47 | 48 | def on_selection_modified_with_info(self, view, info): 49 | if self.modified: 50 | self.on_selection_idle_timeout_scheduler.reset_timeout(1250) 51 | else: 52 | self.on_selection_idle_timeout_scheduler.reset_timeout(100) 53 | self.modified = False 54 | 55 | # HELPER METHODS 56 | 57 | def on_idle(self): 58 | """ 59 | If file hasn't been modified for a period of time, send a request for 60 | errors. 61 | """ 62 | log.debug("on_idle") 63 | view = active_view() 64 | info = get_info(view) 65 | if info: 66 | self.request_errors(view, info, 500) 67 | 68 | def on_selection_idle(self): 69 | """ 70 | If selection is idle (cursor is not moving around), update the status 71 | line (error message or quick info, if any) and update document highlights. 72 | """ 73 | log.debug("on_selection_idle") 74 | view = active_view() 75 | info = get_info(view) 76 | if info: 77 | self.update_status(view, info) 78 | if not IS_ST2 and view.settings().get('typescript_highlight_occurrences', 'true'): 79 | self.request_document_highlights(view, info) 80 | 81 | def request_errors(self, view, info, error_delay): 82 | """ 83 | Ask the server for diagnostic information on all opened ts files in 84 | most-recently-used order 85 | """ 86 | service = cli.get_service() 87 | if not self.event_handler_added: 88 | service.add_event_handler("syntaxDiag", lambda ev: self.show_errors(ev["body"], syntactic=True)) 89 | service.add_event_handler("semanticDiag", lambda ev: self.show_errors(ev["body"], syntactic=False)) 90 | self.event_handler_added = True 91 | 92 | # Todo: limit this request to ts files currently visible in views 93 | if info and (self.just_changed_focus or info.change_count_when_last_err_req_sent < change_count(view)): 94 | self.just_changed_focus = False 95 | info.change_count_when_last_err_req_sent = change_count(view) 96 | window = sublime.active_window() 97 | group_number = window.num_groups() 98 | files = [] 99 | for i in range(group_number): 100 | group_active_view = window.active_view_in_group(i) 101 | info = get_info(group_active_view) 102 | if info: 103 | files.append(group_active_view.file_name()) 104 | check_update_view(group_active_view) 105 | if len(files) > 0: 106 | service = cli.get_service() 107 | if not service: 108 | return None 109 | service.request_get_err(error_delay, files) 110 | 111 | def show_errors(self, diagno_event_body, syntactic): 112 | """ 113 | Error messages arrived from the server; show them in view 114 | """ 115 | log.debug("show_errors") 116 | filename = diagno_event_body["file"] 117 | if os.name == 'nt' and filename: 118 | filename = filename.replace('/', '\\') 119 | 120 | info = get_info_with_filename(filename) 121 | if not info: 122 | return 123 | 124 | view = info.view 125 | if not info.change_count_when_last_err_req_sent == change_count(view): 126 | log.debug("The error info is outdated") 127 | self.on_idle_timeout_scheduler.reset_timeout(200) 128 | return 129 | 130 | region_key = 'syntacticDiag' if syntactic else 'semanticDiag' 131 | view.erase_regions(region_key) 132 | 133 | client_info = cli.get_or_add_file(filename) 134 | client_info.errors[region_key] = [] 135 | error_regions = [] 136 | 137 | diagnos = diagno_event_body["diagnostics"] 138 | if diagnos: 139 | for diagno in diagnos: 140 | start_line, start_offset = extract_line_offset(diagno["start"]) 141 | start = view.text_point(start_line, start_offset) 142 | 143 | end_line, end_offset = extract_line_offset(diagno["end"]) 144 | end = view.text_point(end_line, end_offset) 145 | 146 | if end <= view.size(): 147 | # Creates a from to to 148 | # highlight the error. If the region coincides with the 149 | # EOF character, use the region of the last visible 150 | # character instead so user can still see the highlight. 151 | if start == view.size() and start == end: 152 | region = last_visible_character_region(view) 153 | else: 154 | region = sublime.Region(start, end) 155 | 156 | client_info.errors[region_key].append((region, diagno["text"])) 157 | error_regions.append(region) 158 | 159 | # Update status bar with error information 160 | info.has_errors = cli.has_errors(filename) 161 | self.update_status(view, info) 162 | 163 | # Highlight error regions in view 164 | if IS_ST2: 165 | view.add_regions(region_key, error_regions, "keyword", "", 166 | sublime.DRAW_OUTLINED) 167 | else: 168 | view.add_regions(region_key, error_regions, "keyword", "", 169 | sublime.DRAW_NO_FILL + 170 | sublime.DRAW_NO_OUTLINE + 171 | sublime.DRAW_SQUIGGLY_UNDERLINE) 172 | 173 | def update_status(self, view, info): 174 | """Update the status line with error info and quick info if no error info""" 175 | # Error info 176 | if PHANTOM_SUPPORT: 177 | view.erase_phantoms("typescript_error") 178 | view.erase_status("typescript_error") 179 | 180 | if info.has_errors: 181 | view.run_command('typescript_error_info') 182 | 183 | # Quick info 184 | error_status = view.get_status('typescript_error') 185 | if error_status and len(error_status) > 0: 186 | view.erase_status("typescript_info") 187 | else: 188 | view.run_command('typescript_quick_info') 189 | 190 | def request_document_highlights(self, view, info): 191 | if is_supported_ext(view): 192 | location = get_location_from_view(view) 193 | service = cli.get_service() 194 | if not service: 195 | return None 196 | service.async_document_highlights(view.file_name(), location, self.highlight_occurrences) 197 | 198 | def highlight_occurrences(self, response): 199 | view = active_view() 200 | if not view.file_name(): 201 | return 202 | 203 | region_key = 'occurrences' 204 | view.erase_regions(region_key) 205 | 206 | if not response['success']: 207 | return 208 | 209 | occurrence_regions = [] 210 | 211 | for file_highlight in response['body']: 212 | if file_highlight['file'] != view.file_name().replace('\\', '/'): 213 | continue 214 | 215 | for occurrence in file_highlight['highlightSpans']: 216 | start_line, start_offset = extract_line_offset(occurrence['start']) 217 | start_point = view.text_point(start_line, start_offset) 218 | 219 | end_line, end_offset = extract_line_offset(occurrence['end']) 220 | end_point = view.text_point(end_line, end_offset) 221 | 222 | occurrence_regions.append(sublime.Region(start_point, end_point)) 223 | 224 | view.add_regions(region_key, occurrence_regions, 'comment', '', 225 | sublime.DRAW_NO_FILL | 226 | sublime.DRAW_NO_OUTLINE | 227 | sublime.DRAW_SOLID_UNDERLINE) 228 | 229 | listener = IdleListener() 230 | EventHub.subscribe("on_activated_with_info", listener.on_activated_with_info) 231 | EventHub.subscribe("post_on_modified", listener.post_on_modified) 232 | EventHub.subscribe("on_selection_modified_with_info", listener.on_selection_modified_with_info) 233 | -------------------------------------------------------------------------------- /typescript/listeners/listeners.py: -------------------------------------------------------------------------------- 1 | import sublime_plugin 2 | 3 | from ..libs.view_helpers import * 4 | from ..libs import * 5 | from .event_hub import EventHub 6 | 7 | class TypeScriptEventListener(sublime_plugin.EventListener): 8 | """To avoid duplicated behavior among event listeners""" 9 | 10 | # During the "close all" process, handling on_activated events is 11 | # undesirable (not required and can be costly due to reloading buffers). 12 | # This flag provides a way to know whether the "close all" process is 13 | # happening so we can ignore unnecessary on_activated callbacks. 14 | about_to_close_all = False 15 | 16 | def on_activated(self, view): 17 | log.debug("on_activated") 18 | 19 | if TypeScriptEventListener.about_to_close_all: 20 | return 21 | 22 | if is_special_view(view): 23 | self.on_activated_special_view(view) 24 | else: 25 | info = get_info(view) 26 | if info: 27 | self.on_activated_with_info(view, info) 28 | 29 | def on_activated_special_view(self, view): 30 | log.debug("on_activated_special_view") 31 | EventHub.run_listeners("on_activated_special_view", view) 32 | 33 | def on_activated_with_info(self, view, info): 34 | log.debug("on_activated_with_info") 35 | EventHub.run_listeners("on_activated_with_info", view, info) 36 | 37 | def on_modified(self, view): 38 | """ 39 | Usually called by Sublime when the buffer is modified 40 | not called for undo, redo 41 | """ 42 | log.debug("on_modified") 43 | if is_special_view(view): 44 | self.on_modified_special_view(view) 45 | else: 46 | info = get_info(view) 47 | if info: 48 | self.on_modified_with_info(view, info) 49 | self.post_on_modified(view) 50 | 51 | def on_modified_special_view(self, view): 52 | log.debug("on_modified_special_view") 53 | EventHub.run_listeners("on_modified_special_view", view) 54 | 55 | def on_modified_with_info(self, view, info): 56 | log.debug("on_modified_with_info") 57 | 58 | # A series state-updating for the info object to sync the file content on the server 59 | info.modified = True 60 | 61 | # Todo: explain 62 | if IS_ST2: 63 | info.modify_count += 1 64 | info.last_modify_change_count = change_count(view) 65 | last_command, args, repeat_times = view.command_history(0) 66 | 67 | if info.pre_change_sent: 68 | # change handled in on_text_command 69 | info.client_info.change_count = change_count(view) 70 | info.pre_change_sent = False 71 | 72 | else: 73 | if last_command == "insert": 74 | if ( 75 | "\n" not in args['characters'] # no new line inserted 76 | and info.prev_sel # it is not a newly opened file 77 | and len(info.prev_sel) == 1 # not a multi-cursor session 78 | and info.prev_sel[0].empty() # the last selection is not a highlighted selection 79 | and not info.client_info.pending_changes # no pending changes in the buffer 80 | ): 81 | info.client_info.change_count = change_count(view) 82 | prev_cursor = info.prev_sel[0].begin() 83 | cursor = view.sel()[0].begin() 84 | key = view.substr(sublime.Region(prev_cursor, cursor)) 85 | # send_replace_changes_for_regions(view, static_regions_to_regions(info.prev_sel), key) 86 | # mark change as handled so that on_post_text_command doesn't try to handle it 87 | info.change_sent = True 88 | else: 89 | # request reload because we have strange insert 90 | info.client_info.pending_changes = True 91 | 92 | # Reload buffer after insert_snippet. 93 | # For Sublime 2 only. In Sublime 3, this logic is implemented in 94 | # on_post_text_command callback. 95 | # Issue: https://github.com/Microsoft/TypeScript-Sublime-Plugin/issues/277 96 | if IS_ST2 and last_command == "insert_snippet": 97 | reload_buffer(view); 98 | 99 | # Other listeners 100 | EventHub.run_listeners("on_modified_with_info", view, info) 101 | 102 | def post_on_modified(self, view): 103 | log.debug("post_on_modified") 104 | send_replace_changes_for_regions(view, None, None) 105 | EventHub.run_listeners("post_on_modified", view) 106 | 107 | def on_selection_modified(self, view): 108 | """ 109 | Called by Sublime when the cursor moves (or when text is selected) 110 | called after on_modified (when on_modified is called) 111 | """ 112 | log.debug("on_selection_modified") 113 | # Todo: why do we only check this here? anyway to globally disable the listener for non-ts files 114 | if not is_supported_ext(view): 115 | return 116 | 117 | EventHub.run_listeners("on_selection_modified", view) 118 | 119 | info = get_info(view) 120 | if info: 121 | self.on_selection_modified_with_info(view, info) 122 | 123 | def on_selection_modified_with_info(self, view, info): 124 | log.debug("on_selection_modified_with_info") 125 | if not info.client_info: 126 | info.client_info = cli.get_or_add_file(view.file_name()) 127 | 128 | if ( 129 | info.client_info.change_count < change_count(view) 130 | and info.last_modify_change_count != change_count(view) 131 | ): 132 | # detected a change to the view for which Sublime did not call 133 | # 'on_modified' and for which we have no hope of discerning 134 | # what changed 135 | info.client_info.pending_changes = True 136 | # save the current cursor position so that we can see (in 137 | # on_modified) what was inserted 138 | info.prev_sel = regions_to_static_regions(view.sel()) 139 | EventHub.run_listeners("on_selection_modified_with_info", view, info) 140 | 141 | def on_load(self, view): 142 | log.debug("on_load") 143 | EventHub.run_listeners("on_load", view) 144 | 145 | def on_window_command(self, window, command_name, args): 146 | log.debug("on_window_command") 147 | 148 | service = cli.get_service() 149 | if not service: 150 | return 151 | 152 | if command_name == "hide_panel" and service.__worker_comm.started(): 153 | cli.worker_client.stop() 154 | 155 | elif command_name == "exit": 156 | for client in cli.client_manager.get_clients(): 157 | client.exit() 158 | 159 | elif command_name in ["close_all", "close_window", "close_project"]: 160 | # Only set flag if there exists at least one 161 | # view in the active window. This is important because we need 162 | # some view's on_close callback to reset the flag. 163 | window = sublime.active_window() 164 | if window is not None and window.views(): 165 | TypeScriptEventListener.about_to_close_all = True 166 | 167 | def on_text_command(self, view, command_name, args): 168 | """ 169 | ST3 only (called by ST3 for some, but not all, text commands) 170 | for certain text commands, learn what changed and notify the 171 | server, to avoid sending the whole buffer during completion 172 | or when key can be held down and repeated. 173 | If we had a popup session active, and we get the command to 174 | hide it, then do the necessary clean up. 175 | """ 176 | log.debug("on_text_command") 177 | EventHub.run_listeners("on_text_command", view, command_name, args) 178 | info = get_info(view) 179 | if info: 180 | self.on_text_command_with_info(view, command_name, args, info) 181 | 182 | def on_text_command_with_info(self, view, command_name, args, info): 183 | log.debug("on_text_command_with_info") 184 | info.change_sent = True 185 | info.pre_change_sent = True 186 | # if command_name == "left_delete": 187 | # backspace 188 | # send_replace_changes_for_regions(view, left_expand_empty_region(view.sel()), "") 189 | # elif command_name == "right_delete": 190 | # delete 191 | # send_replace_changes_for_regions(view, right_expand_empty_region(view.sel()), "") 192 | # else: 193 | # notify on_modified and on_post_text_command events that 194 | # nothing was handled. There are multiple flags because Sublime 195 | # does not always call all three events. 196 | info.pre_change_sent = False 197 | info.change_sent = False 198 | info.modified = False 199 | 200 | EventHub.run_listeners("on_text_command_with_info", view, command_name, args, info) 201 | 202 | def on_post_text_command(self, view, command_name, args): 203 | """ 204 | ST3 only 205 | called by ST3 for some, but not all, text commands 206 | not called for insert command 207 | """ 208 | log.debug("on_post_text_command") 209 | info = get_info(view) 210 | if info: 211 | if not info.change_sent and info.modified: 212 | self.on_post_text_command_with_info(view, command_name, args, info) 213 | 214 | # we are up-to-date because either change was sent to server or 215 | # whole buffer was sent to server 216 | info.client_info.change_count = view.change_count() 217 | # reset flags and saved regions used for communication among 218 | # on_text_command, on_modified, on_selection_modified, 219 | # on_post_text_command, and on_query_completion 220 | info.change_sent = False 221 | info.modified = False 222 | info.completion_sel = None 223 | 224 | def on_post_text_command_with_info(self, view, command_name, args, info): 225 | log.debug("on_post_text_command_with_info") 226 | if command_name not in \ 227 | ["commit_completion", 228 | "insert_best_completion", 229 | "typescript_format_on_key", 230 | "typescript_format_document", 231 | "typescript_format_selection", 232 | "typescript_format_line", 233 | "typescript_paste_and_format"]: 234 | print(command_name) 235 | # give up and send whole buffer to server (do this eagerly 236 | # to avoid lag on next request to server) 237 | reload_buffer(view, info.client_info) 238 | EventHub.run_listeners("on_post_text_command_with_info", view, command_name, args, info) 239 | 240 | def on_query_completions(self, view, prefix, locations): 241 | log.debug("on_query_completions") 242 | return EventHub.run_listener_with_return("on_query_completions", view, prefix, locations) 243 | 244 | def on_query_context(self, view, key, operator, operand, match_all): 245 | log.debug("on_query_context") 246 | return EventHub.run_listener_with_return("on_query_context", view, key, operator, operand, match_all) 247 | 248 | def on_close(self, view): 249 | log.debug("on_close") 250 | file_name = view.file_name() 251 | info = get_info(view, open_if_not_cached=False) 252 | if info: 253 | info.is_open = False 254 | if view.is_scratch() and view.name() == "Find References": 255 | cli.dispose_ref_info() 256 | else: 257 | # info = get_info(view) 258 | # if info: 259 | # if info in most_recent_used_file_list: 260 | # most_recent_used_file_list.remove(info) 261 | # notify the server that the file is closed 262 | service = cli.get_service() 263 | if not service: 264 | return None 265 | service.close(file_name) 266 | 267 | # If this is the last view that is closed by a close_all command, 268 | # reset flag. 269 | if TypeScriptEventListener.about_to_close_all: 270 | window = sublime.active_window() 271 | if window is None or not window.views(): 272 | TypeScriptEventListener.about_to_close_all = False 273 | log.debug("all views have been closed") 274 | 275 | def on_pre_save(self, view): 276 | log.debug("on_pre_save") 277 | check_update_view(view) 278 | 279 | def on_hover(self, view, point, hover_zone): 280 | log.debug("on_hover") 281 | EventHub.run_listeners("on_hover", view, point, hover_zone) -------------------------------------------------------------------------------- /typescript/listeners/nav_to.py: -------------------------------------------------------------------------------- 1 | from ..commands.nav_to import TypescriptNavToCommand 2 | from ..libs.view_helpers import * 3 | from ..libs import * 4 | from .event_hub import EventHub 5 | 6 | 7 | class NavToEventListener: 8 | """Event listeners for the TypescriptNavToCommand""" 9 | def on_activated_special_view(self, view): 10 | if TypescriptNavToCommand.nav_to_panel_started: 11 | # The current view is the QuickPanel. Set insert_text_finished to false to suppress 12 | # handling in on_modified 13 | TypescriptNavToCommand.insert_text_finished = False 14 | view.run_command("insert", {"characters": TypescriptNavToCommand.input_text}) 15 | # Re-enable the handling in on_modified 16 | TypescriptNavToCommand.insert_text_finished = True 17 | 18 | def on_modified_special_view(self, view): 19 | logger.log.debug("enter on_modified: special view. started: %s, insert_text_finished: %s" % 20 | (TypescriptNavToCommand.nav_to_panel_started, TypescriptNavToCommand.insert_text_finished)) 21 | 22 | if TypescriptNavToCommand.nav_to_panel_started and TypescriptNavToCommand.insert_text_finished: 23 | new_content = view.substr(sublime.Region(0, view.size())) 24 | active_window().run_command("hide_overlay") 25 | sublime.set_timeout( 26 | lambda: active_window().run_command("typescript_nav_to", {'input_text': new_content}), 27 | 0) 28 | 29 | logger.log.debug("exit on_modified: special view. started: %s, insert_text_finished: %s" % 30 | (TypescriptNavToCommand.nav_to_panel_started, TypescriptNavToCommand.insert_text_finished)) 31 | 32 | listener = NavToEventListener() 33 | EventHub.subscribe("on_activated_special_view", listener.on_activated_special_view) 34 | EventHub.subscribe("on_modified_special_view", listener.on_modified_special_view) 35 | -------------------------------------------------------------------------------- /typescript/listeners/quick_info_tool_tip.py: -------------------------------------------------------------------------------- 1 | from .event_hub import EventHub 2 | from ..libs.view_helpers import * 3 | from ..libs.logger import log 4 | from ..libs import cli 5 | 6 | class QuickInfoToolTipEventListener: 7 | def on_hover(self, view, point, hover_zone): 8 | view.run_command('typescript_quick_info_doc', {"hover_point": point}) 9 | 10 | listen = QuickInfoToolTipEventListener() 11 | EventHub.subscribe("on_hover", listen.on_hover) 12 | -------------------------------------------------------------------------------- /typescript/listeners/rename.py: -------------------------------------------------------------------------------- 1 | from ..libs import * 2 | from .event_hub import EventHub 3 | 4 | 5 | class RenameEventListener: 6 | def on_load(self, view): 7 | client_info = cli.get_or_add_file(view.file_name()) 8 | # finish the renaming 9 | if client_info and client_info.rename_on_load: 10 | view.run_command( 11 | 'typescript_delayed_rename_file', 12 | {"locs_name": client_info.rename_on_load} 13 | ) 14 | client_info.rename_on_load = None 15 | 16 | listener = RenameEventListener() 17 | EventHub.subscribe("on_load", listener.on_load) -------------------------------------------------------------------------------- /typescript/listeners/tooltip.py: -------------------------------------------------------------------------------- 1 | from ..libs.global_vars import * 2 | from ..libs.view_helpers import active_window 3 | from ..libs import * 4 | from .event_hub import EventHub 5 | 6 | 7 | class TooltipEventListener: 8 | 9 | def __init__(self): 10 | self.was_paren_pressed = False 11 | 12 | def on_selection_modified(self, view): 13 | if TOOLTIP_SUPPORT: 14 | popup_manager = get_popup_manager() 15 | # Always reset this flag 16 | _paren_pressed = self.was_paren_pressed 17 | self.was_paren_pressed = False 18 | 19 | if popup_manager.is_active(): 20 | popup_manager.queue_signature_popup(view) 21 | else: 22 | if _paren_pressed: 23 | # TODO: Check 'typescript_auto_popup' setting is True 24 | logger.log.debug('Triggering popup of sig help on paren') 25 | popup_manager.queue_signature_popup(view) 26 | 27 | def on_selection_modified_with_info(self, view, info): 28 | # hide the doc info output panel if it's up 29 | panel_view = active_window().get_output_panel("doc") 30 | if panel_view.window(): 31 | active_window().run_command("hide_panel", {"cancel": True}) 32 | 33 | def on_text_command(self, view, command_name, args): 34 | if command_name == 'hide_popup': 35 | popup_manager = get_popup_manager() 36 | popup_manager.on_close_popup() 37 | 38 | def on_query_context(self, view, key, operator, operand, match_all): 39 | if key == 'is_popup_visible' and TOOLTIP_SUPPORT: 40 | return view.is_popup_visible() 41 | if key == 'paren_pressed': 42 | # Dummy check we never intercept, used as a notification paren was 43 | # pressed. Used to automatically display signature help. 44 | self.was_paren_pressed = True 45 | return False 46 | if key == 'tooltip_supported': 47 | return TOOLTIP_SUPPORT == operand 48 | return None 49 | 50 | 51 | listen = TooltipEventListener() 52 | EventHub.subscribe("on_selection_modified", listen.on_selection_modified) 53 | EventHub.subscribe("on_selection_modified_with_info", listen.on_selection_modified_with_info) 54 | EventHub.subscribe("on_text_command", listen.on_text_command) 55 | EventHub.subscribe("on_query_context", listen.on_query_context) 56 | --------------------------------------------------------------------------------