Skip to main content

just_lsp/
point_ext.rs

1use super::*;
2
3pub trait PointExt {
4  #[must_use]
5  fn advance(self, delta: Point) -> Self;
6  fn position(&self, document: &Document) -> lsp::Position;
7}
8
9impl PointExt for Point {
10  /// Returns a new point shifted by `delta`, resetting the column when the
11  /// delta moves to a different row.
12  fn advance(self, delta: Point) -> Self {
13    if delta.row == 0 {
14      Point::new(self.row, self.column + delta.column)
15    } else {
16      Point::new(self.row + delta.row, delta.column)
17    }
18  }
19
20  /// Tree-sitter points use a zero-based `row` plus UTF-8 byte offset
21  /// `column`, while the LSP expects UTF-16 code-unit offsets.
22  ///
23  /// We take the document line for the point’s row, convert the byte column
24  /// into a char index, and then into a UTF-16 offset to produce an `lsp::Position`.
25  fn position(&self, document: &Document) -> lsp::Position {
26    let line = document.content.line(self.row);
27
28    let utf16_cu = line.char_to_utf16_cu(line.byte_to_char(self.column));
29
30    lsp::Position {
31      line: u32::try_from(self.row).expect("line index exceeds u32::MAX"),
32      character: u32::try_from(utf16_cu)
33        .expect("column index exceeds u32::MAX"),
34    }
35  }
36}
37
38#[cfg(test)]
39mod tests {
40  use {super::*, pretty_assertions::assert_eq};
41
42  #[test]
43  fn advance_adds_columns_when_staying_on_same_row() {
44    assert_eq!(Point::new(2, 3).advance(Point::new(0, 5)), Point::new(2, 8));
45  }
46
47  #[test]
48  fn advance_moves_rows_and_resets_column_when_row_delta_positive() {
49    assert_eq!(Point::new(1, 4).advance(Point::new(2, 3)), Point::new(3, 3));
50  }
51
52  #[test]
53  fn converts_utf8_columns_to_utf16_offsets() {
54    let document = Document::from("a𐐀b");
55
56    assert_eq!(
57      Point::new(0, document.content.line(0).char_to_byte(2))
58        .position(&document),
59      lsp::Position {
60        line: 0,
61        character: 3
62      }
63    );
64  }
65}