1use std::fmt;
2use std::path::{Path, PathBuf};
3
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
8pub struct SourceSpan {
9 pub file: PathBuf,
10 pub start: u32,
11 pub end: u32,
12}
13
14impl SourceSpan {
15 pub fn new(file: impl AsRef<Path>, start: u32, end: u32) -> Self {
17 debug_assert!(start <= end, "span start must not exceed end");
18
19 Self {
20 file: file.as_ref().to_path_buf(),
21 start,
22 end,
23 }
24 }
25
26 pub fn len(&self) -> u32 {
28 self.end.saturating_sub(self.start)
29 }
30
31 pub fn is_empty(&self) -> bool {
33 self.start == self.end
34 }
35
36 pub fn merge(&self, other: &Self) -> Option<Self> {
38 if self.file != other.file {
39 return None;
40 }
41
42 Some(Self {
43 file: self.file.clone(),
44 start: self.start.min(other.start),
45 end: self.end.max(other.end),
46 })
47 }
48
49 pub fn to_line_col(&self, source: &str) -> (usize, usize) {
51 let mut line = 1usize;
52 let mut col = 1usize;
53
54 for (idx, ch) in source.chars().enumerate() {
55 if idx >= self.start as usize {
56 break;
57 }
58
59 if ch == '\n' {
60 line += 1;
61 col = 1;
62 } else {
63 col += 1;
64 }
65 }
66
67 (line, col)
68 }
69
70 pub fn contains(&self, offset: u32) -> bool {
72 offset >= self.start && offset < self.end
73 }
74}
75
76impl fmt::Display for SourceSpan {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}:{}-{}", self.file.display(), self.start, self.end)
79 }
80}