jaclang 0.8.0__py3-none-any.whl → 0.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/cli/cli.py +11 -9
- jaclang/compiler/jac.lark +2 -12
- jaclang/compiler/larkparse/jac_parser.py +1 -1
- jaclang/compiler/parser.py +360 -521
- jaclang/compiler/passes/main/cfg_build_pass.py +2 -2
- jaclang/compiler/passes/main/def_impl_match_pass.py +14 -13
- jaclang/compiler/passes/main/def_use_pass.py +4 -7
- jaclang/compiler/passes/main/import_pass.py +3 -3
- jaclang/compiler/passes/main/inheritance_pass.py +2 -2
- jaclang/compiler/passes/main/pyast_gen_pass.py +196 -218
- jaclang/compiler/passes/main/pyast_load_pass.py +115 -311
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +8 -7
- jaclang/compiler/passes/main/sym_tab_build_pass.py +3 -3
- jaclang/compiler/passes/main/sym_tab_link_pass.py +4 -4
- jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/action/actions.jac +1 -5
- jaclang/compiler/passes/main/tests/fixtures/symtab_link_tests/main.jac +1 -8
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +4 -2
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +197 -120
- jaclang/compiler/program.py +2 -7
- jaclang/compiler/tests/fixtures/fam.jac +2 -2
- jaclang/compiler/tests/fixtures/pkg_import_lib/__init__.jac +1 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/sub/__init__.jac +1 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/sub/helper.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib/tools.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/__init__.py +11 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/__init__.py +7 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/sub/helper.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_lib_py/tools.jac +3 -0
- jaclang/compiler/tests/fixtures/pkg_import_main.jac +10 -0
- jaclang/compiler/tests/fixtures/pkg_import_main_py.jac +11 -0
- jaclang/compiler/tests/test_importer.py +20 -0
- jaclang/compiler/tests/test_parser.py +1 -0
- jaclang/compiler/unitree.py +456 -304
- jaclang/langserve/engine.jac +498 -0
- jaclang/langserve/sem_manager.jac +309 -0
- jaclang/langserve/server.jac +186 -0
- jaclang/langserve/tests/server_test/test_lang_serve.py +6 -7
- jaclang/langserve/tests/server_test/utils.py +4 -1
- jaclang/langserve/tests/session.jac +294 -0
- jaclang/langserve/tests/test_sem_tokens.py +2 -2
- jaclang/langserve/tests/test_server.py +12 -7
- jaclang/langserve/utils.jac +51 -30
- jaclang/runtimelib/archetype.py +1 -1
- jaclang/runtimelib/builtin.py +17 -14
- jaclang/runtimelib/importer.py +26 -8
- jaclang/runtimelib/machine.py +96 -55
- jaclang/runtimelib/tests/fixtures/traversing_save.jac +7 -5
- jaclang/runtimelib/utils.py +3 -3
- jaclang/tests/fixtures/backward_edge_visit.jac +31 -0
- jaclang/tests/fixtures/builtin_printgraph.jac +85 -0
- jaclang/tests/fixtures/builtin_printgraph_json.jac +21 -0
- jaclang/tests/fixtures/builtin_printgraph_mermaid.jac +16 -0
- jaclang/tests/fixtures/chandra_bugs2.jac +20 -13
- jaclang/tests/fixtures/concurrency.jac +1 -1
- jaclang/tests/fixtures/edge_ability.jac +49 -0
- jaclang/tests/fixtures/guess_game.jac +1 -1
- jaclang/tests/fixtures/here_usage_error.jac +21 -0
- jaclang/tests/fixtures/here_visitor_usage.jac +21 -0
- jaclang/tests/fixtures/node_del.jac +30 -36
- jaclang/tests/fixtures/visit_traversal.jac +47 -0
- jaclang/tests/test_cli.py +12 -7
- jaclang/tests/test_language.py +91 -16
- jaclang/utils/helpers.py +14 -6
- jaclang/utils/lang_tools.py +2 -3
- jaclang/utils/tests/test_lang_tools.py +2 -1
- jaclang/utils/treeprinter.py +3 -4
- {jaclang-0.8.0.dist-info → jaclang-0.8.1.dist-info}/METADATA +4 -3
- {jaclang-0.8.0.dist-info → jaclang-0.8.1.dist-info}/RECORD +71 -55
- {jaclang-0.8.0.dist-info → jaclang-0.8.1.dist-info}/WHEEL +1 -1
- jaclang/langserve/engine.py +0 -553
- jaclang/langserve/sem_manager.py +0 -383
- jaclang/langserve/server.py +0 -167
- jaclang/langserve/tests/session.py +0 -255
- jaclang/tests/fixtures/builtin_dotgen.jac +0 -42
- jaclang/tests/fixtures/builtin_dotgen_json.jac +0 -21
- /jaclang/langserve/{__init__.py → __init__.jac} +0 -0
- {jaclang-0.8.0.dist-info → jaclang-0.8.1.dist-info}/entry_points.txt +0 -0
jaclang/langserve/engine.py
DELETED
|
@@ -1,553 +0,0 @@
|
|
|
1
|
-
"""Living Workspace of Jac project."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
import asyncio
|
|
6
|
-
import logging
|
|
7
|
-
import time
|
|
8
|
-
from concurrent.futures import ThreadPoolExecutor
|
|
9
|
-
from typing import Callable, Optional
|
|
10
|
-
|
|
11
|
-
import jaclang.compiler.unitree as uni
|
|
12
|
-
from jaclang import JacMachineInterface as Jac
|
|
13
|
-
from jaclang.compiler.passes.main import CompilerMode as CMode
|
|
14
|
-
from jaclang.compiler.program import JacProgram
|
|
15
|
-
from jaclang.compiler.unitree import UniScopeNode
|
|
16
|
-
from jaclang.langserve.sem_manager import SemTokManager
|
|
17
|
-
from jaclang.vendor.pygls import uris
|
|
18
|
-
from jaclang.vendor.pygls.server import LanguageServer
|
|
19
|
-
|
|
20
|
-
import lsprotocol.types as lspt
|
|
21
|
-
|
|
22
|
-
(utils,) = Jac.py_jac_import(".utils", base_path=__file__)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
class ModuleManager:
|
|
26
|
-
"""Handles Jac module, semantic manager, and alert management."""
|
|
27
|
-
|
|
28
|
-
def __init__(self, program: JacProgram, sem_managers: dict) -> None:
|
|
29
|
-
"""Initialize ModuleManager."""
|
|
30
|
-
self.program = program
|
|
31
|
-
self.sem_managers = sem_managers
|
|
32
|
-
|
|
33
|
-
def update(
|
|
34
|
-
self, file_path: str, build: uni.Module, update_annexed: bool = True
|
|
35
|
-
) -> None:
|
|
36
|
-
"""Update modules in JacProgram's hub and semantic managers."""
|
|
37
|
-
file_path = file_path.removeprefix("file://")
|
|
38
|
-
self.program.mod.hub[file_path] = build
|
|
39
|
-
self.sem_managers[file_path] = SemTokManager(ir=build)
|
|
40
|
-
if update_annexed:
|
|
41
|
-
for p, mod in self.program.mod.hub.items():
|
|
42
|
-
if p != file_path:
|
|
43
|
-
self.sem_managers[p] = SemTokManager(ir=mod)
|
|
44
|
-
|
|
45
|
-
def clear_alerts_for_file(self, file_path_fs: str) -> None:
|
|
46
|
-
"""Remove errors and warnings for a specific file from the lists."""
|
|
47
|
-
self.program.errors_had[:] = [
|
|
48
|
-
e for e in self.program.errors_had if e.loc.mod_path != file_path_fs
|
|
49
|
-
]
|
|
50
|
-
self.program.warnings_had[:] = [
|
|
51
|
-
w for w in self.program.warnings_had if w.loc.mod_path != file_path_fs
|
|
52
|
-
]
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
class JacLangServer(JacProgram, LanguageServer):
|
|
56
|
-
"""Jac Language Server, manages JacProgram and LSP."""
|
|
57
|
-
|
|
58
|
-
def __init__(self) -> None:
|
|
59
|
-
"""Initialize JacLangServer."""
|
|
60
|
-
LanguageServer.__init__(self, "jac-lsp", "v0.1")
|
|
61
|
-
JacProgram.__init__(self)
|
|
62
|
-
self.executor = ThreadPoolExecutor()
|
|
63
|
-
self.tasks: dict[str, asyncio.Task] = {}
|
|
64
|
-
self.sem_managers: dict[str, SemTokManager] = {}
|
|
65
|
-
self.module_manager = ModuleManager(self, self.sem_managers)
|
|
66
|
-
|
|
67
|
-
@property
|
|
68
|
-
def diagnostics(self) -> dict[str, list]:
|
|
69
|
-
"""Return diagnostics for all files as a dict {uri: diagnostics}."""
|
|
70
|
-
result = {}
|
|
71
|
-
for file_path in self.mod.hub:
|
|
72
|
-
uri = uris.from_fs_path(file_path)
|
|
73
|
-
result[uri] = utils.gen_diagnostics(uri, self.errors_had, self.warnings_had)
|
|
74
|
-
return result
|
|
75
|
-
|
|
76
|
-
def _clear_alerts_for_file(self, file_path_fs: str) -> None:
|
|
77
|
-
"""Remove errors and warnings for a specific file from the lists."""
|
|
78
|
-
self.module_manager.clear_alerts_for_file(file_path_fs)
|
|
79
|
-
|
|
80
|
-
def get_ir(self, file_path: str) -> Optional[uni.Module]:
|
|
81
|
-
"""Get IR for a file path."""
|
|
82
|
-
file_path = file_path.removeprefix("file://")
|
|
83
|
-
return self.mod.hub.get(file_path)
|
|
84
|
-
|
|
85
|
-
def update_modules(
|
|
86
|
-
self, file_path: str, build: uni.Module, need: bool = True
|
|
87
|
-
) -> None:
|
|
88
|
-
"""Update modules in JacProgram's hub and semantic managers."""
|
|
89
|
-
self.log_py(f"Updating modules for {file_path}")
|
|
90
|
-
self.module_manager.update(file_path, build, update_annexed=need)
|
|
91
|
-
|
|
92
|
-
def quick_check(self, file_path: str) -> bool:
|
|
93
|
-
"""Rebuild a file (syntax only)."""
|
|
94
|
-
try:
|
|
95
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
96
|
-
document = self.workspace.get_text_document(file_path)
|
|
97
|
-
self._clear_alerts_for_file(file_path_fs)
|
|
98
|
-
build = self.compile_from_str(
|
|
99
|
-
source_str=document.source,
|
|
100
|
-
file_path=document.path,
|
|
101
|
-
mode=CMode.PARSE,
|
|
102
|
-
)
|
|
103
|
-
self.update_modules(file_path_fs, build, need=False)
|
|
104
|
-
self.publish_diagnostics(
|
|
105
|
-
file_path,
|
|
106
|
-
utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had),
|
|
107
|
-
)
|
|
108
|
-
return len(self.errors_had) == 0
|
|
109
|
-
except Exception as e:
|
|
110
|
-
self.log_error(f"Error during syntax check: {e}")
|
|
111
|
-
return False
|
|
112
|
-
|
|
113
|
-
def deep_check(self, file_path: str, annex_view: Optional[str] = None) -> bool:
|
|
114
|
-
"""Rebuild a file and its dependencies (typecheck)."""
|
|
115
|
-
try:
|
|
116
|
-
start_time = time.time()
|
|
117
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
118
|
-
document = self.workspace.get_text_document(file_path)
|
|
119
|
-
self._clear_alerts_for_file(file_path_fs)
|
|
120
|
-
build = self.compile_from_str(
|
|
121
|
-
source_str=document.source,
|
|
122
|
-
file_path=document.path,
|
|
123
|
-
mode=CMode.TYPECHECK,
|
|
124
|
-
)
|
|
125
|
-
self.update_modules(file_path_fs, build)
|
|
126
|
-
if build.annexable_by:
|
|
127
|
-
return self.deep_check(
|
|
128
|
-
uris.from_fs_path(build.annexable_by), annex_view=file_path
|
|
129
|
-
)
|
|
130
|
-
self.publish_diagnostics(
|
|
131
|
-
annex_view if annex_view else file_path,
|
|
132
|
-
utils.gen_diagnostics(
|
|
133
|
-
annex_view if annex_view else file_path,
|
|
134
|
-
self.errors_had,
|
|
135
|
-
self.warnings_had,
|
|
136
|
-
),
|
|
137
|
-
)
|
|
138
|
-
if annex_view:
|
|
139
|
-
self.publish_diagnostics(
|
|
140
|
-
file_path,
|
|
141
|
-
utils.gen_diagnostics(
|
|
142
|
-
file_path,
|
|
143
|
-
self.errors_had,
|
|
144
|
-
self.warnings_had,
|
|
145
|
-
),
|
|
146
|
-
)
|
|
147
|
-
self.log_py(f"PROFILE: Deep check took {time.time() - start_time} seconds.")
|
|
148
|
-
return len(self.errors_had) == 0
|
|
149
|
-
except Exception as e:
|
|
150
|
-
self.log_error(f"Error during deep check: {e}")
|
|
151
|
-
return False
|
|
152
|
-
|
|
153
|
-
async def launch_quick_check(self, uri: str) -> None:
|
|
154
|
-
"""Analyze and publish diagnostics."""
|
|
155
|
-
await asyncio.get_event_loop().run_in_executor(
|
|
156
|
-
self.executor, self.quick_check, uri
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
async def launch_deep_check(self, uri: str) -> None:
|
|
160
|
-
"""Analyze and publish diagnostics."""
|
|
161
|
-
|
|
162
|
-
async def run_in_executor(
|
|
163
|
-
func: Callable[[str, Optional[str]], bool],
|
|
164
|
-
file_path: str,
|
|
165
|
-
annex_view: Optional[str] = None,
|
|
166
|
-
) -> None:
|
|
167
|
-
loop = asyncio.get_event_loop()
|
|
168
|
-
await loop.run_in_executor(self.executor, func, file_path, annex_view)
|
|
169
|
-
|
|
170
|
-
if uri in self.tasks and not self.tasks[uri].done():
|
|
171
|
-
self.log_py(f"Canceling {uri} deep check...")
|
|
172
|
-
self.tasks[uri].cancel()
|
|
173
|
-
del self.tasks[uri]
|
|
174
|
-
self.log_py(f"Analyzing {uri}...")
|
|
175
|
-
task = asyncio.create_task(run_in_executor(self.deep_check, uri))
|
|
176
|
-
self.tasks[uri] = task
|
|
177
|
-
await task
|
|
178
|
-
|
|
179
|
-
def get_completion(
|
|
180
|
-
self, file_path: str, position: lspt.Position, completion_trigger: Optional[str]
|
|
181
|
-
) -> lspt.CompletionList:
|
|
182
|
-
"""Return completion for a file."""
|
|
183
|
-
document = self.workspace.get_text_document(file_path)
|
|
184
|
-
mod_ir = self.get_ir(file_path)
|
|
185
|
-
if not mod_ir:
|
|
186
|
-
return lspt.CompletionList(is_incomplete=False, items=[])
|
|
187
|
-
current_line = document.lines[position.line]
|
|
188
|
-
current_pos = position.character
|
|
189
|
-
current_symbol_path = utils.parse_symbol_path(current_line, current_pos)
|
|
190
|
-
builtin_mod = next(
|
|
191
|
-
mod for name, mod in self.mod.hub.items() if "builtins" in name
|
|
192
|
-
)
|
|
193
|
-
builtin_tab = builtin_mod.sym_tab
|
|
194
|
-
assert isinstance(builtin_tab, UniScopeNode)
|
|
195
|
-
completion_items = []
|
|
196
|
-
|
|
197
|
-
node_selected = utils.find_deepest_symbol_node_at_pos(
|
|
198
|
-
mod_ir,
|
|
199
|
-
position.line,
|
|
200
|
-
position.character - 2,
|
|
201
|
-
)
|
|
202
|
-
mod_tab = mod_ir.sym_tab if not node_selected else node_selected.sym_tab
|
|
203
|
-
current_symbol_table = mod_tab
|
|
204
|
-
|
|
205
|
-
if completion_trigger == ".":
|
|
206
|
-
if current_symbol_path:
|
|
207
|
-
temp_tab = mod_tab
|
|
208
|
-
for symbol in current_symbol_path:
|
|
209
|
-
if symbol == "self":
|
|
210
|
-
is_ability_def = (
|
|
211
|
-
temp_tab
|
|
212
|
-
if isinstance(temp_tab, uni.ImplDef)
|
|
213
|
-
else temp_tab.find_parent_of_type(uni.ImplDef)
|
|
214
|
-
)
|
|
215
|
-
if not is_ability_def:
|
|
216
|
-
archi_owner = mod_tab.find_parent_of_type(uni.Archetype)
|
|
217
|
-
temp_tab = (
|
|
218
|
-
archi_owner.sym_tab
|
|
219
|
-
if archi_owner and archi_owner.sym_tab
|
|
220
|
-
else mod_tab
|
|
221
|
-
)
|
|
222
|
-
continue
|
|
223
|
-
else:
|
|
224
|
-
archi_owner = (
|
|
225
|
-
(
|
|
226
|
-
is_ability_def.decl_link.find_parent_of_type(
|
|
227
|
-
uni.Archetype
|
|
228
|
-
)
|
|
229
|
-
)
|
|
230
|
-
if is_ability_def.decl_link
|
|
231
|
-
else None
|
|
232
|
-
)
|
|
233
|
-
temp_tab = (
|
|
234
|
-
archi_owner.sym_tab
|
|
235
|
-
if archi_owner and archi_owner.sym_tab
|
|
236
|
-
else temp_tab
|
|
237
|
-
)
|
|
238
|
-
continue
|
|
239
|
-
symb = temp_tab.lookup(symbol)
|
|
240
|
-
if symb:
|
|
241
|
-
fetc_tab = symb.fetch_sym_tab
|
|
242
|
-
if fetc_tab:
|
|
243
|
-
temp_tab = fetc_tab
|
|
244
|
-
else:
|
|
245
|
-
temp_tab = (
|
|
246
|
-
symb.defn[0].type_sym_tab
|
|
247
|
-
if symb.defn[0].type_sym_tab
|
|
248
|
-
else temp_tab
|
|
249
|
-
)
|
|
250
|
-
else:
|
|
251
|
-
break
|
|
252
|
-
completion_items += utils.collect_all_symbols_in_scope(
|
|
253
|
-
temp_tab, up_tree=False
|
|
254
|
-
)
|
|
255
|
-
if isinstance(temp_tab, uni.Archetype) and temp_tab.base_classes:
|
|
256
|
-
base = []
|
|
257
|
-
for base_name in temp_tab.base_classes.items:
|
|
258
|
-
if isinstance(base_name, uni.Name) and base_name.sym:
|
|
259
|
-
base.append(base_name.sym)
|
|
260
|
-
for base_class_symbol in base:
|
|
261
|
-
if base_class_symbol.fetch_sym_tab:
|
|
262
|
-
completion_items += utils.collect_all_symbols_in_scope(
|
|
263
|
-
base_class_symbol.fetch_sym_tab,
|
|
264
|
-
up_tree=False,
|
|
265
|
-
)
|
|
266
|
-
|
|
267
|
-
else:
|
|
268
|
-
if node_selected and (
|
|
269
|
-
node_selected.find_parent_of_type(uni.Archetype)
|
|
270
|
-
or node_selected.find_parent_of_type(uni.ImplDef)
|
|
271
|
-
):
|
|
272
|
-
self_symbol = [
|
|
273
|
-
lspt.CompletionItem(
|
|
274
|
-
label="self", kind=lspt.CompletionItemKind.Variable
|
|
275
|
-
)
|
|
276
|
-
]
|
|
277
|
-
else:
|
|
278
|
-
self_symbol = []
|
|
279
|
-
|
|
280
|
-
completion_items += (
|
|
281
|
-
utils.collect_all_symbols_in_scope(current_symbol_table)
|
|
282
|
-
+ self_symbol
|
|
283
|
-
+ utils.collect_child_tabs(builtin_tab)
|
|
284
|
-
)
|
|
285
|
-
return lspt.CompletionList(is_incomplete=False, items=completion_items)
|
|
286
|
-
|
|
287
|
-
def rename_module(self, old_path: str, new_path: str) -> None:
|
|
288
|
-
"""Rename module."""
|
|
289
|
-
if old_path in self.mod.hub and new_path != old_path:
|
|
290
|
-
self.mod.hub[new_path] = self.mod.hub[old_path]
|
|
291
|
-
self.sem_managers[new_path] = self.sem_managers[old_path]
|
|
292
|
-
del self.mod.hub[old_path]
|
|
293
|
-
del self.sem_managers[old_path]
|
|
294
|
-
|
|
295
|
-
def delete_module(self, uri: str) -> None:
|
|
296
|
-
"""Delete module."""
|
|
297
|
-
if uri in self.mod.hub:
|
|
298
|
-
del self.mod.hub[uri]
|
|
299
|
-
if uri in self.sem_managers:
|
|
300
|
-
del self.sem_managers[uri]
|
|
301
|
-
|
|
302
|
-
def formatted_jac(self, file_path: str) -> list[lspt.TextEdit]:
|
|
303
|
-
"""Return formatted jac."""
|
|
304
|
-
try:
|
|
305
|
-
document = self.workspace.get_text_document(file_path)
|
|
306
|
-
formatted_text = JacProgram.jac_str_formatter(
|
|
307
|
-
source_str=document.source, file_path=document.path
|
|
308
|
-
)
|
|
309
|
-
except Exception as e:
|
|
310
|
-
self.log_error(f"Error during formatting: {e}")
|
|
311
|
-
formatted_text = document.source
|
|
312
|
-
return [
|
|
313
|
-
lspt.TextEdit(
|
|
314
|
-
range=lspt.Range(
|
|
315
|
-
start=lspt.Position(line=0, character=0),
|
|
316
|
-
end=lspt.Position(
|
|
317
|
-
line=len(document.source.splitlines()) + 1, character=0
|
|
318
|
-
),
|
|
319
|
-
),
|
|
320
|
-
new_text=(formatted_text),
|
|
321
|
-
)
|
|
322
|
-
]
|
|
323
|
-
|
|
324
|
-
def get_hover_info(
|
|
325
|
-
self, file_path: str, position: lspt.Position
|
|
326
|
-
) -> Optional[lspt.Hover]:
|
|
327
|
-
"""Return hover information for a file."""
|
|
328
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
329
|
-
if file_path_fs not in self.mod.hub:
|
|
330
|
-
return None
|
|
331
|
-
sem_mgr = self.sem_managers.get(file_path_fs)
|
|
332
|
-
if not sem_mgr:
|
|
333
|
-
return None
|
|
334
|
-
token_index = utils.find_index(
|
|
335
|
-
sem_mgr.sem_tokens,
|
|
336
|
-
position.line,
|
|
337
|
-
position.character,
|
|
338
|
-
)
|
|
339
|
-
if token_index is None:
|
|
340
|
-
return None
|
|
341
|
-
node_selected = sem_mgr.static_sem_tokens[token_index][3]
|
|
342
|
-
value = self.get_node_info(node_selected) if node_selected else None
|
|
343
|
-
if value:
|
|
344
|
-
return lspt.Hover(
|
|
345
|
-
contents=lspt.MarkupContent(
|
|
346
|
-
kind=lspt.MarkupKind.PlainText, value=f"{value}"
|
|
347
|
-
),
|
|
348
|
-
)
|
|
349
|
-
return None
|
|
350
|
-
|
|
351
|
-
def get_node_info(self, node: uni.AstSymbolNode) -> Optional[str]:
|
|
352
|
-
"""Extract meaningful information from the AST node."""
|
|
353
|
-
try:
|
|
354
|
-
if isinstance(node, uni.NameAtom):
|
|
355
|
-
node = node.name_of
|
|
356
|
-
access = node.sym.access.value + " " if node.sym else None
|
|
357
|
-
node_info = (
|
|
358
|
-
f"({access if access else ''}{node.sym_category.value}) {node.sym_name}"
|
|
359
|
-
)
|
|
360
|
-
if node.name_spec.clean_type:
|
|
361
|
-
node_info += f": {node.name_spec.clean_type}"
|
|
362
|
-
if isinstance(node, uni.AstDocNode) and node.doc:
|
|
363
|
-
node_info += f"\n{node.doc.value}"
|
|
364
|
-
if isinstance(node, uni.Ability) and node.signature:
|
|
365
|
-
node_info += f"\n{node.signature.unparse()}"
|
|
366
|
-
except AttributeError as e:
|
|
367
|
-
self.log_warning(f"Attribute error when accessing node attributes: {e}")
|
|
368
|
-
return node_info.strip()
|
|
369
|
-
|
|
370
|
-
def get_outline(self, file_path: str) -> list[lspt.DocumentSymbol]:
|
|
371
|
-
"""Return document symbols for a file."""
|
|
372
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
373
|
-
if file_path_fs in self.mod.hub and (
|
|
374
|
-
root_node := self.mod.hub[file_path_fs].sym_tab
|
|
375
|
-
):
|
|
376
|
-
return utils.get_symbols_for_outline(root_node)
|
|
377
|
-
return []
|
|
378
|
-
|
|
379
|
-
def get_definition(
|
|
380
|
-
self, file_path: str, position: lspt.Position
|
|
381
|
-
) -> Optional[lspt.Location]:
|
|
382
|
-
"""Return definition location for a file."""
|
|
383
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
384
|
-
if file_path_fs not in self.mod.hub:
|
|
385
|
-
return None
|
|
386
|
-
sem_mgr = self.sem_managers.get(file_path_fs)
|
|
387
|
-
if not sem_mgr:
|
|
388
|
-
return None
|
|
389
|
-
token_index = utils.find_index(
|
|
390
|
-
sem_mgr.sem_tokens,
|
|
391
|
-
position.line,
|
|
392
|
-
position.character,
|
|
393
|
-
)
|
|
394
|
-
if token_index is None:
|
|
395
|
-
return None
|
|
396
|
-
node_selected = sem_mgr.static_sem_tokens[token_index][3]
|
|
397
|
-
if node_selected:
|
|
398
|
-
if (
|
|
399
|
-
isinstance(node_selected, uni.Name)
|
|
400
|
-
and node_selected.parent
|
|
401
|
-
and isinstance(node_selected.parent, uni.SubNodeList)
|
|
402
|
-
and node_selected.parent.parent
|
|
403
|
-
and isinstance(node_selected.parent.parent, uni.ModulePath)
|
|
404
|
-
):
|
|
405
|
-
spec = node_selected.parent.parent.abs_path
|
|
406
|
-
if spec:
|
|
407
|
-
spec = spec[5:] if spec.startswith("File:") else spec
|
|
408
|
-
return lspt.Location(
|
|
409
|
-
uri=uris.from_fs_path(spec),
|
|
410
|
-
range=lspt.Range(
|
|
411
|
-
start=lspt.Position(line=0, character=0),
|
|
412
|
-
end=lspt.Position(line=0, character=0),
|
|
413
|
-
),
|
|
414
|
-
)
|
|
415
|
-
else:
|
|
416
|
-
return None
|
|
417
|
-
elif node_selected.parent and isinstance(
|
|
418
|
-
node_selected.parent, uni.ModuleItem
|
|
419
|
-
):
|
|
420
|
-
path = (
|
|
421
|
-
node_selected.parent.abs_path
|
|
422
|
-
or node_selected.parent.from_mod_path.abs_path
|
|
423
|
-
)
|
|
424
|
-
loc_range = (0, 0, 0, 0)
|
|
425
|
-
|
|
426
|
-
if path and loc_range:
|
|
427
|
-
path = path[5:] if path.startswith("File:") else path
|
|
428
|
-
return lspt.Location(
|
|
429
|
-
uri=uris.from_fs_path(path),
|
|
430
|
-
range=lspt.Range(
|
|
431
|
-
start=lspt.Position(
|
|
432
|
-
line=loc_range[0], character=loc_range[1]
|
|
433
|
-
),
|
|
434
|
-
end=lspt.Position(
|
|
435
|
-
line=loc_range[2], character=loc_range[3]
|
|
436
|
-
),
|
|
437
|
-
),
|
|
438
|
-
)
|
|
439
|
-
elif isinstance(node_selected, uni.ElementStmt):
|
|
440
|
-
return None
|
|
441
|
-
decl_node = (
|
|
442
|
-
node_selected.parent.body.target
|
|
443
|
-
if node_selected.parent
|
|
444
|
-
and isinstance(node_selected.parent, uni.AstImplNeedingNode)
|
|
445
|
-
and isinstance(node_selected.parent.body, uni.ImplDef)
|
|
446
|
-
else (
|
|
447
|
-
node_selected.sym.decl
|
|
448
|
-
if (node_selected.sym and node_selected.sym.decl)
|
|
449
|
-
else node_selected
|
|
450
|
-
)
|
|
451
|
-
)
|
|
452
|
-
decl_uri = uris.from_fs_path(decl_node.loc.mod_path)
|
|
453
|
-
try:
|
|
454
|
-
decl_range = utils.create_range(decl_node.loc)
|
|
455
|
-
except ValueError:
|
|
456
|
-
return None
|
|
457
|
-
decl_location = lspt.Location(
|
|
458
|
-
uri=decl_uri,
|
|
459
|
-
range=decl_range,
|
|
460
|
-
)
|
|
461
|
-
|
|
462
|
-
return decl_location
|
|
463
|
-
else:
|
|
464
|
-
return None
|
|
465
|
-
|
|
466
|
-
def get_references(
|
|
467
|
-
self, file_path: str, position: lspt.Position
|
|
468
|
-
) -> list[lspt.Location]:
|
|
469
|
-
"""Return references for a file."""
|
|
470
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
471
|
-
if file_path_fs not in self.mod.hub:
|
|
472
|
-
return []
|
|
473
|
-
sem_mgr = self.sem_managers.get(file_path_fs)
|
|
474
|
-
if not sem_mgr:
|
|
475
|
-
return []
|
|
476
|
-
index1 = utils.find_index(
|
|
477
|
-
sem_mgr.sem_tokens,
|
|
478
|
-
position.line,
|
|
479
|
-
position.character,
|
|
480
|
-
)
|
|
481
|
-
if index1 is None:
|
|
482
|
-
return []
|
|
483
|
-
node_selected = sem_mgr.static_sem_tokens[index1][3]
|
|
484
|
-
if node_selected and node_selected.sym:
|
|
485
|
-
list_of_references: list[lspt.Location] = [
|
|
486
|
-
lspt.Location(
|
|
487
|
-
uri=uris.from_fs_path(node.loc.mod_path),
|
|
488
|
-
range=utils.create_range(node.loc),
|
|
489
|
-
)
|
|
490
|
-
for node in node_selected.sym.uses
|
|
491
|
-
]
|
|
492
|
-
return list_of_references
|
|
493
|
-
return []
|
|
494
|
-
|
|
495
|
-
def rename_symbol(
|
|
496
|
-
self, file_path: str, position: lspt.Position, new_name: str
|
|
497
|
-
) -> Optional[lspt.WorkspaceEdit]:
|
|
498
|
-
"""Rename a symbol in a file."""
|
|
499
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
500
|
-
if file_path_fs not in self.mod.hub:
|
|
501
|
-
return None
|
|
502
|
-
sem_mgr = self.sem_managers.get(file_path_fs)
|
|
503
|
-
if not sem_mgr:
|
|
504
|
-
return None
|
|
505
|
-
index1 = utils.find_index(
|
|
506
|
-
sem_mgr.sem_tokens,
|
|
507
|
-
position.line,
|
|
508
|
-
position.character,
|
|
509
|
-
)
|
|
510
|
-
if index1 is None:
|
|
511
|
-
return None
|
|
512
|
-
node_selected = sem_mgr.static_sem_tokens[index1][3]
|
|
513
|
-
if node_selected and node_selected.sym:
|
|
514
|
-
changes: dict[str, list[lspt.TextEdit]] = {}
|
|
515
|
-
for node in [
|
|
516
|
-
*node_selected.sym.uses,
|
|
517
|
-
node_selected.sym.defn[0],
|
|
518
|
-
]:
|
|
519
|
-
key = uris.from_fs_path(node.loc.mod_path)
|
|
520
|
-
new_edit = lspt.TextEdit(
|
|
521
|
-
range=utils.create_range(node.loc),
|
|
522
|
-
new_text=new_name,
|
|
523
|
-
)
|
|
524
|
-
utils.add_unique_text_edit(changes, key, new_edit)
|
|
525
|
-
return lspt.WorkspaceEdit(changes=changes)
|
|
526
|
-
return None
|
|
527
|
-
|
|
528
|
-
def get_semantic_tokens(self, file_path: str) -> lspt.SemanticTokens:
|
|
529
|
-
"""Return semantic tokens for a file."""
|
|
530
|
-
file_path_fs = file_path.removeprefix("file://")
|
|
531
|
-
sem_mgr = self.sem_managers.get(file_path_fs)
|
|
532
|
-
if not sem_mgr:
|
|
533
|
-
return lspt.SemanticTokens(data=[])
|
|
534
|
-
return lspt.SemanticTokens(data=sem_mgr.sem_tokens)
|
|
535
|
-
|
|
536
|
-
def log_error(self, message: str) -> None:
|
|
537
|
-
"""Log an error message."""
|
|
538
|
-
self.show_message_log(message, lspt.MessageType.Error)
|
|
539
|
-
self.show_message(message, lspt.MessageType.Error)
|
|
540
|
-
|
|
541
|
-
def log_warning(self, message: str) -> None:
|
|
542
|
-
"""Log a warning message."""
|
|
543
|
-
self.show_message_log(message, lspt.MessageType.Warning)
|
|
544
|
-
self.show_message(message, lspt.MessageType.Warning)
|
|
545
|
-
|
|
546
|
-
def log_info(self, message: str) -> None:
|
|
547
|
-
"""Log an info message."""
|
|
548
|
-
self.show_message_log(message, lspt.MessageType.Info)
|
|
549
|
-
self.show_message(message, lspt.MessageType.Info)
|
|
550
|
-
|
|
551
|
-
def log_py(self, message: str) -> None:
|
|
552
|
-
"""Log a message."""
|
|
553
|
-
logging.info(message)
|