Merge pull request 'Show effective (last-loaded) .def declaration in hover and GoTo' (#50) from enhancements into main

This commit is contained in:
2026-09-25 19:39:33 +00:00
4 changed files with 74 additions and 13 deletions
+4
View File
@@ -16,6 +16,10 @@ Versions correspond to the Git tags of this repository.
- Warning for block template and address names in NX commands (`MOM_do_template stedy_rest`) that no loaded `.def` file declares; names built at runtime (`$var`, `"CYCLE_$x"`) are not checked, and the warnings update when a `.def` file changes - Warning for block template and address names in NX commands (`MOM_do_template stedy_rest`) that no loaded `.def` file declares; names built at runtime (`$var`, `"CYCLE_$x"`) are not checked, and the warnings update when a `.def` file changes
- Completion items for block templates and addresses (`BLOCK_LIST`, `ADDR_LIST`, `MOM_do_template`, ...) show the same preview as the hover - Completion items for block templates and addresses (`BLOCK_LIST`, `ADDR_LIST`, `MOM_do_template`, ...) show the same preview as the hover
### Changed
- When a block template or address is declared in several PSC layers, hover and Go to Definition show only the effective one, the declaration of the last loaded `.def` file, and name the declarations it overrides; Find References and Rename still include all declarations
### Documentation ### Documentation
- Document DEF block templates, addresses, `BLOCK_LIST`/`ADDR_LIST`, and the formatting changes in the README - Document DEF block templates, addresses, `BLOCK_LIST`/`ADDR_LIST`, and the formatting changes in the README
+4 -4
View File
@@ -55,8 +55,8 @@ from tools.completion_items import (
from tools.def_navigation import ( from tools.def_navigation import (
all_def_target_locations, all_def_target_locations,
def_declarations, def_declarations,
def_definition_locations,
def_hover_markdown, def_hover_markdown,
effective_def_locations,
def_rename_edits, def_rename_edits,
def_symbol_at, def_symbol_at,
) )
@@ -848,12 +848,12 @@ def goto_definition(params: lsp.DefinitionParams):
context = _navigation_context(params.text_document.uri, params.position) context = _navigation_context(params.text_document.uri, params.position)
if context is None: if context is None:
symbol = _tcl_def_symbol(params.text_document.uri, params.position, _word_at(params.text_document.uri, params.position)) symbol = _tcl_def_symbol(params.text_document.uri, params.position, _word_at(params.text_document.uri, params.position))
return symbol and _sorted_locations(def_definition_locations(symbol[0], symbol[1])) or None return symbol and effective_def_locations(symbol[0], symbol[1]) or None
indexes, definitions, _, identity = context indexes, definitions, _, identity = context
target = _def_target(identity) target = _def_target(identity)
if target is not None: if target is not None:
return _sorted_locations(def_definition_locations(LSP_SERVER.def_documents_snapshot(), target)) or None return effective_def_locations(LSP_SERVER.def_documents_snapshot(), target) or None
locations = [lsp.Location(uri=index.uri, range=occurrence.range) for index, occurrence in matching_occurrences(identity, indexes, definitions) if occurrence.is_definition] locations = [lsp.Location(uri=index.uri, range=occurrence.range) for index, occurrence in matching_occurrences(identity, indexes, definitions) if occurrence.is_definition]
return _sorted_locations(locations) or None return _sorted_locations(locations) or None
@@ -1033,7 +1033,7 @@ def def_definition(params) -> list[lsp.Location] | None:
if context is None: if context is None:
return None return None
documents, def_uris, target, _ = context documents, def_uris, target, _ = context
return _sorted_locations(def_definition_locations(documents, target, def_uris)) or None return effective_def_locations(documents, target, def_uris) or None
@LSP_SERVER.feature(DEF_REQUEST_HOVER) @LSP_SERVER.feature(DEF_REQUEST_HOVER)
+14 -4
View File
@@ -79,6 +79,13 @@ def def_definition_locations(
] ]
def effective_def_locations(
documents: dict[str, DefDocument], target: DefTarget, uris: dict[str, str] | None = None
) -> list[lsp.Location]:
"""The declaration NX uses: ``documents`` follow the PSC layer order, the last one wins."""
return def_definition_locations(documents, target, uris)[-1:]
def def_reference_locations( def def_reference_locations(
documents: dict[str, DefDocument], documents: dict[str, DefDocument],
target: DefTarget, target: DefTarget,
@@ -170,6 +177,7 @@ def _address_table(declaration: DefDeclaration, formats: dict[str, DefDeclaratio
def def_hover_markdown(documents: dict[str, DefDocument], target: DefTarget) -> str | None: def def_hover_markdown(documents: dict[str, DefDocument], target: DefTarget) -> str | None:
"""Describe the effective (last loaded) declaration and name the ones it overrides."""
declarations = def_declarations(documents, target) declarations = def_declarations(documents, target)
if not declarations: if not declarations:
return None return None
@@ -180,12 +188,14 @@ def def_hover_markdown(documents: dict[str, DefDocument], target: DefTarget) ->
if declaration.kind == FORMAT if declaration.kind == FORMAT
} }
kind, name = target kind, name = target
sections = [] path, declaration = declarations[-1]
for path, declaration in declarations:
header = f"**{_KIND_LABELS[kind]}** `{name}` — {Path(path).name}:{declaration.line + 1}" header = f"**{_KIND_LABELS[kind]}** `{name}` — {Path(path).name}:{declaration.line + 1}"
if kind == ADDRESS: if kind == ADDRESS:
body = _address_table(declaration, formats) body = _address_table(declaration, formats)
else: else:
body = f"```def\n{declaration.text}\n```" body = f"```def\n{declaration.text}\n```"
sections.append(f"{header}\n\n{body}") markdown = f"{header}\n\n{body}"
return "\n\n---\n\n".join(sections) if len(declarations) > 1:
overridden = ", ".join(f"{Path(other).name}:{item.line + 1}" for other, item in declarations[:-1])
markdown += f"\n\n---\n\n_Overrides {overridden}_"
return markdown
@@ -240,3 +240,50 @@ def test_def_rename_updates_tcl_callers(tmp_path, monkeypatch):
edits = {Path(uri).name: [(e.range.start.line, e.range.start.character) for e in items] for uri, items in edit.changes.items()} edits = {Path(uri).name: [(e.range.start.line, e.range.start.character) for e in items] for uri, items in edit.changes.items()}
assert edits == {"caller.tcl": [(1, 20)], "service.def": [(14, 19)]} assert edits == {"caller.tcl": [(1, 20)], "service.def": [(14, 19)]}
assert lsp_server.def_rename(_def_params(def_file, "steady_rest", newName="bad name")) is None assert lsp_server.def_rename(_def_params(def_file, "steady_rest", newName="bad name")) is None
LAYERED_PSC = """<?xml version="1.0" encoding="UTF-8"?>
<Configuration>
<Sourcing><Sequence>
<Layer Name="Controller" SubFolder="ctrl"><DefinedEvents><Filename Name="ctrl"/></DefinedEvents></Layer>
<Layer Name="OEM" SubFolder="oem"><DefinedEvents><Filename Name="oem"/></DefinedEvents></Layer>
</Sequence></Sourcing>
</Configuration>
"""
def test_template_in_several_layers_shows_the_last_loaded_one(tmp_path, monkeypatch):
for folder, text in (("ctrl", "Text[M17]"), ("oem", "Text[RET]")):
(tmp_path / folder).mkdir()
(tmp_path / folder / f"{folder}.def").write_text(
f"MACHINE X\n\nFORMATTING\n{{\n BLOCK_TEMPLATE end_of_subprogram\n {{\n {text}\n }}\n}}\n", encoding="utf-8"
)
(tmp_path / "post.psc").write_text(LAYERED_PSC, encoding="utf-8")
source = "MOM_do_template end_of_subprogram\n"
caller = tmp_path / "caller.tcl"
caller.write_text(source, encoding="utf-8")
server = TclLanguageServer(name="def-layer-test", version="1", max_workers=1)
server.protocol._workspace = Workspace( # pylint: disable=protected-access
root_uri=tmp_path.as_uri(),
sync_kind=lsp.TextDocumentSyncKind.Incremental,
workspace_folders=[lsp.WorkspaceFolder(uri=tmp_path.as_uri(), name="root")],
position_encoding=lsp.PositionEncodingKind.Utf16,
)
monkeypatch.setattr(lsp_server, "LSP_SERVER", server)
server.refresh_psc_scripts([tmp_path])
server.workspace.put_text_document(lsp.TextDocumentItem(uri=caller.as_uri(), language_id="tcl", version=1, text=source))
server.update_poco_completion_for_file(server.workspace.get_text_document(caller.as_uri()))
document = lsp.TextDocumentIdentifier(uri=caller.as_uri())
position = lsp.Position(line=0, character=20)
hover = lsp_server.hover(lsp.HoverParams(text_document=document, position=position)).contents.value
assert hover.startswith("**Block template** `end_of_subprogram` — oem.def:5")
assert "Text[RET]" in hover and "Text[M17]" not in hover
assert hover.endswith("_Overrides ctrl.def:5_")
[location] = lsp_server.goto_definition(lsp.DefinitionParams(text_document=document, position=position))
assert location.uri.endswith("oem/oem.def")
references = lsp_server.references(
lsp.ReferenceParams(text_document=document, position=position, context=lsp.ReferenceContext(include_declaration=True))
)
assert {Path(location.uri).name for location in references} == {"ctrl.def", "oem.def", "caller.tcl"}