diff --git a/src/handler.rs b/src/handler.rs index e32f3ef1..38c9fc84 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -56,6 +56,7 @@ pub struct MietteHandlerOpts { pub(crate) tab_width: Option, pub(crate) with_cause_chain: Option, pub(crate) break_words: Option, + pub(crate) wrap_lines: Option, pub(crate) word_separator: Option, pub(crate) word_splitter: Option, } @@ -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) } diff --git a/src/handlers/graphical.rs b/src/handlers/graphical.rs index 31934724..951303ab 100644 --- a/src/handlers/graphical.rs +++ b/src/handlers/graphical.rs @@ -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, pub(crate) word_splitter: Option, @@ -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, diff --git a/tests/graphical.rs b/tests/graphical.rs index aabf167f..21185f52 100644 --- a/tests/graphical.rs +++ b/tests/graphical.rs @@ -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)]