1use std::fmt::{Debug, Display, Formatter};
2use std::hash::{Hash, Hasher};
3use std::ops::Range;
4use std::sync::{
5 Arc, OnceLock,
6 atomic::{AtomicU16, Ordering},
7};
8
9pub type Offset = usize;
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct LineColumn {
28 pub line: usize,
30 pub column: usize,
32}
33
34impl Display for LineColumn {
35 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
36 write!(formatter, "{}:{}", self.line, self.column)
37 }
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub struct LineColumnRange {
46 pub start: LineColumn,
48 pub end: LineColumn,
50}
51
52impl Display for LineColumnRange {
53 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
54 if self.start == self.end {
55 Display::fmt(&self.start, formatter)
56 } else {
57 write!(formatter, "{}-{}", self.start, self.end)
58 }
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum TextEdit {
74 Insertion {
76 at: Offset,
78 text: String,
80 },
81 Deletion {
83 start: Offset,
85 end: Offset,
87 },
88}
89
90impl TextEdit {
91 pub fn insertion(at: Offset, text: impl Into<String>) -> Self {
93 Self::Insertion {
94 at,
95 text: text.into(),
96 }
97 }
98
99 pub fn deletion(start: Offset, end: Offset) -> Self {
105 assert!(start <= end, "deletion start must not exceed its end");
106 Self::Deletion { start, end }
107 }
108
109 fn shift(&self, offset: Offset) -> Offset {
121 match self {
122 Self::Insertion { at, text } => {
123 if offset >= *at {
124 offset + text.len()
125 } else {
126 offset
127 }
128 }
129 Self::Deletion { start, end } => {
130 if offset <= *start {
131 offset
132 } else if offset <= *end {
133 *start
135 } else {
136 offset - (end - start)
137 }
138 }
139 }
140 }
141
142 pub fn apply(&self, source: &str) -> Option<String> {
145 let (cut, resumes) = match self {
146 Self::Insertion { at, .. } => (*at, *at),
147 Self::Deletion { start, end } => (*start, *end),
148 };
149 let mut edited = String::from(source.get(..cut)?);
150 if let Self::Insertion { text, .. } = self {
151 edited.push_str(text);
152 }
153 edited.push_str(source.get(resumes..)?);
154 Some(edited)
155 }
156}
157
158static NEXT_FILE_ID: AtomicU16 = AtomicU16::new(1);
160
161#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
167pub enum File {
168 FileId(u16),
170 Synthetic(u16),
172}
173
174impl File {
175 pub fn fresh() -> Self {
181 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
182 Self::FileId(id)
183 }
184
185 pub const fn from_raw(raw: u16) -> Self {
187 Self::FileId(raw)
188 }
189
190 pub const fn is_synthetic(self) -> bool {
193 matches!(self, Self::Synthetic(_))
194 }
195
196 pub const fn into_raw(self) -> u16 {
198 match self {
199 Self::FileId(id) | Self::Synthetic(id) => id,
200 }
201 }
202}
203
204pub type SourceId = File;
206
207pub struct Source {
209 file: File,
211 text: Arc<str>,
213 line_starts: OnceLock<Box<[Offset]>>,
215}
216
217impl Source {
218 pub fn new(text: impl Into<Arc<str>>) -> Arc<Self> {
220 Self::with_file(File::fresh(), text)
221 }
222
223 pub fn synthetic(text: impl Into<Arc<str>>) -> Arc<Self> {
225 let id = NEXT_FILE_ID.fetch_add(1, Ordering::Relaxed);
226 Self::with_file(File::Synthetic(id), text)
227 }
228
229 pub fn with_file(file: File, text: impl Into<Arc<str>>) -> Arc<Self> {
231 let text = text.into();
232 Arc::new(Self {
233 file,
234 text,
235 line_starts: OnceLock::new(),
236 })
237 }
238
239 pub fn file(&self) -> File {
241 self.file
242 }
243
244 pub fn text(&self) -> &str {
246 &self.text
247 }
248
249 pub fn trailing_whitespace(&self) -> Range<Offset> {
263 let significant = self
264 .text
265 .trim_end_matches(|character: char| character.is_ascii_whitespace());
266 significant.len()..self.text.len()
267 }
268
269 pub fn leading_whitespace(&self) -> Range<Offset> {
283 let layout = self.text.len()
284 - self
285 .text
286 .trim_start_matches(|character: char| character.is_ascii_whitespace())
287 .len();
288 let layout = if layout == self.text.len() { 0 } else { layout };
289 0..layout
290 }
291
292 pub fn line_column(&self, offset: Offset) -> Option<LineColumn> {
297 self.text.get(..offset)?;
298 let line_starts = self.line_starts.get_or_init(|| {
299 std::iter::once(0)
300 .chain(
301 self.text
302 .bytes()
303 .enumerate()
304 .filter_map(|(offset, byte)| (byte == b'\n').then_some(offset + 1)),
305 )
306 .collect::<Vec<_>>()
307 .into_boxed_slice()
308 });
309 let line_index = line_starts
310 .partition_point(|line_start| *line_start <= offset)
311 .checked_sub(1)?;
312 let line_start = line_starts[line_index];
313 let column = self.text.get(line_start..offset)?.chars().count() + 1;
314 Some(LineColumn {
315 line: line_index + 1,
316 column,
317 })
318 }
319}
320
321impl Debug for Source {
322 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
323 formatter
324 .debug_struct("Source")
325 .field("file", &self.file)
326 .field("bytes", &self.text.len())
327 .finish_non_exhaustive()
328 }
329}
330
331#[derive(Clone)]
333pub struct Span {
334 source: Arc<Source>,
335 range: Option<Range<Offset>>,
336}
337
338impl Span {
339 pub fn new(source: Arc<Source>, range: Option<Range<Offset>>) -> Self {
341 Self {
342 source,
343 range: range.filter(|range| range.start <= range.end),
344 }
345 }
346
347 pub fn ranged(source: Arc<Source>, range: Range<Offset>) -> Self {
349 Self::new(source, Some(range))
350 }
351
352 pub fn unlocated(source: Arc<Source>) -> Self {
354 Self {
355 source,
356 range: None,
357 }
358 }
359
360 pub fn for_text(text: impl Into<Arc<str>>) -> Self {
362 let source = Source::synthetic(text);
363 Self::ranged(Arc::clone(&source), 0..source.text().len())
364 }
365
366 pub fn file(&self) -> File {
368 self.source.file()
369 }
370
371 pub fn source(&self) -> &Arc<Source> {
373 &self.source
374 }
375
376 pub fn source_id(&self) -> File {
378 self.file()
379 }
380
381 pub fn range(&self) -> Option<Range<Offset>> {
383 self.range.clone()
384 }
385
386 pub fn with_source(mut self, source: Arc<Source>) -> Self {
388 self.source = source;
389 self
390 }
391
392 pub fn with_text(self, text: impl Into<Arc<str>>) -> Self {
398 let text = text.into();
399 if self.text() == Some(text.as_ref()) {
400 return self;
401 }
402 let source = Source::with_file(self.file(), text);
403 Self::ranged(Arc::clone(&source), 0..source.text().len())
404 }
405
406 pub fn text(&self) -> Option<&str> {
408 self.source.text().get(self.range.as_ref()?.clone())
409 }
410 pub fn at(&self, offset: Offset) -> Self {
412 if self.range.is_none() {
413 return Self::unlocated(Arc::clone(&self.source));
414 }
415 Self {
416 source: Arc::clone(&self.source),
417 range: Some(offset..offset),
418 }
419 }
420
421 pub fn subspan(&self, start: Offset, end: Offset) -> Self {
426 let Some(outer) = &self.range else {
427 return Self::unlocated(Arc::clone(&self.source));
428 };
429 let Some(start) = outer.start.checked_add(start) else {
430 return Self::unlocated(Arc::clone(&self.source));
431 };
432 let Some(end) = outer.start.checked_add(end) else {
433 return Self::unlocated(Arc::clone(&self.source));
434 };
435 if start > end || end > outer.end {
436 return Self::unlocated(Arc::clone(&self.source));
437 }
438 Self {
439 source: Arc::clone(&self.source),
440 range: Some(start..end),
441 }
442 }
443
444 pub fn start(&self) -> Option<Offset> {
446 self.bounds().map(|(start, _)| start)
447 }
448
449 pub fn end(&self) -> Option<Offset> {
451 self.bounds().map(|(_, end)| end)
452 }
453
454 pub fn bounds(&self) -> Option<(Offset, Offset)> {
456 self.range.as_ref().map(|range| (range.start, range.end))
457 }
458
459 pub fn line_column_range(&self) -> Option<LineColumnRange> {
464 let (start, end) = self.bounds()?;
465 Some(LineColumnRange {
466 start: self.source.line_column(start)?,
467 end: self.source.line_column(end)?,
468 })
469 }
470
471 pub fn len(&self) -> Option<usize> {
473 self.bounds().map(|(start, end)| end - start)
474 }
475
476 pub fn is_empty(&self) -> bool {
479 self.len().unwrap_or(0) == 0
480 }
481
482 pub fn shares_source(&self, other: &Self) -> bool {
484 Arc::ptr_eq(&self.source, &other.source)
485 }
486
487 pub fn contains(&self, offset: Offset) -> bool {
495 self.bounds()
496 .is_some_and(|(start, end)| start <= offset && offset < end)
497 }
498
499 pub fn touches(&self, offset: Offset) -> bool {
506 self.bounds()
507 .is_some_and(|(start, end)| start <= offset && offset <= end)
508 }
509
510 pub fn shifted(&self, edit: &TextEdit) -> Self {
523 match self.bounds() {
524 None => Self::unlocated(Arc::clone(&self.source)),
525 Some((start, end)) => Self {
526 source: Arc::clone(&self.source),
527 range: Some(edit.shift(start)..edit.shift(end)),
528 },
529 }
530 }
531
532 pub fn resized(&self, len: usize) -> Self {
535 let Some(start) = self.start() else {
536 return Self::unlocated(Arc::clone(&self.source));
537 };
538 Self {
539 source: Arc::clone(&self.source),
540 range: Some(start..start.saturating_add(len)),
541 }
542 }
543}
544
545impl Span {
546 pub fn join(&self, other: Self) -> Option<Self> {
549 if !self.shares_source(&other) {
550 return None;
551 }
552 match (self.bounds(), other.bounds()) {
553 (None, _) => Some(other),
554 (_, None) => Some(self.clone()),
555 (Some((start, end)), Some((start2, end2))) => {
556 let start = start.min(start2);
557 let end = end.max(end2);
558 Some(Self {
559 source: Arc::clone(&self.source),
560 range: Some(start..end),
561 })
562 }
563 }
564 }
565
566 pub fn meet(&self, other: Self) -> Option<Self> {
569 if !self.shares_source(&other) {
570 return None;
571 }
572 match (self.bounds(), other.bounds()) {
573 (Some((start, end)), Some((start2, end2))) => {
574 let start = start.max(start2);
575 let end = end.min(end2);
576 if start < end {
577 Some(Self {
578 source: Arc::clone(&self.source),
579 range: Some(start..end),
580 })
581 } else {
582 None
583 }
584 }
585 _ => None,
586 }
587 }
588
589 pub fn join_all<I, S>(spans: I) -> Option<Self>
592 where
593 I: IntoIterator<Item = S>,
594 S: Into<Option<Self>>,
595 {
596 let mut spans = spans.into_iter().filter_map(Into::into);
597 let first = spans.next()?;
598 spans.try_fold(first, |left, right| left.join(right))
599 }
600}
601
602impl PartialEq for Span {
603 fn eq(&self, other: &Self) -> bool {
604 self.shares_source(other) && self.range == other.range
605 }
606}
607
608impl Eq for Span {}
609
610impl Hash for Span {
611 fn hash<H: Hasher>(&self, state: &mut H) {
612 Arc::as_ptr(&self.source).hash(state);
613 self.range.hash(state);
614 }
615}
616
617impl Debug for Span {
618 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
619 let mut span = formatter.debug_struct("Span");
620 span.field("file", &self.file())
621 .field("range", &self.line_column_range())
622 .field("bytes", &self.range);
623 if let Some(text) = self.text() {
624 span.field("text", &text);
625 }
626 span.finish()
627 }
628}
629
630impl Display for Span {
631 fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
632 match self.line_column_range() {
633 Some(range) => Display::fmt(&range, formatter),
634 None => formatter.write_str("unlocated"),
635 }
636 }
637}
638
639pub trait Spanned {
641 fn span(&self) -> Span {
648 self.try_span()
649 .expect("syntax must have one shared source snapshot")
650 }
651
652 fn try_span(&self) -> Option<Span> {
655 Some(self.span())
656 }
657
658 fn expect_span(&self) -> Span {
664 self.span()
665 }
666}
667
668impl Spanned for Span {
669 fn span(&self) -> Span {
670 self.clone()
671 }
672}
673
674impl<T: Spanned + ?Sized> Spanned for &T {
675 fn span(&self) -> Span {
676 (*self).span()
677 }
678
679 fn try_span(&self) -> Option<Span> {
680 (*self).try_span()
681 }
682}
683
684impl<T: Spanned + ?Sized> Spanned for Box<T> {
685 fn span(&self) -> Span {
686 self.as_ref().span()
687 }
688
689 fn try_span(&self) -> Option<Span> {
690 self.as_ref().try_span()
691 }
692}
693
694impl<T: Spanned> Spanned for Option<T> {
695 fn try_span(&self) -> Option<Span> {
696 self.as_ref().and_then(Spanned::try_span)
697 }
698}
699
700impl<T: Spanned> Spanned for Vec<T> {
701 fn try_span(&self) -> Option<Span> {
702 Span::join_all(self.iter().map(Spanned::try_span))
703 }
704}
705
706impl<A: Spanned, B: Spanned> Spanned for (A, B) {
707 fn try_span(&self) -> Option<Span> {
708 self.0.try_span()?.join(self.1.try_span()?)
709 }
710}
711
712impl<T: Spanned> Spanned for &[T] {
713 fn try_span(&self) -> Option<Span> {
714 Span::join_all(self.iter().map(Spanned::try_span))
715 }
716}
717
718#[cfg(test)]
719mod source_tests {
720 use std::sync::Arc;
721
722 use super::*;
723
724 #[test]
725 fn spans_resolve_text_from_one_shared_source() {
726 let source = Source::new("quotient ring");
727 let quotient = Span::ranged(Arc::clone(&source), 0..8);
728 let ring = Span::ranged(Arc::clone(&source), 9..13);
729
730 assert_eq!(quotient.text(), Some("quotient"));
731 assert_eq!(ring.text(), Some("ring"));
732 assert!(Arc::ptr_eq(quotient.source(), ring.source()));
733 }
734
735 #[test]
736 fn joining_different_sources_returns_none() {
737 let left = Span::for_text("x");
738 let right = Span::for_text("y");
739
740 assert_eq!(left.join(right), None);
741 }
742
743 #[test]
744 fn an_unlocated_span_still_knows_its_source() {
745 let source = Source::new("x + y");
746 let span = Span::unlocated(Arc::clone(&source));
747
748 assert_eq!(span.file(), source.file());
749 assert_eq!(span.range(), None);
750 assert_eq!(span.text(), None);
751 }
752
753 #[test]
754 fn source_snapshots_with_the_same_file_do_not_alias() {
755 let file = File::from_raw(7);
756 let before = Span::ranged(Source::with_file(file, "x"), 0..1);
757 let after = Span::ranged(Source::with_file(file, "y"), 0..1);
758
759 assert_ne!(before, after);
760 assert_eq!(before.join(after), None);
761 }
762
763 #[test]
764 fn shifting_preserves_source_identity() {
765 let source = Source::new("ring");
766 let span = Span::ranged(Arc::clone(&source), 0..4);
767 let shifted = span.shifted(&TextEdit::insertion(0, "graded "));
768
769 assert!(Arc::ptr_eq(&source, shifted.source()));
770 assert_eq!(shifted.range(), Some(7..11));
771 }
772
773 #[test]
774 fn line_columns_are_one_based_and_count_characters() {
775 let source = Source::new("aα\nβc");
776
777 assert_eq!(
778 source.line_column(0),
779 Some(LineColumn { line: 1, column: 1 }),
780 );
781 assert_eq!(
782 source.line_column("aα\n".len()),
783 Some(LineColumn { line: 2, column: 1 }),
784 );
785 assert_eq!(
786 source.line_column("aα\nβ".len()),
787 Some(LineColumn { line: 2, column: 2 }),
788 );
789 assert_eq!(source.line_column(2), None);
790 }
791
792 #[test]
793 fn spans_format_as_half_open_line_column_ranges() {
794 let source = Source::new("one\ntwo");
795 let range = Span::ranged(Arc::clone(&source), 2..6);
796 let point = range.at(4);
797
798 assert_eq!(range.to_string(), "1:3-2:3");
799 assert_eq!(point.to_string(), "2:1");
800 }
801}