lsp_async_stub/
util.rs

1#![allow(dead_code)]
2//! Utilities for mapping between offset:length bytes and col:row character positions.
3
4use rowan::{TextRange, TextSize};
5use std::collections::BTreeMap;
6
7#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)]
8pub struct Position {
9    /// Line position in a document (could be zero-based or one-based based on the usage).
10    pub line: u64,
11    /// Character offset on a line in a document (could be zero-based or one-based based on the usage).
12    pub character: u64,
13}
14
15impl Position {
16    #[must_use]
17    pub fn new(line: u64, character: u64) -> Self {
18        Position { line, character }
19    }
20}
21
22#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
23pub struct Range {
24    /// The range's start position.
25    pub start: Position,
26    /// The range's end position.
27    pub end: Position,
28}
29
30/// Offset in characters instead of bytes.
31/// It is u64 because `lsp_types` uses u64.
32pub type CharacterOffset = u64;
33
34/// Inclusive offset range in characters instead of bytes.
35#[derive(Debug, Clone, Copy)]
36pub struct CharacterRange(u64, u64);
37
38/// A mapper that translates offset:length bytes to
39/// 1-based line:row characters.
40#[derive(Debug, Clone)]
41pub struct Mapper {
42    /// Mapping offsets to positions.
43    offset_to_position: BTreeMap<TextSize, Position>,
44
45    /// Mapping positions to offsets.
46    position_to_offset: BTreeMap<Position, TextSize>,
47
48    /// Line count.
49    lines: usize,
50
51    /// Ending position.
52    end: Position,
53}
54
55impl Mapper {
56    /// Creates a new Mapper that remembers where
57    /// each line starts and ends.
58    ///
59    /// Uses UTF-16 character sizes for positions.
60    #[must_use]
61    pub fn new_utf16(source: &str, one_based: bool) -> Self {
62        Self::new_impl(source, true, if one_based { 1 } else { 0 })
63    }
64
65    /// Uses UTF-8 character sizes for positions.
66    #[must_use]
67    pub fn new_utf8(source: &str, one_based: bool) -> Self {
68        Self::new_impl(source, false, if one_based { 1 } else { 0 })
69    }
70
71    #[must_use]
72    pub fn offset(&self, position: Position) -> Option<TextSize> {
73        self.position_to_offset.get(&position).copied()
74    }
75
76    #[must_use]
77    pub fn text_range(&self, range: Range) -> Option<TextRange> {
78        self.offset(range.start)
79            .and_then(|start| self.offset(range.end).map(|end| TextRange::new(start, end)))
80    }
81
82    #[must_use]
83    pub fn position(&self, offset: TextSize) -> Option<Position> {
84        self.offset_to_position.get(&offset).copied()
85    }
86
87    #[must_use]
88    pub fn range(&self, range: TextRange) -> Option<Range> {
89        self.position(range.start())
90            .and_then(|start| self.position(range.end()).map(|end| Range { start, end }))
91    }
92
93    #[must_use]
94    pub fn mappings(&self) -> (&BTreeMap<TextSize, Position>, &BTreeMap<Position, TextSize>) {
95        (&self.offset_to_position, &self.position_to_offset)
96    }
97
98    #[must_use]
99    pub fn line_count(&self) -> usize {
100        self.lines
101    }
102
103    #[must_use]
104    pub fn all_range(&self) -> Range {
105        Range {
106            start: Position {
107                line: 0,
108                character: 0,
109            },
110            end: self.end,
111        }
112    }
113
114    fn new_impl(source: &str, utf16: bool, base: u64) -> Self {
115        let mut offset_to_position = BTreeMap::new();
116        let mut position_to_offset = BTreeMap::new();
117
118        let mut line: u64 = base;
119        let mut character: u64 = base;
120        let mut last_offset = 0;
121
122        for c in source.chars() {
123            let new_offset = last_offset + c.len_utf8();
124
125            let character_size = if utf16 { c.len_utf16() } else { 1 };
126
127            offset_to_position.extend(
128                (last_offset..new_offset)
129                    .map(|b| (TextSize::from(b as u32), Position { line, character })),
130            );
131
132            position_to_offset.extend(
133                (last_offset..new_offset)
134                    .map(|b| (Position { line, character }, TextSize::from(b as u32))),
135            );
136
137            last_offset = new_offset;
138
139            character += character_size as u64;
140            if c == '\n' {
141                // LF is at the start of each line.
142                line += 1;
143                character = base;
144            }
145        }
146
147        // Last imaginary character.
148        offset_to_position.insert(
149            TextSize::from(last_offset as u32),
150            Position { line, character },
151        );
152        position_to_offset.insert(
153            Position { line, character },
154            TextSize::from(last_offset as u32),
155        );
156
157        Self {
158            offset_to_position,
159            position_to_offset,
160            lines: line as usize,
161            end: Position { line, character },
162        }
163    }
164}
165
166#[must_use]
167pub fn relative_position(position: Position, to: Position) -> Position {
168    if position.line == to.line {
169        Position {
170            line: 0,
171            character: position.character - to.character,
172        }
173    } else {
174        Position {
175            line: position.line - to.line,
176            character: position.character,
177        }
178    }
179}
180
181/// Ranges are relative start to start, not end to start.
182#[must_use]
183pub fn relative_range(range: Range, to: Range) -> Range {
184    let line_diff = range.end.line - range.start.line;
185    let start = relative_position(range.start, to.start);
186
187    let end = if line_diff == 0 {
188        Position {
189            line: start.line,
190            character: start.character + range.end.character - range.start.character,
191        }
192    } else {
193        Position {
194            line: start.line + line_diff,
195            character: range.end.character,
196        }
197    };
198
199    Range { start, end }
200}
201
202pub trait LspExt<T>: private::Sealed {
203    fn into_lsp(self) -> T;
204    fn from_lsp(val: T) -> Self;
205}
206
207impl private::Sealed for Position {}
208impl LspExt<lsp_types::Position> for Position {
209    fn into_lsp(self) -> lsp_types::Position {
210        lsp_types::Position {
211            line: self.line as u32,
212            character: self.character as u32,
213        }
214    }
215
216    fn from_lsp(val: lsp_types::Position) -> Self {
217        Self {
218            line: val.line as u64,
219            character: val.character as u64,
220        }
221    }
222}
223
224impl private::Sealed for Range {}
225impl LspExt<lsp_types::Range> for Range {
226    fn into_lsp(self) -> lsp_types::Range {
227        lsp_types::Range {
228            start: self.start.into_lsp(),
229            end: self.end.into_lsp(),
230        }
231    }
232
233    fn from_lsp(val: lsp_types::Range) -> Self {
234        Self {
235            start: Position::from_lsp(val.start),
236            end: Position::from_lsp(val.end),
237        }
238    }
239}
240
241mod private {
242    pub trait Sealed {}
243}
244
245#[cfg(test)]
246#[test]
247fn test_mapper() {
248    let s1 = r#"
249line-2
250line-3"#;
251
252    let mapper = Mapper::new_utf16(s1, false);
253
254    assert!(s1.len() == mapper.mappings().0.len() - 1);
255
256    assert!(
257        mapper.position(0.into()).unwrap()
258            == Position {
259                line: 0,
260                character: 0
261            }
262    );
263
264    assert!(
265        mapper
266            .position(TextSize::from(s1.len() as u32 - 1_u32))
267            .unwrap()
268            == Position {
269                line: 2,
270                character: 5
271            }
272    );
273}