nextest_runner/indenter.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152
// Copyright (c) The nextest Contributors
// SPDX-License-Identifier: MIT OR Apache-2.0
//! Support for indenting multi-line displays.
//!
//! This module is adapted from [indenter](https://github.com/eyre-rs/indenter) and is used under the
//! terms of the MIT or Apache-2.0 licenses.
//!
//! # Notes
//!
//! We previously used to use `indenter` to indent multi-line `fmt::Display`
//! instances. However, at some point we needed to also indent
//! `std::io::Write`s, not just `fmt::Write`. `indenter` 0.3.3 doesn't support
//! `std::io::Write`, so we switched to `indent_write`.
//!
//! This file still has the `indenter` API. So we have both APIs floating around
//! for a bit... oh well. Still in two minds about which one's better here.
use crate::write_str::WriteStr;
use std::io;
/// The set of supported formats for indentation
#[expect(missing_debug_implementations)]
pub enum Format<'a> {
/// Insert uniform indentation before every line
///
/// This format takes a static string as input and inserts it after every newline
Uniform {
/// The string to insert as indentation
indentation: &'static str,
},
/// Inserts a number before the first line
///
/// This format hard codes the indentation level to match the indentation from
/// `core::backtrace::Backtrace`
Numbered {
/// The index to insert before the first line of output
ind: usize,
},
/// A custom indenter which is executed after every newline
///
/// Custom indenters are passed the current line number and the buffer to be written to as args
Custom {
/// The custom indenter
inserter: &'a mut Inserter,
},
}
/// Helper struct for efficiently indenting multi line display implementations
///
/// # Explanation
///
/// This type will never allocate a string to handle inserting indentation. It instead leverages
/// the `write_str` function that serves as the foundation of the `core::fmt::Write` trait. This
/// lets it intercept each piece of output as its being written to the output buffer. It then
/// splits on newlines giving slices into the original string. Finally we alternate writing these
/// lines and the specified indentation to the output buffer.
#[expect(missing_debug_implementations)]
pub struct Indented<'a, D: ?Sized> {
inner: &'a mut D,
needs_indent: bool,
format: Format<'a>,
}
/// A callback for `Format::Custom` used to insert indenation after a new line
///
/// The first argument is the line number within the output, starting from 0
pub type Inserter = dyn FnMut(usize, &mut dyn WriteStr) -> io::Result<()>;
impl Format<'_> {
fn insert_indentation(&mut self, line: usize, f: &mut dyn WriteStr) -> io::Result<()> {
match self {
Format::Uniform { indentation } => write!(f, "{indentation}"),
Format::Numbered { ind } => {
if line == 0 {
write!(f, "{ind: >4}: ")
} else {
write!(f, " ")
}
}
Format::Custom { inserter } => inserter(line, f),
}
}
}
impl<'a, D: ?Sized> Indented<'a, D> {
/// Sets the format to `Format::Numbered` with the provided index
pub fn ind(self, ind: usize) -> Self {
self.with_format(Format::Numbered { ind })
}
/// Sets the format to `Format::Uniform` with the provided static string
pub fn with_str(self, indentation: &'static str) -> Self {
self.with_format(Format::Uniform { indentation })
}
/// Construct an indenter with a user defined format
pub fn with_format(mut self, format: Format<'a>) -> Self {
self.format = format;
self
}
/// Returns the inner writer.
pub fn into_inner(self) -> &'a mut D {
self.inner
}
}
impl<T> WriteStr for Indented<'_, T>
where
T: WriteStr + ?Sized,
{
fn write_str(&mut self, s: &str) -> io::Result<()> {
for (ind, line) in s.split('\n').enumerate() {
if ind > 0 {
self.inner.write_char('\n')?;
self.needs_indent = true;
}
if self.needs_indent {
// Don't render the line unless its actually got text on it
if line.is_empty() {
continue;
}
self.format.insert_indentation(ind, &mut self.inner)?;
self.needs_indent = false;
}
self.inner.write_fmt(format_args!("{line}"))?;
}
Ok(())
}
fn write_str_flush(&mut self) -> io::Result<()> {
// We don't need to do any flushing ourselves, because there's no intermediate state
// possible here.
self.inner.write_str_flush()
}
}
/// Helper function for creating a default indenter
pub fn indented<D: ?Sized>(f: &mut D) -> Indented<'_, D> {
Indented {
inner: f,
needs_indent: true,
format: Format::Uniform {
indentation: " ",
},
}
}