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:
@@ -7,6 +7,7 @@ from tclint.commands.plugins import PluginManager
|
||||
from tclint.syntax_tree import BareWord, Command, QuotedWord, Visitor
|
||||
from tools.variable_names import variable_name
|
||||
from tools.tcloo_symbols import class_symbols
|
||||
from tools.tcloo_completion import _analyze
|
||||
|
||||
# Constructing a PluginManager scans entry points, and get_commands() rebuilds
|
||||
# the builtin command set on every call. Semantic tokens are requested often, so
|
||||
@@ -72,12 +73,9 @@ class _Highlighter(Visitor):
|
||||
self._commands = _load_commands(plugins)
|
||||
self._tokens = []
|
||||
self._class_tokens = {}
|
||||
self._method_tokens = {}
|
||||
if isinstance(custom_functions, dict):
|
||||
self._custom_function_names = frozenset(
|
||||
item.label
|
||||
for items in custom_functions.values()
|
||||
for item in items
|
||||
)
|
||||
self._custom_function_names = frozenset(item.label for items in custom_functions.values() for item in items)
|
||||
else:
|
||||
self._custom_function_names = frozenset(custom_functions)
|
||||
|
||||
@@ -86,15 +84,18 @@ class _Highlighter(Visitor):
|
||||
return
|
||||
self._tokens.append((position, length, tok_type, modifiers or []))
|
||||
|
||||
def highlight_classes(self, tree):
|
||||
declarations, references = class_symbols(tree)
|
||||
def highlight_classes(self, tree, external_classes=None):
|
||||
declarations, references = class_symbols(tree, external_classes)
|
||||
for node, modifiers in [
|
||||
*((node, [TokenModifier.declaration]) for node in declarations.values()),
|
||||
*((node, []) for node in references),
|
||||
]:
|
||||
line, col = node.contents_pos
|
||||
self._class_tokens[(line - 1, col - 1)] = (
|
||||
(line - 1, col - 1), len(node.contents), "class", modifiers,
|
||||
(line - 1, col - 1),
|
||||
len(node.contents),
|
||||
"class",
|
||||
modifiers,
|
||||
)
|
||||
|
||||
def _get_token_info(self, node):
|
||||
@@ -124,6 +125,28 @@ class _Highlighter(Visitor):
|
||||
|
||||
return None
|
||||
|
||||
def highlight_methods(self, tree, source, uri, external_classes=None):
|
||||
"""Use the same function token as procs for resolved TclOO methods."""
|
||||
classes, _, calls = _analyze(tree, external_classes=external_classes, uri=uri, source=source)
|
||||
lines = source.splitlines()
|
||||
for info in classes.values():
|
||||
for location in info.method_definitions.values():
|
||||
if location.uri != uri:
|
||||
continue
|
||||
start, end = location.range.start, location.range.end
|
||||
encoded = lines[start.line].encode("utf-16-le")
|
||||
column = len(encoded[:start.character * 2].decode("utf-16-le"))
|
||||
length = len(encoded[start.character * 2:end.character * 2].decode("utf-16-le"))
|
||||
position = (start.line, column)
|
||||
self._method_tokens[position] = (position, length, "function", [TokenModifier.declaration])
|
||||
for call in calls:
|
||||
node = call.command.args[0]
|
||||
if node.contents is None or node.contents_pos is None:
|
||||
continue
|
||||
line, column = node.contents_pos
|
||||
position = (line - 1, column - 1)
|
||||
self._method_tokens[position] = (position, len(node.contents), "function", [])
|
||||
|
||||
def visit_quoted_word(self, word: QuotedWord):
|
||||
if not word.contents:
|
||||
return
|
||||
@@ -170,7 +193,7 @@ class _Highlighter(Visitor):
|
||||
if routine.contents == "puts":
|
||||
line, col = routine.contents_pos
|
||||
self._append_token((line - 1, col - 1), len(routine.contents), "function", [TokenModifier.builtin])
|
||||
if routine.contents == "set" and command.args:
|
||||
if routine.contents in ["set", "append", "lappend"] and command.args:
|
||||
first_arg = command.args[0]
|
||||
token_info = self._get_token_info(first_arg)
|
||||
if first_arg.contents is None:
|
||||
@@ -217,8 +240,9 @@ class _Highlighter(Visitor):
|
||||
tokens = []
|
||||
last_line = 0
|
||||
last_col = 0
|
||||
raw_tokens = [token for token in self._tokens if token[0] not in self._class_tokens]
|
||||
raw_tokens.extend(self._class_tokens.values())
|
||||
overrides = {**self._method_tokens, **self._class_tokens}
|
||||
raw_tokens = [token for token in self._tokens if token[0] not in overrides]
|
||||
raw_tokens.extend(overrides.values())
|
||||
for (line, col), length, tok_type, tok_modifier in sorted(raw_tokens, key=lambda x: x[0]):
|
||||
line_delta = line - last_line
|
||||
col_delta = col
|
||||
|
||||
Reference in New Issue
Block a user