132 lines
3.7 KiB
Python
132 lines
3.7 KiB
Python
from typing import Optional
|
|
|
|
from docutils import nodes
|
|
from docutils.nodes import NodeVisitor
|
|
from lsprotocol.types import DocumentSymbol
|
|
from lsprotocol.types import Position
|
|
from lsprotocol.types import Range
|
|
from lsprotocol.types import SymbolKind
|
|
|
|
|
|
class SymbolVisitor(NodeVisitor):
|
|
"""A visitor used to build the hierarchy we return from a
|
|
``textDocument/documentSymbol`` request.
|
|
|
|
"""
|
|
|
|
def __init__(self, rst, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
|
|
self.logger = rst.logger
|
|
self.symbols = []
|
|
self.symbol_stack = []
|
|
|
|
@property
|
|
def current_symbol(self) -> Optional[DocumentSymbol]:
|
|
if len(self.symbol_stack) == 0:
|
|
return None
|
|
|
|
return self.symbol_stack[-1]
|
|
|
|
def push_symbol(self):
|
|
symbol = DocumentSymbol(
|
|
name="",
|
|
kind=SymbolKind.String,
|
|
range=Range(
|
|
start=Position(line=1, character=0),
|
|
end=Position(line=1, character=10),
|
|
),
|
|
selection_range=Range(
|
|
start=Position(line=1, character=0),
|
|
end=Position(line=1, character=10),
|
|
),
|
|
children=[],
|
|
)
|
|
current_symbol = self.current_symbol
|
|
|
|
if not current_symbol:
|
|
self.symbols.append(symbol)
|
|
else:
|
|
if current_symbol.children is None:
|
|
current_symbol.children = [symbol]
|
|
else:
|
|
current_symbol.children.append(symbol)
|
|
|
|
self.symbol_stack.append(symbol)
|
|
return symbol
|
|
|
|
def pop_symbol(self):
|
|
self.symbol_stack.pop()
|
|
|
|
def visit_section(self, node: nodes.Node) -> None:
|
|
self.push_symbol()
|
|
|
|
def depart_section(self, node: nodes.Node) -> None:
|
|
self.pop_symbol()
|
|
|
|
def visit_title(self, node: nodes.Node) -> None:
|
|
symbol = self.current_symbol
|
|
has_parent = True
|
|
|
|
if symbol is None:
|
|
has_parent = False
|
|
symbol = self.push_symbol()
|
|
|
|
name = node.astext()
|
|
line = (node.line or 1) - 1
|
|
|
|
symbol.name = name
|
|
symbol.range.start.line = line
|
|
symbol.range.end.line = line
|
|
symbol.range.end.character = len(name) - 1
|
|
symbol.selection_range.start.line = line
|
|
symbol.selection_range.end.line = line
|
|
symbol.selection_range.end.character = len(name) - 1
|
|
|
|
if not has_parent:
|
|
self.pop_symbol()
|
|
|
|
def depart_title(self, node: nodes.Node) -> None:
|
|
pass
|
|
|
|
def visit_a_directive(self, node: nodes.Element):
|
|
symbol = self.push_symbol()
|
|
|
|
name = node["text"] # type: ignore
|
|
line = (node.line or 1) - 1
|
|
|
|
symbol.name = name
|
|
symbol.kind = SymbolKind.Class
|
|
symbol.range.start.line = line
|
|
symbol.range.end.line = line
|
|
symbol.range.end.character = len(name) - 1
|
|
symbol.selection_range.start.line = line
|
|
symbol.selection_range.end.line = line
|
|
symbol.selection_range.end.character = len(name) - 1
|
|
|
|
def depart_a_directive(self, node: nodes.Node):
|
|
self.pop_symbol()
|
|
|
|
# TODO: Enable symbols for roles
|
|
# However the reported line numbers can be inaccurate...
|
|
def visit_a_role(self, node: nodes.Node) -> None:
|
|
...
|
|
|
|
def depart_a_role(self, node: nodes.Node) -> None:
|
|
...
|
|
|
|
# TODO: Enable symbols for definition list items
|
|
# However the reported line numbers appear to be inaccurate...
|
|
|
|
def visit_Text(self, node: nodes.Node) -> None:
|
|
pass
|
|
|
|
def depart_Text(self, node: nodes.Node) -> None:
|
|
pass
|
|
|
|
def unknown_visit(self, node: nodes.Node) -> None:
|
|
pass
|
|
|
|
def unknown_departure(self, node: nodes.Node) -> None:
|
|
pass
|