1#[cfg(test)]
5use std::str::from_utf8;
6use std::{borrow::Cow, collections::VecDeque, fmt::Debug, sync::Arc};
7
8use crate::{MietteError, MietteSpanContents, SourceCode, SourceSpan};
9
10fn context_info<'a>(
11 input: &'a [u8],
12 span: &SourceSpan,
13 context_lines_before: usize,
14 context_lines_after: usize,
15) -> Result<MietteSpanContents<'a>, MietteError> {
16 let span_offset = span.offset() as usize;
17 let span_len = span.len() as usize;
18 let mut line_count = 0usize;
19 let mut start_line = 0usize;
20 let mut before_lines_starts = VecDeque::new();
21 let mut current_line_start = 0usize;
22 let mut end_lines = 0usize;
23 let mut post_span = false;
24 let mut post_span_got_newline = false;
25
26 let mut cut = span_offset.saturating_sub(1).min(input.len());
32 if cut > 0 && input[cut - 1] == b'\r' {
33 cut -= 1;
34 }
35 for pos in memchr::memchr2_iter(b'\r', b'\n', &input[..cut]) {
36 if input[pos] == b'\n' && pos > 0 && input[pos - 1] == b'\r' {
38 continue;
39 }
40 let line_end = if input[pos] == b'\r' && pos + 1 < cut && input[pos + 1] == b'\n' {
42 pos + 1
43 } else {
44 pos
45 };
46 line_count += 1;
47 before_lines_starts.push_back(current_line_start);
48 if before_lines_starts.len() > context_lines_before {
49 start_line += 1;
50 before_lines_starts.pop_front();
51 }
52 current_line_start = line_end + 1;
53 }
54 let mut start_column = cut - current_line_start;
56 let mut offset = cut;
57 let mut iter = input[cut..].iter().copied().peekable();
58 while let Some(char) = iter.next() {
59 if matches!(char, b'\r' | b'\n') {
60 line_count += 1;
61 if char == b'\r' && iter.next_if_eq(&b'\n').is_some() {
62 offset += 1;
63 }
64 if offset < span_offset {
65 start_column = 0;
67 before_lines_starts.push_back(current_line_start);
68 if before_lines_starts.len() > context_lines_before {
69 start_line += 1;
70 before_lines_starts.pop_front();
71 }
72 } else if offset >= span_offset + span_len.saturating_sub(1) {
73 if post_span {
77 start_column = 0;
78 if post_span_got_newline {
79 end_lines += 1;
80 } else {
81 post_span_got_newline = true;
82 }
83 if end_lines >= context_lines_after {
84 offset += 1;
85 break;
86 }
87 }
88 }
89 current_line_start = offset + 1;
90 } else if offset < span_offset {
91 start_column += 1;
92 }
93
94 if offset >= (span_offset + span_len).saturating_sub(1) {
95 post_span = true;
96 if end_lines >= context_lines_after {
97 offset += 1;
98 break;
99 }
100 }
101
102 offset += 1;
103 }
104
105 if offset >= (span_offset + span_len).saturating_sub(1) {
106 let starting_offset = before_lines_starts
107 .front()
108 .copied()
109 .unwrap_or(if context_lines_before == 0 { span_offset } else { 0 });
110 if starting_offset > offset {
113 return Err(MietteError::OutOfBounds);
114 }
115 Ok(MietteSpanContents::new(
116 &input[starting_offset..offset],
117 (starting_offset as u32, (offset - starting_offset) as u32).into(),
118 start_line,
119 if context_lines_before == 0 { start_column } else { 0 },
120 line_count,
121 ))
122 } else {
123 Err(MietteError::OutOfBounds)
124 }
125}
126
127impl SourceCode for [u8] {
128 fn read_span<'a>(
129 &'a self,
130 span: &SourceSpan,
131 context_lines_before: usize,
132 context_lines_after: usize,
133 ) -> Result<MietteSpanContents<'a>, MietteError> {
134 let contents = context_info(self, span, context_lines_before, context_lines_after)?;
135 Ok(contents)
136 }
137}
138
139impl SourceCode for &[u8] {
140 fn read_span<'a>(
141 &'a self,
142 span: &SourceSpan,
143 context_lines_before: usize,
144 context_lines_after: usize,
145 ) -> Result<MietteSpanContents<'a>, MietteError> {
146 <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
147 }
148}
149
150impl SourceCode for Vec<u8> {
151 fn read_span<'a>(
152 &'a self,
153 span: &SourceSpan,
154 context_lines_before: usize,
155 context_lines_after: usize,
156 ) -> Result<MietteSpanContents<'a>, MietteError> {
157 <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
158 }
159}
160
161impl SourceCode for str {
162 fn read_span<'a>(
163 &'a self,
164 span: &SourceSpan,
165 context_lines_before: usize,
166 context_lines_after: usize,
167 ) -> Result<MietteSpanContents<'a>, MietteError> {
168 <[u8] as SourceCode>::read_span(
169 self.as_bytes(),
170 span,
171 context_lines_before,
172 context_lines_after,
173 )
174 }
175}
176
177impl SourceCode for &str {
179 fn read_span<'a>(
180 &'a self,
181 span: &SourceSpan,
182 context_lines_before: usize,
183 context_lines_after: usize,
184 ) -> Result<MietteSpanContents<'a>, MietteError> {
185 <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
186 }
187}
188
189impl SourceCode for String {
190 fn read_span<'a>(
191 &'a self,
192 span: &SourceSpan,
193 context_lines_before: usize,
194 context_lines_after: usize,
195 ) -> Result<MietteSpanContents<'a>, MietteError> {
196 <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
197 }
198}
199
200impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
201 fn read_span<'a>(
202 &'a self,
203 span: &SourceSpan,
204 context_lines_before: usize,
205 context_lines_after: usize,
206 ) -> Result<MietteSpanContents<'a>, MietteError> {
207 self.as_ref().read_span(span, context_lines_before, context_lines_after)
208 }
209
210 fn name(&self) -> Option<&str> {
211 self.as_ref().name()
212 }
213}
214
215impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
216where
217 T::Owned: Debug + Send + Sync,
223{
224 fn read_span<'a>(
225 &'a self,
226 span: &SourceSpan,
227 context_lines_before: usize,
228 context_lines_after: usize,
229 ) -> Result<MietteSpanContents<'a>, MietteError> {
230 self.as_ref().read_span(span, context_lines_before, context_lines_after)
231 }
232
233 fn name(&self) -> Option<&str> {
234 self.as_ref().name()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::SpanContents;
242
243 #[test]
244 fn basic() -> Result<(), MietteError> {
245 let src = String::from("foo\n");
246 let contents = src.read_span(&(0, 4).into(), 0, 0)?;
247 assert_eq!("foo\n", from_utf8(contents.data()).unwrap());
248 assert_eq!(0, contents.line());
249 assert_eq!(0, contents.column());
250 Ok(())
251 }
252
253 #[test]
254 fn shifted() -> Result<(), MietteError> {
255 let src = String::from("foobar");
256 let contents = src.read_span(&(3, 3).into(), 1, 1)?;
257 assert_eq!("foobar", from_utf8(contents.data()).unwrap());
258 assert_eq!(0, contents.line());
259 assert_eq!(0, contents.column());
260 Ok(())
261 }
262
263 #[test]
264 fn middle() -> Result<(), MietteError> {
265 let src = String::from("foo\nbar\nbaz\n");
266 let contents = src.read_span(&(4, 4).into(), 0, 0)?;
267 assert_eq!("bar\n", from_utf8(contents.data()).unwrap());
268 assert_eq!(1, contents.line());
269 assert_eq!(0, contents.column());
270 Ok(())
271 }
272
273 #[test]
274 fn middle_of_line() -> Result<(), MietteError> {
275 let src = String::from("foo\nbarbar\nbaz\n");
276 let contents = src.read_span(&(7, 4).into(), 0, 0)?;
277 assert_eq!("bar\n", from_utf8(contents.data()).unwrap());
278 assert_eq!(1, contents.line());
279 assert_eq!(3, contents.column());
280 Ok(())
281 }
282
283 #[test]
284 fn with_crlf() -> Result<(), MietteError> {
285 let src = String::from("foo\r\nbar\r\nbaz\r\n");
286 let contents = src.read_span(&(5, 5).into(), 0, 0)?;
287 assert_eq!("bar\r\n", from_utf8(contents.data()).unwrap());
288 assert_eq!(1, contents.line());
289 assert_eq!(0, contents.column());
290 Ok(())
291 }
292
293 #[test]
294 fn with_context() -> Result<(), MietteError> {
295 let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
296 let contents = src.read_span(&(8, 3).into(), 1, 1)?;
297 assert_eq!("foo\nbar\nbaz\n", from_utf8(contents.data()).unwrap());
298 assert_eq!(1, contents.line());
299 assert_eq!(0, contents.column());
300 Ok(())
301 }
302
303 #[test]
304 fn multiline_with_context() -> Result<(), MietteError> {
305 let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
306 let contents = src.read_span(&(9, 11).into(), 1, 1)?;
307 assert_eq!("\nfoo\nbar\nbaz\n\n", from_utf8(contents.data()).unwrap());
308 assert_eq!(2, contents.line());
309 assert_eq!(0, contents.column());
310 let span: SourceSpan = (8, 14).into();
311 assert_eq!(&span, contents.span());
312 Ok(())
313 }
314
315 #[test]
316 fn zero_length_span_just_past_eof() {
317 let src = String::from("a");
320 assert!(matches!(src.read_span(&(2, 0).into(), 0, 0), Err(MietteError::OutOfBounds)));
321 let src = String::new();
322 assert!(matches!(src.read_span(&(1, 0).into(), 0, 0), Err(MietteError::OutOfBounds)));
323 }
324
325 #[test]
326 fn multiline_with_context_line_start() -> Result<(), MietteError> {
327 let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
328 let contents = src.read_span(&(2, 0).into(), 2, 2)?;
329 assert_eq!("one\ntwo\n\n", from_utf8(contents.data()).unwrap());
330 assert_eq!(0, contents.line());
331 assert_eq!(0, contents.column());
332 let span: SourceSpan = (0, 9).into();
333 assert_eq!(&span, contents.span());
334 Ok(())
335 }
336}