nextest_runner/indenter.rs
1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Support for indenting multi-line displays.
5//!
6//! This module is adapted from [indenter](https://github.com/eyre-rs/indenter) and is used under
7//! the terms of the MIT or Apache-2.0 licenses.
8//!
9//! The main type is [`Indented`], which wraps a writer and indents each line. It works with both
10//! [`WriteStr`] and [`fmt::Write`].
11
12use crate::write_str::WriteStr;
13use std::{
14 fmt::{self, Write as _},
15 io,
16};
17
18/// Helper struct for efficiently indenting multi-line display implementations.
19///
20/// This type will never allocate a string to handle inserting indentation. It instead leverages
21/// the `write_str` function that serves as the foundation of the `core::fmt::Write` trait. This
22/// lets it intercept each piece of output as it's being written to the output buffer. It then
23/// splits on newlines giving slices into the original string. Finally we alternate writing these
24/// lines and the specified indentation to the output buffer.
25#[expect(missing_debug_implementations)]
26pub struct Indented<'a, D: ?Sized> {
27 inner: &'a mut D,
28 needs_indent: bool,
29 indentation: &'static str,
30}
31
32impl<'a, D: ?Sized> Indented<'a, D> {
33 /// Sets the indentation string.
34 pub fn with_str(mut self, indentation: &'static str) -> Self {
35 self.indentation = indentation;
36 self
37 }
38
39 /// Skip indenting the initial line.
40 ///
41 /// This is useful when you've already written some content on the current line
42 /// and want to start indenting only from the next line onwards.
43 pub fn skip_initial(mut self) -> Self {
44 self.needs_indent = false;
45 self
46 }
47
48 /// Returns the inner writer.
49 pub fn into_inner(self) -> &'a mut D {
50 self.inner
51 }
52}
53
54impl<T> WriteStr for Indented<'_, T>
55where
56 T: WriteStr + ?Sized,
57{
58 fn write_str(&mut self, s: &str) -> io::Result<()> {
59 for (ind, line) in s.split('\n').enumerate() {
60 if ind > 0 {
61 self.inner.write_char('\n')?;
62 self.needs_indent = true;
63 }
64
65 if self.needs_indent {
66 // Don't render the line unless it actually has text on it.
67 if line.is_empty() {
68 continue;
69 }
70
71 self.inner.write_str(self.indentation)?;
72 self.needs_indent = false;
73 }
74
75 self.inner.write_str(line)?;
76 }
77
78 Ok(())
79 }
80
81 fn write_str_flush(&mut self) -> io::Result<()> {
82 // We don't need to do any flushing ourselves, because there's no intermediate state
83 // possible here.
84 self.inner.write_str_flush()
85 }
86}
87
88impl<T> fmt::Write for Indented<'_, T>
89where
90 T: fmt::Write + ?Sized,
91{
92 fn write_str(&mut self, s: &str) -> fmt::Result {
93 for (ind, line) in s.split('\n').enumerate() {
94 if ind > 0 {
95 self.inner.write_char('\n')?;
96 self.needs_indent = true;
97 }
98
99 if self.needs_indent {
100 // Don't render the line unless it actually has text on it.
101 if line.is_empty() {
102 continue;
103 }
104
105 self.inner.write_str(self.indentation)?;
106 self.needs_indent = false;
107 }
108
109 self.inner.write_str(line)?;
110 }
111
112 Ok(())
113 }
114}
115
116/// Helper function for creating a default indenter.
117pub fn indented<D: ?Sized>(f: &mut D) -> Indented<'_, D> {
118 Indented {
119 inner: f,
120 needs_indent: true,
121 indentation: " ",
122 }
123}
124
125/// Wraps a `Display` item to indent each line when displayed.
126///
127/// This is useful for indenting error messages or other multi-line output.
128#[derive(Clone, Debug)]
129pub struct DisplayIndented<T> {
130 /// The item to display with indentation.
131 pub item: T,
132 /// The indentation string to prepend to each line.
133 pub indent: &'static str,
134}
135
136impl<T: fmt::Display> fmt::Display for DisplayIndented<T> {
137 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138 let mut indented = Indented {
139 inner: f,
140 needs_indent: true,
141 indentation: self.indent,
142 };
143 write!(indented, "{}", self.item)
144 }
145}