128 lines
4.8 KiB
Python
128 lines
4.8 KiB
Python
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()
|