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
172impl<T: ?Sized + SourceCode + ToOwned> SourceCode for Cow<'_, T>
173where
174 T::Owned: Debug + Send + Sync,
180{
181 fn read_span<'a>(
182 &'a self,
183 span: &SourceSpan,
184 context_lines_before: usize,
185 context_lines_after: usize,
186 ) -> Result<Box<dyn SpanContents<'a> + 'a>, MietteError> {
187 self.as_ref().read_span(span, context_lines_before, context_lines_after)
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[test]
196 fn basic() -> Result<(), MietteError> {
197 let src = String::from("foo\n");
198 let contents = src.read_span(&(0, 4).into(), 0, 0)?;
199 assert_eq!("foo\n", std::str::from_utf8(contents.data()).unwrap());
200 assert_eq!(0, contents.line());
201 assert_eq!(0, contents.column());
202 Ok(())
203 }
204
205 #[test]
206 fn shifted() -> Result<(), MietteError> {
207 let src = String::from("foobar");
208 let contents = src.read_span(&(3, 3).into(), 1, 1)?;
209 assert_eq!("foobar", std::str::from_utf8(contents.data()).unwrap());
210 assert_eq!(0, contents.line());
211 assert_eq!(0, contents.column());
212 Ok(())
213 }
214
215 #[test]
216 fn middle() -> Result<(), MietteError> {
217 let src = String::from("foo\nbar\nbaz\n");
218 let contents = src.read_span(&(4, 4).into(), 0, 0)?;
219 assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
220 assert_eq!(1, contents.line());
221 assert_eq!(0, contents.column());
222 Ok(())
223 }
224
225 #[test]
226 fn middle_of_line() -> Result<(), MietteError> {
227 let src = String::from("foo\nbarbar\nbaz\n");
228 let contents = src.read_span(&(7, 4).into(), 0, 0)?;
229 assert_eq!("bar\n", std::str::from_utf8(contents.data()).unwrap());
230 assert_eq!(1, contents.line());
231 assert_eq!(3, contents.column());
232 Ok(())
233 }
234
235 #[test]
236 fn with_crlf() -> Result<(), MietteError> {
237 let src = String::from("foo\r\nbar\r\nbaz\r\n");
238 let contents = src.read_span(&(5, 5).into(), 0, 0)?;
239 assert_eq!("bar\r\n", std::str::from_utf8(contents.data()).unwrap());
240 assert_eq!(1, contents.line());
241 assert_eq!(0, contents.column());
242 Ok(())
243 }
244
245 #[test]
246 fn with_context() -> Result<(), MietteError> {
247 let src = String::from("xxx\nfoo\nbar\nbaz\n\nyyy\n");
248 let contents = src.read_span(&(8, 3).into(), 1, 1)?;
249 assert_eq!("foo\nbar\nbaz\n", std::str::from_utf8(contents.data()).unwrap());
250 assert_eq!(1, contents.line());
251 assert_eq!(0, contents.column());
252 Ok(())
253 }
254
255 #[test]
256 fn multiline_with_context() -> Result<(), MietteError> {
257 let src = String::from("aaa\nxxx\n\nfoo\nbar\nbaz\n\nyyy\nbbb\n");
258 let contents = src.read_span(&(9, 11).into(), 1, 1)?;
259 assert_eq!("\nfoo\nbar\nbaz\n\n", std::str::from_utf8(contents.data()).unwrap());
260 assert_eq!(2, contents.line());
261 assert_eq!(0, contents.column());
262 let span: SourceSpan = (8, 14).into();
263 assert_eq!(&span, contents.span());
264 Ok(())
265 }
266
267 #[test]
268 fn multiline_with_context_line_start() -> Result<(), MietteError> {
269 let src = String::from("one\ntwo\n\nthree\nfour\nfive\n\nsix\nseven\n");
270 let contents = src.read_span(&(2, 0).into(), 2, 2)?;
271 assert_eq!("one\ntwo\n\n", std::str::from_utf8(contents.data()).unwrap());
272 assert_eq!(0, contents.line());
273 assert_eq!(0, contents.column());
274 let span: SourceSpan = (0, 9).into();
275 assert_eq!(&span, contents.span());
276 Ok(())
277 }
278}