1use super::SourceId;
2use crate::{encodings::*, FileSystem};
3use std::{any::TypeId, convert::TryInto, fmt, ops::Range};
4
5#[allow(unused)]
7fn is_empty<T: 'static>(_t: &T) -> bool {
8 TypeId::of::<T>() == TypeId::of::<()>()
9}
10
11#[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 pub fn is_adjacent_to(&self, other: impl Into<Start>) -> bool {
73 self.end == other.into().0
74 }
75
76 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
146impl<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#[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#[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#[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#[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#[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 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}