1use crate::Span;
2use std::fmt;
3use std::fs;
4use std::io;
5use std::path::{Path, PathBuf};
6use std::slice;
7
8#[derive(Default, Clone)]
10pub struct Source {
11 name: String,
13 source: String,
15 path: Option<PathBuf>,
17 line_starts: Vec<usize>,
19}
20
21impl Source {
22 pub fn new<N, S>(name: N, source: S) -> Self
24 where
25 N: AsRef<str>,
26 S: AsRef<str>,
27 {
28 let source = source.as_ref();
29 let line_starts = line_starts(source).collect::<Vec<_>>();
30
31 Self {
32 name: name.as_ref().to_owned(),
33 source: source.to_owned(),
34 path: None,
35 line_starts,
36 }
37 }
38
39 pub fn line_starts(&self) -> &[usize] {
41 &self.line_starts
42 }
43
44 pub fn from_path(path: &Path) -> io::Result<Self> {
46 let name = path.display().to_string();
47 let path = &path.canonicalize()?;
48
49 let source = fs::read_to_string(path)?;
50 let line_starts = line_starts(&source).collect::<Vec<_>>();
51
52 Ok(Self {
53 name,
54 source,
55 path: Some(path.to_owned()),
56 line_starts,
57 })
58 }
59
60 pub fn is_empty(&self) -> bool {
62 self.source.is_empty()
63 }
64
65 pub fn len(&self) -> usize {
67 self.source.len()
68 }
69
70 pub fn name(&self) -> &str {
72 &self.name
73 }
74
75 pub fn source(&self, span: Span) -> Option<&'_ str> {
77 self.get(span.range())
78 }
79
80 pub fn get<I>(&self, i: I) -> Option<&I::Output>
82 where
83 I: slice::SliceIndex<str>,
84 {
85 self.source.get(i)
86 }
87
88 pub fn end(&self) -> usize {
90 self.source.len()
91 }
92
93 pub fn as_str(&self) -> &str {
95 &self.source
96 }
97
98 pub fn path(&self) -> Option<&Path> {
100 self.path.as_deref()
101 }
102
103 pub fn path_mut(&mut self) -> &mut Option<PathBuf> {
105 &mut self.path
106 }
107
108 pub fn position_to_utf16cu_line_char(&self, offset: usize) -> Option<(usize, usize)> {
110 if offset == 0 {
111 return Some((0, 0));
112 }
113
114 let line = match self.line_starts.binary_search(&offset) {
115 Ok(exact) => exact,
116 Err(0) => return None,
117 Err(n) => n - 1,
118 };
119
120 let line_start = self.line_starts[line];
121
122 let rest = &self.source[line_start..];
123 let offset = offset - line_start;
124 let mut line_count = 0;
125
126 for (n, c) in rest.char_indices() {
127 if n == offset {
128 return Some((line, line_count));
129 }
130
131 if n > offset {
132 break;
133 }
134
135 line_count += c.encode_utf16(&mut [0u16; 2]).len();
136 }
137
138 Some((line, line_count))
139 }
140
141 pub fn position_to_unicode_line_char(&self, offset: usize) -> (usize, usize) {
143 if offset == 0 {
144 return (0, 0);
145 }
146
147 let line = match self.line_starts.binary_search(&offset) {
148 Ok(exact) => exact,
149 Err(0) => return (0, 0),
150 Err(n) => n - 1,
151 };
152
153 let line_start = self.line_starts[line];
154
155 let rest = &self.source[line_start..];
156 let offset = offset - line_start;
157 let mut line_count = 0;
158
159 for (n, _) in rest.char_indices() {
160 if n == offset {
161 return (line, line_count);
162 }
163
164 if n > offset {
165 break;
166 }
167
168 line_count += 1;
169 }
170
171 (line, line_count)
172 }
173}
174
175impl fmt::Debug for Source {
176 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177 f.debug_struct("Source")
178 .field("name", &self.name)
179 .field("path", &self.path)
180 .finish()
181 }
182}
183
184fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
185 std::iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
186}