jaclang 0.8.5__py3-none-any.whl → 0.8.7__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.md +4 -3
- jaclang/cli/cli.py +63 -29
- jaclang/cli/cmdreg.py +1 -140
- jaclang/compiler/passes/main/__init__.py +2 -0
- jaclang/compiler/passes/main/cfg_build_pass.py +21 -1
- jaclang/compiler/passes/main/inheritance_pass.py +8 -1
- jaclang/compiler/passes/main/pyast_gen_pass.py +70 -11
- jaclang/compiler/passes/main/pyast_load_pass.py +14 -20
- jaclang/compiler/passes/main/sym_tab_build_pass.py +4 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_has_var.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_if_no_else.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/cfg_return.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_imported.jac +2 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_importer.jac +6 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
- jaclang/compiler/passes/main/tests/fixtures/data_spatial_types.jac +1 -1
- jaclang/compiler/passes/main/tests/fixtures/import_symbol_type_infer.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/infer_type_assignment.jac +5 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_inferred.jac +13 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +11 -0
- jaclang/compiler/passes/main/tests/fixtures/type_annotation_assignment.jac +8 -0
- jaclang/compiler/passes/main/tests/test_cfg_build_pass.py +62 -24
- jaclang/compiler/passes/main/tests/test_checker_pass.py +161 -0
- jaclang/compiler/passes/main/type_checker_pass.py +147 -0
- jaclang/compiler/passes/tool/tests/fixtures/simple_walk_fmt.jac +1 -4
- jaclang/compiler/program.py +17 -3
- jaclang/compiler/type_system/__init__.py +1 -0
- jaclang/compiler/type_system/operations.py +104 -0
- jaclang/compiler/type_system/type_evaluator.py +560 -0
- jaclang/compiler/type_system/type_utils.py +41 -0
- jaclang/compiler/type_system/types.py +240 -0
- jaclang/compiler/unitree.py +15 -9
- jaclang/langserve/dev_engine.jac +645 -0
- jaclang/langserve/dev_server.jac +201 -0
- jaclang/langserve/engine.jac +135 -91
- jaclang/langserve/server.jac +21 -14
- jaclang/langserve/tests/server_test/test_lang_serve.py +2 -5
- jaclang/langserve/tests/test_dev_server.py +80 -0
- jaclang/langserve/tests/test_server.py +9 -2
- jaclang/langserve/utils.jac +44 -48
- jaclang/runtimelib/builtin.py +28 -39
- jaclang/runtimelib/importer.py +1 -1
- jaclang/runtimelib/machine.py +48 -64
- jaclang/runtimelib/memory.py +23 -5
- jaclang/runtimelib/tests/fixtures/savable_object.jac +10 -2
- jaclang/runtimelib/utils.py +13 -6
- jaclang/tests/fixtures/edge_node_walk.jac +1 -1
- jaclang/tests/fixtures/edges_walk.jac +1 -1
- jaclang/tests/fixtures/gendot_bubble_sort.jac +1 -1
- jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
- jaclang/tests/fixtures/jac_run_py_import.py +13 -0
- jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
- jaclang/tests/fixtures/lambda_self.jac +18 -0
- jaclang/tests/fixtures/py_run.jac +8 -0
- jaclang/tests/fixtures/py_run.py +23 -0
- jaclang/tests/fixtures/pyfunc.py +2 -0
- jaclang/tests/test_cli.py +103 -14
- jaclang/tests/test_language.py +10 -4
- jaclang/utils/lang_tools.py +3 -0
- jaclang/utils/module_resolver.py +1 -1
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/METADATA +4 -2
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/RECORD +70 -37
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/WHEEL +1 -1
- {jaclang-0.8.5.dist-info → jaclang-0.8.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"""Jaclang Language Server."""
|
|
2
|
+
|
|
3
|
+
import from typing { Optional }
|
|
4
|
+
|
|
5
|
+
import from jaclang.compiler.constant {
|
|
6
|
+
JacSemTokenModifier as SemTokMod,
|
|
7
|
+
JacSemTokenType as SemTokType
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
import from dev_engine { JacLangServer }
|
|
11
|
+
import from jaclang.settings { settings }
|
|
12
|
+
|
|
13
|
+
import lsprotocol.types as lspt;
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
with entry {
|
|
17
|
+
server = JacLangServer();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
"""Check syntax on change."""
|
|
22
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_OPEN)
|
|
23
|
+
async def did_open(ls: JacLangServerV2, params: lspt.DidOpenTextDocumentParams) -> None {
|
|
24
|
+
await ls.launch_deep_check(params.text_document.uri);
|
|
25
|
+
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
"""Check syntax on change."""
|
|
30
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
|
|
31
|
+
async def did_save(ls: JacLangServerV2, params: lspt.DidOpenTextDocumentParams) -> None {
|
|
32
|
+
file_path = params.text_document.uri;
|
|
33
|
+
quick_check_passed = await ls.launch_quick_check(file_path);
|
|
34
|
+
if not quick_check_passed {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
await ls.launch_deep_check(file_path);
|
|
38
|
+
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
"""Check syntax on change."""
|
|
43
|
+
@server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
|
|
44
|
+
async def did_change(
|
|
45
|
+
ls: JacLangServerV2,
|
|
46
|
+
params: lspt.DidChangeTextDocumentParams
|
|
47
|
+
) -> None {
|
|
48
|
+
file_path = params.text_document.uri;
|
|
49
|
+
quick_check_passed = await ls.launch_quick_check(file_path);
|
|
50
|
+
|
|
51
|
+
if quick_check_passed {
|
|
52
|
+
document = ls.workspace.get_text_document(file_path);
|
|
53
|
+
lines = document.source.splitlines();
|
|
54
|
+
sem_manager = ls.sem_managers[file_path.removeprefix('file://')];
|
|
55
|
+
sem_manager.update_sem_tokens(
|
|
56
|
+
params,sem_manager.sem_tokens,lines
|
|
57
|
+
);
|
|
58
|
+
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
59
|
+
await ls.launch_deep_check(file_path);
|
|
60
|
+
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
"""Format the given document."""
|
|
66
|
+
@server.feature(lspt.TEXT_DOCUMENT_FORMATTING)
|
|
67
|
+
def formatting(
|
|
68
|
+
ls: JacLangServerV2,
|
|
69
|
+
params: lspt.DocumentFormattingParams
|
|
70
|
+
) -> list[lspt.TextEdit] {
|
|
71
|
+
return ls.formatted_jac(params.text_document.uri);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
"""Check syntax on file creation."""
|
|
76
|
+
@server.feature(
|
|
77
|
+
lspt.WORKSPACE_DID_CREATE_FILES,
|
|
78
|
+
lspt.FileOperationRegistrationOptions(
|
|
79
|
+
filters=[lspt.FileOperationFilter(pattern=lspt.FileOperationPattern('**/*.jac'))]
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
def did_create_files(ls: JacLangServerV2, params: lspt.CreateFilesParams) -> None {}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
"""Check syntax on file rename."""
|
|
86
|
+
@server.feature(
|
|
87
|
+
lspt.WORKSPACE_DID_RENAME_FILES,
|
|
88
|
+
lspt.FileOperationRegistrationOptions(
|
|
89
|
+
filters=[lspt.FileOperationFilter(pattern=lspt.FileOperationPattern('**/*.jac'))]
|
|
90
|
+
)
|
|
91
|
+
)
|
|
92
|
+
def did_rename_files(ls: JacLangServerV2, params: lspt.RenameFilesParams) -> None {
|
|
93
|
+
new_uris = [ file.new_uri for file in params.files ];
|
|
94
|
+
old_uris = [ file.old_uri for file in params.files ];
|
|
95
|
+
for i in range(len(new_uris)) {
|
|
96
|
+
ls.rename_module(old_uris[i], new_uris[i]);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
"""Check syntax on file delete."""
|
|
102
|
+
@server.feature(
|
|
103
|
+
lspt.WORKSPACE_DID_DELETE_FILES,
|
|
104
|
+
lspt.FileOperationRegistrationOptions(
|
|
105
|
+
filters=[lspt.FileOperationFilter(pattern=lspt.FileOperationPattern('**/*.jac'))]
|
|
106
|
+
)
|
|
107
|
+
)
|
|
108
|
+
def did_delete_files(ls: JacLangServerV2, params: lspt.DeleteFilesParams) -> None {
|
|
109
|
+
for file in params.files {
|
|
110
|
+
ls.delete_module(file.uri);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
"""Provide completion."""
|
|
116
|
+
@server.feature(
|
|
117
|
+
lspt.TEXT_DOCUMENT_COMPLETION,
|
|
118
|
+
lspt.CompletionOptions(trigger_characters=['.', ':', 'a-zA-Z0-9'])
|
|
119
|
+
)
|
|
120
|
+
def completion(ls: JacLangServerV2, params: lspt.CompletionParams) -> lspt.CompletionList {
|
|
121
|
+
return ls.get_completion(
|
|
122
|
+
params.text_document.uri,
|
|
123
|
+
params.position,
|
|
124
|
+
params.context.trigger_character if params.context else None
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
"""Provide hover information for the given hover request."""
|
|
130
|
+
@server.feature(lspt.TEXT_DOCUMENT_HOVER, lspt.HoverOptions(work_done_progress=True))
|
|
131
|
+
def hover(
|
|
132
|
+
ls: JacLangServerV2,
|
|
133
|
+
params: lspt.TextDocumentPositionParams
|
|
134
|
+
) -> Optional[lspt.Hover] {
|
|
135
|
+
return ls.get_hover_info(params.text_document.uri, params.position);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
"""Provide document symbols."""
|
|
140
|
+
@server.feature(lspt.TEXT_DOCUMENT_DOCUMENT_SYMBOL)
|
|
141
|
+
def document_symbol(
|
|
142
|
+
ls: JacLangServerV2,
|
|
143
|
+
params: lspt.DocumentSymbolParams
|
|
144
|
+
) -> list[lspt.DocumentSymbol] {
|
|
145
|
+
return ls.get_outline(params.text_document.uri);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
"""Provide definition."""
|
|
150
|
+
@server.feature(lspt.TEXT_DOCUMENT_DEFINITION)
|
|
151
|
+
def definition(
|
|
152
|
+
ls: JacLangServerV2,
|
|
153
|
+
params: lspt.TextDocumentPositionParams
|
|
154
|
+
) -> Optional[lspt.Location] {
|
|
155
|
+
return ls.get_definition(params.text_document.uri, params.position);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
"""Provide references."""
|
|
160
|
+
@server.feature(lspt.TEXT_DOCUMENT_REFERENCES)
|
|
161
|
+
def references(ls: JacLangServerV2, params: lspt.ReferenceParams) -> list[lspt.Location] {
|
|
162
|
+
return ls.get_references(params.text_document.uri, params.position);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
"""Rename symbol."""
|
|
167
|
+
@server.feature(lspt.TEXT_DOCUMENT_RENAME)
|
|
168
|
+
def rename(ls: JacLangServerV2, params: lspt.RenameParams) -> Optional[lspt.WorkspaceEdit] {
|
|
169
|
+
ls.log_warning('Auto Rename is Experimental, Please use with caution.');
|
|
170
|
+
return ls.rename_symbol(params.text_document.uri, params.position, params.new_name);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
"""Provide semantic tokens."""
|
|
175
|
+
@server.feature(
|
|
176
|
+
lspt.TEXT_DOCUMENT_SEMANTIC_TOKENS_FULL,
|
|
177
|
+
lspt.SemanticTokensLegend(
|
|
178
|
+
token_types=SemTokType.as_str_list(),
|
|
179
|
+
token_modifiers=SemTokMod.as_str_list()
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
def semantic_tokens_full(
|
|
183
|
+
ls: JacLangServerV2,
|
|
184
|
+
params: lspt.SemanticTokensParams
|
|
185
|
+
) -> lspt.SemanticTokens {
|
|
186
|
+
return ls.get_semantic_tokens(params.text_document.uri);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
"""Run the language server."""
|
|
191
|
+
def run_lang_server() -> None {
|
|
192
|
+
settings.pass_timer = True;
|
|
193
|
+
server.start_io();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
with entry {
|
|
198
|
+
if __name__ == '__main__' {
|
|
199
|
+
run_lang_server();
|
|
200
|
+
}
|
|
201
|
+
}
|
jaclang/langserve/engine.jac
CHANGED
|
@@ -10,6 +10,7 @@ import jaclang.compiler.unitree as uni;
|
|
|
10
10
|
import from jaclang { JacMachineInterface as Jac }
|
|
11
11
|
import from jaclang.compiler.constant {SymbolType}
|
|
12
12
|
import from jaclang.compiler.program { JacProgram }
|
|
13
|
+
import from jaclang.compiler.type_system.types{ ClassType}
|
|
13
14
|
import from jaclang.compiler.unitree { UniScopeNode }
|
|
14
15
|
import from sem_manager { SemTokManager }
|
|
15
16
|
import from jaclang.vendor.pygls { uris }
|
|
@@ -22,7 +23,7 @@ import utils;
|
|
|
22
23
|
"""Handles Jac module, semantic manager, and alert management."""
|
|
23
24
|
class ModuleManager {
|
|
24
25
|
"""Initialize ModuleManager."""
|
|
25
|
-
def init(self: ModuleManager, program: JacProgram, sem_managers:
|
|
26
|
+
def init(self: ModuleManager, program: JacProgram, sem_managers: dict) -> None {
|
|
26
27
|
self.program = program;
|
|
27
28
|
self.sem_managers = sem_managers;
|
|
28
29
|
}
|
|
@@ -34,7 +35,6 @@ class ModuleManager {
|
|
|
34
35
|
build: uni.Module,
|
|
35
36
|
update_annexed: bool = True
|
|
36
37
|
) -> None {
|
|
37
|
-
file_path = file_path.removeprefix('file://');
|
|
38
38
|
self.program.mod.hub[file_path] = build;
|
|
39
39
|
if update_annexed {
|
|
40
40
|
self.sem_managers[file_path] = SemTokManager(ir=build);
|
|
@@ -63,31 +63,32 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
63
63
|
LanguageServer.init(self, 'jac-lsp', 'v0.1');
|
|
64
64
|
JacProgram.init(self);
|
|
65
65
|
self.executor = ThreadPoolExecutor();
|
|
66
|
-
self.tasks:
|
|
67
|
-
self.sem_managers:
|
|
66
|
+
self.tasks: dict[(str, asyncio.Task)] = {};
|
|
67
|
+
self.sem_managers: dict[(str, SemTokManager)] = {};
|
|
68
68
|
self.module_manager = ModuleManager(self, self.sem_managers);
|
|
69
|
+
self._quick_tasks: dict[str, asyncio.Task] = {};
|
|
70
|
+
self._deep_tasks: dict[str, asyncio.Task] = {};
|
|
69
71
|
}
|
|
70
72
|
|
|
71
73
|
"""Return diagnostics for all files as a dict {uri: diagnostics}."""
|
|
72
74
|
@ property
|
|
73
|
-
def diagnostics(self: JacLangServer, ) ->
|
|
75
|
+
def diagnostics(self: JacLangServer, ) -> dict[str, list] {
|
|
74
76
|
result = {};
|
|
75
77
|
for file_path in self.mod.hub {
|
|
76
78
|
uri = uris.from_fs_path(file_path);
|
|
77
79
|
result[uri] =
|
|
78
|
-
utils.gen_diagnostics(
|
|
80
|
+
utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had);
|
|
79
81
|
}
|
|
80
82
|
return result;
|
|
81
83
|
}
|
|
82
84
|
|
|
83
85
|
"""Remove errors and warnings for a specific file from the lists."""
|
|
84
|
-
def _clear_alerts_for_file(self: JacLangServer,
|
|
85
|
-
self.module_manager.clear_alerts_for_file(
|
|
86
|
+
def _clear_alerts_for_file(self: JacLangServer, file_path: str) -> None {
|
|
87
|
+
self.module_manager.clear_alerts_for_file(file_path);
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
"""Get IR for a file path."""
|
|
89
91
|
def get_ir(self: JacLangServer, file_path: str) -> Optional[uni.Module] {
|
|
90
|
-
file_path = file_path.removeprefix('file://');
|
|
91
92
|
return self.mod.hub.get(file_path);
|
|
92
93
|
}
|
|
93
94
|
|
|
@@ -105,17 +106,19 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
105
106
|
"""Rebuild a file (syntax only)."""
|
|
106
107
|
def quick_check(self: JacLangServer, file_path: str) -> bool {
|
|
107
108
|
try {
|
|
108
|
-
file_path_fs = file_path.removeprefix('file://');
|
|
109
109
|
document = self.workspace.get_text_document(file_path);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
self.
|
|
110
|
+
fs_path = document.path;
|
|
111
|
+
|
|
112
|
+
self._clear_alerts_for_file(fs_path);
|
|
113
|
+
build = self.compile(use_str=document.source, file_path=fs_path);
|
|
114
|
+
self.update_modules(fs_path, build, need=False);
|
|
115
|
+
# to display diagnostics, it needs URI starts with "file://"
|
|
113
116
|
self.publish_diagnostics(
|
|
114
117
|
file_path,
|
|
115
|
-
utils.gen_diagnostics(
|
|
118
|
+
utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
|
|
116
119
|
);
|
|
117
120
|
build_errors =
|
|
118
|
-
[ e for e in self.errors_had if e.loc.mod_path ==
|
|
121
|
+
[ e for e in self.errors_had if e.loc.mod_path == fs_path];
|
|
119
122
|
return len(build_errors) == 0;
|
|
120
123
|
} except Exception as e {
|
|
121
124
|
self.log_error(f"'Error during syntax check: '{e}");
|
|
@@ -131,29 +134,31 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
131
134
|
) -> bool {
|
|
132
135
|
try {
|
|
133
136
|
start_time = time.time();
|
|
134
|
-
file_path_fs = file_path.removeprefix('file://');
|
|
135
137
|
document = self.workspace.get_text_document(file_path);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
self.
|
|
138
|
+
fs_path = document.path;
|
|
139
|
+
|
|
140
|
+
self._clear_alerts_for_file(fs_path);
|
|
141
|
+
build = self.compile(use_str=document.source, file_path=document.path,type_check=True);
|
|
142
|
+
self.update_modules(fs_path, build);
|
|
139
143
|
if build.annexable_by {
|
|
140
144
|
return self.deep_check(
|
|
141
145
|
uris.from_fs_path(build.annexable_by),
|
|
142
|
-
annex_view=
|
|
146
|
+
annex_view=fs_path
|
|
143
147
|
);
|
|
144
148
|
}
|
|
145
149
|
self.publish_diagnostics(
|
|
146
|
-
|
|
150
|
+
# to display diagnostic , it need URI starts with "file://"
|
|
151
|
+
uris.from_fs_path(annex_view) if annex_view else uris.from_fs_path(fs_path) ,
|
|
147
152
|
utils.gen_diagnostics(
|
|
148
|
-
annex_view if annex_view else
|
|
153
|
+
annex_view if annex_view else fs_path,
|
|
149
154
|
self.errors_had,
|
|
150
155
|
self.warnings_had
|
|
151
156
|
)
|
|
152
157
|
);
|
|
153
158
|
if annex_view {
|
|
154
159
|
self.publish_diagnostics(
|
|
155
|
-
|
|
156
|
-
utils.gen_diagnostics(
|
|
160
|
+
uris.from_fs_path(fs_path) ,
|
|
161
|
+
utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
|
|
157
162
|
);
|
|
158
163
|
}
|
|
159
164
|
self.log_py(
|
|
@@ -166,34 +171,34 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
166
171
|
}
|
|
167
172
|
}
|
|
168
173
|
|
|
169
|
-
"""Analyze and publish diagnostics."""
|
|
170
|
-
async def launch_quick_check(self: JacLangServer, uri: str) -> bool {
|
|
171
|
-
|
|
172
|
-
self.
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
174
|
+
"""Analyze and publish diagnostics with debouncing."""
|
|
175
|
+
async def launch_quick_check(self: JacLangServer, uri: str, delay: float = 0.5) -> bool {
|
|
176
|
+
try {
|
|
177
|
+
return await self._debounced_run(self.quick_check, uri, delay, self._quick_tasks);
|
|
178
|
+
} except asyncio.CancelledError {
|
|
179
|
+
return False;
|
|
180
|
+
} except Exception as e {
|
|
181
|
+
self.log_error(f" 'Error during quick check launch: ' {e} ");
|
|
182
|
+
return False;
|
|
183
|
+
}
|
|
176
184
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
) ->
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
task = asyncio.create_task(run_in_executor(self.deep_check, uri));
|
|
195
|
-
self.tasks[uri] = task;
|
|
196
|
-
await task;
|
|
185
|
+
"""Analyze and publish diagnostics with debouncing."""
|
|
186
|
+
async def launch_deep_check(
|
|
187
|
+
self: JacLangServer,
|
|
188
|
+
uri: str,
|
|
189
|
+
delay: float = 1.0,
|
|
190
|
+
annex_view: Optional[str] = None
|
|
191
|
+
) -> bool {
|
|
192
|
+
def deep_check_wrapper(file_uri: str) -> bool {
|
|
193
|
+
return self.deep_check(file_uri, annex_view);
|
|
194
|
+
}
|
|
195
|
+
try {
|
|
196
|
+
return await self._debounced_run(deep_check_wrapper, uri, delay, self._deep_tasks);
|
|
197
|
+
} except asyncio.CancelledError {
|
|
198
|
+
return False;
|
|
199
|
+
} except Exception as e {
|
|
200
|
+
self.log_error(f" 'Error during deep check launch: ' {e} ");
|
|
201
|
+
}
|
|
197
202
|
}
|
|
198
203
|
|
|
199
204
|
"""Return completion for a file."""
|
|
@@ -205,7 +210,7 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
205
210
|
) -> lspt.CompletionList {
|
|
206
211
|
try {
|
|
207
212
|
document = self.workspace.get_text_document(file_path);
|
|
208
|
-
mod_ir = self.get_ir(
|
|
213
|
+
mod_ir = self.get_ir(document.path);
|
|
209
214
|
if not mod_ir {
|
|
210
215
|
return lspt.CompletionList(is_incomplete=False, items=[]);
|
|
211
216
|
}
|
|
@@ -334,7 +339,7 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
334
339
|
}
|
|
335
340
|
|
|
336
341
|
"""Return formatted jac."""
|
|
337
|
-
def formatted_jac(self: JacLangServer, file_path: str) ->
|
|
342
|
+
def formatted_jac(self: JacLangServer, file_path: str) -> list[lspt.TextEdit] {
|
|
338
343
|
try {
|
|
339
344
|
document = self.workspace.get_text_document(file_path);
|
|
340
345
|
formatted_text =
|
|
@@ -365,11 +370,12 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
365
370
|
file_path: str,
|
|
366
371
|
position: lspt.Position
|
|
367
372
|
) -> Optional[lspt.Hover] {
|
|
368
|
-
|
|
369
|
-
|
|
373
|
+
fs_path = uris.to_fs_path(file_path);
|
|
374
|
+
|
|
375
|
+
if fs_path not in self.mod.hub {
|
|
370
376
|
return None;
|
|
371
377
|
}
|
|
372
|
-
sem_mgr = self.sem_managers.get(
|
|
378
|
+
sem_mgr = self.sem_managers.get(fs_path);
|
|
373
379
|
if not sem_mgr {
|
|
374
380
|
return None;
|
|
375
381
|
}
|
|
@@ -392,22 +398,25 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
392
398
|
}
|
|
393
399
|
|
|
394
400
|
"""Extract meaningful information from the AST node."""
|
|
395
|
-
def get_node_info(self: JacLangServer,
|
|
401
|
+
def get_node_info(self: JacLangServer, sym_node: uni.AstSymbolNode) -> Optional[str] {
|
|
396
402
|
try {
|
|
397
|
-
if isinstance(
|
|
398
|
-
|
|
403
|
+
if isinstance(sym_node, uni.NameAtom) {
|
|
404
|
+
sym_node = sym_node.name_of;
|
|
399
405
|
}
|
|
400
|
-
access = (
|
|
406
|
+
access = (sym_node.sym.access.value + ' ') if sym_node.sym else None;
|
|
401
407
|
node_info =
|
|
402
|
-
f"'('{access if access else ''}{
|
|
403
|
-
if
|
|
404
|
-
node_info += f"': '{
|
|
408
|
+
f"'('{access if access else ''}{sym_node.sym_category.value}') '{sym_node.sym_name}";
|
|
409
|
+
if sym_node.name_spec.clean_type {
|
|
410
|
+
node_info += f"': '{sym_node.name_spec.clean_type}";
|
|
411
|
+
}
|
|
412
|
+
if isinstance(sym_node,uni.AstSymbolNode) and isinstance(sym_node.name_spec.type,ClassType) {
|
|
413
|
+
node_info += f"': '{sym_node.name_spec.type.shared.class_name}";
|
|
405
414
|
}
|
|
406
|
-
if isinstance(
|
|
407
|
-
node_info += f"'\n'{
|
|
415
|
+
if isinstance(sym_node, uni.AstDocNode) and sym_node.doc {
|
|
416
|
+
node_info += f"'\n'{sym_node.doc.value}";
|
|
408
417
|
}
|
|
409
|
-
if isinstance(
|
|
410
|
-
node_info += f"'\n'{
|
|
418
|
+
if isinstance(sym_node, uni.Ability) and sym_node.signature {
|
|
419
|
+
node_info += f"'\n'{sym_node.signature.unparse()}";
|
|
411
420
|
}
|
|
412
421
|
} except AttributeError as e {
|
|
413
422
|
self.log_warning(f"'Attribute error when accessing node attributes: '{e}");
|
|
@@ -416,10 +425,10 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
416
425
|
}
|
|
417
426
|
|
|
418
427
|
"""Return document symbols for a file."""
|
|
419
|
-
def get_outline(self: JacLangServer, file_path: str) ->
|
|
420
|
-
|
|
421
|
-
if
|
|
422
|
-
and (root_node := self.mod.hub[
|
|
428
|
+
def get_outline(self: JacLangServer, file_path: str) -> list[lspt.DocumentSymbol] {
|
|
429
|
+
fs_path = uris.to_fs_path(file_path);
|
|
430
|
+
if fs_path in self.mod.hub
|
|
431
|
+
and (root_node := self.mod.hub[fs_path].sym_tab)
|
|
423
432
|
{
|
|
424
433
|
return utils.get_symbols_for_outline(root_node);
|
|
425
434
|
}
|
|
@@ -432,11 +441,12 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
432
441
|
file_path: str,
|
|
433
442
|
position: lspt.Position
|
|
434
443
|
) -> Optional[lspt.Location] {
|
|
435
|
-
|
|
436
|
-
|
|
444
|
+
fs_path = uris.to_fs_path(file_path);
|
|
445
|
+
|
|
446
|
+
if fs_path not in self.mod.hub {
|
|
437
447
|
return None;
|
|
438
448
|
}
|
|
439
|
-
sem_mgr = self.sem_managers.get(
|
|
449
|
+
sem_mgr = self.sem_managers.get(fs_path);
|
|
440
450
|
if not sem_mgr {
|
|
441
451
|
return None;
|
|
442
452
|
}
|
|
@@ -543,12 +553,13 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
543
553
|
self: JacLangServer,
|
|
544
554
|
file_path: str,
|
|
545
555
|
position: lspt.Position
|
|
546
|
-
) ->
|
|
547
|
-
|
|
548
|
-
|
|
556
|
+
) -> list[lspt.Location] {
|
|
557
|
+
fs_path = uris.to_fs_path(file_path);
|
|
558
|
+
|
|
559
|
+
if fs_path not in self.mod.hub {
|
|
549
560
|
return [];
|
|
550
561
|
}
|
|
551
|
-
sem_mgr = self.sem_managers.get(
|
|
562
|
+
sem_mgr = self.sem_managers.get(fs_path);
|
|
552
563
|
if not sem_mgr {
|
|
553
564
|
return [];
|
|
554
565
|
}
|
|
@@ -559,11 +570,11 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
559
570
|
}
|
|
560
571
|
node_selected = sem_mgr.static_sem_tokens[index1][3];
|
|
561
572
|
if node_selected and node_selected.sym {
|
|
562
|
-
list_of_references:
|
|
573
|
+
list_of_references: list[lspt.Location] =
|
|
563
574
|
[ lspt.Location(
|
|
564
|
-
uri=uris.from_fs_path(
|
|
565
|
-
range=utils.create_range(
|
|
566
|
-
) for
|
|
575
|
+
uri=uris.from_fs_path(cur_node.loc.mod_path),
|
|
576
|
+
range=utils.create_range(cur_node.loc)
|
|
577
|
+
) for cur_node in node_selected.sym.uses ];
|
|
567
578
|
return list_of_references;
|
|
568
579
|
}
|
|
569
580
|
return [];
|
|
@@ -576,11 +587,11 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
576
587
|
position: lspt.Position,
|
|
577
588
|
new_name: str
|
|
578
589
|
) -> Optional[lspt.WorkspaceEdit] {
|
|
579
|
-
|
|
580
|
-
if
|
|
590
|
+
fs_path = uris.to_fs_path(file_path);
|
|
591
|
+
if fs_path not in self.mod.hub {
|
|
581
592
|
return None;
|
|
582
593
|
}
|
|
583
|
-
sem_mgr = self.sem_managers.get(
|
|
594
|
+
sem_mgr = self.sem_managers.get(fs_path);
|
|
584
595
|
if not sem_mgr {
|
|
585
596
|
return None;
|
|
586
597
|
}
|
|
@@ -591,12 +602,12 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
591
602
|
}
|
|
592
603
|
node_selected = sem_mgr.static_sem_tokens[index1][3];
|
|
593
604
|
if node_selected and node_selected.sym {
|
|
594
|
-
changes:
|
|
595
|
-
for
|
|
596
|
-
key = uris.from_fs_path(
|
|
605
|
+
changes: dict[(str, list[lspt.TextEdit])] = {};
|
|
606
|
+
for node in [*node_selected.sym.uses, node_selected.sym.defn[0]] {
|
|
607
|
+
key = uris.from_fs_path(node.loc.mod_path);
|
|
597
608
|
new_edit =
|
|
598
609
|
lspt.TextEdit(
|
|
599
|
-
range=utils.create_range(
|
|
610
|
+
range=utils.create_range(node.loc),
|
|
600
611
|
new_text=new_name
|
|
601
612
|
);
|
|
602
613
|
utils.add_unique_text_edit(changes, key, new_edit);
|
|
@@ -608,8 +619,8 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
608
619
|
|
|
609
620
|
"""Return semantic tokens for a file."""
|
|
610
621
|
def get_semantic_tokens(self: JacLangServer, file_path: str) -> lspt.SemanticTokens {
|
|
611
|
-
|
|
612
|
-
sem_mgr = self.sem_managers.get(
|
|
622
|
+
fs_path = uris.to_fs_path(file_path);
|
|
623
|
+
sem_mgr = self.sem_managers.get(fs_path);
|
|
613
624
|
if not sem_mgr {
|
|
614
625
|
return lspt.SemanticTokens(data=[]);
|
|
615
626
|
}
|
|
@@ -638,4 +649,37 @@ class JacLangServer(JacProgram , LanguageServer) {
|
|
|
638
649
|
def log_py(self: JacLangServer, message: str) -> None {
|
|
639
650
|
logging.info(message);
|
|
640
651
|
}
|
|
652
|
+
|
|
653
|
+
"""Run a function with debouncing per file."""
|
|
654
|
+
async def _debounced_run(
|
|
655
|
+
self: JacLangServer,
|
|
656
|
+
func: Callable,
|
|
657
|
+
file_uri: str,
|
|
658
|
+
delay: float,
|
|
659
|
+
task_dict: dict[(str, asyncio.Task)]
|
|
660
|
+
) -> None {
|
|
661
|
+
if ((file_uri in task_dict) and not task_dict[file_uri].done() ) {
|
|
662
|
+
task_dict[file_uri].cancel();
|
|
663
|
+
self.log_py(
|
|
664
|
+
f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
async def wrapper() {
|
|
668
|
+
try {
|
|
669
|
+
await asyncio.sleep(delay);
|
|
670
|
+
loop = asyncio.get_event_loop();
|
|
671
|
+
result = await loop.run_in_executor(None, func, file_uri);
|
|
672
|
+
return result;
|
|
673
|
+
} except asyncio.CancelledError {
|
|
674
|
+
self.log_py(
|
|
675
|
+
f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
|
|
676
|
+
);
|
|
677
|
+
raise ;
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
new_task = asyncio.create_task(wrapper());
|
|
681
|
+
task_dict[file_uri] = new_task;
|
|
682
|
+
return await new_task;
|
|
683
|
+
}
|
|
684
|
+
|
|
641
685
|
}
|
jaclang/langserve/server.jac
CHANGED
|
@@ -21,7 +21,7 @@ with entry {
|
|
|
21
21
|
"""Check syntax on change."""
|
|
22
22
|
@server.feature(lspt.TEXT_DOCUMENT_DID_OPEN)
|
|
23
23
|
async def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None {
|
|
24
|
-
await ls.launch_deep_check(params.text_document.uri);
|
|
24
|
+
await ls.launch_deep_check(params.text_document.uri, delay=0.001);
|
|
25
25
|
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -30,38 +30,45 @@ async def did_open(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) ->
|
|
|
30
30
|
@server.feature(lspt.TEXT_DOCUMENT_DID_SAVE)
|
|
31
31
|
async def did_save(ls: JacLangServer, params: lspt.DidOpenTextDocumentParams) -> None {
|
|
32
32
|
file_path = params.text_document.uri;
|
|
33
|
-
quick_check_passed = await ls.launch_quick_check(file_path);
|
|
33
|
+
quick_check_passed = await ls.launch_quick_check(file_path, delay=0.1);
|
|
34
34
|
if not quick_check_passed {
|
|
35
35
|
return;
|
|
36
36
|
}
|
|
37
|
-
await ls.launch_deep_check(file_path);
|
|
37
|
+
await ls.launch_deep_check(file_path, delay=0.2);
|
|
38
38
|
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
"""Check syntax on change."""
|
|
43
|
-
@server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
|
|
42
|
+
"""Check syntax on change with debouncing."""
|
|
43
|
+
@ server.feature(lspt.TEXT_DOCUMENT_DID_CHANGE)
|
|
44
44
|
async def did_change(
|
|
45
45
|
ls: JacLangServer,
|
|
46
46
|
params: lspt.DidChangeTextDocumentParams
|
|
47
47
|
) -> None {
|
|
48
48
|
file_path = params.text_document.uri;
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if quick_check_passed {
|
|
49
|
+
try {
|
|
52
50
|
document = ls.workspace.get_text_document(file_path);
|
|
53
51
|
lines = document.source.splitlines();
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
52
|
+
fs_path = document.path;
|
|
53
|
+
if (fs_path in ls.sem_managers) {
|
|
54
|
+
sem_manager = ls.sem_managers[fs_path];
|
|
55
|
+
sem_manager.update_sem_tokens(params, sem_manager.sem_tokens, lines);
|
|
56
|
+
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
57
|
+
}
|
|
58
|
+
} except Exception as e {
|
|
59
|
+
ls.log_py(f" 'Failed to update semantic tokens: ' {e} ");
|
|
60
|
+
}
|
|
61
|
+
ls.log_py(f" 'Launching debounced quick check for ' {file_path} ");
|
|
62
|
+
quick_check_passed = await ls.launch_quick_check(file_path, delay=0.5);
|
|
63
|
+
if quick_check_passed {
|
|
64
|
+
ls.log_py(
|
|
65
|
+
f" 'Quick check passed, launching debounced deep check for ' {file_path} "
|
|
57
66
|
);
|
|
58
|
-
ls.
|
|
59
|
-
await ls.launch_deep_check(file_path);
|
|
67
|
+
await ls.launch_deep_check(file_path, delay=1.0);
|
|
60
68
|
ls.lsp.send_request(lspt.WORKSPACE_SEMANTIC_TOKENS_REFRESH);
|
|
61
69
|
}
|
|
62
70
|
}
|
|
63
71
|
|
|
64
|
-
|
|
65
72
|
"""Format the given document."""
|
|
66
73
|
@server.feature(lspt.TEXT_DOCUMENT_FORMATTING)
|
|
67
74
|
def formatting(
|