feat(def_flow): recognize .def names propagated via variables and wrapper procs
Add a new def_flow analysis module that follows .def block template and address names through local variables and proc parameters, and a wrapper-table builder to resolve proc arguments that forward .def names. Derived names are resolved only for hover/definition (not for rename). Integrate this into the LSP: - lsp_server: add _word_at and _tcl_def_symbol helpers; fallback to derived_def_symbol when direct NX-argument navigation fails for hover, goto-definition and references; return proper ranges for hover. - lsp_tclserver: cache and expose a def_wrapper_table built from index def_flows (with cache invalidation on index generation). Also add unit tests for def_flow and update CHANGELOG to note hover/ definition and completion preview improvements for derived names.
This commit is contained in:
@@ -60,6 +60,7 @@ from tools.def_navigation import (
|
||||
def_rename_edits,
|
||||
def_symbol_at,
|
||||
)
|
||||
from tools.def_flow import derived_def_symbol
|
||||
from tools.def_symbols import parse_def_document
|
||||
from tools.folding_ranges import build_folding_ranges
|
||||
from tools.index_cache import IndexCache
|
||||
@@ -778,16 +779,53 @@ def _def_target(identity: SymbolIdentity) -> tuple[str, str] | None:
|
||||
return (identity.kind, identity.name) if identity.kind in DEF_SYMBOL_KINDS else None
|
||||
|
||||
|
||||
def _tcl_def_hover(uri: str, position: lsp.Position, token: str) -> lsp.Hover | None:
|
||||
def _word_at(uri: str, position: lsp.Position) -> str | None:
|
||||
document = LSP_SERVER.workspace.get_text_document(uri)
|
||||
try:
|
||||
line = LSP_SERVER.get_lines(document)[position.line]
|
||||
except IndexError:
|
||||
return None
|
||||
for match in re.finditer(r"\b\w+\b", line):
|
||||
if match.start() <= position.character <= match.end():
|
||||
return match.group(0)
|
||||
return None
|
||||
|
||||
|
||||
def _tcl_def_symbol(uri: str, position: lsp.Position, token: str | None):
|
||||
"""The .def target at ``position``: a direct NX command argument or a derived name.
|
||||
|
||||
Returns (documents, target, range) or None.
|
||||
"""
|
||||
documents = LSP_SERVER.def_documents_snapshot()
|
||||
if not any(token in document.names(kind) for document in documents.values() for kind in DEF_SYMBOL_KINDS):
|
||||
# Cheap guard: only names declared in a .def file are analyzed at all.
|
||||
kinds = {kind for kind in DEF_SYMBOL_KINDS if token and any(token in document.names(kind) for document in documents.values())}
|
||||
if not kinds:
|
||||
return None
|
||||
context = _navigation_context(uri, position)
|
||||
target = context and _def_target(context[3])
|
||||
markdown = target and def_hover_markdown(documents, target)
|
||||
if target:
|
||||
return documents, target, context[2].range
|
||||
try:
|
||||
tree = LSP_SERVER.get_tree(LSP_SERVER.workspace.get_text_document(uri))
|
||||
except TclSyntaxError:
|
||||
return None
|
||||
derived = derived_def_symbol(tree, position, LSP_SERVER.def_wrapper_table())
|
||||
if derived is None or derived[1] != token:
|
||||
return None
|
||||
found, name, range_ = derived
|
||||
kind = next((kind for kind in sorted(found & kinds) if def_declarations(documents, (kind, name))), None)
|
||||
return (documents, (kind, name), range_) if kind else None
|
||||
|
||||
|
||||
def _tcl_def_hover(uri: str, position: lsp.Position, token: str) -> lsp.Hover | None:
|
||||
symbol = _tcl_def_symbol(uri, position, token)
|
||||
if symbol is None:
|
||||
return None
|
||||
documents, target, range_ = symbol
|
||||
markdown = def_hover_markdown(documents, target)
|
||||
if not markdown:
|
||||
return None
|
||||
return lsp.Hover(lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=markdown), range=context[2].range)
|
||||
return lsp.Hover(lsp.MarkupContent(kind=lsp.MarkupKind.Markdown, value=markdown), range=range_)
|
||||
|
||||
|
||||
@LSP_SERVER.feature(lsp.TEXT_DOCUMENT_DEFINITION)
|
||||
@@ -809,7 +847,8 @@ def goto_definition(params: lsp.DefinitionParams):
|
||||
return [target]
|
||||
context = _navigation_context(params.text_document.uri, params.position)
|
||||
if context is None:
|
||||
return None
|
||||
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
|
||||
|
||||
indexes, definitions, _, identity = context
|
||||
target = _def_target(identity)
|
||||
@@ -853,7 +892,11 @@ def _sorted_locations(locations: list[lsp.Location]) -> list[lsp.Location]:
|
||||
def references(params: lsp.ReferenceParams) -> list[lsp.Location]:
|
||||
context = _navigation_context(params.text_document.uri, params.position)
|
||||
if context is None:
|
||||
return []
|
||||
symbol = _tcl_def_symbol(params.text_document.uri, params.position, _word_at(params.text_document.uri, params.position))
|
||||
if symbol is None:
|
||||
return []
|
||||
indexes, _ = LSP_SERVER.navigation_state()
|
||||
return all_def_target_locations(symbol[0], indexes, symbol[1], params.context.include_declaration)
|
||||
|
||||
indexes, definitions, _, identity = context
|
||||
target = _def_target(identity)
|
||||
|
||||
Reference in New Issue
Block a user