1use std::sync::Arc;
2
3use crate::span::Span;
4
5#[derive(Clone)]
6pub struct Source {
7 text: Arc<str>,
8 name: Arc<str>,
9 line_starts: Vec<u32>,
10}
11
12impl Source {
13 pub fn new(name: impl Into<Arc<str>>, text: impl Into<Arc<str>>) -> Self {
14 let text: Arc<str> = text.into();
15 let line_starts = compute_line_starts(&text);
16 Self {
17 text,
18 name: name.into(),
19 line_starts,
20 }
21 }
22
23 pub fn name(&self) -> &str {
24 &self.name
25 }
26
27 pub fn text(&self) -> &str {
28 &self.text
29 }
30
31 pub fn line_col(&self, offset: u32) -> (usize, usize) {
32 let offset = offset as usize;
33 let line = self
34 .line_starts
35 .partition_point(|&start| (start as usize) <= offset)
36 .saturating_sub(1);
37 let line_start = self.line_starts.get(line).copied().unwrap_or(0) as usize;
38 let col = offset.saturating_sub(line_start);
39 (line + 1, col + 1)
40 }
41
42 pub fn line_text(&self, line: usize) -> &str {
43 if line == 0 || line > self.line_starts.len() {
44 return "";
45 }
46 let start = self.line_starts[line - 1] as usize;
47 let end = self
48 .line_starts
49 .get(line)
50 .map(|&e| e as usize)
51 .unwrap_or(self.text.len());
52 let text = &self.text[start..end];
53 text.trim_end_matches('\n').trim_end_matches('\r')
54 }
55
56 pub fn line_count(&self) -> usize {
57 self.line_starts.len()
58 }
59
60 pub fn slice(&self, span: Span) -> &str {
61 let start = span.start as usize;
62 let end = (span.end as usize).min(self.text.len());
63 if start <= end && end <= self.text.len() {
64 &self.text[start..end]
65 } else {
66 ""
67 }
68 }
69}
70
71fn compute_line_starts(text: &str) -> Vec<u32> {
72 let mut starts = vec![0];
73 for (i, c) in text.char_indices() {
74 if c == '\n' {
75 starts.push((i + 1) as u32);
76 }
77 }
78 starts
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn line_col_single_line() {
87 let src = Source::new("test.tla", "x = 42");
88 assert_eq!(src.line_col(0), (1, 1));
89 assert_eq!(src.line_col(2), (1, 3));
90 assert_eq!(src.line_col(5), (1, 6));
91 }
92
93 #[test]
94 fn line_col_multi_line() {
95 let src = Source::new("test.tla", "line1\nline2\nline3");
96 assert_eq!(src.line_col(0), (1, 1));
97 assert_eq!(src.line_col(5), (1, 6));
98 assert_eq!(src.line_col(6), (2, 1));
99 assert_eq!(src.line_col(12), (3, 1));
100 }
101
102 #[test]
103 fn line_text_retrieval() {
104 let src = Source::new("test.tla", "first\nsecond\nthird");
105 assert_eq!(src.line_text(1), "first");
106 assert_eq!(src.line_text(2), "second");
107 assert_eq!(src.line_text(3), "third");
108 }
109
110 #[test]
111 fn slice_extraction() {
112 let src = Source::new("test.tla", "hello world");
113 assert_eq!(src.slice(Span::new(0, 5)), "hello");
114 assert_eq!(src.slice(Span::new(6, 11)), "world");
115 }
116}