From 17fb79a3469e590fad90c0b82ba5ab072c6a7d71 Mon Sep 17 00:00:00 2001 From: Christoph Brandau Date: Fri, 25 Sep 2026 07:58:56 +0200 Subject: [PATCH] feat(lsp): support completion item resolve to show .def previews Enable completion item resolution so selecting a completion can populate documentation with a preview of the corresponding .def declaration. - Turn on resolve_provider in the completion options. - Implement on_completion_resolve to attach Markdown documentation computed by def_hover_markdown when the completion item's data contains a ("def" -> [kind, name]) payload. - Include that "def" payload when generating def-related completion items. - Add tests that verify block-template and address-list items resolve to Markdown previews. Before: def-related completion items had no documentation on resolve. After: selecting such items will return a Markdown preview of the .def entry. --- server/src/lsp_server.py | 13 ++++++++++++- server/src/lsp_tclserver.py | 1 + server/tests/python_tests/test_def_symbols.py | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/server/src/lsp_server.py b/server/src/lsp_server.py index 778365d..d903008 100644 --- a/server/src/lsp_server.py +++ b/server/src/lsp_server.py @@ -287,7 +287,7 @@ def document_diagnostic(params: lsp.DocumentDiagnosticParams): @LSP_SERVER.feature( lsp.TEXT_DOCUMENT_COMPLETION, - lsp.CompletionOptions(trigger_characters=["$", " ", "-", "(", ","]), + lsp.CompletionOptions(trigger_characters=["$", " ", "-", "(", ","], resolve_provider=True), ) def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList: result = _on_completion(params) @@ -300,6 +300,17 @@ def on_completion(params: lsp.CompletionParams) -> lsp.CompletionList: return result +@LSP_SERVER.feature(lsp.COMPLETION_ITEM_RESOLVE) +def on_completion_resolve(item: lsp.CompletionItem) -> lsp.CompletionItem: + """Show the .def declaration of a selected block template/address, as on hover.""" + target = item.data.get("def") if isinstance(item.data, dict) else None + if item.documentation is None and isinstance(target, list) and len(target) == 2: + markdown = def_hover_markdown(LSP_SERVER.def_documents_snapshot(), tuple(target)) + if markdown: + item.documentation = lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=markdown) + return item + + # Keywords that expand to all loaded .def names: keyword -> (items, description). SYMBOL_LIST_KEYWORDS = { "BLOCK_LIST": (lambda: LSP_SERVER.block_template_items(), "block templates"), diff --git a/server/src/lsp_tclserver.py b/server/src/lsp_tclserver.py index 15489cc..35db8b4 100644 --- a/server/src/lsp_tclserver.py +++ b/server/src/lsp_tclserver.py @@ -290,6 +290,7 @@ class TclLanguageServer(LanguageServer): label=name, kind=item_kind, detail=f"{description} ({pathlib.Path(path).name})", + data={"def": [kind, name]}, ) for path, document in self.def_documents_snapshot().items() for name in document.names(kind) diff --git a/server/tests/python_tests/test_def_symbols.py b/server/tests/python_tests/test_def_symbols.py index 6e703bb..f9df77e 100644 --- a/server/tests/python_tests/test_def_symbols.py +++ b/server/tests/python_tests/test_def_symbols.py @@ -227,6 +227,25 @@ def test_block_list_shows_all_templates_quoted(tmp_path, monkeypatch): assert all(item.filter_text.startswith("BLOCK_LIST") for item in items) +def test_block_list_item_resolves_to_template_preview(tmp_path, monkeypatch): + server, _ = _project(tmp_path, monkeypatch) + items = _complete(server, tmp_path, "set a 1\n BLOCK_LIST") + assert all(item.documentation is None for item in items) + item = next(item for item in items if item.label == "steady_rest") + resolved = lsp_server.on_completion_resolve(item) + assert resolved.documentation.kind == lsp.MarkupKind.Markdown + assert "**Block template** `steady_rest`" in resolved.documentation.value + assert "Text[M60]" in resolved.documentation.value + + +def test_address_list_item_resolves_to_address_table(tmp_path, monkeypatch): + server, _ = _project(tmp_path, monkeypatch) + items = _complete(server, tmp_path, "set a 1\n ADDR_LIST") + item = next(item for item in items if item.label == "SPOS") + resolved = lsp_server.on_completion_resolve(item) + assert "| Format | `Coordinate` |" in resolved.documentation.value + + def test_block_list_ignores_variables_and_other_words(tmp_path, monkeypatch): server, _ = _project(tmp_path, monkeypatch) for source in ("set x $BLOCK_LIST", "set x MY_BLOCK_LIST", "set x steady"):