feat(graphical): Add `wrap_lines: bool` option allowing wrapping be disabled entirely (#328)

This commit is contained in:
Zanie Blue 2024-01-04 12:44:52 -06:00 committed by GitHub
parent 7ff4f874d6
commit b0744462ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 107 additions and 6 deletions

View File

@ -56,6 +56,7 @@ pub struct MietteHandlerOpts {
pub(crate) tab_width: Option<usize>,
pub(crate) with_cause_chain: Option<bool>,
pub(crate) break_words: Option<bool>,
pub(crate) wrap_lines: Option<bool>,
pub(crate) word_separator: Option<textwrap::WordSeparator>,
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
}
@ -89,6 +90,16 @@ impl MietteHandlerOpts {
self
}
/// If true, long lines can be wrapped.
///
/// If false, long lines will not be broken when they exceed the width.
///
/// Defaults to true.
pub fn wrap_lines(mut self, wrap_lines: bool) -> Self {
self.wrap_lines = Some(wrap_lines);
self
}
/// If true, long words can be broken when wrapping.
///
/// If false, long words will not be broken when they exceed the width.
@ -98,7 +109,6 @@ impl MietteHandlerOpts {
self.break_words = Some(break_words);
self
}
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
self.word_separator = Some(word_separator);
@ -260,6 +270,9 @@ impl MietteHandlerOpts {
if let Some(b) = self.break_words {
handler = handler.with_break_words(b)
}
if let Some(b) = self.wrap_lines {
handler = handler.with_wrap_lines(b)
}
if let Some(s) = self.word_separator {
handler = handler.with_word_separator(s)
}

View File

@ -30,6 +30,7 @@ pub struct GraphicalReportHandler {
pub(crate) context_lines: usize,
pub(crate) tab_width: usize,
pub(crate) with_cause_chain: bool,
pub(crate) wrap_lines: bool,
pub(crate) break_words: bool,
pub(crate) word_separator: Option<textwrap::WordSeparator>,
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
@ -54,6 +55,7 @@ impl GraphicalReportHandler {
context_lines: 1,
tab_width: 4,
with_cause_chain: true,
wrap_lines: true,
break_words: true,
word_separator: None,
word_splitter: None,
@ -69,6 +71,7 @@ impl GraphicalReportHandler {
footer: None,
context_lines: 1,
tab_width: 4,
wrap_lines: true,
with_cause_chain: true,
break_words: true,
word_separator: None,
@ -131,6 +134,12 @@ impl GraphicalReportHandler {
self
}
/// Enables or disables wrapping of lines to fit the width.
pub fn with_wrap_lines(mut self, wrap_lines: bool) -> Self {
self.wrap_lines = wrap_lines;
self
}
/// Enables or disables breaking of words during wrapping.
pub fn with_break_words(mut self, break_words: bool) -> Self {
self.break_words = break_words;
@ -197,7 +206,7 @@ impl GraphicalReportHandler {
opts = opts.word_splitter(word_splitter);
}
writeln!(f, "{}", textwrap::fill(footer, opts))?;
writeln!(f, "{}", self.wrap(footer, opts))?;
}
Ok(())
}
@ -258,7 +267,7 @@ impl GraphicalReportHandler {
opts = opts.word_splitter(word_splitter);
}
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
writeln!(f, "{}", self.wrap(&diagnostic.to_string(), opts))?;
if !self.with_cause_chain {
return Ok(());
@ -314,10 +323,10 @@ impl GraphicalReportHandler {
inner_renderer.with_cause_chain = false;
inner_renderer.render_report(&mut inner, diag)?;
writeln!(f, "{}", textwrap::fill(&inner, opts))?;
writeln!(f, "{}", self.wrap(&inner, opts))?;
}
ErrorKind::StdError(err) => {
writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
}
}
}
@ -341,7 +350,7 @@ impl GraphicalReportHandler {
opts = opts.word_splitter(word_splitter);
}
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
writeln!(f, "{}", self.wrap(&help.to_string(), opts))?;
}
Ok(())
}
@ -810,6 +819,41 @@ impl GraphicalReportHandler {
Ok(())
}
fn wrap(&self, text: &str, opts: textwrap::Options<'_>) -> String {
if self.wrap_lines {
textwrap::fill(text, opts)
} else {
// Format without wrapping, but retain the indentation options
// Implementation based on `textwrap::indent`
let mut result = String::with_capacity(2 * text.len());
let trimmed_indent = opts.subsequent_indent.trim_end();
for (idx, line) in text.split_terminator('\n').enumerate() {
if idx > 0 {
result.push('\n');
}
if idx == 0 {
if line.trim().is_empty() {
result.push_str(opts.initial_indent.trim_end());
} else {
result.push_str(opts.initial_indent);
}
} else {
if line.trim().is_empty() {
result.push_str(trimmed_indent);
} else {
result.push_str(opts.subsequent_indent);
}
}
result.push_str(line);
}
if text.ends_with('\n') {
// split_terminator will have eaten the final '\n'.
result.push('\n');
}
result
}
}
fn write_linum(&self, f: &mut impl fmt::Write, width: usize, linum: usize) -> fmt::Result {
write!(
f,

View File

@ -218,6 +218,50 @@ fn word_wrap_options() -> Result<(), MietteError> {
Ok(())
}
#[test]
fn wrap_option() -> Result<(), MietteError> {
// A line should break on the width
let out = fmt_report_with_settings(
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15),
);
let expected = r#" × abc def
ghi jkl
mno pqr
stu vwx
yz abc
def ghi
jkl mno
pqr stu
vwx yz
"#
.to_string();
assert_eq!(expected, out);
// Unless, wrapping is disabled
let out = fmt_report_with_settings(
Report::msg("abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15).with_wrap_lines(false),
);
let expected =
" × abc def ghi jkl mno pqr stu vwx yz abc def ghi jkl mno pqr stu vwx yz\n".to_string();
assert_eq!(expected, out);
// Then, user-defined new lines should be preserved wrapping is disabled
let out = fmt_report_with_settings(
Report::msg("abc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz\nabc def ghi jkl mno pqr stu vwx yz"),
|handler| handler.with_width(15).with_wrap_lines(false),
);
let expected = r#" × abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz
abc def ghi jkl mno pqr stu vwx yz
"#
.to_string();
assert_eq!(expected, out);
Ok(())
}
#[test]
fn empty_source() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]