from dataclasses import dataclass from functools import cached_property from typing import overload from lsprotocol.types import Position, Range from parsimonious.nodes import Node # @total_ordering # class Position(NamedTuple): # line: int # char: int # # def __lt__(self, other: Self) -> bool: # return (self.line < other.line) or ( # (self.line == other.line) and (self.char < other.char) # ) # # def __eq__(self, other: Self) -> bool: # return (self.line == other.line) and (self.char == other.char) # # # class Range(NamedTuple): # start: Position # end: Position # # def __add__(self, other: Self) -> Self: # start = min(self.start, other.start) # end = max(self.end, other.end) # return Range(start, end) # # def contained_by(self, possibly_contained_by: Self) -> bool: # return (self.start >= possibly_contained_by.start) and ( # self.end <= possibly_contained_by.end # ) # # def contains(self, possibly_contains: Self) -> bool: # return (self.start <= possibly_contains.start) and ( # self.end >= possibly_contains.end # ) @dataclass(frozen=True) class Locator: raw: str @cached_property def newlines(self) -> tuple[int, ...]: t = tuple(i for i, char in enumerate(self.raw) if char == "\n") return t def _locate_pos(self, index: int) -> Position: line = next(i for i, char in enumerate(self.newlines) if char >= index) return Position(line - 1, index - (self.newlines[line - 1] if line > 0 else 0)) @overload def locate(self, index: int) -> Position: ... @overload def locate(self, index: Node) -> Range: ... def locate(self, index: int | Node) -> Position | Range: if isinstance(index, int): return self._locate_pos(index) start = self._locate_pos(index.start) end = self._locate_pos(index.end) return Range(start, end)