feat(tcloo): add document-local TclOO completions, signature help and hints

Add conservative, document-local TclOO type inference and tooling so the server
can offer method completions, signature help, and inlay parameter hints for
statically resolvable TclOO receivers (including `new`/`create`, `my`, and
simple return-chains). Also surface class names as completion items and emit
semantic tokens for class declarations/references.

Notable changes:
- New tcloo_* tools: completion, symbols, and argument parsing; integrated into
  on_completion, signature_help, inlay hint generation and semantic token
  highlighting. Completions are returned early when an OO receiver context is
  detected.
- Use a completion-friendly parser fallback when the main AST fails (TclSyntaxError)
  so editing-in-progress code still yields useful completions.
- Add CompletionItemKind.Class to command kinds, exclude class items from the
  poco completion name cache, and include new unit tests for the TclOO helpers.
This commit is contained in:
Christoph Brandau
2026-09-11 23:38:42 +02:00
parent c28836933c
commit f88a50d4ab
12 changed files with 1007 additions and 42 deletions
+18 -1
View File
@@ -9,6 +9,8 @@ from typing import Any
import lsprotocol.types as lsp
from tclint.syntax_tree import Command, VarSub, Visitor
from tools.navigation import FileSymbolIndex
from tools.tcloo_arguments import method_parameters
from tools.tcloo_completion import resolved_method_calls
@dataclass(frozen=True)
@@ -160,6 +162,7 @@ class InlayHintGenerator(Visitor):
self.source_lines = (
source_lines if source_lines is not None else source.splitlines()
)
self.source = source
self.proc_signatures = proc_signatures
self.requested_range = requested_range
self.parameter_names = parameter_names
@@ -194,6 +197,17 @@ class InlayHintGenerator(Visitor):
walk(child)
walk(tree)
if self.parameter_names != "none":
for call in resolved_method_calls(self.source):
if not self._node_intersects_requested_range(call.command):
continue
parameters = method_parameters(call.parameters)
signature = InlayHintSignature(
parameters=tuple(InlayHintParameter(p.name, variadic=p.variadic) for p in parameters),
display_label=" ".join([call.label, *(p.label for p in parameters)]),
)
self._argument_hints(signature, call.command.args[call.argument_offset:])
self.hints.sort(key=lambda hint: (hint.position.line, hint.position.character))
return self.hints
def _position(self, line: int, column: int) -> lsp.Position:
@@ -238,7 +252,10 @@ class InlayHintGenerator(Visitor):
if signature is None or self.parameter_names == "none":
return
for argument_index, argument in enumerate(command.args):
self._argument_hints(signature, command.args)
def _argument_hints(self, signature, arguments):
for argument_index, argument in enumerate(arguments):
parameter = self._parameter_for_argument(signature, argument_index)
if parameter is None:
break