update formatter

This commit is contained in:
2025-07-20 11:53:40 +02:00
parent d77bd5dc29
commit 7c8634b971
6 changed files with 228 additions and 591 deletions
+58
View File
@@ -0,0 +1,58 @@
import re
def format_tcl(src: str, indent_str=" ") -> str:
"""
Simple Tcl formatter with special handling for:
1) Single-line 'if {cond} {action}' blocks remain on one line.
2) Combined closing-and-opening lines like '} else {' dedent then re-indent.
3) Lines like '} Tag' stay on the same line: '} Tag'.
4) Standard multi-line blocks for 'if', 'elseif', 'else', '{', '}'.
"""
level = 0
out_lines = []
for raw_line in src.splitlines():
stripped = raw_line.strip()
# 1) Single-line 'if {cond} {action}' → no indent change
if re.match(r"^(if|elseif)\s*\{[^}]+\}\s*\{[^}]+\}$", stripped):
out_lines.append(indent_str * level + stripped)
continue
# 2) Combined '} else {' → dedent, print, then indent
if re.match(r"^\}\s*(elseif|else)\b.*\{$", stripped):
level = max(level - 1, 0)
out_lines.append(indent_str * level + stripped)
level += 1
continue
# 3) SPECIAL: closing brace plus tag on same line: '} Tag'
m = re.match(r"^\}\s+(\w+)$", stripped)
if m:
# close one block
level = max(level - 1, 0)
# stay on one line: "} Tag"
out_lines.append(f"{indent_str * level}}} {m.group(1)}")
continue
# 4) Pure '}' → dedent then print
if stripped == "}":
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# 5) 'elseif' or 'else' alone → align with matching 'if'
if re.match(r"^(elseif|else)\b(?!.*\{)", stripped):
level = max(level - 1, 0)
out_lines.append(f"{indent_str * level}{stripped}")
continue
# 6) Default: print at current indent
out_lines.append(f"{indent_str * level}{stripped}")
# 7) Open a new block on lines ending with '{'
if re.match(r"^(if|elseif)\b.*\{$", stripped) or stripped.endswith("{"):
level += 1
return "\n".join(out_lines)
+81
View File
@@ -0,0 +1,81 @@
# tokens.py
from typing import List
import attrs
import enum
from lark import Tree, Token
# Legend must match the client
SEMANTIC_TOKEN_TYPES = {
"keyword": 0,
"variable": 2,
"string": 3,
}
SEMANTIC_TOKEN_MODIFIERS = {
"declaration": 1 << 0,
}
class TokenModifier(enum.IntFlag):
deprecated = enum.auto()
readonly = enum.auto()
defaultLibrary = enum.auto()
definition = enum.auto()
@attrs.define
class TokenData:
line: int
offset: int
text: str
tok_type: str = ""
tok_modifiers: List[TokenModifier] = attrs.field(factory=list)
TokenTypes = ["keyword", "variable", "function", "operator", "parameter", "type"]
def collect_semantic_tokens(tree: Tree) -> list[int]:
"""
Walk the parse tree and return a flat LSP semanticTokens/full array:
[line, char, length, tokenType, tokenModifiers, …]
"""
data = []
for tok in tree.scan_values(lambda v: isinstance(v, Token)):
# 'set' keyword
if tok.type == "SET":
data.append(
[
tok.line - 1,
tok.column - 1,
len(tok.value),
SEMANTIC_TOKEN_TYPES["keyword"],
0,
]
)
# variable being declared
elif tok.type == "NAME":
data.append(
[
tok.line - 1,
tok.column - 1,
len(tok.value),
SEMANTIC_TOKEN_TYPES["variable"],
SEMANTIC_TOKEN_MODIFIERS["declaration"],
]
)
# string literal
elif tok.type == "STRING":
data.append(
[
tok.line - 1,
tok.column - 1,
len(tok.value),
SEMANTIC_TOKEN_TYPES["string"],
0,
]
)
# sort by position and flatten
data.sort(key=lambda x: (x[0], x[1]))
return [p for token in data for p in token]