refacxtor the code
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import lsprotocol.types as lsp
|
||||
|
||||
# Precompiled regex patterns for performance
|
||||
NS_RE = re.compile(r"^\s*namespace\s+eval\s+([^\s\{]+)")
|
||||
PROC_RE = re.compile(r"^\s*proc\s+([^\s\{]+)\s+\{.*\}\s+\{")
|
||||
SET_RE = re.compile(r"^\s*set\s+([^\s\}]+)")
|
||||
EVENT_START_RE = re.compile(r"^\s*LIB_GE_command_buffer_edit_(prepend|append|insert|replace)\b")
|
||||
EVENT_CLOSE_INLINE_RE = re.compile(r"^\s*\}\s*(\S+)\s*.*$")
|
||||
EVENT_NAME_LINE_RE = re.compile(r"^\s*([A-Za-z_][A-Za-z0-9_]*)\b")
|
||||
|
||||
|
||||
def build_document_symbols(source: str) -> list[lsp.DocumentSymbol]:
|
||||
"""Parse a Tcl document source and return DocumentSymbols.
|
||||
|
||||
This mirrors the previous inline implementation in lsp_server, but is extracted
|
||||
for readability and reuse.
|
||||
"""
|
||||
lines = source.split("\n")
|
||||
|
||||
class Scope:
|
||||
def __init__(self, name: str, symbol: lsp.DocumentSymbol, start_line: int):
|
||||
self.name = name
|
||||
self.symbol = symbol
|
||||
self.start_line = start_line
|
||||
self.brace_count = 0
|
||||
|
||||
root_symbol = lsp.DocumentSymbol(
|
||||
name="root",
|
||||
detail="",
|
||||
kind=lsp.SymbolKind.Namespace,
|
||||
range=lsp.Range(start=lsp.Position(0, 0), end=lsp.Position(len(lines), 0)),
|
||||
selection_range=lsp.Range(start=lsp.Position(0, 0), end=lsp.Position(0, 0)),
|
||||
children=[],
|
||||
)
|
||||
|
||||
scope_stack: list[Scope] = [Scope("", root_symbol, 0)]
|
||||
pending_event: dict | None = None
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
# If inside an event, update its brace count for this line
|
||||
if pending_event is not None:
|
||||
pe_open = line.count("{")
|
||||
pe_close = line.count("}")
|
||||
pending_event["brace_count"] = pending_event.get("brace_count", 1) + pe_open - pe_close
|
||||
# If this line closed the outer event block, finalize the event
|
||||
if pending_event["brace_count"] <= 0:
|
||||
# Try inline name on the same line
|
||||
close_inline = EVENT_CLOSE_INLINE_RE.match(line)
|
||||
if close_inline:
|
||||
event_name = close_inline.group(1)
|
||||
name_line_index = i
|
||||
else:
|
||||
# Look ahead to next non-empty line for the name
|
||||
j = i + 1
|
||||
while j < len(lines) and lines[j].strip() == "":
|
||||
j += 1
|
||||
event_name = None
|
||||
name_line_index = i
|
||||
if j < len(lines):
|
||||
name_line = lines[j]
|
||||
name_match = EVENT_NAME_LINE_RE.match(name_line)
|
||||
if name_match:
|
||||
event_name = name_match.group(1)
|
||||
name_line_index = j
|
||||
if event_name:
|
||||
start_line = pending_event["start"]
|
||||
edit_type = pending_event["edit_type"]
|
||||
children = pending_event.get("children", [])
|
||||
detail = f"Event ({edit_type})"
|
||||
event_symbol = lsp.DocumentSymbol(
|
||||
name=event_name,
|
||||
detail=detail,
|
||||
kind=lsp.SymbolKind.Event,
|
||||
range=lsp.Range(start=lsp.Position(start_line, 0), end=lsp.Position(name_line_index, len(lines[name_line_index]))),
|
||||
selection_range=lsp.Range(start=lsp.Position(name_line_index, 0), end=lsp.Position(name_line_index, len(lines[name_line_index]))),
|
||||
children=children or [],
|
||||
)
|
||||
if scope_stack[-1].symbol.children is None:
|
||||
scope_stack[-1].symbol.children = []
|
||||
scope_stack[-1].symbol.children.append(event_symbol)
|
||||
# Clear event tracking and continue
|
||||
pending_event = None
|
||||
continue
|
||||
|
||||
ns_match = NS_RE.match(line)
|
||||
proc_match = PROC_RE.match(line)
|
||||
set_match = SET_RE.match(line)
|
||||
|
||||
# Namespace
|
||||
if ns_match:
|
||||
ns_name = ns_match.group(1)
|
||||
start = lsp.Position(i, 0)
|
||||
end = lsp.Position(i, len(line))
|
||||
sel_start_char = line.find(ns_name)
|
||||
sel_end_char = sel_start_char + len(ns_name) if sel_start_char >= 0 else len(line)
|
||||
ns_symbol = lsp.DocumentSymbol(
|
||||
name=ns_name,
|
||||
detail="Namespace",
|
||||
kind=lsp.SymbolKind.Namespace,
|
||||
range=lsp.Range(start=start, end=end),
|
||||
selection_range=lsp.Range(
|
||||
start=lsp.Position(i, max(sel_start_char, 0)),
|
||||
end=lsp.Position(i, max(sel_end_char, 0)),
|
||||
),
|
||||
children=[],
|
||||
)
|
||||
scope = Scope(ns_name, ns_symbol, i)
|
||||
if scope_stack[-1].symbol.children is None:
|
||||
scope_stack[-1].symbol.children = []
|
||||
scope_stack[-1].symbol.children.append(ns_symbol)
|
||||
scope_stack.append(scope)
|
||||
|
||||
# Proc
|
||||
elif proc_match:
|
||||
proc_name = proc_match.group(1)
|
||||
start = lsp.Position(i, 0)
|
||||
end = lsp.Position(i, len(line))
|
||||
sel_start_char = line.find(proc_name)
|
||||
sel_end_char = sel_start_char + len(proc_name) if sel_start_char >= 0 else len(line)
|
||||
proc_symbol = lsp.DocumentSymbol(
|
||||
name=proc_name,
|
||||
detail="Procedure",
|
||||
kind=lsp.SymbolKind.Function,
|
||||
range=lsp.Range(start=start, end=end),
|
||||
selection_range=lsp.Range(
|
||||
start=lsp.Position(i, max(sel_start_char, 0)),
|
||||
end=lsp.Position(i, max(sel_end_char, 0)),
|
||||
),
|
||||
children=[],
|
||||
)
|
||||
scope = Scope(proc_name, proc_symbol, i)
|
||||
if scope_stack[-1].symbol.children is None:
|
||||
scope_stack[-1].symbol.children = []
|
||||
scope_stack[-1].symbol.children.append(proc_symbol)
|
||||
scope_stack.append(scope)
|
||||
|
||||
# set variable
|
||||
elif set_match:
|
||||
var_name = set_match.group(1)
|
||||
start = lsp.Position(i, 0)
|
||||
end = lsp.Position(i, len(line))
|
||||
sel_start_char = line.find(var_name)
|
||||
sel_end_char = sel_start_char + len(var_name) if sel_start_char >= 0 else len(line)
|
||||
var_symbol = lsp.DocumentSymbol(
|
||||
name=var_name,
|
||||
detail="Variable",
|
||||
kind=lsp.SymbolKind.Variable,
|
||||
range=lsp.Range(start=start, end=end),
|
||||
selection_range=lsp.Range(
|
||||
start=lsp.Position(i, max(sel_start_char, 0)),
|
||||
end=lsp.Position(i, max(sel_end_char, 0)),
|
||||
),
|
||||
children=None,
|
||||
)
|
||||
# Attach to current scope or pending event as child
|
||||
if pending_event is not None:
|
||||
pending_event["children"].append(var_symbol)
|
||||
else:
|
||||
if scope_stack[-1].symbol.children is None:
|
||||
scope_stack[-1].symbol.children = []
|
||||
scope_stack[-1].symbol.children.append(var_symbol)
|
||||
|
||||
# Event start (buffer edit)
|
||||
m = EVENT_START_RE.match(line)
|
||||
if m:
|
||||
edit_type = m.group(1)
|
||||
pending_event = {"start": i, "edit_type": edit_type, "children": []}
|
||||
continue
|
||||
|
||||
# Brace balancing for scopes (namespace/proc)
|
||||
open_count = line.count("{")
|
||||
close_count = line.count("}")
|
||||
scope_stack[-1].brace_count += open_count - close_count
|
||||
|
||||
# Close finished scopes
|
||||
while len(scope_stack) > 1 and scope_stack[-1].brace_count <= 0:
|
||||
finished = scope_stack.pop()
|
||||
# Set the full range from startLine to current line
|
||||
finished.symbol.range = lsp.Range(
|
||||
start=lsp.Position(finished.start_line, 0),
|
||||
end=lsp.Position(i, len(line)),
|
||||
)
|
||||
|
||||
# Return top-level children
|
||||
return root_symbol.children
|
||||
|
||||
@@ -26,6 +26,11 @@ class Token:
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user