Add PSC (.psc) indexing and share TclOO class metadata across files so class definitions discovered via PSC layers can be used for completions, signature help, inlay hints, and "go to definition". Key behavior changes: - Client file watcher now includes *.psc and .vscode launch paths/tests updated to use the postprocessor test folder; .gitignore updated to ignore that folder. - Server watches .psc changes and refreshes a PSC script index; new tools/tcloo_navigation.py exposes tcloo_definition used by the language server to resolve cross-file class/constructor/method definitions. - Language server uses class_snapshot(document.path) when producing TclOO completions, signature help, and inlay hints so resolved class metadata is available across files. Also includes related docs/changelog updates, minor code formatting cleanups, and added tests for PSC/TclOO behavior.
80 lines
2.9 KiB
Python
80 lines
2.9 KiB
Python
"""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,
|
|
)
|