jaclang 0.8.6__py3-none-any.whl → 0.8.8__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.

Files changed (103) hide show
  1. jaclang/cli/cli.md +3 -3
  2. jaclang/cli/cli.py +37 -37
  3. jaclang/cli/cmdreg.py +45 -140
  4. jaclang/compiler/constant.py +0 -1
  5. jaclang/compiler/jac.lark +3 -6
  6. jaclang/compiler/larkparse/jac_parser.py +2 -2
  7. jaclang/compiler/parser.py +213 -34
  8. jaclang/compiler/passes/main/__init__.py +2 -4
  9. jaclang/compiler/passes/main/def_use_pass.py +0 -4
  10. jaclang/compiler/passes/main/predynamo_pass.py +221 -0
  11. jaclang/compiler/passes/main/pyast_gen_pass.py +83 -55
  12. jaclang/compiler/passes/main/pyast_load_pass.py +66 -40
  13. jaclang/compiler/passes/main/sym_tab_build_pass.py +1 -1
  14. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym.jac +2 -0
  15. jaclang/compiler/passes/main/tests/fixtures/checker/import_sym_test.jac +6 -0
  16. jaclang/compiler/passes/main/tests/fixtures/checker/imported_sym.jac +5 -0
  17. jaclang/compiler/passes/main/tests/fixtures/checker_arg_param_match.jac +37 -0
  18. jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +18 -0
  19. jaclang/compiler/passes/main/tests/fixtures/checker_binary_op.jac +21 -0
  20. jaclang/compiler/passes/main/tests/fixtures/checker_call_expr_class.jac +12 -0
  21. jaclang/compiler/passes/main/tests/fixtures/checker_cat_is_animal.jac +18 -0
  22. jaclang/compiler/passes/main/tests/fixtures/checker_cyclic_symbol.jac +4 -0
  23. jaclang/compiler/passes/main/tests/fixtures/checker_expr_call.jac +9 -0
  24. jaclang/compiler/passes/main/tests/fixtures/checker_float.jac +7 -0
  25. jaclang/compiler/passes/main/tests/fixtures/checker_import_missing_module.jac +13 -0
  26. jaclang/compiler/passes/main/tests/fixtures/checker_magic_call.jac +17 -0
  27. jaclang/compiler/passes/main/tests/fixtures/checker_mod_path.jac +8 -0
  28. jaclang/compiler/passes/main/tests/fixtures/checker_param_types.jac +11 -0
  29. jaclang/compiler/passes/main/tests/fixtures/checker_self_type.jac +9 -0
  30. jaclang/compiler/passes/main/tests/fixtures/checker_sym_inherit.jac +42 -0
  31. jaclang/compiler/passes/main/tests/fixtures/predynamo_fix3.jac +43 -0
  32. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_assign.jac +13 -0
  33. jaclang/compiler/passes/main/tests/fixtures/predynamo_where_return.jac +11 -0
  34. jaclang/compiler/passes/main/tests/test_checker_pass.py +265 -0
  35. jaclang/compiler/passes/main/tests/test_predynamo_pass.py +57 -0
  36. jaclang/compiler/passes/main/type_checker_pass.py +36 -61
  37. jaclang/compiler/passes/tool/doc_ir_gen_pass.py +204 -44
  38. jaclang/compiler/passes/tool/jac_formatter_pass.py +119 -69
  39. jaclang/compiler/passes/tool/tests/fixtures/corelib_fmt.jac +3 -3
  40. jaclang/compiler/passes/tool/tests/fixtures/general_format_checks/triple_quoted_string.jac +4 -5
  41. jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +171 -11
  42. jaclang/compiler/passes/transform.py +12 -8
  43. jaclang/compiler/program.py +14 -6
  44. jaclang/compiler/tests/fixtures/jac_import_py_files.py +4 -0
  45. jaclang/compiler/tests/fixtures/jac_module.jac +3 -0
  46. jaclang/compiler/tests/fixtures/multiple_syntax_errors.jac +10 -0
  47. jaclang/compiler/tests/fixtures/python_module.py +1 -0
  48. jaclang/compiler/tests/test_importer.py +39 -0
  49. jaclang/compiler/tests/test_parser.py +49 -0
  50. jaclang/compiler/type_system/operations.py +104 -0
  51. jaclang/compiler/type_system/type_evaluator.py +470 -47
  52. jaclang/compiler/type_system/type_utils.py +246 -0
  53. jaclang/compiler/type_system/types.py +58 -2
  54. jaclang/compiler/unitree.py +79 -94
  55. jaclang/langserve/engine.jac +253 -230
  56. jaclang/langserve/server.jac +46 -15
  57. jaclang/langserve/tests/fixtures/circle.jac +3 -3
  58. jaclang/langserve/tests/fixtures/circle_err.jac +3 -3
  59. jaclang/langserve/tests/fixtures/circle_pure.test.jac +3 -3
  60. jaclang/langserve/tests/fixtures/completion_test_err.jac +10 -0
  61. jaclang/langserve/tests/server_test/circle_template.jac +80 -0
  62. jaclang/langserve/tests/server_test/glob_template.jac +4 -0
  63. jaclang/langserve/tests/server_test/test_lang_serve.py +154 -312
  64. jaclang/langserve/tests/server_test/utils.py +153 -116
  65. jaclang/langserve/tests/test_dev_server.py +1 -1
  66. jaclang/langserve/tests/test_server.py +30 -86
  67. jaclang/langserve/utils.jac +56 -63
  68. jaclang/runtimelib/machine.py +7 -0
  69. jaclang/runtimelib/meta_importer.py +27 -1
  70. jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
  71. jaclang/runtimelib/tests/fixtures/savable_object.jac +2 -2
  72. jaclang/settings.py +18 -14
  73. jaclang/tests/fixtures/abc_check.jac +3 -3
  74. jaclang/tests/fixtures/arch_rel_import_creation.jac +12 -12
  75. jaclang/tests/fixtures/chandra_bugs2.jac +3 -3
  76. jaclang/tests/fixtures/create_dynamic_archetype.jac +13 -13
  77. jaclang/tests/fixtures/jac_run_py_bugs.py +18 -0
  78. jaclang/tests/fixtures/jac_run_py_import.py +13 -0
  79. jaclang/tests/fixtures/lambda_arg_annotation.jac +15 -0
  80. jaclang/tests/fixtures/lambda_self.jac +18 -0
  81. jaclang/tests/fixtures/maxfail_run_test.jac +4 -4
  82. jaclang/tests/fixtures/params/param_syntax_err.jac +9 -0
  83. jaclang/tests/fixtures/params/test_complex_params.jac +42 -0
  84. jaclang/tests/fixtures/params/test_failing_kwonly.jac +207 -0
  85. jaclang/tests/fixtures/params/test_failing_posonly.jac +116 -0
  86. jaclang/tests/fixtures/params/test_failing_varargs.jac +300 -0
  87. jaclang/tests/fixtures/params/test_kwonly_params.jac +29 -0
  88. jaclang/tests/fixtures/py2jac_params.py +8 -0
  89. jaclang/tests/fixtures/run_test.jac +4 -4
  90. jaclang/tests/test_cli.py +103 -18
  91. jaclang/tests/test_language.py +74 -16
  92. jaclang/utils/helpers.py +47 -2
  93. jaclang/utils/module_resolver.py +11 -1
  94. jaclang/utils/test.py +8 -0
  95. jaclang/utils/treeprinter.py +0 -18
  96. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/METADATA +3 -3
  97. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/RECORD +99 -62
  98. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/WHEEL +1 -1
  99. jaclang/compiler/passes/main/inheritance_pass.py +0 -131
  100. jaclang/langserve/dev_engine.jac +0 -645
  101. jaclang/langserve/dev_server.jac +0 -201
  102. jaclang/langserve/tests/server_test/code_test.py +0 -0
  103. {jaclang-0.8.6.dist-info → jaclang-0.8.8.dist-info}/entry_points.txt +0 -0
@@ -8,8 +8,10 @@ import from typing { Callable, Optional }
8
8
 
9
9
  import jaclang.compiler.unitree as uni;
10
10
  import from jaclang { JacMachineInterface as Jac }
11
- import from jaclang.compiler.constant {SymbolType}
11
+ import from jaclang.compiler.constant { SymbolType }
12
12
  import from jaclang.compiler.program { JacProgram }
13
+ import from jaclang.compiler.type_system.type_utils { get_completion_items }
14
+ import from jaclang.compiler.type_system.types { ClassType, TypeBase }
13
15
  import from jaclang.compiler.unitree { UniScopeNode }
14
16
  import from sem_manager { SemTokManager }
15
17
  import from jaclang.vendor.pygls { uris }
@@ -22,7 +24,7 @@ import utils;
22
24
  """Handles Jac module, semantic manager, and alert management."""
23
25
  class ModuleManager {
24
26
  """Initialize ModuleManager."""
25
- def init(self: ModuleManager, program: JacProgram, sem_managers: <>dict) -> None {
27
+ def init(self: ModuleManager, program: JacProgram, sem_managers: dict) -> None {
26
28
  self.program = program;
27
29
  self.sem_managers = sem_managers;
28
30
  }
@@ -34,7 +36,6 @@ class ModuleManager {
34
36
  build: uni.Module,
35
37
  update_annexed: bool = True
36
38
  ) -> None {
37
- file_path = file_path.removeprefix('file://');
38
39
  self.program.mod.hub[file_path] = build;
39
40
  if update_annexed {
40
41
  self.sem_managers[file_path] = SemTokManager(ir=build);
@@ -63,31 +64,32 @@ class JacLangServer(JacProgram , LanguageServer) {
63
64
  LanguageServer.init(self, 'jac-lsp', 'v0.1');
64
65
  JacProgram.init(self);
65
66
  self.executor = ThreadPoolExecutor();
66
- self.tasks: <>dict[(str, asyncio.Task)] = {};
67
- self.sem_managers: <>dict[(str, SemTokManager)] = {};
67
+ self.tasks: dict[(str, asyncio.Task)] = {};
68
+ self.sem_managers: dict[(str, SemTokManager)] = {};
68
69
  self.module_manager = ModuleManager(self, self.sem_managers);
70
+ self._quick_tasks: dict[str, asyncio.Task] = {};
71
+ self._deep_tasks: dict[str, asyncio.Task] = {};
69
72
  }
70
73
 
71
74
  """Return diagnostics for all files as a dict {uri: diagnostics}."""
72
75
  @ property
73
- def diagnostics(self: JacLangServer, ) -> <>dict[str, <>list] {
76
+ def diagnostics(self: JacLangServer, ) -> dict[str, list] {
74
77
  result = {};
75
78
  for file_path in self.mod.hub {
76
79
  uri = uris.from_fs_path(file_path);
77
80
  result[uri] =
78
- utils.gen_diagnostics(uri, self.errors_had, self.warnings_had);
81
+ utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had);
79
82
  }
80
83
  return result;
81
84
  }
82
85
 
83
86
  """Remove errors and warnings for a specific file from the lists."""
84
- def _clear_alerts_for_file(self: JacLangServer, file_path_fs: str) -> None {
85
- self.module_manager.clear_alerts_for_file(file_path_fs);
87
+ def _clear_alerts_for_file(self: JacLangServer, file_path: str) -> None {
88
+ self.module_manager.clear_alerts_for_file(file_path);
86
89
  }
87
90
 
88
91
  """Get IR for a file path."""
89
92
  def get_ir(self: JacLangServer, file_path: str) -> Optional[uni.Module] {
90
- file_path = file_path.removeprefix('file://');
91
93
  return self.mod.hub.get(file_path);
92
94
  }
93
95
 
@@ -105,17 +107,19 @@ class JacLangServer(JacProgram , LanguageServer) {
105
107
  """Rebuild a file (syntax only)."""
106
108
  def quick_check(self: JacLangServer, file_path: str) -> bool {
107
109
  try {
108
- file_path_fs = file_path.removeprefix('file://');
109
110
  document = self.workspace.get_text_document(file_path);
110
- self._clear_alerts_for_file(file_path_fs);
111
- build = self.compile(use_str=document.source, file_path=document.path);
112
- self.update_modules(file_path_fs, build, need=False);
111
+ fs_path = document.path;
112
+
113
+ self._clear_alerts_for_file(fs_path);
114
+ build = self.compile(use_str=document.source, file_path=fs_path);
115
+ self.update_modules(fs_path, build, need=False);
116
+ # to display diagnostics, it needs URI starts with "file://"
113
117
  self.publish_diagnostics(
114
118
  file_path,
115
- utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had)
119
+ utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
116
120
  );
117
121
  build_errors =
118
- [ e for e in self.errors_had if e.loc.mod_path == file_path_fs ];
122
+ [ e for e in self.errors_had if e.loc.mod_path == fs_path];
119
123
  return len(build_errors) == 0;
120
124
  } except Exception as e {
121
125
  self.log_error(f"'Error during syntax check: '{e}");
@@ -131,29 +135,31 @@ class JacLangServer(JacProgram , LanguageServer) {
131
135
  ) -> bool {
132
136
  try {
133
137
  start_time = time.time();
134
- file_path_fs = file_path.removeprefix('file://');
135
138
  document = self.workspace.get_text_document(file_path);
136
- self._clear_alerts_for_file(file_path_fs);
137
- build = self.build(use_str=document.source, file_path=document.path,type_check=True);
138
- self.update_modules(file_path_fs, build);
139
+ fs_path = document.path;
140
+
141
+ self._clear_alerts_for_file(fs_path);
142
+ build = self.compile(use_str=document.source, file_path=document.path,type_check=True);
143
+ self.update_modules(fs_path, build);
139
144
  if build.annexable_by {
140
145
  return self.deep_check(
141
146
  uris.from_fs_path(build.annexable_by),
142
- annex_view=file_path
147
+ annex_view=fs_path
143
148
  );
144
149
  }
145
150
  self.publish_diagnostics(
146
- annex_view if annex_view else file_path,
151
+ # to display diagnostic , it need URI starts with "file://"
152
+ uris.from_fs_path(annex_view) if annex_view else uris.from_fs_path(fs_path) ,
147
153
  utils.gen_diagnostics(
148
- annex_view if annex_view else file_path,
154
+ annex_view if annex_view else fs_path,
149
155
  self.errors_had,
150
156
  self.warnings_had
151
157
  )
152
158
  );
153
159
  if annex_view {
154
160
  self.publish_diagnostics(
155
- file_path,
156
- utils.gen_diagnostics(file_path, self.errors_had, self.warnings_had)
161
+ uris.from_fs_path(fs_path) ,
162
+ utils.gen_diagnostics(fs_path, self.errors_had, self.warnings_had)
157
163
  );
158
164
  }
159
165
  self.log_py(
@@ -166,151 +172,184 @@ class JacLangServer(JacProgram , LanguageServer) {
166
172
  }
167
173
  }
168
174
 
169
- """Analyze and publish diagnostics."""
170
- async def launch_quick_check(self: JacLangServer, uri: str) -> bool {
171
- return await asyncio.get_event_loop().run_in_executor(
172
- self.executor,
173
- self.quick_check,
174
- uri
175
- );
175
+ """Analyze and publish diagnostics with debouncing."""
176
+ async def launch_quick_check(self: JacLangServer, uri: str, delay: float = 0.5) -> bool {
177
+ try {
178
+ return await self._debounced_run(self.quick_check, uri, delay, self._quick_tasks);
179
+ } except asyncio.CancelledError {
180
+ return False;
181
+ } except Exception as e {
182
+ self.log_error(f" 'Error during quick check launch: ' {e} ");
183
+ return False;
184
+ }
185
+ }
186
+ """Analyze and publish diagnostics with debouncing."""
187
+ async def launch_deep_check(
188
+ self: JacLangServer,
189
+ uri: str,
190
+ delay: float = 1.0,
191
+ annex_view: Optional[str] = None
192
+ ) -> bool {
193
+ def deep_check_wrapper(file_uri: str) -> bool {
194
+ return self.deep_check(file_uri, annex_view);
195
+ }
196
+ try {
197
+ return await self._debounced_run(deep_check_wrapper, uri, delay, self._deep_tasks);
198
+ } except asyncio.CancelledError {
199
+ return False;
200
+ } except Exception as e {
201
+ self.log_error(f" 'Error during deep check launch: ' {e} ");
202
+ }
176
203
  }
177
204
 
178
- """Analyze and publish diagnostics."""
179
- async def launch_deep_check(self: JacLangServer, uri: str) -> None {
180
- async def run_in_executor(
181
- func: Callable[([str, Optional[str]], bool)],
182
- file_path: str,
183
- annex_view: Optional[str] = None
184
- ) -> None {
185
- loop = asyncio.get_event_loop();
186
- await loop.run_in_executor(self.executor, func, file_path, annex_view);
205
+ def get_token_at_position(self: JacLangServer, file_path: str, position: lspt.Position) -> Optional[uni.AstNode] {
206
+ fs_path = uris.to_fs_path(file_path);
207
+ if fs_path not in self.mod.hub {
208
+ return None;
187
209
  }
188
- if uri in self.tasks and not self.tasks[uri].done() {
189
- self.log_py(f"'Canceling '{uri}' deep check...'");
190
- self.tasks[uri].cancel();
191
- del (self.tasks[uri], ) ;
210
+ sem_mgr = self.sem_managers.get(fs_path);
211
+ if not sem_mgr {
212
+ return None;
192
213
  }
193
- self.log_py(f"'Analyzing '{uri}'...'");
194
- task = asyncio.create_task(run_in_executor(self.deep_check, uri));
195
- self.tasks[uri] = task;
196
- await task;
214
+ token_index =
215
+ utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
216
+ if token_index is None {
217
+ return None;
218
+ }
219
+ node_selected = sem_mgr.static_sem_tokens[token_index][3];
220
+ return node_selected;
221
+ }
222
+
223
+ def debug(self: JacLangServer, msg: str) -> None {
224
+ self.log_py("[DEBUG] " + ("-" * 80));
225
+ self.log_py(f"[DEBUG] {msg}");
226
+ self.log_py("[DEBUG] " + ("-" * 80));
197
227
  }
198
228
 
199
229
  """Return completion for a file."""
200
230
  def get_completion(
201
231
  self: JacLangServer,
202
- file_path: str,
232
+ file_uri: str,
203
233
  position: lspt.Position,
204
234
  completion_trigger: Optional[str]
205
235
  ) -> lspt.CompletionList {
236
+ self.debug("getting completion for " + file_uri + " at " + str(position));
237
+
206
238
  try {
207
- document = self.workspace.get_text_document(file_path);
208
- mod_ir = self.get_ir(file_path);
209
- if not mod_ir {
210
- return lspt.CompletionList(is_incomplete=False, items=[]);
211
- }
212
- current_line = document.lines[position.line];
213
- current_pos = position.character;
214
- current_symbol_path = utils.parse_symbol_path(current_line, current_pos);
215
- builtin_mod =
216
- next(
217
- ( mod for (name, mod) in self.mod.hub.items() if 'builtins' in name )
218
- );
219
- builtin_tab = builtin_mod.sym_tab;
220
- assert isinstance(builtin_tab, UniScopeNode) ;
221
- completion_items = [];
222
- node_selected =
223
- utils.find_deepest_symbol_node_at_pos(
224
- mod_ir,
225
- position.line,
226
- (position.character - 2)
227
- );
228
- mod_tab = mod_ir.sym_tab if not node_selected else node_selected.sym_tab;
229
- current_symbol_table = mod_tab;
230
- if completion_trigger == '.' {
231
- if current_symbol_path {
232
- temp_tab = mod_tab;
233
- for symbol in current_symbol_path {
234
- if symbol == 'self' {
235
- is_ability_def =
236
- temp_tab
237
- if isinstance(temp_tab, uni.ImplDef)
238
- else temp_tab.find_parent_of_type(uni.ImplDef);
239
- if not is_ability_def {
240
- archi_owner =
241
- mod_tab.find_parent_of_type(uni.Archetype);
242
- temp_tab =
243
- archi_owner.sym_tab
244
- if archi_owner and archi_owner.sym_tab
245
- else mod_tab;
246
- continue;
247
- } else {
248
- archi_owner =
249
- is_ability_def.decl_link.find_parent_of_type(
250
- uni.Archetype
251
- )
252
- if is_ability_def.decl_link
253
- else None;
254
- temp_tab =
255
- archi_owner.sym_tab
256
- if archi_owner and archi_owner.sym_tab
257
- else temp_tab;
258
- continue;
259
- }
260
- }
261
- symb = temp_tab.lookup(symbol);
262
- if symb {
263
- fetc_tab = symb.symbol_table;
264
- if fetc_tab {
265
- temp_tab = fetc_tab;
266
- } else {
267
- temp_tab =
268
- symb.defn[0].type_sym_tab
269
- if symb.defn[0].type_sym_tab
270
- else temp_tab;
271
- }
272
- } else {
273
- break;
239
+ file_path = uris.to_fs_path(file_uri);
240
+
241
+ if (node_at_pos := self.get_node_at_position(file_path, position.line, position.character - 1)) {
242
+ self.debug("found the node at pos " + str(position) + " " + str(node_at_pos));
243
+
244
+ # For each trigger character we need to handle the completion differently
245
+ if isinstance(node_at_pos, uni.Token) {
246
+ if node_at_pos.name == "DOT" {
247
+ member_access = node_at_pos.parent;
248
+ self.debug("found dot " + str(member_access));
249
+ if isinstance(member_access, uni.AtomTrailer) {
250
+ self.debug("before returning the list");
251
+ return self.get_completion_of_node(member_access.target);
274
252
  }
275
- }
276
- completion_items += utils.collect_all_symbols_in_scope(
277
- temp_tab,
278
- up_tree=False
279
- );
280
- if isinstance(temp_tab, uni.Archetype) and temp_tab.base_classes {
281
- base = [];
282
- for base_name in temp_tab.base_classes {
283
- if isinstance(base_name, uni.Name) and base_name.sym {
284
- base.append(base_name.sym);
285
- }
253
+
254
+ # FIXME: This is wrong but imma do it anyways like this for now.
255
+ } elif node_at_pos.name == "NAME" {
256
+
257
+ # Name of atom trailer.
258
+ if node_at_pos.parent and isinstance(node_at_pos.parent, uni.AtomTrailer) {
259
+ self.debug("found name in atom trailer " + str(node_at_pos.parent));
260
+ return self.get_completion_of_node(node_at_pos.parent.target);
286
261
  }
287
- for base_class_symbol in base {
288
- if base_class_symbol.symbol_table {
289
- completion_items += utils.collect_all_symbols_in_scope(
290
- base_class_symbol.symbol_table,
291
- up_tree=False
292
- );
293
- }
262
+
263
+ # Just a name field.
264
+ if scope_node := node_at_pos.find_parent_of_type(uni.UniScopeNode) {
265
+ self.debug("found name in scope node " + str(scope_node));
266
+ return self.get_completion_of_node(scope_node);
294
267
  }
295
268
  }
296
269
  }
297
- } elif node_selected and node_selected.find_parent_of_type(uni.Archetype)
298
- or node_selected.find_parent_of_type(uni.ImplDef)
299
- {
300
- self_symbol =
301
-
302
- [lspt.CompletionItem(
303
- label='self',
304
- kind=lspt.CompletionItemKind.Variable
305
- )];
306
- } else {
307
- self_symbol = [];
308
270
  }
309
- return lspt.CompletionList(is_incomplete=False, items=completion_items);
271
+
272
+ return lspt.CompletionList(is_incomplete=False, items=[]);
273
+
310
274
  } except Exception as e {
311
275
  self.log_py(f"'Error during completion: '{e}");
312
276
  return lspt.CompletionList(is_incomplete=False, items=[]);
313
277
  }
278
+
279
+ self.debug("returning empty list");
280
+ }
281
+
282
+ def get_ast_of_file(self: JacLangServer, file_path: str) -> Optional[uni.AstNode] {
283
+ if file_path in self.mod.hub {
284
+ return self.mod.hub[file_path];
285
+ }
286
+ return None;
287
+ }
288
+
289
+ def get_node_at_position(self: JacLangServer, file_path: str, line:int, col: int) -> Optional[uni.AstNode] {
290
+ if (ast := self.get_ast_of_file(file_path)) {
291
+ for ast_node in ast._in_mod_nodes {
292
+ if not isinstance(ast_node, uni.Token) {
293
+ continue;
294
+ }
295
+ if (utils.position_within_node(ast_node, line + 1, col + 1)) {
296
+ return ast_node;
297
+ }
298
+ }
299
+ }
300
+ return None;
301
+ }
302
+
303
+ def get_completion_of_node(self: JacLangServer, node: uni.AstNode) -> lspt.CompletionList {
304
+ if (node_type := self.get_node_type(node)) {
305
+ self.debug("found type " + str(node_type));
306
+ return self.get_completion_items_of(node_type);
307
+ } elif isinstance(node, UniScopeNode) {
308
+ self.debug("found scope node " + str(node));
309
+ return self.get_completion_items_of(node);
310
+ }
311
+
312
+ self.debug("no type found for node " + str(node));
313
+ return lspt.CompletionList(is_incomplete=False, items=[]);
314
+ }
315
+
316
+ """Return the type of an AST node if it has one."""
317
+ def get_node_type(self: JacLangServer, n: uni.AstNode) -> Optional[TypeBase] {
318
+ if isinstance(n, uni.Expr) {
319
+ typ = self.get_type_evaluator().get_type_of_expression(n);
320
+ self.debug("found type " + str(typ) + " for expr " + str(n));
321
+ return typ;
322
+ }
323
+ self.debug("no type found for node " + str(n));
324
+ return None;
325
+ }
326
+
327
+ """Get type members for completion."""
328
+ def get_completion_items_of(self: JacLangServer, ty: TypeBase | uni.UniScopeNode) -> lspt.CompletionList {
329
+ evaluator = self.get_type_evaluator();
330
+ self.debug("getting completion of " + str(ty));
331
+ items = get_completion_items(ty);
332
+ self.debug("completion items are " + str(items));
333
+
334
+ items: list[lspt.CompletionItem] = [];
335
+ for item in get_completion_items(ty) {
336
+ detail: lspt.CompletionItemLabelDetails | None = None;
337
+ if item.detail {
338
+ detail = lspt.CompletionItemLabelDetails(detail=item.detail);
339
+ }
340
+ items.append(
341
+ lspt.CompletionItem(
342
+ label=item.label,
343
+ kind=item.kind,
344
+ label_details=detail,
345
+ )
346
+ );
347
+ }
348
+
349
+ return lspt.CompletionList(
350
+ is_incomplete=False,
351
+ items=items,
352
+ );
314
353
  }
315
354
 
316
355
  """Rename module."""
@@ -334,7 +373,7 @@ class JacLangServer(JacProgram , LanguageServer) {
334
373
  }
335
374
 
336
375
  """Return formatted jac."""
337
- def formatted_jac(self: JacLangServer, file_path: str) -> <>list[lspt.TextEdit] {
376
+ def formatted_jac(self: JacLangServer, file_path: str) -> list[lspt.TextEdit] {
338
377
  try {
339
378
  document = self.workspace.get_text_document(file_path);
340
379
  formatted_text =
@@ -365,20 +404,7 @@ class JacLangServer(JacProgram , LanguageServer) {
365
404
  file_path: str,
366
405
  position: lspt.Position
367
406
  ) -> Optional[lspt.Hover] {
368
- file_path_fs = file_path.removeprefix('file://');
369
- if file_path_fs not in self.mod.hub {
370
- return None;
371
- }
372
- sem_mgr = self.sem_managers.get(file_path_fs);
373
- if not sem_mgr {
374
- return None;
375
- }
376
- token_index =
377
- utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
378
- if token_index is None {
379
- return None;
380
- }
381
- node_selected = sem_mgr.static_sem_tokens[token_index][3];
407
+ node_selected = self.get_token_at_position(file_path, position);
382
408
  value = self.get_node_info(node_selected) if node_selected else None;
383
409
  if value {
384
410
  return lspt.Hover(
@@ -392,22 +418,25 @@ class JacLangServer(JacProgram , LanguageServer) {
392
418
  }
393
419
 
394
420
  """Extract meaningful information from the AST node."""
395
- def get_node_info(self: JacLangServer, <>node: uni.AstSymbolNode) -> Optional[str] {
421
+ def get_node_info(self: JacLangServer, sym_node: uni.AstSymbolNode) -> Optional[str] {
396
422
  try {
397
- if isinstance(<>node, uni.NameAtom) {
398
- <>node = <>node.name_of;
423
+ if isinstance(sym_node, uni.NameAtom) {
424
+ sym_node = sym_node.name_of;
399
425
  }
400
- access = (<>node.sym.access.value + ' ') if <>node.sym else None;
426
+ access = (sym_node.sym.access.value + ' ') if sym_node.sym else None;
401
427
  node_info =
402
- f"'('{access if access else ''}{<>node.sym_category.value}') '{<>node.sym_name}";
403
- if <>node.name_spec.clean_type {
404
- node_info += f"': '{<>node.name_spec.clean_type}";
428
+ f"'('{access if access else ''}{sym_node.sym_category.value}') '{sym_node.sym_name}";
429
+ if sym_node.name_spec.clean_type {
430
+ node_info += f"': '{sym_node.name_spec.clean_type}";
405
431
  }
406
- if isinstance(<>node, uni.AstDocNode) and <>node.doc {
407
- node_info += f"'\n'{<>node.doc.value}";
432
+ if isinstance(sym_node,uni.AstSymbolNode) and isinstance(sym_node.name_spec.type,ClassType) {
433
+ node_info += f"': '{sym_node.name_spec.type.shared.class_name}";
408
434
  }
409
- if isinstance(<>node, uni.Ability) and <>node.signature {
410
- node_info += f"'\n'{<>node.signature.unparse()}";
435
+ if isinstance(sym_node, uni.AstDocNode) and sym_node.doc {
436
+ node_info += f"'\n'{sym_node.doc.value}";
437
+ }
438
+ if isinstance(sym_node, uni.Ability) and sym_node.signature {
439
+ node_info += f"'\n'{sym_node.signature.unparse()}";
411
440
  }
412
441
  } except AttributeError as e {
413
442
  self.log_warning(f"'Attribute error when accessing node attributes: '{e}");
@@ -416,10 +445,10 @@ class JacLangServer(JacProgram , LanguageServer) {
416
445
  }
417
446
 
418
447
  """Return document symbols for a file."""
419
- def get_outline(self: JacLangServer, file_path: str) -> <>list[lspt.DocumentSymbol] {
420
- file_path_fs = file_path.removeprefix('file://');
421
- if file_path_fs in self.mod.hub
422
- and (root_node := self.mod.hub[file_path_fs].sym_tab)
448
+ def get_outline(self: JacLangServer, file_path: str) -> list[lspt.DocumentSymbol] {
449
+ fs_path = uris.to_fs_path(file_path);
450
+ if fs_path in self.mod.hub
451
+ and (root_node := self.mod.hub[fs_path].sym_tab)
423
452
  {
424
453
  return utils.get_symbols_for_outline(root_node);
425
454
  }
@@ -432,20 +461,7 @@ class JacLangServer(JacProgram , LanguageServer) {
432
461
  file_path: str,
433
462
  position: lspt.Position
434
463
  ) -> Optional[lspt.Location] {
435
- file_path_fs = file_path.removeprefix('file://');
436
- if file_path_fs not in self.mod.hub {
437
- return None;
438
- }
439
- sem_mgr = self.sem_managers.get(file_path_fs);
440
- if not sem_mgr {
441
- return None;
442
- }
443
- token_index =
444
- utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
445
- if token_index is None {
446
- return None;
447
- }
448
- node_selected = sem_mgr.static_sem_tokens[token_index][3];
464
+ node_selected = self.get_token_at_position(file_path, position);
449
465
  if node_selected {
450
466
  if (node_selected.sym.sym_type == SymbolType.MODULE) {
451
467
  spec = node_selected.sym.decl.parent.resolve_relative_path();
@@ -543,27 +559,14 @@ class JacLangServer(JacProgram , LanguageServer) {
543
559
  self: JacLangServer,
544
560
  file_path: str,
545
561
  position: lspt.Position
546
- ) -> <>list[lspt.Location] {
547
- file_path_fs = file_path.removeprefix('file://');
548
- if file_path_fs not in self.mod.hub {
549
- return [];
550
- }
551
- sem_mgr = self.sem_managers.get(file_path_fs);
552
- if not sem_mgr {
553
- return [];
554
- }
555
- index1 =
556
- utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
557
- if index1 is None {
558
- return [];
559
- }
560
- node_selected = sem_mgr.static_sem_tokens[index1][3];
562
+ ) -> list[lspt.Location] {
563
+ node_selected = self.get_token_at_position(file_path, position);
561
564
  if node_selected and node_selected.sym {
562
- list_of_references: <>list[lspt.Location] =
565
+ list_of_references: list[lspt.Location] =
563
566
  [ lspt.Location(
564
- uri=uris.from_fs_path(<>node.loc.mod_path),
565
- range=utils.create_range(<>node.loc)
566
- ) for <>node in node_selected.sym.uses ];
567
+ uri=uris.from_fs_path(cur_node.loc.mod_path),
568
+ range=utils.create_range(cur_node.loc)
569
+ ) for cur_node in node_selected.sym.uses ];
567
570
  return list_of_references;
568
571
  }
569
572
  return [];
@@ -576,27 +579,14 @@ class JacLangServer(JacProgram , LanguageServer) {
576
579
  position: lspt.Position,
577
580
  new_name: str
578
581
  ) -> Optional[lspt.WorkspaceEdit] {
579
- file_path_fs = file_path.removeprefix('file://');
580
- if file_path_fs not in self.mod.hub {
581
- return None;
582
- }
583
- sem_mgr = self.sem_managers.get(file_path_fs);
584
- if not sem_mgr {
585
- return None;
586
- }
587
- index1 =
588
- utils.find_index(sem_mgr.sem_tokens, position.line, position.character);
589
- if index1 is None {
590
- return None;
591
- }
592
- node_selected = sem_mgr.static_sem_tokens[index1][3];
582
+ node_selected = self.get_token_at_position(file_path, position);
593
583
  if node_selected and node_selected.sym {
594
- changes: <>dict[(str, <>list[lspt.TextEdit])] = {};
595
- for <>node in [*node_selected.sym.uses, node_selected.sym.defn[0]] {
596
- key = uris.from_fs_path(<>node.loc.mod_path);
584
+ changes: dict[(str, list[lspt.TextEdit])] = {};
585
+ for node in [*node_selected.sym.uses, node_selected.sym.defn[0]] {
586
+ key = uris.from_fs_path(node.loc.mod_path);
597
587
  new_edit =
598
588
  lspt.TextEdit(
599
- range=utils.create_range(<>node.loc),
589
+ range=utils.create_range(node.loc),
600
590
  new_text=new_name
601
591
  );
602
592
  utils.add_unique_text_edit(changes, key, new_edit);
@@ -608,8 +598,8 @@ class JacLangServer(JacProgram , LanguageServer) {
608
598
 
609
599
  """Return semantic tokens for a file."""
610
600
  def get_semantic_tokens(self: JacLangServer, file_path: str) -> lspt.SemanticTokens {
611
- file_path_fs = file_path.removeprefix('file://');
612
- sem_mgr = self.sem_managers.get(file_path_fs);
601
+ fs_path = uris.to_fs_path(file_path);
602
+ sem_mgr = self.sem_managers.get(fs_path);
613
603
  if not sem_mgr {
614
604
  return lspt.SemanticTokens(data=[]);
615
605
  }
@@ -638,4 +628,37 @@ class JacLangServer(JacProgram , LanguageServer) {
638
628
  def log_py(self: JacLangServer, message: str) -> None {
639
629
  logging.info(message);
640
630
  }
631
+
632
+ """Run a function with debouncing per file."""
633
+ async def _debounced_run(
634
+ self: JacLangServer,
635
+ func: Callable,
636
+ file_uri: str,
637
+ delay: float,
638
+ task_dict: dict[(str, asyncio.Task)]
639
+ ) -> None {
640
+ if ((file_uri in task_dict) and not task_dict[file_uri].done() ) {
641
+ task_dict[file_uri].cancel();
642
+ self.log_py(
643
+ f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
644
+ );
645
+ }
646
+ async def wrapper() {
647
+ try {
648
+ await asyncio.sleep(delay);
649
+ loop = asyncio.get_event_loop();
650
+ result = await loop.run_in_executor(None, func, file_uri);
651
+ return result;
652
+ } except asyncio.CancelledError {
653
+ self.log_py(
654
+ f" {func.__name__} ' was cancelled due to debounce for ' {file_uri} "
655
+ );
656
+ raise ;
657
+ }
658
+ }
659
+ new_task = asyncio.create_task(wrapper());
660
+ task_dict[file_uri] = new_task;
661
+ return await new_task;
662
+ }
663
+
641
664
  }