198 lines
7.0 KiB
Python
198 lines
7.0 KiB
Python
import enum
|
||
from typing import List
|
||
from tclint.syntax_tree import Visitor, QuotedWord, Command, BareWord
|
||
from tclint.commands import get_commands
|
||
import attrs
|
||
from common.load_data import standard_items
|
||
from tools.completion_items import completion
|
||
import lsprotocol.types as lsp
|
||
|
||
|
||
class TokenModifier(enum.IntFlag):
|
||
deprecated = enum.auto()
|
||
readonly = enum.auto()
|
||
defaultLibrary = enum.auto()
|
||
definition = enum.auto()
|
||
declaration = enum.auto()
|
||
builtin = enum.auto()
|
||
|
||
|
||
@attrs.define
|
||
class Token:
|
||
line: int
|
||
offset: int
|
||
lenght: int
|
||
|
||
tok_type: str = ""
|
||
tok_modifiers: List[TokenModifier] = attrs.field(factory=list)
|
||
|
||
@property
|
||
def length(self) -> int:
|
||
"""Compatibility alias for misspelled 'lenght' field."""
|
||
return self.lenght
|
||
|
||
|
||
TOKEN_TYPES = [
|
||
"keyword",
|
||
"variable",
|
||
"function",
|
||
"operator",
|
||
"parameter",
|
||
"type",
|
||
"class",
|
||
"string",
|
||
"parameter",
|
||
]
|
||
|
||
|
||
class _Highlighter(Visitor):
|
||
def __init__(self, plugins, custom_functions: dict[str : list[lsp.CompletionItem]]):
|
||
self._commands = get_commands(plugins)
|
||
self._tokens = []
|
||
self.custom_functions = custom_functions
|
||
|
||
def _get_token_info(self, node):
|
||
"""Hilfsmethode um Token-Informationen aus verschiedenen Node-Typen zu extrahieren."""
|
||
if not hasattr(node, "pos"):
|
||
return None
|
||
|
||
# Einfacher Fall: Node hat direkten value
|
||
if hasattr(node, "value") and node.value is not None:
|
||
line, col = node.pos
|
||
return (line - 1, col - 1), len(node.value)
|
||
|
||
# CompoundBareWord: versuche erstes Segment
|
||
if hasattr(node, "children") and node.children:
|
||
first_segment = node.children[0]
|
||
if hasattr(first_segment, "value") and first_segment.value is not None and hasattr(first_segment, "pos"):
|
||
line, col = first_segment.pos
|
||
return (line - 1, col - 1), len(first_segment.value)
|
||
|
||
# Fallback: Gesamtlänge aus Positionen berechnen
|
||
if hasattr(node, "end_pos"):
|
||
start_line, start_col = node.pos
|
||
end_line, end_col = node.end_pos
|
||
if start_line == end_line:
|
||
length = end_col - start_col
|
||
return (start_line - 1, start_col - 1), length
|
||
|
||
return None
|
||
|
||
def visit_quoted_word(self, word: QuotedWord):
|
||
if not word.contents:
|
||
return
|
||
line, col = word.contents_pos
|
||
self._tokens.append(((line - 1, col - 1), len(word.contents), "string", []))
|
||
pass
|
||
|
||
def visit_bare_word(self, word: BareWord):
|
||
name = word.value
|
||
|
||
in_standard = any(item.label == name for item in standard_items.nx_procs)
|
||
|
||
in_custom = any(item.label == name for items in self.custom_functions.values() for item in items)
|
||
|
||
if in_standard or in_custom:
|
||
line, col = word.pos
|
||
self._tokens.append((((line - 1, col - 1), len(name), "function", [])))
|
||
|
||
def visit_command(self, command: Command):
|
||
routine = command.routine
|
||
|
||
if routine.contents == "puts":
|
||
line, col = routine.contents_pos
|
||
self._tokens.append(
|
||
(
|
||
(
|
||
(line - 1, col - 1),
|
||
len(routine.contents),
|
||
"function",
|
||
[TokenModifier.builtin],
|
||
)
|
||
)
|
||
)
|
||
if routine.contents == "set" and command.args:
|
||
first_arg = command.args[0]
|
||
token_info = self._get_token_info(first_arg)
|
||
if token_info:
|
||
(line, col), length = token_info
|
||
self._tokens.append(
|
||
(
|
||
(
|
||
(line, col),
|
||
length,
|
||
"variable",
|
||
[TokenModifier.declaration],
|
||
)
|
||
)
|
||
)
|
||
if routine.contents == "proc" and command.args:
|
||
first_arg = command.args[0]
|
||
if hasattr(first_arg, "pos") and hasattr(first_arg, "value"):
|
||
line, col = first_arg.pos
|
||
self._tokens.append(
|
||
(
|
||
(
|
||
(line - 1, col - 1),
|
||
len(first_arg.value),
|
||
"function",
|
||
[TokenModifier.declaration],
|
||
)
|
||
)
|
||
)
|
||
|
||
if len(command.args) >= 2:
|
||
param_list = command.args[1]
|
||
|
||
# BracedWord oder Liste erwartet
|
||
if hasattr(param_list, "children"):
|
||
for child in param_list.children:
|
||
# Parameter kann einfaches Wort sein
|
||
if hasattr(child, "value") and child.value is not None:
|
||
line, col = child.pos
|
||
self._tokens.append(
|
||
(
|
||
(line - 1, col - 1),
|
||
len(child.value),
|
||
"parameter",
|
||
[TokenModifier.declaration],
|
||
)
|
||
)
|
||
|
||
# Parameter mit Default-Wert ist meist eine List (z. B. {arg default})
|
||
elif hasattr(child, "children") and len(child.children) >= 1:
|
||
name_node = child.children[0]
|
||
if hasattr(name_node, "value") and hasattr(name_node, "pos"):
|
||
line, col = name_node.pos
|
||
self._tokens.append(
|
||
(
|
||
(line - 1, col - 1),
|
||
len(name_node.value),
|
||
"parameter",
|
||
[TokenModifier.declaration],
|
||
)
|
||
)
|
||
if routine.contents == "namespace" and command.args:
|
||
first_arg = command.args[1]
|
||
if hasattr(first_arg, "pos") and first_arg.value is not None:
|
||
line, col = first_arg.pos
|
||
self._tokens.append((((line - 1, col - 1), len(first_arg.value), "class", [])))
|
||
|
||
def tokens(self) -> list[Token]:
|
||
"""Encode tokens as described in
|
||
https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#textDocument_semanticTokens.
|
||
"""
|
||
tokens = []
|
||
last_line = 0
|
||
last_col = 0
|
||
for (line, col), length, tok_type, tok_modifier in sorted(self._tokens, key=lambda x: x[0]):
|
||
line_delta = line - last_line
|
||
col_delta = col
|
||
if line == last_line:
|
||
col_delta -= last_col
|
||
|
||
tokens.append(Token(line_delta, col_delta, length, tok_type, tok_modifier))
|
||
last_line, last_col = line, col
|
||
|
||
return tokens
|