feat(def): add cross-file navigation and rename for PSC .def block templates and addresses
Add client and server support to navigate, inspect, reference, and rename block template and address symbols declared in PSC .def files: - Client: register language providers for .def (definition, hover, references, prepare/provide rename) and send the current document text with each request. - Server: parse .def files into DefDocument/DefDeclaration/DefReference, expose def-specific LSP endpoints (definition/hover/references/prepareRename/rename), and integrate .def lookups into existing Tcl hover/definition/references/rename flows so Tcl calls jump to .def declarations. - Tcl server keeps a snapshot API for .def documents (current editor content can replace file on request); only declared names in loaded .def files can be renamed. Name validation uses DEF_NAME_RE. Update README and CHANGELOG to document navigation features.
This commit is contained in:
+21
-14
@@ -18,7 +18,7 @@ from tools import checks, incremental_parse, parser
|
||||
from tools.completion_items import CompletionCollector
|
||||
from tools.tcloo_symbols import class_completion_items
|
||||
from tools.tcloo_completion import indexed_classes
|
||||
from tools.def_symbols import DefSymbols, read_def_symbols
|
||||
from tools.def_symbols import ADDRESS, BLOCK_TEMPLATE, DefDocument, parse_def_document, read_def_source
|
||||
from tools.file_sourcing import get_all_psc_files, psc_defined_event_files, psc_script_files
|
||||
from tools.formatter import NxFormatter as Formatter
|
||||
from tools.index_cache import FileStat, IndexCache, file_stat
|
||||
@@ -62,8 +62,8 @@ class TclLanguageServer(LanguageServer):
|
||||
self.psc_script_paths: list[str] = []
|
||||
self._psc_files: dict[str, list[pathlib.Path]] = {}
|
||||
self._psc_lock = threading.RLock()
|
||||
# .def path -> BLOCK_TEMPLATE/ADDRESS names, in PSC DefinedEvents order.
|
||||
self.def_symbols: dict[str, DefSymbols] = {}
|
||||
# .def path -> parsed declarations, in PSC DefinedEvents order.
|
||||
self.def_documents: dict[str, DefDocument] = {}
|
||||
self.navigation_indexes: dict[str, FileSymbolIndex] = {}
|
||||
self.variable_indexes: dict[
|
||||
str,
|
||||
@@ -257,7 +257,7 @@ class TclLanguageServer(LanguageServer):
|
||||
|
||||
def refresh_def_symbols(self, roots, report=LOGGER.warning):
|
||||
"""Read the block templates and addresses of all .def files listed as PSC DefinedEvents."""
|
||||
symbols: dict[str, DefSymbols] = {}
|
||||
documents: dict[str, DefDocument] = {}
|
||||
for root in roots:
|
||||
for psc in get_all_psc_files(root):
|
||||
try:
|
||||
@@ -266,33 +266,40 @@ class TclLanguageServer(LanguageServer):
|
||||
report(f"Could not read PSC {psc}: {error}")
|
||||
continue
|
||||
for def_file in def_files:
|
||||
if str(def_file) in symbols:
|
||||
if str(def_file) in documents:
|
||||
continue
|
||||
try:
|
||||
symbols[str(def_file)] = read_def_symbols(def_file)
|
||||
documents[str(def_file)] = parse_def_document(read_def_source(def_file))
|
||||
except OSError as error:
|
||||
report(f"Could not read DEF file {def_file}: {error}")
|
||||
with self._index_lock:
|
||||
self.def_symbols = symbols
|
||||
self.def_documents = documents
|
||||
|
||||
def _def_symbol_items(self, attribute: str, kind, description: str) -> list[lsp.CompletionItem]:
|
||||
def def_documents_snapshot(self, current_path=None, current_source: str | None = None) -> dict[str, DefDocument]:
|
||||
"""Return the PSC .def documents; ``current_source`` replaces the file being edited."""
|
||||
with self._index_lock:
|
||||
symbols = dict(self.def_symbols)
|
||||
documents = dict(self.def_documents)
|
||||
if current_path is not None and current_source is not None:
|
||||
key = next((path for path in documents if self.paths_equal(path, current_path)), os.fspath(current_path))
|
||||
documents[key] = parse_def_document(current_source)
|
||||
return documents
|
||||
|
||||
def _def_symbol_items(self, kind: str, item_kind, description: str) -> list[lsp.CompletionItem]:
|
||||
return [
|
||||
lsp.CompletionItem(
|
||||
label=name,
|
||||
kind=kind,
|
||||
kind=item_kind,
|
||||
detail=f"{description} ({pathlib.Path(path).name})",
|
||||
)
|
||||
for path, def_symbols in symbols.items()
|
||||
for name in getattr(def_symbols, attribute)
|
||||
for path, document in self.def_documents_snapshot().items()
|
||||
for name in document.names(kind)
|
||||
]
|
||||
|
||||
def block_template_items(self) -> list[lsp.CompletionItem]:
|
||||
return self._def_symbol_items("block_templates", lsp.CompletionItemKind.Struct, "Block template")
|
||||
return self._def_symbol_items(BLOCK_TEMPLATE, lsp.CompletionItemKind.Struct, "Block template")
|
||||
|
||||
def address_items(self) -> list[lsp.CompletionItem]:
|
||||
return self._def_symbol_items("addresses", lsp.CompletionItemKind.Field, "Address")
|
||||
return self._def_symbol_items(ADDRESS, lsp.CompletionItemKind.Field, "Address")
|
||||
|
||||
def refresh_psc_scripts(self, roots, report=LOGGER.warning):
|
||||
"""Index PSC dependencies through the same pipeline as workspace procs."""
|
||||
|
||||
Reference in New Issue
Block a user