Skip to main content

fission_text_engine/
coordinate.rs

1//! Coordinate mapper: byte offset <-> line/col <-> LSP position (UTF-16).
2
3use crate::line_index::{LineCol, LineIndex};
4
5/// An LSP-compatible position (0-based line, 0-based UTF-16 code-unit column).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7pub struct LspPosition {
8    /// 0-based line number.
9    pub line: usize,
10    /// 0-based column measured in UTF-16 code units.
11    pub character: usize,
12}
13
14impl LspPosition {
15    pub fn new(line: usize, character: usize) -> Self {
16        Self { line, character }
17    }
18}
19
20/// Stateless mapper that converts freely between three coordinate systems:
21///
22/// 1. **Byte offset** — absolute position in the UTF-8 buffer.
23/// 2. **Line / Col** ([`LineCol`]) — 0-based line and byte-column.
24/// 3. **LSP position** ([`LspPosition`]) — 0-based line and UTF-16 column.
25///
26/// All conversions go through a [`LineIndex`] that must be kept in sync with
27/// the buffer text (rebuild after each edit batch).
28pub struct CoordinateMapper<'a> {
29    index: &'a LineIndex,
30}
31
32impl<'a> CoordinateMapper<'a> {
33    /// Create a mapper backed by the given line index.
34    pub fn new(index: &'a LineIndex) -> Self {
35        Self { index }
36    }
37
38    // ── Byte <-> LineCol ────────────────────────────────────────────────
39
40    /// Byte offset -> `LineCol`.
41    pub fn byte_to_line_col(&self, byte_offset: usize) -> Option<LineCol> {
42        self.index.byte_to_line_col(byte_offset)
43    }
44
45    /// `LineCol` -> byte offset.
46    pub fn line_col_to_byte(&self, lc: LineCol) -> Option<usize> {
47        self.index.line_col_to_byte(lc)
48    }
49
50    // ── Byte <-> LspPosition ───────────────────────────────────────────
51
52    /// Byte offset -> `LspPosition`.
53    pub fn byte_to_lsp(&self, byte_offset: usize) -> Option<LspPosition> {
54        let (line, character) = self.index.byte_to_utf16_col(byte_offset)?;
55        Some(LspPosition { line, character })
56    }
57
58    /// `LspPosition` -> byte offset.
59    pub fn lsp_to_byte(&self, pos: LspPosition) -> Option<usize> {
60        self.index.utf16_col_to_byte(pos.line, pos.character)
61    }
62
63    // ── LineCol <-> LspPosition ────────────────────────────────────────
64
65    /// `LineCol` -> `LspPosition`.
66    pub fn line_col_to_lsp(&self, lc: LineCol) -> Option<LspPosition> {
67        let byte = self.index.line_col_to_byte(lc)?;
68        self.byte_to_lsp(byte)
69    }
70
71    /// `LspPosition` -> `LineCol`.
72    pub fn lsp_to_line_col(&self, pos: LspPosition) -> Option<LineCol> {
73        let byte = self.lsp_to_byte(pos)?;
74        self.byte_to_line_col(byte)
75    }
76}