-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathcommand_syntax_tree.py
More file actions
227 lines (190 loc) · 7.07 KB
/
Copy pathcommand_syntax_tree.py
File metadata and controls
227 lines (190 loc) · 7.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
from __future__ import annotations
from functools import partial
from LSP.plugin import LspTextCommand
from LSP.plugin import Promise
from LSP.plugin import Request
from LSP.plugin.core.tree_view import new_tree_view_sheet
from LSP.plugin.core.tree_view import TreeDataProvider
from LSP.plugin.core.tree_view import TreeItem
from LSP.plugin.core.views import text_document_position_params
from LSP.protocol import NotRequired
from LSP.protocol import Range
from typing import Any
from typing import cast
from typing import Literal
from typing import Tuple
from typing import TypedDict
from typing import Union
import json
import sublime
import sublime_plugin
class Offsets(TypedDict):
start: int
end: int
class InnerNode(TypedDict):
range: Range
offsets: Offsets
class SyntaxNode(TypedDict):
type: Literal['Node']
kind: str
offsets: Offsets
range: Range
# This element's position within a Rust string literal, if it's inside of one.
inner: InnerNode | None
parent: SyntaxElement | None
children: list[SyntaxElement]
class SyntaxToken(TypedDict):
type: Literal['Token']
kind: str
range: Range
offsets: Offsets
# This element's position within a Rust string literal, if it's inside of one.
inner: InnerNode | None
parent: SyntaxElement | None
SyntaxElement = Union[SyntaxNode, SyntaxToken]
class RawNode(TypedDict):
type: Literal['Node']
kind: str
start: Tuple[int, int, int]
end: Tuple[int, int, int]
istart: NotRequired[Tuple[int, int, int]]
iend: NotRequired[Tuple[int, int, int]]
children: list[SyntaxElement]
class RawToken(TypedDict):
type: Literal['Token']
kind: str
start: Tuple[int, int, int]
end: Tuple[int, int, int]
istart: NotRequired[Tuple[int, int, int]]
iend: NotRequired[Tuple[int, int, int]]
RawElement = Union[RawNode, RawToken]
def parseSyntaxTree(value: str) -> SyntaxElement:
def object_hook(value: RawElement) -> SyntaxElement:
if value['type'] != 'Node' and value['type'] != 'Token':
# This is something other than a RawElement.
return value
startOffset, startLine, startCol = value['start']
endOffset, endLine, endCol = value['end']
range: Range = {
'start': {
'line': startLine,
'character': startCol,
},
'end': {
'line': endLine,
'character': endCol
}
}
offsets: Offsets = {
'start': startOffset,
'end': endOffset,
}
inner: InnerNode | None = None
if (istart := value.get('istart')) and (iend := value.get('iend')):
istartOffset, istartLine, istartCol = istart
iendOffset, iendLine, iendCol = iend
inner = {
'offsets': {
'start': istartOffset,
'end': iendOffset,
},
'range': {
'start': {
'line': istartLine,
'character': istartCol
},
'end': {
'line': iendLine,
'character': iendCol
}
}
}
if value['type'] == 'Node':
result: SyntaxNode = {
'type': value['type'],
'kind': value['kind'],
'offsets': offsets,
'range': range,
'inner': inner,
'children': value.get('children', []),
'parent': None,
}
for child in result['children']:
child['parent'] = result
return result
else:
return {
'type': value['type'],
'kind': value['kind'],
'offsets': offsets,
'range': range,
'inner': inner,
'parent': None,
}
return json.loads(value, object_hook=lambda v: object_hook(cast('RawElement', v)))
class SyntaxTreeProvider(TreeDataProvider):
def __init__(self, root_element: SyntaxElement, view_id: int) -> None:
self.root_element = root_element
self.view_id = view_id
def get_children(self, element: SyntaxElement | None) -> Promise[list[SyntaxElement]]:
if element is None:
return Promise.resolve([self.root_element])
return Promise.resolve(element.get('children', []))
def get_tree_item(self, element: SyntaxElement) -> TreeItem:
inner = element.get('inner', {})
offsets = inner['offsets'] if inner else element['offsets']
offsets_text = f'{offsets["start"]}..{offsets["end"]}'
return TreeItem(
label=f'{element["kind"]}',
description=f'({element["type"]} - {offsets_text})',
action_command=('rust_analyzer_syntax_tree_click_node', {
'view_id': self.view_id,
'range': element['range'],
})
)
class RustAnalyzerSyntaxTreeCommand(LspTextCommand):
def is_enabled(self) -> bool:
selection = self.view.sel()
if len(selection) == 0:
return False
return super().is_enabled()
def run(self, edit: sublime.Edit) -> None:
session = self.session_by_name(self.session_name)
if session is None:
return
params = text_document_position_params(self.view, self.view.sel()[0].b)
session.send_request(
Request("rust-analyzer/viewSyntaxTree", params),
lambda response: sublime.set_timeout(partial(self.on_result, response))
)
def on_result(self, out: str) -> None:
session = self.session_by_name(self.session_name)
if session is None:
return
window = self.view.window()
if window is None:
return
sheet_name = 'Syntax Tree'
root_element = parseSyntaxTree(out)
data_provider = SyntaxTreeProvider(root_element, self.view.id())
new_tree_view_sheet(window, sheet_name, data_provider, sheet_name, flags=sublime.NewFileFlags.ADD_TO_SELECTION)
class RustAnalyzerSyntaxTreeClickNode(sublime_plugin.WindowCommand):
def run(self, view_id: int, range: Range) -> None:
if view := self.find_view_id(view_id):
view.run_command('rust_analyzer_syntax_tree_select_node_in_view', {'range': cast('dict[str, Any]', range)})
def find_view_id(self, view_id) -> sublime.View | None:
for view in self.window.views():
if view.id() == view_id:
return view
return None
class RustAnalyzerSyntaxTreeSelectNodeInView(LspTextCommand):
def run(self, _: sublime.Edit, *, range: Range) -> None:
view = self.view
selection = view.sel()
selection.clear()
start = range['start']
end = range['end']
region = sublime.Region(view.text_point_utf16(start['line'], start['character']),
view.text_point_utf16(end['line'], end['character']),)
selection.add(region)
view.show_at_center(region)