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}