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
+24 -2
View File
@@ -75,6 +75,10 @@ from tools.semantic_tokens import (
_Highlighter,
)
from tools.signature_help import build_signature_help
from tools.tcloo_arguments import method_signature_help
from tclint.lexer import TclSyntaxError
from tools.tcloo_completion import parse_completion_source, tcloo_completions
from tools.tcloo_symbols import class_completion_items
from tools.tcl_command_completion import (
TCL_COMMAND_ITEMS,
TCL_COMMAND_NAMES,
@@ -278,6 +282,9 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
doc = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
position = params.position
source_lines = LSP_SERVER.get_lines(doc)
oo_items = tcloo_completions(source_lines, position)
if oo_items is not None:
return lsp.CompletionList(is_incomplete=False, items=oo_items)
array_items = array_element_completions(
source_lines, position, LSP_SERVER.navigation_snapshot().values(),
str(pathlib.Path(uris.to_fs_path(doc.uri))),
@@ -334,7 +341,12 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
):
return lsp.CompletionList(is_incomplete=False, items=[])
tree = LSP_SERVER.get_tree(doc)
try:
tree = LSP_SERVER.get_tree(doc)
except TclSyntaxError:
tree = parse_completion_source(doc.source)
if tree is None:
return lsp.CompletionList(is_incomplete=False, items=[])
globals_set, procs_locals, proc_ranges = LSP_SERVER.variable_index_for_document(
doc, tree
)
@@ -347,6 +359,7 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
break
candidates: list[tuple[int, lsp.CompletionItem]] = []
candidates.extend((0, item) for item in class_completion_items(tree))
for name in sorted(local_names - globals_set):
candidates.append(
(
@@ -457,7 +470,15 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList:
)
def signature_help(params: lsp.SignatureHelpParams) -> lsp.SignatureHelp | None:
document = LSP_SERVER.workspace.get_text_document(params.text_document.uri)
tree = LSP_SERVER.get_tree(document)
method_help = method_signature_help(document.source, params.position)
if method_help is not None:
return method_help
try:
tree = LSP_SERVER.get_tree(document)
except TclSyntaxError:
tree = parse_completion_source(document.source)
if tree is None:
return None
custom_signatures, custom_docs = LSP_SERVER.proc_metadata_snapshot(
document.path
@@ -541,6 +562,7 @@ def semantic_tokens(params: lsp.SemanticTokensParams):
# Reuse cached AST
tree = LSP_SERVER.get_tree(document)
hl.highlight_classes(tree)
tree.accept(hl, recurse=True)
tokens = hl.tokens()