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}