├── .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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------