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.front().copied().unwrap_or_else(|| {
73 if context_lines_before == 0 {
74 span.offset()
75 } else {
76 0
77 }
78 });
79 Ok(MietteSpanContents::new(
80 &input[starting_offset..offset],
81 (starting_offset, offset - starting_offset).into(),
82 start_line,
83 if context_lines_before == 0 {
84 start_column
85 } else {
86 0
87 },
88 line_count,
89 ))
90 } else {
91 Err(MietteError::OutOfBounds)
92 }
93}
94
95impl SourceCode for [u8] {
96 fn read_span<'a>(
97 &'a self,
98 span: &SourceSpan,
99 context_lines_before: usize,
100 context_lines_after: usize,
101 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
102 let contents = context_info(self, span, context_lines_before, context_lines_after)?;
103 Ok(Box::new(contents))
104 }
105}
106
107impl<'src> SourceCode for &'src [u8] {
108 fn read_span<'a>(
109 &'a self,
110 span: &SourceSpan,
111 context_lines_before: usize,
112 context_lines_after: usize,
113 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
114 <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
115 }
116}
117
118impl SourceCode for Vec<u8> {
119 fn read_span<'a>(
120 &'a self,
121 span: &SourceSpan,
122 context_lines_before: usize,
123 context_lines_after: usize,
124 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
125 <[u8] as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
126 }
127}
128
129impl SourceCode for str {
130 fn read_span<'a>(
131 &'a self,
132 span: &SourceSpan,
133 context_lines_before: usize,
134 context_lines_after: usize,
135 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
136 <[u8] as SourceCode>::read_span(
137 self.as_bytes(),
138 span,
139 context_lines_before,
140 context_lines_after,
141 )
142 }
143}
144
145impl<'s> SourceCode for &'s str {
147 fn read_span<'a>(
148 &'a self,
149 span: &SourceSpan,
150 context_lines_before: usize,
151 context_lines_after: usize,
152 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
153 <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
154 }
155}
156
157impl SourceCode for String {
158 fn read_span<'a>(
159 &'a self,
160 span: &SourceSpan,
161 context_lines_before: usize,
162 context_lines_after: usize,
163 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
164 <str as SourceCode>::read_span(self, span, context_lines_before, context_lines_after)
165 }
166}
167
168impl<T: ?Sized + SourceCode> SourceCode for Arc<T> {
169 fn read_span<'a>(
170 &'a self,
171 span: &SourceSpan,
172 context_lines_before: usize,
173 context_lines_after: usize,
174 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
175 self.as_ref()
176 .read_span(span, context_lines_before, context_lines_after)
177 }
178}
179
180impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
181where
182 T::Owned: Debug + Send + Sync,
188{
189 fn read_span<'a>(
190 &'a self,
191 span: &SourceSpan,
192 context_lines_before: usize,
193 context_lines_after: usize,
194 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
195 self.as_ref()
196 .read_span(span, context_lines_before, context_lines_after)
197 }
198}
199
200#[cfg(test)]
201mod tests {
202 use super::*;
203
204 #[test]
205 fn basic() -> Result<(), MietteError> {
206 let src = String::from("foo\n");
207 let contents = src.read_span(&(0, 4).into(), 0, 0)?;
208 assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
209 assert_eq!(0, contents.line());
210 assert_eq!(0, contents.column());
211 Ok(())
212 }
213
214 #[test]
215 fn shifted() -> Result<(), MietteError> {
216 let src = String::from("foobar");
217 let contents = src.read_span(&(3, 3).into(), 1, 1)?;
218 assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
219 assert_eq!(0, contents.line());
220 assert_eq!(0, contents.column());
221 Ok(())
222 }
223
224 #[test]
225 fn middle() -> Result<(), MietteError> {
226 let src = String::from("foo\nbar\nbaz\n");
227 let contents = src.read_span(&(4, 4).into(), 0, 0)?;
228 assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
229 assert_eq!(1, contents.line());
230 assert_eq!(0, contents.column());
231 Ok(())
232 }
233
234 #[test]
235 fn middle_of_line() -> Result<(), MietteError> {
236 let src = String::from("foo\nbarbar\nbaz\n");
237 let contents = src.read_span(&(7, 4).into(), 0, 0)?;
238 assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
239 assert_eq!(1, contents.line());
240 assert_eq!(3, contents.column());
241 Ok(())
242 }
243
244 #[test]
245 fn with_crlf() -> Result<(), MietteError> {
246 let src = String::from("foo\r\nbar\r\nbaz\r\n");
247 let contents = src.read_span(&(5, 5).into(), 0, 0)?;
248 assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
249 assert_eq!(1, contents.line());
250 assert_eq!(0, contents.column());
251 Ok(())
252 }
253
254 #[test]
255 fn with_context() -> Result<(), MietteError> {
256 let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
257 let contents = src.read_span(&(8, 3).into(), 1, 1)?;
258 assert_eq!(
259 "foo\nbar\nbaz\n",
260 std::str::from_utf8(contents.data()).unwrap()
261 );
262 assert_eq!(1, contents.line());
263 assert_eq!(0, contents.column());
264 Ok(())
265 }
266
267 #[test]
268 fn multiline_with_context() -> Result<(), MietteError> {
269 let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
270 let contents = src.read_span(&(9, 11).into(), 1, 1)?;
271 assert_eq!(
272 "\nfoo\nbar\nbaz\n\n",
273 std::str::from_utf8(contents.data()).unwrap()
274 );
275 assert_eq!(2, contents.line());
276 assert_eq!(0, contents.column());
277 let span: SourceSpan = (8, 14).into();
278 assert_eq!(&span, contents.span());
279 Ok(())
280 }
281
282 #[test]
283 fn multiline_with_context_line_start() -> Result<(), MietteError> {
284 let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
285 let contents = src.read_span(&(2, 0).into(), 2, 2)?;
286 assert_eq!(
287 "one\ntwo\n\n",
288 std::str::from_utf8(contents.data()).unwrap()
289 );
290 assert_eq!(0, contents.line());
291 assert_eq!(0, contents.column());
292 let span: SourceSpan = (0, 9).into();
293 assert_eq!(&span, contents.span());
294 Ok(())
295 }
296}