source_map/
span.rs

1use super::SourceId;
2use crate::{encodings::*, FileSystem};
3use std::{any::TypeId, convert::TryInto, fmt, ops::Range};
4
5/// For serialization checking
6#[allow(unused)]
7fn is_empty<T: 'static>(_t: &T) -> bool {
8    TypeId::of::<T>() == TypeId::of::<()>()
9}
10
11/// A start and end. Also contains trace of original source (depending on `T`)
12#[derive(PartialEq, Eq, Clone, Copy, Hash)]
13#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
14#[cfg_attr(target_family = "wasm", derive(tsify::Tsify))]
15#[cfg_attr(
16    feature = "self-rust-tokenize",
17    derive(self_rust_tokenize::SelfRustTokenize)
18)]
19pub struct BaseSpan<T: 'static> {
20    pub start: u32,
21    pub end: u32,
22    #[cfg_attr(feature = "serde-serialize", serde(skip_serializing_if = "is_empty"))]
23    pub source: T,
24}
25
26#[cfg_attr(target_family = "wasm", tsify::declare)]
27pub type Span = BaseSpan<()>;
28#[cfg_attr(target_family = "wasm", tsify::declare)]
29pub type SpanWithSource = BaseSpan<SourceId>;
30
31pub trait Nullable: PartialEq + Eq + Sized {
32    const NULL: Self;
33
34    fn is_null(&self) -> bool {
35        self == &Self::NULL
36    }
37}
38
39impl Nullable for () {
40    const NULL: Self = ();
41}
42
43impl Nullable for SourceId {
44    const NULL: Self = SourceId(0);
45}
46
47impl<T: Nullable> Nullable for BaseSpan<T> {
48    const NULL: Self = BaseSpan {
49        start: 0,
50        end: 0,
51        source: T::NULL,
52    };
53}
54
55impl fmt::Debug for SpanWithSource {
56    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57        f.write_fmt(format_args!(
58            "{}..{}#{}",
59            self.start, self.end, self.source.0
60        ))
61    }
62}
63
64impl fmt::Debug for Span {
65    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
66        f.write_fmt(format_args!("{}..{}", self.start, self.end,))
67    }
68}
69
70impl Span {
71    /// Returns whether the end of `self` is the start of `other`
72    pub fn is_adjacent_to(&self, other: impl Into<Start>) -> bool {
73        self.end == other.into().0
74    }
75
76    /// Returns a new [`Span`] which starts at the start of `self` a ends at the end of `other`
77    pub fn union(&self, end: impl Into<End>) -> Span {
78        Span {
79            start: self.start,
80            end: end.into().0,
81            source: (),
82        }
83    }
84
85    pub fn get_end(&self) -> End {
86        End(self.end)
87    }
88
89    pub fn get_start(&self) -> Start {
90        Start(self.start)
91    }
92
93    pub fn with_source(self, source: SourceId) -> SpanWithSource {
94        SpanWithSource {
95            start: self.start,
96            end: self.end,
97            source,
98        }
99    }
100}
101
102impl SpanWithSource {
103    pub fn get_start(&self) -> Position {
104        Position(self.start, self.source)
105    }
106
107    pub fn get_end(&self) -> Position {
108        Position(self.end, self.source)
109    }
110
111    pub fn into_line_column_span<T: StringEncoding>(
112        self,
113        fs: &impl FileSystem,
114    ) -> LineColumnSpan<T> {
115        fs.get_source_by_id(self.source, |source| {
116            let line_start = source.line_starts.get_line_pos_is_on(self.start as usize);
117            let line_start_byte = source.line_starts.0[line_start];
118            let column_start =
119                T::get_encoded_length(&source.content[line_start_byte..(self.start as usize)]);
120
121            let line_end = source.line_starts.get_line_pos_is_on(self.end as usize);
122            let line_end_byte = source.line_starts.0[line_end];
123            let column_end =
124                T::get_encoded_length(&source.content[line_end_byte..(self.end as usize)]);
125
126            LineColumnSpan {
127                line_start: line_start as u32,
128                column_start: column_start as u32,
129                line_end: line_end as u32,
130                column_end: column_end as u32,
131                encoding: T::new(),
132                source: self.source,
133            }
134        })
135    }
136
137    pub fn without_source(self) -> Span {
138        Span {
139            start: self.start,
140            end: self.end,
141            source: (),
142        }
143    }
144}
145
146// TODO why are two implementations needed
147impl<T> From<BaseSpan<T>> for Range<u32> {
148    fn from(span: BaseSpan<T>) -> Range<u32> {
149        Range {
150            start: span.start,
151            end: span.end,
152        }
153    }
154}
155
156impl<T> From<BaseSpan<T>> for Range<usize> {
157    fn from(span: BaseSpan<T>) -> Range<usize> {
158        Range {
159            start: span.start.try_into().unwrap(),
160            end: span.end.try_into().unwrap(),
161        }
162    }
163}
164
165/// The byte start
166#[derive(Debug, Clone, Copy)]
167pub struct Start(pub u32);
168
169impl Start {
170    pub fn new(pos: u32) -> Self {
171        Self(pos)
172    }
173
174    pub fn with_length(&self, len: usize) -> BaseSpan<()> {
175        BaseSpan {
176            start: self.0,
177            end: self.0 + len as u32,
178            source: (),
179        }
180    }
181
182    pub fn get_end_after(&self, len: usize) -> End {
183        End(self.0 + len as u32)
184    }
185}
186
187/// The byte start
188#[derive(Debug, Clone, Copy)]
189pub struct End(pub u32);
190
191impl End {
192    pub fn new(pos: u32) -> Self {
193        Self(pos)
194    }
195
196    pub fn is_adjacent_to(&self, other: impl Into<Start>) -> bool {
197        self.0 == other.into().0
198    }
199}
200
201impl From<Span> for Start {
202    fn from(value: Span) -> Self {
203        Start(value.start)
204    }
205}
206
207impl From<Span> for End {
208    fn from(value: Span) -> Self {
209        End(value.end)
210    }
211}
212
213impl<'a> From<&'a Span> for Start {
214    fn from(value: &'a Span) -> Self {
215        Start(value.start)
216    }
217}
218
219impl<'a> From<&'a Span> for End {
220    fn from(value: &'a Span) -> Self {
221        End(value.end)
222    }
223}
224
225impl Start {
226    pub fn union(&self, end: impl Into<End>) -> Span {
227        Span {
228            start: self.0,
229            end: end.into().0,
230            source: (),
231        }
232    }
233}
234
235/// A scalar/singular byte wise position. **Zero based**
236#[derive(PartialEq, Eq, Clone)]
237pub struct Position(pub u32, pub SourceId);
238
239impl fmt::Debug for Position {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        f.write_fmt(format_args!("{}#{}", self.0, self.1 .0))
242    }
243}
244
245impl Position {
246    pub fn into_line_column_position<T: StringEncoding>(
247        self,
248        fs: &impl FileSystem,
249    ) -> LineColumnPosition<T> {
250        fs.get_source_by_id(self.1, |source| {
251            let line = source.line_starts.get_line_pos_is_on(self.0 as usize);
252            let line_byte = source.line_starts.0[line];
253            let column =
254                T::get_encoded_length(&source.content[line_byte..(self.0 as usize)]) as u32;
255            LineColumnPosition {
256                line: line as u32,
257                column,
258                encoding: T::new(),
259                source: self.1,
260            }
261        })
262    }
263}
264
265/// **Zero based**
266#[derive(Debug, PartialEq, Eq, Clone)]
267pub struct LineColumnPosition<T: StringEncoding> {
268    pub line: u32,
269    pub column: u32,
270    pub source: SourceId,
271    pub encoding: T,
272}
273
274impl<T: StringEncoding> LineColumnPosition<T> {
275    pub fn into_scalar_position(self, fs: &impl FileSystem) -> Position {
276        fs.get_source_by_id(self.source, |source| {
277            let line_byte = source.line_starts.0[self.line as usize];
278            let column_length =
279                T::encoded_length_to_byte_count(&source.content[line_byte..], self.column as usize);
280            Position((line_byte + column_length).try_into().unwrap(), self.source)
281        })
282    }
283}
284
285/// **Zero based**
286#[derive(Debug, PartialEq, Eq, Clone)]
287pub struct LineColumnSpan<T: StringEncoding> {
288    pub line_start: u32,
289    pub column_start: u32,
290    pub line_end: u32,
291    pub column_end: u32,
292    pub source: SourceId,
293    pub encoding: T,
294}
295
296impl<T: StringEncoding> LineColumnSpan<T> {
297    pub fn into_scalar_span(self, fs: &impl FileSystem) -> SpanWithSource {
298        fs.get_source_by_id(self.source, |source| {
299            let line_start_byte = source.line_starts.0[self.line_start as usize];
300            let column_start_length = T::encoded_length_to_byte_count(
301                &source.content[line_start_byte..],
302                self.column_start as usize,
303            );
304
305            let line_end_byte = source.line_starts.0[self.line_end as usize];
306            let column_end_length = T::encoded_length_to_byte_count(
307                &source.content[line_end_byte..],
308                self.column_start as usize,
309            );
310
311            SpanWithSource {
312                start: (line_start_byte + column_start_length).try_into().unwrap(),
313                end: (line_end_byte + column_end_length).try_into().unwrap(),
314                source: self.source,
315            }
316        })
317    }
318}
319
320#[cfg(feature = "lsp-types-morphisms")]
321impl Into<lsp_types::Position> for LineColumnPosition<Utf8> {
322    fn into(self) -> lsp_types::Position {
323        lsp_types::Position {
324            line: self.line,
325            character: self.column,
326        }
327    }
328}
329
330#[cfg(feature = "lsp-types-morphisms")]
331impl Into<lsp_types::Range> for LineColumnSpan<Utf8> {
332    fn into(self) -> lsp_types::Range {
333        lsp_types::Range {
334            start: lsp_types::Position {
335                line: self.line_start,
336                character: self.column_start,
337            },
338            end: lsp_types::Position {
339                line: self.line_end,
340                character: self.column_end,
341            },
342        }
343    }
344}
345
346#[cfg(feature = "lsp-types-morphisms")]
347impl From<lsp_types::Position> for LineColumnPosition<Utf8> {
348    fn from(lsp_position: lsp_types::Position) -> Self {
349        LineColumnPosition {
350            column: lsp_position.character,
351            line: lsp_position.line,
352            encoding: Utf8,
353            source: SourceId::NULL,
354        }
355    }
356}
357
358#[cfg(feature = "lsp-types-morphisms")]
359impl From<lsp_types::Range> for LineColumnSpan<Utf8> {
360    fn from(lsp_range: lsp_types::Range) -> Self {
361        LineColumnSpan {
362            line_start: lsp_range.start.line,
363            column_start: lsp_range.start.character,
364            line_end: lsp_range.end.line,
365            column_end: lsp_range.end.character,
366            encoding: Utf8,
367            // TODO not great
368            source: SourceId::NULL,
369        }
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use crate::{encodings::Utf8, MapFileStore, NoPathMap};
376
377    use super::*;
378
379    const SOURCE: &str = "Hello World
380I am a paragraph over two lines
381Another line";
382
383    fn get_file_system_and_source() -> (MapFileStore<NoPathMap>, SourceId) {
384        let mut fs = MapFileStore::default();
385        let source = fs.new_source_id("".into(), SOURCE.into());
386        (fs, source)
387    }
388
389    #[test]
390    fn scalar_span_to_line_column() {
391        let (fs, source) = get_file_system_and_source();
392
393        let paragraph_span = SpanWithSource {
394            start: 19,
395            end: 28,
396            source,
397        };
398
399        assert_eq!(&SOURCE[Range::from(paragraph_span.clone())], "paragraph");
400        assert_eq!(
401            paragraph_span.into_line_column_span(&fs),
402            LineColumnSpan {
403                line_start: 1,
404                column_start: 7,
405                line_end: 1,
406                column_end: 16,
407                encoding: Utf8,
408                source
409            }
410        );
411    }
412
413    #[test]
414    fn scalar_position_to_line_column() {
415        let (fs, source) = get_file_system_and_source();
416
417        let l_of_line_position = Position(52, source);
418        assert_eq!(&SOURCE[l_of_line_position.0.try_into().unwrap()..], "line");
419
420        assert_eq!(
421            l_of_line_position.into_line_column_position(&fs),
422            LineColumnPosition {
423                line: 2,
424                column: 8,
425                encoding: Utf8,
426                source
427            }
428        );
429    }
430
431    #[test]
432    fn line_column_position_to_position() {
433        let (fs, source) = get_file_system_and_source();
434        let start_of_another_position = LineColumnPosition {
435            line: 2,
436            column: 0,
437            source,
438            encoding: Utf8,
439        };
440        assert_eq!(
441            start_of_another_position.into_scalar_position(&fs),
442            Position(44, source)
443        );
444    }
445
446    #[test]
447    fn line_column_span_to_span() {
448        let (fs, source) = get_file_system_and_source();
449        let line_another_span = LineColumnSpan {
450            line_start: 1,
451            column_start: 26,
452            line_end: 2,
453            column_end: 12,
454            source,
455            encoding: Utf8,
456        };
457
458        let line_another_span = line_another_span.into_scalar_span(&fs);
459        assert_eq!(
460            &SOURCE[Range::from(line_another_span)],
461            "lines\nAnother line"
462        );
463    }
464}