"""Parameter presentation for statically resolved TclOO calls.""" from dataclasses import dataclass import lsprotocol.types as lsp from tclint.lexer import TclSyntaxError from tclint.syntax_tree import BracedWord, Command from tools.parser import CustomParser from tools.signature_help import _active_argument, _contains_cursor from tools.tcloo_completion import resolved_method_calls @dataclass(frozen=True) class MethodParameter: name: str label: str variadic: bool = False def method_parameters(parameters: str) -> list[MethodParameter]: parser = CustomParser() try: words = parser.parse_list(BracedWord(parameters, pos=(1, 1))).children result = [] for index, word in enumerate(words): parts = parser.parse_list(word).children if not parts or len(parts) > 2: return [] name = parts[0].contents if name is None: return [] variadic = name == "args" and len(parts) == 1 and index == len(words) - 1 label = "{" + word.contents + "}" if len(parts) == 2 else name result.append(MethodParameter(name, label, variadic)) return result except TclSyntaxError: return [] def method_signature_help(source: str, position: lsp.Position, external_classes=None) -> lsp.SignatureHelp | None: lines = source.split("\n") if position.line >= len(lines): return None # AST columns are codepoints; LSP columns are UTF-16 code units. prefix = lines[position.line].encode("utf-16-le")[:position.character * 2].decode("utf-16-le", errors="ignore") cursor = (position.line, len(prefix)) candidates = [call for call in resolved_method_calls(source, external_classes) if _contains_cursor(call.command, lines, cursor)] if not candidates: return None call = max(candidates, key=lambda candidate: candidate.command.pos) def nested_active(node): return any( isinstance(child, Command) and _contains_cursor(child, lines, cursor) or nested_active(child) for child in node.children ) # Let the inner command's own signature provider handle its arguments. if nested_active(call.command): return None argument = _active_argument(call.command, cursor) - call.argument_offset if argument < 0: return None parameters = method_parameters(call.parameters) label = call.label infos = [] for parameter in parameters: label += " " start = len(label.encode("utf-16-le")) // 2 label += parameter.label infos.append(lsp.ParameterInformation(label=(start, len(label.encode("utf-16-le")) // 2))) active = min(argument, len(parameters) - 1) if parameters else None return lsp.SignatureHelp( signatures=[lsp.SignatureInformation(label=label, parameters=infos, active_parameter=active)], active_signature=0, active_parameter=active, )