This adds tooling to statically extract Tcl variable names from syntax trees without evaluating substitutions, enabling better completions for array elements and plain variables. The new helpers are wired into the completion and navigation flows and are supported by tests covering array keys and substitutions. - Introduce variable_names.py with variable_name() and array_key_parts() - Wire static name extraction into completion and symbol indexing - Add tests for array key completion with substitutions
45 lines
1.8 KiB
Python
45 lines
1.8 KiB
Python
"""Extract statically known variable names without evaluating Tcl substitutions."""
|
|
|
|
from tclint.syntax_tree import BareWord, CompoundBareWord, Node, QuotedWord, VarSub
|
|
|
|
|
|
def variable_name(node: Node) -> str | None:
|
|
contents = node.contents
|
|
if isinstance(contents, str):
|
|
return contents
|
|
if isinstance(node, (CompoundBareWord, QuotedWord)) and node.children:
|
|
first = node.children[0]
|
|
last = node.children[-1]
|
|
if (
|
|
isinstance(first, BareWord)
|
|
and isinstance(last, BareWord)
|
|
and "(" in first.value
|
|
and last.value.endswith(")")
|
|
):
|
|
# Substitutions in an array index do not change the array's name.
|
|
return first.value.split("(", 1)[0] or None
|
|
return None
|
|
|
|
|
|
def array_key_parts(node: Node, *, preserve_variables: bool = False) -> tuple[str | None, ...]:
|
|
"""Keep comma-separated literal index components; substitutions are unknown."""
|
|
contents = node.contents
|
|
if not isinstance(contents, str):
|
|
if not isinstance(node, (CompoundBareWord, QuotedWord)) or not variable_name(node):
|
|
return ()
|
|
chunks = []
|
|
for child in node.children:
|
|
if isinstance(child, BareWord):
|
|
chunks.append(child.value)
|
|
elif preserve_variables and isinstance(child, VarSub) and not child.children:
|
|
chunks.append("${" + child.value + "}" if child.braced else "$" + child.value)
|
|
else:
|
|
chunks.append("\0")
|
|
contents = "".join(chunks)
|
|
if "(" not in contents or not contents.endswith(")"):
|
|
return ()
|
|
return tuple(
|
|
part if part and not any(char in part for char in ("\0[]\\" if preserve_variables else "\0$[]\\")) else None
|
|
for part in contents.split("(", 1)[1][:-1].split(",")
|
|
)
|