feat(server): index PSC scripts and provide cross-file TclOO navigation/completions

Add PSC (.psc) indexing and share TclOO class metadata across files so class
definitions discovered via PSC layers can be used for completions, signature
help, inlay hints, and "go to definition". Key behavior changes:

- Client file watcher now includes *.psc and .vscode launch paths/tests updated
  to use the postprocessor test folder; .gitignore updated to ignore that folder.
- Server watches .psc changes and refreshes a PSC script index; new
  tools/tcloo_navigation.py exposes tcloo_definition used by the language server
  to resolve cross-file class/constructor/method definitions.
- Language server uses class_snapshot(document.path) when producing TclOO
  completions, signature help, and inlay hints so resolved class metadata is
  available across files.

Also includes related docs/changelog updates, minor code formatting cleanups,
and added tests for PSC/TclOO behavior.
This commit is contained in:
Christoph Brandau
2026-09-21 20:47:51 +02:00
parent f88a50d4ab
commit 757b885f28
21 changed files with 699 additions and 378 deletions
+22 -10
View File
@@ -1,4 +1,5 @@
import xml.etree.ElementTree as ET
import os
from dataclasses import dataclass
from typing import List, Optional
from pathlib import Path
@@ -14,6 +15,9 @@ class SourcedFile:
def read_psc_file(psc_file: Path) -> List[SourcedFile]:
tree = ET.parse(psc_file)
root = tree.getroot()
# PSC exports may use a default XML namespace.
for element in root.iter():
element.tag = element.tag.rsplit("}", 1)[-1]
layers = root.findall(".//Layer")
@@ -37,15 +41,23 @@ def read_psc_file(psc_file: Path) -> List[SourcedFile]:
def get_all_psc_files(root_path: Path) -> list[Path]:
return [path for path in root_path.rglob("*.psc")]
return sorted(root_path.rglob("*.psc"), key=lambda path: str(path).casefold())
if __name__ == "__main__":
test = get_all_psc_files(
Path(
r"H:\janus-engineering-customers\KSB_Frankenthal\custom\library\machine\installed_machines\ksb_pe_grob_g550_sone\postprocessor"
)
)
print(test)
for pp in test:
read_psc_file(pp)
def psc_script_files(psc_file: Path) -> list[Path]:
"""Resolve layer script paths relative to the PSC, preserving load order."""
def expanded(value):
return Path(os.path.expandvars(value).replace("\\", "/"))
paths = []
for layer in read_psc_file(psc_file):
folder = layer.subfolder or "."
base = psc_file.parent / expanded(os.environ.get(folder, folder))
for name in layer.files:
filename = expanded(name)
if not filename.suffix:
filename = filename.with_suffix(".tcl")
path = (base / filename).resolve()
if path.suffix.lower() == ".tcl":
paths.append(path)
return paths