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