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:
@@ -5,6 +5,16 @@ from pathlib import Path
|
||||
|
||||
import lsprotocol.types as lsp
|
||||
from tclint.syntax_tree import Command, List, Node, QuotedWord, Script, VarSub
|
||||
from tools.def_flow import ( # noqa: F401 (re-exported)
|
||||
DEF_ADDRESS,
|
||||
DEF_BLOCK_TEMPLATE,
|
||||
DEF_SYMBOL_KINDS,
|
||||
ProcDefFlows,
|
||||
command_flow_facts,
|
||||
def_argument_kinds,
|
||||
proc_def_flows,
|
||||
)
|
||||
from tools.def_flow import def_name as _def_name
|
||||
from tools.variable_names import array_key_parts, variable_name
|
||||
|
||||
ROOT_NAMESPACE = "::"
|
||||
@@ -39,6 +49,8 @@ class FileSymbolIndex:
|
||||
uri: str
|
||||
occurrences: tuple[SymbolOccurrence, ...]
|
||||
document_range: lsp.Range | None = None
|
||||
# Procs whose parameters reach .def arguments: (qualified proc name, flows).
|
||||
def_flows: tuple[tuple[str, ProcDefFlows], ...] = ()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -114,49 +126,6 @@ def _name_range(node: Node, raw_name: str, *, variable_sub: bool = False) -> lsp
|
||||
)
|
||||
|
||||
|
||||
DEF_BLOCK_TEMPLATE = "block_template"
|
||||
DEF_ADDRESS = "address"
|
||||
DEF_SYMBOL_KINDS = frozenset({DEF_BLOCK_TEMPLATE, DEF_ADDRESS})
|
||||
|
||||
# NX commands taking .def names: command -> (first argument index, last index or
|
||||
# None for all following arguments, kind).
|
||||
_DEF_ARGUMENTS: dict[str, tuple[tuple[int, int | None, str], ...]] = {
|
||||
"MOM_do_template": ((0, 0, DEF_BLOCK_TEMPLATE),),
|
||||
"MOM_add_to_block_buffer": ((0, 0, DEF_BLOCK_TEMPLATE),),
|
||||
"MOM_polar_motion": ((0, 0, DEF_BLOCK_TEMPLATE),),
|
||||
"MOM_force_block": ((1, None, DEF_BLOCK_TEMPLATE),),
|
||||
"MOM_ask_address_value": ((0, 0, DEF_ADDRESS),),
|
||||
"MOM_add_to_address_buffer": ((0, 0, DEF_ADDRESS),),
|
||||
"MOM_enable_address": ((0, None, DEF_ADDRESS),),
|
||||
"MOM_disable_address": ((0, None, DEF_ADDRESS),),
|
||||
"MOM_force": ((1, None, DEF_ADDRESS),),
|
||||
"MOM_suppress": ((1, None, DEF_ADDRESS),),
|
||||
"MOM_incremental": ((1, None, DEF_ADDRESS),),
|
||||
}
|
||||
_DEFINITION_ELEMENT_KINDS = {"ADDRESS": DEF_ADDRESS, "BLOCK": DEF_BLOCK_TEMPLATE}
|
||||
|
||||
|
||||
def def_argument_kinds(command: Command) -> list[tuple[Node, str]]:
|
||||
"""Return the arguments of ``command`` that name a .def block template or address."""
|
||||
routine = _static_contents(command.routine)
|
||||
if routine in {"MOM_ask_definition_element", "MOM_has_definition_element"}:
|
||||
kind = _DEFINITION_ELEMENT_KINDS.get((_static_contents(command.args[0]) or "").upper()) if command.args else None
|
||||
return [(command.args[1], kind)] if kind and len(command.args) >= 2 else []
|
||||
result = []
|
||||
for first, last, kind in _DEF_ARGUMENTS.get(routine or "", ()):
|
||||
for position, argument in enumerate(command.args):
|
||||
if position >= first and (last is None or position <= last):
|
||||
result.append((argument, kind))
|
||||
return result
|
||||
|
||||
|
||||
def _def_name(node: Node) -> str | None:
|
||||
name = _static_contents(node)
|
||||
if not name or node.contents_pos is None or any(char.isspace() or char in "$[]{}\\\"" for char in name):
|
||||
return None
|
||||
return name
|
||||
|
||||
|
||||
def _node_range(node: Node) -> lsp.Range | None:
|
||||
if node.pos is None or node.end_pos is None:
|
||||
return None
|
||||
@@ -311,6 +280,9 @@ def build_file_symbol_index(
|
||||
# Most occurrences repeat a few identities; sharing one object per identity
|
||||
# keeps the index (and its persistent cache) small.
|
||||
identities: dict[SymbolIdentity, SymbolIdentity] = {}
|
||||
def_flows: list[tuple[str, ProcDefFlows]] = []
|
||||
# Flow facts of the procs being walked, innermost last.
|
||||
flow_facts: list[list] = []
|
||||
|
||||
def shared(identity: SymbolIdentity | None) -> SymbolIdentity | None:
|
||||
return None if identity is None else identities.setdefault(identity, identity)
|
||||
@@ -427,11 +399,13 @@ def build_file_symbol_index(
|
||||
)
|
||||
|
||||
parameters = command.args[1]
|
||||
parameter_names = []
|
||||
for parameter in getattr(parameters, "children", []):
|
||||
parameter_node = parameter
|
||||
if isinstance(parameter, List) and parameter.children:
|
||||
parameter_node = parameter.children[0]
|
||||
parameter_name = _static_contents(parameter_node)
|
||||
parameter_names.append(parameter_name or "")
|
||||
if parameter_name:
|
||||
add_variable(
|
||||
parameter_node,
|
||||
@@ -440,7 +414,12 @@ def build_file_symbol_index(
|
||||
is_definition=True,
|
||||
)
|
||||
|
||||
flow_facts.append([])
|
||||
walk_script(body, proc_scope)
|
||||
facts = flow_facts.pop()
|
||||
flows = proc_def_flows(facts, parameter_names, proc_namespace) if facts else ()
|
||||
if flows:
|
||||
def_flows.append((proc_identity.name, flows))
|
||||
|
||||
def walk_namespace(command: Command, scope: _Scope) -> bool:
|
||||
if len(command.args) < 3 or _static_contents(command.args[0]) != "eval":
|
||||
@@ -478,6 +457,8 @@ def build_file_symbol_index(
|
||||
|
||||
if routine:
|
||||
add_proc(command.routine, routine, scope, is_definition=False)
|
||||
if flow_facts:
|
||||
flow_facts[-1].extend(command_flow_facts(command))
|
||||
|
||||
for node, kind in def_argument_kinds(command):
|
||||
name = _def_name(node)
|
||||
@@ -550,6 +531,7 @@ def build_file_symbol_index(
|
||||
uri=uri,
|
||||
occurrences=tuple(occurrences),
|
||||
document_range=_node_range(tree),
|
||||
def_flows=tuple(def_flows),
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user