/home/runner/work/bare_err_tree/bare_err_tree/bare_err_tree/src/fmt_logic.rs
Line | Count | Source |
1 | | /* |
2 | | * This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at https://mozilla.org/MPL/2.0/. |
5 | | */ |
6 | | |
7 | | use core::{ |
8 | | fmt::{self, Display, Formatter, Write}, |
9 | | str::{self, Chars}, |
10 | | }; |
11 | | |
12 | | use crate::{AsErrTree, ErrTree, ErrTreeDisplay}; |
13 | | |
14 | | impl<E: AsErrTree, const FRONT_MAX: usize> ErrTreeDisplay<E, FRONT_MAX> { |
15 | 1 | pub fn new(tree: E) -> Self { |
16 | 1 | Self(tree) |
17 | 1 | } |
18 | | } |
19 | | |
20 | | impl<E: AsErrTree, const FRONT_MAX: usize> Display for ErrTreeDisplay<E, FRONT_MAX> { |
21 | | #[track_caller] |
22 | 4 | fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { |
23 | 4 | let mut res = Ok(()); |
24 | 4 | self.0.as_err_tree(&mut |tree| { |
25 | 4 | res = fmt_tree::<FRONT_MAX, _, _>(tree, f); |
26 | 4 | }); |
27 | 4 | res |
28 | 4 | } |
29 | | } |
30 | | |
31 | 4 | pub(crate) fn fmt_tree<const FRONT_MAX: usize, T, W>(tree: T, f: &mut W) -> fmt::Result |
32 | 4 | where |
33 | 4 | T: ErrTreeFormattable, |
34 | 4 | W: fmt::Write + ?Sized, |
35 | 4 | { |
36 | 4 | #[cfg(not(feature = "heap_buffer"))] |
37 | 4 | let mut front_lines = [0; FRONT_MAX]; |
38 | 4 | |
39 | 4 | #[cfg(feature = "heap_buffer")] |
40 | 4 | let mut front_lines = alloc::vec![0; FRONT_MAX].into_boxed_slice(); |
41 | 4 | |
42 | 4 | #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))] |
43 | 4 | let mut found_traces: [_; FRONT_MAX] = core::array::from_fn(|_| None); |
44 | 4 | |
45 | 4 | #[cfg(all(feature = "heap_buffer", feature = "tracing"))] |
46 | 4 | let mut found_traces = core::iter::repeat_with(|| None) |
47 | 4 | .take(FRONT_MAX) |
48 | 4 | .collect::<alloc::vec::Vec<_>>() |
49 | 4 | .into_boxed_slice(); |
50 | 4 | |
51 | 4 | ErrTreeFmt::<FRONT_MAX, _> { |
52 | 4 | tree, |
53 | 4 | scratch_fill: 0, |
54 | 4 | front_lines: &mut front_lines, |
55 | 4 | |
56 | 4 | #[cfg(feature = "tracing")] |
57 | 4 | found_traces: &mut found_traces, |
58 | 4 | } |
59 | 4 | .fmt(f) |
60 | 4 | } |
61 | | |
62 | | #[cfg(feature = "tracing")] |
63 | | pub(crate) struct TraceSpan<T: Eq, CharIter> { |
64 | | pub identifier: T, |
65 | | pub target: CharIter, |
66 | | pub name: CharIter, |
67 | | pub fields: CharIter, |
68 | | pub location: Option<(CharIter, u32)>, |
69 | | } |
70 | | |
71 | | pub(crate) trait ErrTreeFormattable { |
72 | | fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result; |
73 | | |
74 | | type Source<'a>: ErrTreeFormattable<TraceSpanId = Self::TraceSpanId>; |
75 | | |
76 | | #[allow(unused)] |
77 | | fn sources_empty(&mut self) -> bool; |
78 | | |
79 | | fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result |
80 | | where |
81 | | F: FnMut(Self::Source<'_>) -> fmt::Result; |
82 | | fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result |
83 | | where |
84 | | F: FnMut(Self::Source<'_>) -> fmt::Result; |
85 | | #[cfg(feature = "source_line")] |
86 | | fn has_source_line(&self) -> bool; |
87 | | #[cfg(feature = "source_line")] |
88 | | fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result; |
89 | | |
90 | | #[cfg(feature = "tracing")] |
91 | | fn trace_empty(&self) -> bool; |
92 | | |
93 | | type TraceSpanId: Eq; |
94 | | type TraceSpanIter<'a>: IntoIterator<Item = char>; |
95 | | |
96 | | #[cfg(feature = "tracing")] |
97 | | fn apply_trace<F>(&self, func: F) -> fmt::Result |
98 | | where |
99 | | F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result; |
100 | | } |
101 | | |
102 | | impl<T> ErrTreeFormattable for &mut T |
103 | | where |
104 | | T: ErrTreeFormattable, |
105 | | { |
106 | 0 | fn apply_msg<W: fmt::Write>(&self, f: W) -> fmt::Result { |
107 | 0 | T::apply_msg(self, f) |
108 | 0 | } |
109 | | |
110 | | type Source<'a> = T::Source<'a>; |
111 | 0 | fn sources_empty(&mut self) -> bool { |
112 | 0 | T::sources_empty(self) |
113 | 0 | } |
114 | 0 | fn apply_to_leading_sources<F>(&mut self, func: F) -> fmt::Result |
115 | 0 | where |
116 | 0 | F: FnMut(Self::Source<'_>) -> fmt::Result, |
117 | 0 | { |
118 | 0 | T::apply_to_leading_sources(self, func) |
119 | 0 | } |
120 | 0 | fn apply_to_last_source<F>(&mut self, func: F) -> fmt::Result |
121 | 0 | where |
122 | 0 | F: FnMut(Self::Source<'_>) -> fmt::Result, |
123 | 0 | { |
124 | 0 | T::apply_to_last_source(self, func) |
125 | 0 | } |
126 | | |
127 | | #[cfg(feature = "source_line")] |
128 | | fn has_source_line(&self) -> bool { |
129 | | T::has_source_line(self) |
130 | | } |
131 | | #[cfg(feature = "source_line")] |
132 | | fn apply_source_line<W: fmt::Write>(&self, f: W) -> fmt::Result { |
133 | | T::apply_source_line(self, f) |
134 | | } |
135 | | |
136 | | #[cfg(feature = "tracing")] |
137 | | fn trace_empty(&self) -> bool { |
138 | | T::trace_empty(self) |
139 | | } |
140 | | |
141 | | type TraceSpanId = T::TraceSpanId; |
142 | | type TraceSpanIter<'a> = T::TraceSpanIter<'a>; |
143 | | |
144 | | #[cfg(feature = "tracing")] |
145 | | fn apply_trace<F>(&self, func: F) -> fmt::Result |
146 | | where |
147 | | F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result, |
148 | | { |
149 | | T::apply_trace(self, func) |
150 | | } |
151 | | } |
152 | | |
153 | | impl ErrTreeFormattable for ErrTree<'_> { |
154 | 11 | fn apply_msg<W: fmt::Write>(&self, mut f: W) -> fmt::Result { |
155 | 11 | write!(f, "{}", self.inner) |
156 | 11 | } |
157 | | |
158 | | type Source<'a> = ErrTree<'a>; |
159 | 0 | fn sources_empty(&mut self) -> bool { |
160 | 0 | self.sources.is_empty() |
161 | 0 | } |
162 | 11 | fn apply_to_leading_sources<F>(&mut self, mut func: F) -> fmt::Result |
163 | 11 | where |
164 | 11 | F: FnMut(Self::Source<'_>) -> fmt::Result, |
165 | 11 | { |
166 | 11 | if let Some(initial_slice2 ) = self.sources.take_stored_and_next() { |
167 | 2 | let mut initial_iter = initial_slice.as_slice().iter().cloned(); |
168 | 2 | if let Some(mut source) = initial_iter.next() { |
169 | 5 | for next_source in initial_iter.chain(self.sources.by_ref())2 { |
170 | 5 | let mut res = Ok(()); |
171 | 5 | source.as_err_tree(&mut |tree| res = (func)(tree)); |
172 | 5 | res?0 ; |
173 | 5 | source = next_source |
174 | | } |
175 | 0 | } |
176 | 9 | } |
177 | 11 | Ok(()) |
178 | 11 | } |
179 | 11 | fn apply_to_last_source<F>(&mut self, mut func: F) -> fmt::Result |
180 | 11 | where |
181 | 11 | F: FnMut(Self::Source<'_>) -> fmt::Result, |
182 | 11 | { |
183 | 11 | let _ = self.sources.by_ref().last(); |
184 | 11 | if let Some(source2 ) = self.sources.take_stored() { |
185 | 2 | let mut res = Ok(()); |
186 | 2 | source.as_err_tree(&mut |tree| res = (func)(tree)); |
187 | 2 | res?0 ; |
188 | 9 | } |
189 | 11 | Ok(()) |
190 | 11 | } |
191 | | |
192 | | #[cfg(feature = "source_line")] |
193 | | fn has_source_line(&self) -> bool { |
194 | | self.location.is_some() |
195 | | } |
196 | | |
197 | | #[cfg(feature = "source_line")] |
198 | | fn apply_source_line<W: fmt::Write>(&self, mut f: W) -> fmt::Result { |
199 | | if let Some(loc) = self.location { |
200 | | write!(f, "{}", loc)?; |
201 | | } |
202 | | Ok(()) |
203 | | } |
204 | | |
205 | | #[cfg(feature = "tracing")] |
206 | | fn trace_empty(&self) -> bool { |
207 | | let mut empty = true; |
208 | | if let Some(trace) = &self.trace { |
209 | | trace.with_spans(|_, _| { |
210 | | empty = false; |
211 | | true |
212 | | }); |
213 | | } |
214 | | empty |
215 | | } |
216 | | |
217 | | #[cfg(not(feature = "tracing"))] |
218 | | type TraceSpanId = (); |
219 | | |
220 | | #[cfg(feature = "tracing")] |
221 | | type TraceSpanId = tracing_core::callsite::Identifier; |
222 | | |
223 | | type TraceSpanIter<'a> = Chars<'a>; |
224 | | |
225 | | #[cfg(feature = "tracing")] |
226 | | fn apply_trace<F>(&self, mut func: F) -> fmt::Result |
227 | | where |
228 | | F: FnMut(TraceSpan<Self::TraceSpanId, Self::TraceSpanIter<'_>>) -> fmt::Result, |
229 | | { |
230 | | if let Some(trace) = &self.trace { |
231 | | let mut res = Ok(()); |
232 | | trace.with_spans(|metadata, fields| { |
233 | | res = (func)(TraceSpan { |
234 | | identifier: metadata.callsite(), |
235 | | target: metadata.target().chars(), |
236 | | name: metadata.name().chars(), |
237 | | fields: fields.chars(), |
238 | | location: metadata |
239 | | .file() |
240 | | .and_then(|file| metadata.line().map(|line| (file.chars(), line))), |
241 | | }); |
242 | | res.is_ok() |
243 | | }); |
244 | | res |
245 | | } else { |
246 | | Ok(()) |
247 | | } |
248 | | } |
249 | | } |
250 | | |
251 | | pub(crate) struct ErrTreeFmt<'a, const FRONT_MAX: usize, T: ErrTreeFormattable> { |
252 | | pub tree: T, |
253 | | pub scratch_fill: usize, |
254 | | /// Most be initialized large enough to fit 6 x (max depth) bytes |
255 | | pub front_lines: &'a mut [u8], |
256 | | |
257 | | #[cfg(feature = "tracing")] |
258 | | pub found_traces: &'a mut [Option<T::TraceSpanId>], |
259 | | } |
260 | | |
261 | | /// Workaround for lack of `const` in [`core::cmp::max`]. |
262 | | #[cfg_attr(coverage, coverage(off))] |
263 | | const fn max_const(lhs: usize, rhs: usize) -> usize { |
264 | | if lhs >= rhs { |
265 | | lhs |
266 | | } else { |
267 | | rhs |
268 | | } |
269 | | } |
270 | | |
271 | | const CONTINUING: &str = "│ "; |
272 | | const DANGLING: &str = " "; |
273 | | const MAX_CELL_LEN: usize = max_const(CONTINUING.len(), DANGLING.len()); |
274 | | |
275 | | impl<const FRONT_MAX: usize, T: ErrTreeFormattable> ErrTreeFmt<'_, FRONT_MAX, T> { |
276 | | /// The front lines |
277 | | #[inline] |
278 | 25 | fn front_lines_str(front_lines: &[u8], scratch_fill: usize) -> &str { |
279 | 25 | str::from_utf8(&front_lines[..scratch_fill]) |
280 | 25 | .expect("All characters are static and guaranteed to be valid UTF-8") |
281 | 25 | } |
282 | | |
283 | | /// Preamble arrow connections |
284 | | #[inline] |
285 | 14 | fn write_front_lines<W>(front_lines: &[u8], f: &mut W, scratch_fill: usize) -> fmt::Result |
286 | 14 | where |
287 | 14 | W: fmt::Write + ?Sized, |
288 | 14 | { |
289 | 14 | f.write_char('\n')?0 ; |
290 | 14 | f.write_str(Self::front_lines_str(front_lines, scratch_fill)) |
291 | 14 | } |
292 | | |
293 | | /// Push in the correct fill characters |
294 | | #[inline] |
295 | 13 | fn add_front_line(front_lines: &mut [u8], last: bool, scratch_fill: usize) { |
296 | 13 | let chars: &str = if last { DANGLING2 } else { CONTINUING11 }; |
297 | | |
298 | 13 | front_lines[scratch_fill..scratch_fill + chars.len()].copy_from_slice(chars.as_bytes()); |
299 | 13 | } |
300 | | #[cfg(feature = "tracing")] |
301 | | /// There is tracing after if the trace is nonempty |
302 | | fn tracing_after(&self) -> bool { |
303 | | !self.tree.trace_empty() |
304 | | } |
305 | | |
306 | | #[cfg(not(feature = "tracing"))] |
307 | 11 | fn tracing_after(&self) -> bool { |
308 | 11 | false |
309 | 11 | } |
310 | | |
311 | | #[cfg(feature = "source_line")] |
312 | | fn source_line<W>(&mut self, f: &mut W, tracing_after: bool) -> fmt::Result |
313 | | where |
314 | | W: fmt::Write + ?Sized, |
315 | | { |
316 | | if self.tree.has_source_line() { |
317 | | Self::write_front_lines(self.front_lines, f, self.scratch_fill)?; |
318 | | |
319 | | if !tracing_after && self.tree.sources_empty() { |
320 | | f.write_str("╰─ ")?; |
321 | | } else { |
322 | | f.write_str("├─ ")?; |
323 | | } |
324 | | if cfg!(feature = "unix_color") { |
325 | | f.write_str("at \x1b[3m")?; |
326 | | self.tree.apply_source_line(&mut *f)?; |
327 | | f.write_str("\x1b[0m")?; |
328 | | } else { |
329 | | f.write_str("at ")?; |
330 | | self.tree.apply_source_line(f)?; |
331 | | } |
332 | | } |
333 | | |
334 | | Ok(()) |
335 | | } |
336 | | |
337 | | /// Simple implementation of pretty formatting |
338 | | #[cfg(feature = "tracing")] |
339 | | fn tracing_field_fmt<I, W>( |
340 | | f: &mut W, |
341 | | front_lines: &[u8], |
342 | | fields: I, |
343 | | scratch_fill: usize, |
344 | | ) -> fmt::Result |
345 | | where |
346 | | I: IntoIterator<Item = char>, |
347 | | W: fmt::Write + ?Sized, |
348 | | { |
349 | | let mut depth = 0; |
350 | | let mut in_quote = false; |
351 | | |
352 | | const START_CHARS: [char; 3] = ['{', '[', '(']; |
353 | | const END_CHARS: [char; 3] = ['}', ']', ')']; |
354 | | const ESC: char = '\\'; |
355 | | |
356 | | let push_front = |f: &mut W, depth| { |
357 | | Self::write_front_lines(front_lines, f, scratch_fill)?; |
358 | | f.write_str("│ ")?; |
359 | | for _ in 0..depth { |
360 | | f.write_str(" ")?; |
361 | | } |
362 | | Ok(()) |
363 | | }; |
364 | | |
365 | | push_front(f, depth)?; |
366 | | let mut prev = '\0'; |
367 | | for c in fields { |
368 | | let mut space_except = false; |
369 | | |
370 | | if in_quote { |
371 | | if prev == '"' { |
372 | | in_quote = false; |
373 | | if c == ' ' { |
374 | | space_except = true; |
375 | | } |
376 | | } |
377 | | } else { |
378 | | match prev { |
379 | | x if START_CHARS.contains(&x) => { |
380 | | depth += 1; |
381 | | push_front(f, depth)?; |
382 | | if c == ' ' { |
383 | | space_except = true; |
384 | | } |
385 | | } |
386 | | ',' => { |
387 | | push_front(f, depth)?; |
388 | | if c == ' ' { |
389 | | space_except = true; |
390 | | } |
391 | | } |
392 | | '"' => in_quote = true, |
393 | | x => { |
394 | | if END_CHARS.contains(&c) { |
395 | | depth -= 1; |
396 | | push_front(f, depth)?; |
397 | | } else if c == ' ' && END_CHARS.contains(&x) { |
398 | | space_except = true; |
399 | | if depth == 0 { |
400 | | push_front(f, depth)?; |
401 | | } |
402 | | } |
403 | | } |
404 | | } |
405 | | } |
406 | | |
407 | | // Special case for escaping |
408 | | prev = if prev == ESC { '\0' } else { c }; |
409 | | |
410 | | if !space_except { |
411 | | f.write_char(c)?; |
412 | | } |
413 | | } |
414 | | |
415 | | Ok(()) |
416 | | } |
417 | | |
418 | | #[cfg(feature = "tracing")] |
419 | | fn tracing<W>(&mut self, f: &mut W) -> fmt::Result |
420 | | where |
421 | | W: fmt::Write + ?Sized, |
422 | | { |
423 | | if !self.tree.trace_empty() { |
424 | | Self::write_front_lines(self.front_lines, f, self.scratch_fill)?; |
425 | | write!(f, "│")?; |
426 | | |
427 | | #[cfg(all(not(feature = "heap_buffer"), feature = "tracing"))] |
428 | | let mut repeated: [_; FRONT_MAX] = core::array::from_fn(|_| None); |
429 | | |
430 | | #[cfg(all(feature = "heap_buffer", feature = "tracing"))] |
431 | | let mut repeated = core::iter::repeat_with(|| None) |
432 | | .take(FRONT_MAX) |
433 | | .collect::<alloc::vec::Vec<_>>() |
434 | | .into_boxed_slice(); |
435 | | |
436 | | let mut repeated_idx = 0; |
437 | | |
438 | | self.tree.apply_trace(|trace_span| { |
439 | | let pos_dup = self |
440 | | .found_traces |
441 | | .iter() |
442 | | .take_while(|x| x.is_some()) |
443 | | .flatten() |
444 | | .position(|c| *c == trace_span.identifier); |
445 | | |
446 | | if let Some(pos_dup) = pos_dup { |
447 | | repeated[repeated_idx] = Some(pos_dup); |
448 | | repeated_idx += 1; |
449 | | } else { |
450 | | let depth = self.found_traces.partition_point(|x| x.is_some()); |
451 | | if depth < self.found_traces.len() { |
452 | | self.found_traces[depth] = Some(trace_span.identifier); |
453 | | } |
454 | | |
455 | | Self::write_front_lines(self.front_lines, f, self.scratch_fill)?; |
456 | | write!(f, "├─ tracing frame {} => ", depth)?; |
457 | | //depth, trace_span.target, trace_span.name |
458 | | for c in trace_span.target { |
459 | | f.write_char(c)? |
460 | | } |
461 | | f.write_str("::")?; |
462 | | for c in trace_span.name { |
463 | | f.write_char(c)? |
464 | | } |
465 | | |
466 | | let mut fields = trace_span.fields.into_iter().peekable(); |
467 | | if fields.peek().is_some() { |
468 | | write!(f, " with")?; |
469 | | Self::tracing_field_fmt(f, self.front_lines, fields, self.scratch_fill)?; |
470 | | } |
471 | | |
472 | | if let Some((file, line)) = trace_span.location { |
473 | | Self::write_front_lines(self.front_lines, f, self.scratch_fill)?; |
474 | | f.write_str("│ at ")?; |
475 | | for c in file { |
476 | | f.write_char(c)? |
477 | | } |
478 | | f.write_char(':')?; |
479 | | write!(f, "{line}")?; |
480 | | }; |
481 | | }; |
482 | | |
483 | | Ok(()) |
484 | | })?; |
485 | | |
486 | | if repeated_idx > 0 { |
487 | | Self::write_front_lines(self.front_lines, f, self.scratch_fill)?; |
488 | | if self.tree.sources_empty() { |
489 | | f.write_str("╰─ ")?; |
490 | | } else { |
491 | | f.write_str("├─ ")?; |
492 | | } |
493 | | |
494 | | write!(f, "{} duplicate tracing frame(s): [", repeated_idx)?; |
495 | | |
496 | | for idx in 0..repeated_idx - 1 { |
497 | | write!(f, "{}, ", repeated[idx].expect("Previously set as Some"))?; |
498 | | } |
499 | | |
500 | | write!( |
501 | | f, |
502 | | "{}]", |
503 | | repeated[repeated_idx - 1].expect("Previously set as Some") |
504 | | )?; |
505 | | } |
506 | | } |
507 | | Ok(()) |
508 | | } |
509 | | |
510 | | #[allow(unused_mut)] |
511 | 11 | fn fmt<W>(mut self, f: &mut W) -> fmt::Result |
512 | 11 | where |
513 | 11 | W: fmt::Write + ?Sized, |
514 | 11 | { |
515 | 11 | self.tree.apply_msg(LeadingLineFormatter::new( |
516 | 11 | &mut *f, |
517 | 11 | Self::front_lines_str(self.front_lines, self.scratch_fill), |
518 | 11 | ))?0 ; |
519 | | |
520 | | #[cfg_attr( |
521 | | not(any(feature = "source_line", feature = "tracing")), |
522 | | expect(unused_variables, reason = "only used to track for a tracing line") |
523 | | )] |
524 | 11 | let tracing_after = self.tracing_after(); |
525 | 11 | |
526 | 11 | #[cfg(feature = "source_line")] |
527 | 11 | self.source_line(f, tracing_after)?; |
528 | 11 | |
529 | 11 | #[cfg(feature = "tracing")] |
530 | 11 | self.tracing(f)?; |
531 | 11 | |
532 | 11 | let mut source_fmt = |
533 | | |front_lines: &mut [u8], |
534 | | scratch_fill: usize, |
535 | | #[cfg(feature = "tracing")] found_traces: &mut [Option<T::TraceSpanId>], |
536 | | source: T::Source<'_>, |
537 | 7 | last: bool| { |
538 | 7 | Self::write_front_lines(front_lines, f, scratch_fill)?0 ; |
539 | 7 | f.write_char('│')?0 ; |
540 | 7 | Self::write_front_lines(front_lines, f, scratch_fill)?0 ; |
541 | | |
542 | 7 | if last { |
543 | 2 | f.write_str("╰─▶ ")?0 ; |
544 | | } else { |
545 | 5 | f.write_str("├─▶ ")?0 ; |
546 | | } |
547 | | |
548 | 7 | let additional_scratch = if last { |
549 | 2 | DANGLING.len() |
550 | | } else { |
551 | 5 | CONTINUING.len() |
552 | | }; |
553 | | |
554 | 7 | ErrTreeFmt::<FRONT_MAX, _> { |
555 | 7 | tree: source, |
556 | 7 | scratch_fill: scratch_fill + additional_scratch, |
557 | 7 | front_lines, |
558 | 7 | |
559 | 7 | #[cfg(feature = "tracing")] |
560 | 7 | found_traces, |
561 | 7 | } |
562 | 7 | .fmt(f) |
563 | 7 | }; |
564 | | |
565 | 11 | if self.scratch_fill + MAX_CELL_LEN >= FRONT_MAX { |
566 | | // Stop printing deeper in the stack past this point |
567 | 0 | writeln!(f, "{:.<1$}", "", MAX_CELL_LEN)?; |
568 | | } else { |
569 | | // Normal operation |
570 | | |
571 | 11 | Self::add_front_line(self.front_lines, false, self.scratch_fill); |
572 | 11 | self.tree.apply_to_leading_sources(|source| { |
573 | 5 | source_fmt( |
574 | 5 | self.front_lines, |
575 | 5 | self.scratch_fill, |
576 | 5 | #[cfg(feature = "tracing")] |
577 | 5 | self.found_traces, |
578 | 5 | source, |
579 | 5 | false, |
580 | 5 | ) |
581 | 11 | })?0 ; |
582 | | |
583 | 11 | self.tree.apply_to_last_source(|source| { |
584 | 2 | Self::add_front_line(self.front_lines, true, self.scratch_fill); |
585 | 2 | source_fmt( |
586 | 2 | self.front_lines, |
587 | 2 | self.scratch_fill, |
588 | 2 | #[cfg(feature = "tracing")] |
589 | 2 | self.found_traces, |
590 | 2 | source, |
591 | 2 | true, |
592 | 2 | ) |
593 | 11 | })?0 ; |
594 | | }; |
595 | | |
596 | 11 | Ok(()) |
597 | 11 | } |
598 | | } |
599 | | |
600 | | /// Injects the newline leader |
601 | | struct LeadingLineFormatter<'a, F> { |
602 | | formatter: F, |
603 | | leading: &'a str, |
604 | | } |
605 | | |
606 | | impl<'a, F> LeadingLineFormatter<'a, F> { |
607 | 11 | pub fn new(formatter: F, leading: &'a str) -> Self { |
608 | 11 | Self { formatter, leading } |
609 | 11 | } |
610 | | } |
611 | | |
612 | | impl<F: Write> Write for LeadingLineFormatter<'_, F> { |
613 | 14 | fn write_str(&mut self, s: &str) -> fmt::Result { |
614 | 203 | if s.chars().all(|c| c != '\n')14 { |
615 | 12 | self.formatter.write_str(s)?0 |
616 | | } else { |
617 | 44 | for c in s.chars()2 { |
618 | 44 | self.write_char(c)?0 ; |
619 | | } |
620 | | } |
621 | 14 | Ok(()) |
622 | 14 | } |
623 | | |
624 | 44 | fn write_char(&mut self, c: char) -> fmt::Result { |
625 | 44 | self.formatter.write_char(c)?0 ; |
626 | | |
627 | 44 | if c == '\n' { |
628 | 3 | self.formatter.write_str(self.leading)?0 ; |
629 | 3 | self.formatter.write_str("│ ")?0 ; |
630 | 41 | } |
631 | | |
632 | 44 | Ok(()) |
633 | 44 | } |
634 | | } |