Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ pub struct MietteHandlerOpts {
pub(crate) context_lines: Option<usize>,
pub(crate) tab_width: Option<usize>,
pub(crate) with_cause_chain: Option<bool>,
pub(crate) break_words: Option<bool>,
pub(crate) word_separator: Option<textwrap::WordSeparator>,
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
}

impl MietteHandlerOpts {
Expand Down Expand Up @@ -86,6 +89,27 @@ impl MietteHandlerOpts {
self
}

/// If true, long words can be broken when wrapping.
///
/// If false, long words will not be broken when they exceed the width.
///
/// Defaults to true.
pub fn break_words(mut self, break_words: bool) -> Self {
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);
self
}

/// Sets the `textwrap::WordSplitter` to use when determining wrap points.
pub fn word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
self.word_splitter = Some(word_splitter);
self
}
/// Include the cause chain of the top-level error in the report.
pub fn with_cause_chain(mut self) -> Self {
self.with_cause_chain = Some(true);
Expand Down Expand Up @@ -233,6 +257,16 @@ impl MietteHandlerOpts {
if let Some(w) = self.tab_width {
handler = handler.tab_width(w);
}
if let Some(b) = self.break_words {
handler = handler.with_break_words(b)
}
if let Some(s) = self.word_separator {
handler = handler.with_word_separator(s)
}
if let Some(s) = self.word_splitter {
handler = handler.with_word_splitter(s)
}

MietteHandler {
inner: Box::new(handler),
}
Expand Down
74 changes: 66 additions & 8 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub struct GraphicalReportHandler {
pub(crate) context_lines: usize,
pub(crate) tab_width: usize,
pub(crate) with_cause_chain: bool,
pub(crate) break_words: bool,
pub(crate) word_separator: Option<textwrap::WordSeparator>,
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
Expand All @@ -51,6 +54,9 @@ impl GraphicalReportHandler {
context_lines: 1,
tab_width: 4,
with_cause_chain: true,
break_words: true,
word_separator: None,
word_splitter: None,
}
}

Expand All @@ -64,6 +70,9 @@ impl GraphicalReportHandler {
context_lines: 1,
tab_width: 4,
with_cause_chain: true,
break_words: true,
word_separator: None,
word_splitter: None,
}
}

Expand Down Expand Up @@ -122,6 +131,24 @@ impl GraphicalReportHandler {
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;
self
}

/// Sets the word separator to use when wrapping.
pub fn with_word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
self.word_separator = Some(word_separator);
self
}

/// Sets the word splitter to usewhen wrapping.
pub fn with_word_splitter(mut self, word_splitter: textwrap::WordSplitter) -> Self {
self.word_splitter = Some(word_splitter);
self
}

/// Sets the 'global' footer for this handler.
pub fn with_footer(mut self, footer: String) -> Self {
self.footer = Some(footer);
Expand Down Expand Up @@ -159,9 +186,17 @@ impl GraphicalReportHandler {
if let Some(footer) = &self.footer {
writeln!(f)?;
let width = self.termwidth.saturating_sub(4);
let opts = textwrap::Options::new(width)
let mut opts = textwrap::Options::new(width)
.initial_indent(" ")
.subsequent_indent(" ");
.subsequent_indent(" ")
.break_words(self.break_words);
if let Some(word_separator) = self.word_separator {
opts = opts.word_separator(word_separator);
}
if let Some(word_splitter) = self.word_splitter.clone() {
opts = opts.word_splitter(word_splitter);
}

writeln!(f, "{}", textwrap::fill(footer, opts))?;
}
Ok(())
Expand Down Expand Up @@ -212,9 +247,16 @@ impl GraphicalReportHandler {
let initial_indent = format!(" {} ", severity_icon.style(severity_style));
let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style));
let width = self.termwidth.saturating_sub(2);
let opts = textwrap::Options::new(width)
let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(&rest_indent);
.subsequent_indent(&rest_indent)
.break_words(self.break_words);
if let Some(word_separator) = self.word_separator {
opts = opts.word_separator(word_separator);
}
if let Some(word_splitter) = self.word_splitter.clone() {
opts = opts.word_splitter(word_splitter);
}

writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;

Expand Down Expand Up @@ -251,9 +293,17 @@ impl GraphicalReportHandler {
)
.style(severity_style)
.to_string();
let opts = textwrap::Options::new(width)
let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(&rest_indent);
.subsequent_indent(&rest_indent)
.break_words(self.break_words);
if let Some(word_separator) = self.word_separator {
opts = opts.word_separator(word_separator);
}
if let Some(word_splitter) = self.word_splitter.clone() {
opts = opts.word_splitter(word_splitter);
}
Comment on lines -254 to +305

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is done repeatedly, perhaps a helper method should be introduced.


match error {
ErrorKind::Diagnostic(diag) => {
let mut inner = String::new();
Expand All @@ -280,9 +330,17 @@ impl GraphicalReportHandler {
if let Some(help) = diagnostic.help() {
let width = self.termwidth.saturating_sub(4);
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
let opts = textwrap::Options::new(width)
let mut opts = textwrap::Options::new(width)
.initial_indent(&initial_indent)
.subsequent_indent(" ");
.subsequent_indent(" ")
.break_words(self.break_words);
if let Some(word_separator) = self.word_separator {
opts = opts.word_separator(word_separator);
}
if let Some(word_splitter) = self.word_splitter.clone() {
opts = opts.word_splitter(word_splitter);
}

writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
}
Ok(())
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 +593,7 @@
//! .unicode(false)
//! .context_lines(3)
//! .tab_width(4)
//! .break_words(true)
//! .build(),
//! )
//! }))
Expand Down
184 changes: 184 additions & 0 deletions tests/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,190 @@ fn fmt_report(diag: Report) -> String {
out
}

fn fmt_report_with_settings(
diag: Report,
with_settings: fn(GraphicalReportHandler) -> GraphicalReportHandler,
) -> String {
let mut out = String::new();

let handler = with_settings(GraphicalReportHandler::new_themed(
GraphicalTheme::unicode_nocolor(),
));

handler.render_report(&mut out, diag.as_ref()).unwrap();

println!("Error:\n```\n{}\n```", out);

out
}

#[test]
fn word_wrap_options() -> Result<(), MietteError> {
// By default, a long word should not break
let out =
fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| handler);

let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out);

// A long word can break with a smaller width
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10)
});
let expected = r#" × abcd
│ efgh
│ ijkl
│ mnop
│ qrst
│ uvwx
│ yz
"#
.to_string();
assert_eq!(expected, out);

// Unless, word breaking is disabled
let out = fmt_report_with_settings(Report::msg("abcdefghijklmnopqrstuvwxyz"), |handler| {
handler.with_width(10).with_break_words(false)
});
let expected = " × abcdefghijklmnopqrstuvwxyz\n".to_string();
assert_eq!(expected, out);

// Breaks should start at the boundary of each word if possible
let out = fmt_report_with_settings(
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10),
);
let expected = r#" × 12
│ 123
│ 1234
│ 1234
│ 5
│ 1234
│ 56
│ 1234
│ 567
│ 1234
│ 5678
│ 90
"#
.to_string();
assert_eq!(expected, out);

// But long words should not break if word breaking is disabled
let out = fmt_report_with_settings(
Report::msg("12 123 1234 12345 123456 1234567 1234567890"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × 12
│ 123
│ 1234
│ 12345
│ 123456
│ 1234567
│ 1234567890
"#
.to_string();
assert_eq!(expected, out);

// Unless, of course, there are hyphens
let out = fmt_report_with_settings(
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × a-b
│ a-b-
│ c a-
│ b-c-
│ d a-
│ b-c-
│ d-e
│ a-b-
│ c-d-
│ e-f
│ a-b-
│ c-d-
│ e-f-
│ g a-
│ b-c-
│ d-e-
│ f-g-
│ h
"#
.to_string();
assert_eq!(expected, out);

// Which requires an additional opt-out
let out = fmt_report_with_settings(
Report::msg("a-b a-b-c a-b-c-d a-b-c-d-e a-b-c-d-e-f a-b-c-d-e-f-g a-b-c-d-e-f-g-h"),
|handler| {
handler
.with_width(10)
.with_break_words(false)
.with_word_splitter(textwrap::WordSplitter::NoHyphenation)
},
);
let expected = r#" × a-b
│ a-b-c
│ a-b-c-d
│ a-b-c-d-e
│ a-b-c-d-e-f
│ a-b-c-d-e-f-g
│ a-b-c-d-e-f-g-h
"#
.to_string();
assert_eq!(expected, out);

// Or if there are _other_ unicode word boundaries
let out = fmt_report_with_settings(
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|handler| handler.with_width(10).with_break_words(false),
);
let expected = r#" × a/b
│ a/b/
│ c a/
│ b/c/
│ d a/
│ b/c/
│ d/e
│ a/b/
│ c/d/
│ e/f
│ a/b/
│ c/d/
│ e/f/
│ g a/
│ b/c/
│ d/e/
│ f/g/
│ h
"#
.to_string();
assert_eq!(expected, out);

// Such things require you to opt-in to only breaking on ASCII whitespace
let out = fmt_report_with_settings(
Report::msg("a/b a/b/c a/b/c/d a/b/c/d/e a/b/c/d/e/f a/b/c/d/e/f/g a/b/c/d/e/f/g/h"),
|handler| {
handler
.with_width(10)
.with_break_words(false)
.with_word_separator(textwrap::WordSeparator::AsciiSpace)
},
);
let expected = r#" × a/b
│ a/b/c
│ a/b/c/d
│ a/b/c/d/e
│ a/b/c/d/e/f
│ a/b/c/d/e/f/g
│ a/b/c/d/e/f/g/h
"#
.to_string();
assert_eq!(expected, out);

Ok(())
}

#[test]
fn empty_source() -> Result<(), MietteError> {
#[derive(Debug, Diagnostic, Error)]
Expand Down