from tclint.syntax_tree import Visitor, Command, BareWord, List import lsprotocol.types as lsp from common.load_data import standard_items BUILTIN_VAR_LABELS = {ci.label for ci in standard_items.nx_variables} class CompletionItems: def __init__(self): self._custom_functions: list[lsp.CompletionItem] = [] @property def custom_functions(self) -> list[lsp.CompletionItem]: return self._custom_functions @custom_functions.setter def custom_functions(self, value: lsp.CompletionItem): self._custom_functions.append(value) class _Completion(Visitor): def __init__(self): super().__init__() self._custom_functions: list[lsp.CompletionItem] = [] self._proc_signatures = {} @property def custom_functions(self) -> list[lsp.CompletionItem]: return self._custom_functions @property def proc_signatures(self): return self._proc_signatures def reset(self): self._custom_functions = [] self._proc_signatures = {} def _append_unique(self, item: lsp.CompletionItem): # Avoid duplicate labels within the same file scan if not any(ci.label == item.label for ci in self._custom_functions): # For functions, attach a command to trigger signature help after completion if item.kind == lsp.CompletionItemKind.Function and item.command is None: item.command = lsp.Command(title="Trigger Signature Help", command="editor.action.triggerParameterHints") self._custom_functions.append(item) def visit_command(self, command: Command): routine = command.routine # Collect custom proc names and their signatures if routine.contents == "proc" and command.args: first_arg = command.args[0] if not getattr(first_arg, "value", None): return if any(item.label == first_arg.value for item in standard_items.nx_procs): return # Record proc name as a completion item self._append_unique(lsp.CompletionItem(label=first_arg.value, kind=lsp.CompletionItemKind.Function)) if len(command.args) < 2: return param_list_node = command.args[1] if not hasattr(param_list_node, "children"): return param_names = [] for arg in param_list_node.children: if isinstance(arg, BareWord): param_names.append(arg.value) elif isinstance(arg, List) and len(arg.children) >= 1: first = arg.children[0] if isinstance(first, BareWord): param_names.append(first.value) self._proc_signatures[first_arg.value] = param_names # Collect global variables declared with: global var1 var2 ... elif routine.contents == "global" and command.args: for arg in command.args: if isinstance(arg, BareWord) and getattr(arg, "value", None): if arg.value not in BUILTIN_VAR_LABELS: self._append_unique(lsp.CompletionItem(label=arg.value, kind=lsp.CompletionItemKind.Variable)) # Collect variables set with explicit global namespace: set ::var_name ... elif routine.contents == "set" and command.args: first = command.args[0] if isinstance(first, BareWord) and getattr(first, "value", None): var_name = first.value if var_name.startswith("::"): base_name = var_name.split("(", 1)[0] clean_name = base_name[2:] # remove leading '::' for completion display if clean_name not in BUILTIN_VAR_LABELS: self._append_unique(lsp.CompletionItem(label=clean_name, kind=lsp.CompletionItemKind.Variable)) def remove_existing_items(items: list[lsp.CompletionItem], store: dict) -> None: """ Entfernt alle CompletionItems aus dem store, deren label in der items-Liste vorkommt. Änderungen erfolgen in-place. """ labels_to_remove = {item.label for item in items} for key in list(store.keys()): filtered = [ci for ci in store[key] if ci.label not in labels_to_remove] if filtered: store[key] = filtered else: del store[key] def remove_shared_keys(nested_dict: dict[str, dict[str, list]], flat_dict: dict[str, list]) -> None: """ Entfernt alle Keys aus nested_dict[file][func], wenn func auch in flat_dict vorhanden ist. Änderungen erfolgen in-place. """ for file_path, func_dict in list(nested_dict.items()): for func_name in list(func_dict.keys()): if func_name in flat_dict: del nested_dict[file_path][func_name] if not nested_dict[file_path]: del nested_dict[file_path] completion = _Completion()