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