1#![allow(dead_code)]
2use rowan::{TextRange, TextSize};
5use std::collections::BTreeMap;
6
7#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Copy, Clone, Default)]
8pub struct Position {
9 pub line: u64,
11 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 pub start: Position,
26 pub end: Position,
28}
29
30pub type CharacterOffset = u64;
33
34#[derive(Debug, Clone, Copy)]
36pub struct CharacterRange(u64, u64);
37
38#[derive(Debug, Clone)]
41pub struct Mapper {
42 offset_to_position: BTreeMap<TextSize, Position>,
44
45 position_to_offset: BTreeMap<Position, TextSize>,
47
48 lines: usize,
50
51 end: Position,
53}
54
55impl Mapper {
56 #[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 #[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 line += 1;
143 character = base;
144 }
145 }
146
147 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#[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}