Skip to main content

just_lsp/
position_ext.rs

1use super::*;
2
3pub trait PositionExt {
4  fn point(&self, document: &Document) -> Point;
5}
6
7impl PositionExt for lsp::Position {
8  /// LSP positions use a zero-based line index for `line` and a UTF-16
9  /// code-unit offset within that line for `character`.
10  ///
11  /// Ropey and Tree-sitter, however, operate on UTF-8 byte offsets. To bridge
12  /// this mismatch, we take the line number directly as the Tree-sitter `row`,
13  /// then look up the corresponding line in the Rope and convert the UTF-16
14  /// `character` offset into a char index and, from there, into a UTF-8 byte
15  /// offset for the `column`.
16  ///
17  /// The resulting `(row, column)` byte position is then used to locate the
18  /// node in the syntax tree.
19  fn point(&self, document: &Document) -> Point {
20    let row =
21      usize::try_from(self.line).expect("line index exceeds usize::MAX");
22
23    let line = document.content.line(row);
24
25    let utf16_cu = usize::try_from(self.character)
26      .expect("character index exceeds usize::MAX");
27
28    Point {
29      row,
30      column: line.char_to_byte(line.utf16_cu_to_char(utf16_cu)),
31    }
32  }
33}
34
35#[cfg(test)]
36mod tests {
37  use {super::*, pretty_assertions::assert_eq};
38
39  #[test]
40  fn converts_utf16_offsets_to_utf8_columns() {
41    let document = Document::from("a🧪b");
42
43    let position = lsp::Position {
44      line: 0,
45      character: 3,
46    };
47
48    let point = position.point(&document);
49
50    assert_eq!(point, Point { row: 0, column: 5 });
51  }
52}