feat(navigation): add symbol index and LSP navigation features

The changes introduce a Tcl symbol index powering LSP navigation
features across the workspace. A navigation API exposes
snapshots and update hooks, enabling goto-definition,
references, and rename using the index. Background indexing
now watches Tcl files and rebuilds the index to stay in sync.

- Add Tcl symbol index and navigation snapshot API
- Wire go-to-definition, references, and rename using the index
- Watch Tcl files and refresh the index in the background
This commit is contained in:
Christoph Brandau
2026-08-17 09:24:45 +02:00
parent f5bd79f067
commit 35a4357551
8 changed files with 1128 additions and 88 deletions
+14
View File
@@ -15,6 +15,7 @@ from tclint.violations import Violation
from tools import checks, parser
from tools.completion_items import CompletionCollector
from tools.formatter import NxFormatter as Formatter
from tools.navigation import FileSymbolIndex, build_file_symbol_index
from tools.proc_docs import build_proc_docs
@@ -31,6 +32,7 @@ class TclLanguageServer(server.LanguageServer):
self.poco_completion: dict = {}
self.proc_signatures: dict = {}
self.proc_docs: dict = {}
self.navigation_indexes: dict[str, FileSymbolIndex] = {}
# Cache: (uri, version) -> (tree, violations)
self._ast_cache = {}
self._parser_lock = threading.RLock()
@@ -115,6 +117,11 @@ class TclLanguageServer(server.LanguageServer):
with self._index_lock:
return self.diagnostics.get(uri)
def navigation_snapshot(self) -> dict[str, FileSymbolIndex]:
"""Return an immutable snapshot of the workspace symbol indexes."""
with self._index_lock:
return dict(self.navigation_indexes)
def _begin_index_update(self, filepath: str, version: int | None) -> int | None:
with self._index_lock:
indexed_version = self._index_versions.get(filepath)
@@ -140,6 +147,7 @@ class TclLanguageServer(server.LanguageServer):
self.poco_completion.pop(filepath, None)
self.proc_signatures.pop(filepath, None)
self.proc_docs.pop(filepath, None)
self.navigation_indexes.pop(filepath, None)
def indexed_paths_under_uri(self, uri: str) -> list[pathlib.Path]:
target = pathlib.Path(uris.to_fs_path(uri))
@@ -147,6 +155,7 @@ class TclLanguageServer(server.LanguageServer):
indexed_paths = set(self.poco_completion)
indexed_paths.update(self.proc_signatures)
indexed_paths.update(self.proc_docs)
indexed_paths.update(self.navigation_indexes)
indexed_paths.update(self._index_tokens)
return [
pathlib.Path(path)
@@ -163,6 +172,7 @@ class TclLanguageServer(server.LanguageServer):
self.poco_completion,
self.proc_signatures,
self.proc_docs,
self.navigation_indexes,
self._index_tokens,
self._index_versions,
):
@@ -212,6 +222,9 @@ class TclLanguageServer(server.LanguageServer):
)
tree.accept(collector, recurse=True)
docs = build_proc_docs(tree, document.source)
navigation_index = build_file_symbol_index(
filepath, document.uri, tree
)
except Exception as e:
logging.debug(f"Error parsing {filepath}: {e}")
self._discard_index_update(filepath, token)
@@ -227,6 +240,7 @@ class TclLanguageServer(server.LanguageServer):
self.poco_completion[filepath] = list(collector.custom_functions)
self.proc_signatures[filepath] = dict(collector.proc_signatures)
self.proc_docs[filepath] = docs
self.navigation_indexes[filepath] = navigation_index
return True
def format(