mirror of https://github.com/zkat/miette.git
Merge branch 'main' of github.com:zkat/miette into rendering-bug
This commit is contained in:
commit
2c704bb168
|
|
@ -14,7 +14,7 @@ exclude = ["images/", "tests/", "miette-derive/"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.40"
|
thiserror = "1.0.40"
|
||||||
miette-derive = { path = "miette-derive", version = "=5.10.0" }
|
miette-derive = { path = "miette-derive", version = "=5.10.0", optional = true }
|
||||||
once_cell = "1.8.0"
|
once_cell = "1.8.0"
|
||||||
unicode-width = "0.1.9"
|
unicode-width = "0.1.9"
|
||||||
|
|
||||||
|
|
@ -44,7 +44,8 @@ lazy_static = "1.4"
|
||||||
serde_json = "1.0.64"
|
serde_json = "1.0.64"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = ["derive"]
|
||||||
|
derive = ["miette-derive"]
|
||||||
no-format-args-capture = []
|
no-format-args-capture = []
|
||||||
fancy-no-backtrace = [
|
fancy-no-backtrace = [
|
||||||
"owo-colors",
|
"owo-colors",
|
||||||
|
|
|
||||||
17
README.md
17
README.md
|
|
@ -305,6 +305,23 @@ enabled:
|
||||||
miette = { version = "X.Y.Z", features = ["fancy"] }
|
miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Another way to display a diagnostic is by printing them using the debug formatter.
|
||||||
|
This is, in fact, what returning diagnostics from main ends up doing.
|
||||||
|
To do it yourself, you can write the following:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use miette::{IntoDiagnostic, Result};
|
||||||
|
use semver::Version;
|
||||||
|
|
||||||
|
fn just_a_random_function() {
|
||||||
|
let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||||
|
match version_result {
|
||||||
|
Err(e) => println!("{:?}", e),
|
||||||
|
Ok(version) => println!("{}", version),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
#### ... diagnostic code URLs
|
#### ... diagnostic code URLs
|
||||||
|
|
||||||
`miette` supports providing a URL for individual diagnostics. This URL will
|
`miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
|
|
|
||||||
42
src/error.rs
42
src/error.rs
|
|
@ -1,27 +1,51 @@
|
||||||
use std::io;
|
use std::{fmt, io};
|
||||||
|
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
use crate::{self as miette, Diagnostic};
|
use crate::Diagnostic;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Error enum for miette. Used by certain operations in the protocol.
|
Error enum for miette. Used by certain operations in the protocol.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum MietteError {
|
pub enum MietteError {
|
||||||
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
/// Wrapper around [`std::io::Error`]. This is returned when something went
|
||||||
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
/// wrong while reading a [`SourceCode`](crate::SourceCode).
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
#[diagnostic(code(miette::io_error), url(docsrs))]
|
|
||||||
IoError(#[from] io::Error),
|
IoError(#[from] io::Error),
|
||||||
|
|
||||||
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
/// Returned when a [`SourceSpan`](crate::SourceSpan) extends beyond the
|
||||||
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
/// bounds of a given [`SourceCode`](crate::SourceCode).
|
||||||
#[error("The given offset is outside the bounds of its Source")]
|
#[error("The given offset is outside the bounds of its Source")]
|
||||||
#[diagnostic(
|
|
||||||
code(miette::span_out_of_bounds),
|
|
||||||
help("Double-check your spans. Do you have an off-by-one error?"),
|
|
||||||
url(docsrs)
|
|
||||||
)]
|
|
||||||
OutOfBounds,
|
OutOfBounds,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Diagnostic for MietteError {
|
||||||
|
fn code<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(_) => Some(Box::new("miette::io_error")),
|
||||||
|
MietteError::OutOfBounds => Some(Box::new("miette::span_out_of_bounds")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn help<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
match self {
|
||||||
|
MietteError::IoError(_) => None,
|
||||||
|
MietteError::OutOfBounds => Some(Box::new(
|
||||||
|
"Double-check your spans. Do you have an off-by-one error?",
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn url<'a>(&'a self) -> Option<Box<dyn fmt::Display + 'a>> {
|
||||||
|
let crate_version = env!("CARGO_PKG_VERSION");
|
||||||
|
let variant = match self {
|
||||||
|
MietteError::IoError(_) => "#variant.IoError",
|
||||||
|
MietteError::OutOfBounds => "#variant.OutOfBounds",
|
||||||
|
};
|
||||||
|
Some(Box::new(format!(
|
||||||
|
"https://docs.rs/miette/{}/miette/enum.MietteError.html{}",
|
||||||
|
crate_version, variant,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,9 @@ pub struct MietteHandlerOpts {
|
||||||
pub(crate) context_lines: Option<usize>,
|
pub(crate) context_lines: Option<usize>,
|
||||||
pub(crate) tab_width: Option<usize>,
|
pub(crate) tab_width: Option<usize>,
|
||||||
pub(crate) with_cause_chain: Option<bool>,
|
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 {
|
impl MietteHandlerOpts {
|
||||||
|
|
@ -86,6 +89,27 @@ impl MietteHandlerOpts {
|
||||||
self
|
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.
|
/// Include the cause chain of the top-level error in the report.
|
||||||
pub fn with_cause_chain(mut self) -> Self {
|
pub fn with_cause_chain(mut self) -> Self {
|
||||||
self.with_cause_chain = Some(true);
|
self.with_cause_chain = Some(true);
|
||||||
|
|
@ -233,6 +257,16 @@ impl MietteHandlerOpts {
|
||||||
if let Some(w) = self.tab_width {
|
if let Some(w) = self.tab_width {
|
||||||
handler = handler.tab_width(w);
|
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 {
|
MietteHandler {
|
||||||
inner: Box::new(handler),
|
inner: Box::new(handler),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,9 @@ pub struct GraphicalReportHandler {
|
||||||
pub(crate) context_lines: usize,
|
pub(crate) context_lines: usize,
|
||||||
pub(crate) tab_width: usize,
|
pub(crate) tab_width: usize,
|
||||||
pub(crate) with_cause_chain: bool,
|
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)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
|
@ -51,6 +54,9 @@ impl GraphicalReportHandler {
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
break_words: true,
|
||||||
|
word_separator: None,
|
||||||
|
word_splitter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,6 +70,9 @@ impl GraphicalReportHandler {
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
break_words: true,
|
||||||
|
word_separator: None,
|
||||||
|
word_splitter: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -122,6 +131,24 @@ impl GraphicalReportHandler {
|
||||||
self
|
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.
|
/// Sets the 'global' footer for this handler.
|
||||||
pub fn with_footer(mut self, footer: String) -> Self {
|
pub fn with_footer(mut self, footer: String) -> Self {
|
||||||
self.footer = Some(footer);
|
self.footer = Some(footer);
|
||||||
|
|
@ -159,9 +186,17 @@ impl GraphicalReportHandler {
|
||||||
if let Some(footer) = &self.footer {
|
if let Some(footer) = &self.footer {
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(4);
|
||||||
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(footer, opts))?;
|
writeln!(f, "{}", textwrap::fill(footer, opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -212,9 +247,16 @@ impl GraphicalReportHandler {
|
||||||
let initial_indent = format!(" {} ", severity_icon.style(severity_style));
|
let initial_indent = format!(" {} ", severity_icon.style(severity_style));
|
||||||
let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style));
|
let rest_indent = format!(" {} ", self.theme.characters.vbar.style(severity_style));
|
||||||
let width = self.termwidth.saturating_sub(2);
|
let width = self.termwidth.saturating_sub(2);
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.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))?;
|
writeln!(f, "{}", textwrap::fill(&diagnostic.to_string(), opts))?;
|
||||||
|
|
||||||
|
|
@ -251,9 +293,17 @@ impl GraphicalReportHandler {
|
||||||
)
|
)
|
||||||
.style(severity_style)
|
.style(severity_style)
|
||||||
.to_string();
|
.to_string();
|
||||||
let opts = textwrap::Options::new(width)
|
let mut opts = textwrap::Options::new(width)
|
||||||
.initial_indent(&initial_indent)
|
.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);
|
||||||
|
}
|
||||||
|
|
||||||
match error {
|
match error {
|
||||||
ErrorKind::Diagnostic(diag) => {
|
ErrorKind::Diagnostic(diag) => {
|
||||||
let mut inner = String::new();
|
let mut inner = String::new();
|
||||||
|
|
@ -280,9 +330,17 @@ impl GraphicalReportHandler {
|
||||||
if let Some(help) = diagnostic.help() {
|
if let Some(help) = diagnostic.help() {
|
||||||
let width = self.termwidth.saturating_sub(4);
|
let width = self.termwidth.saturating_sub(4);
|
||||||
let initial_indent = " help: ".style(self.theme.styles.help).to_string();
|
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)
|
.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))?;
|
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -487,7 +545,13 @@ impl GraphicalReportHandler {
|
||||||
// no line number!
|
// no line number!
|
||||||
self.write_no_linum(f, linum_width)?;
|
self.write_no_linum(f, linum_width)?;
|
||||||
// gutter _again_
|
// gutter _again_
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
self.render_single_line_highlights(
|
self.render_single_line_highlights(
|
||||||
f,
|
f,
|
||||||
line,
|
line,
|
||||||
|
|
@ -499,11 +563,7 @@ impl GraphicalReportHandler {
|
||||||
}
|
}
|
||||||
for hl in multi_line {
|
for hl in multi_line {
|
||||||
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
if hl.label().is_some() && line.span_ends(hl) && !line.span_starts(hl) {
|
||||||
// no line number!
|
self.render_multi_line_end(f, &labels, max_gutter, linum_width, line, hl)?;
|
||||||
self.write_no_linum(f, linum_width)?;
|
|
||||||
// gutter _again_
|
|
||||||
self.render_highlight_gutter(f, max_gutter, line, &labels)?;
|
|
||||||
self.render_multi_line_end(f, hl)?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -517,6 +577,91 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
labels: &[FancySpan],
|
||||||
|
max_gutter: usize,
|
||||||
|
linum_width: usize,
|
||||||
|
line: &Line,
|
||||||
|
label: &FancySpan,
|
||||||
|
) -> fmt::Result {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
|
||||||
|
if let Some(label_parts) = label.label_parts() {
|
||||||
|
// if it has a label, how long is it?
|
||||||
|
let (first, rest) = label_parts
|
||||||
|
.split_first()
|
||||||
|
.expect("cannot crash because rest would have been None, see docs on the `label` field of FancySpan");
|
||||||
|
|
||||||
|
if rest.is_empty() {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
first,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineFirst,
|
||||||
|
)?;
|
||||||
|
for label_line in rest {
|
||||||
|
// no line number!
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
self.render_multi_line_end_single(
|
||||||
|
f,
|
||||||
|
label_line,
|
||||||
|
label.style,
|
||||||
|
LabelRenderMode::MultiLineRest,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// gutter _again_
|
||||||
|
self.render_highlight_gutter(
|
||||||
|
f,
|
||||||
|
max_gutter,
|
||||||
|
line,
|
||||||
|
&labels,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
|
)?;
|
||||||
|
// has no label
|
||||||
|
writeln!(f, "{}", self.theme.characters.hbar.style(label.style))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn render_line_gutter(
|
fn render_line_gutter(
|
||||||
&self,
|
&self,
|
||||||
f: &mut impl fmt::Write,
|
f: &mut impl fmt::Write,
|
||||||
|
|
@ -585,6 +730,7 @@ impl GraphicalReportHandler {
|
||||||
max_gutter: usize,
|
max_gutter: usize,
|
||||||
line: &Line,
|
line: &Line,
|
||||||
highlights: &[FancySpan],
|
highlights: &[FancySpan],
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if max_gutter == 0 {
|
if max_gutter == 0 {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -600,23 +746,50 @@ impl GraphicalReportHandler {
|
||||||
let applicable = highlights.iter().filter(|hl| line.span_applies_gutter(hl));
|
let applicable = highlights.iter().filter(|hl| line.span_applies_gutter(hl));
|
||||||
for (i, hl) in applicable.enumerate() {
|
for (i, hl) in applicable.enumerate() {
|
||||||
if !line.span_line_only(hl) && line.span_ends(hl) {
|
if !line.span_line_only(hl) && line.span_ends(hl) {
|
||||||
let num_repeat = max_gutter.saturating_sub(i) + 2;
|
if render_mode == LabelRenderMode::MultiLineRest {
|
||||||
|
// this is to make multiline labels work. We want to make the right amount
|
||||||
|
// of horizontal space for them, but not actually draw the lines
|
||||||
|
let horizontal_space = max_gutter.saturating_sub(i) + 2;
|
||||||
|
for _ in 0..horizontal_space {
|
||||||
|
gutter.push(' ');
|
||||||
|
}
|
||||||
|
// account for one more horizontal space, since in multiline mode
|
||||||
|
// we also add in the vertical line before the label like this:
|
||||||
|
// 2 │ ╭─▶ text
|
||||||
|
// 3 │ ├─▶ here
|
||||||
|
// · ╰──┤ these two lines
|
||||||
|
// · │ are the problem
|
||||||
|
// ^this
|
||||||
|
gutter_cols += horizontal_space + 1;
|
||||||
|
} else {
|
||||||
|
let num_repeat = max_gutter.saturating_sub(i) + 2;
|
||||||
|
|
||||||
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
gutter.push_str(&chars.lbot.style(hl.style).to_string());
|
||||||
gutter.push_str(
|
|
||||||
&chars
|
|
||||||
.hbar
|
|
||||||
.to_string()
|
|
||||||
.repeat(num_repeat)
|
|
||||||
.style(hl.style)
|
|
||||||
.to_string(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// we count 1 for the lbot char, and then a few more, the same number
|
gutter.push_str(
|
||||||
// as we just repeated for. For each repeat we only add 1, even though
|
&chars
|
||||||
// due to ansi escape codes the number of bytes in the string could grow
|
.hbar
|
||||||
// a lot each time.
|
.to_string()
|
||||||
gutter_cols += num_repeat + 1;
|
.repeat(
|
||||||
|
num_repeat
|
||||||
|
// if we are rendering a multiline label, then leave a bit of space for the
|
||||||
|
// rcross character
|
||||||
|
- if render_mode == LabelRenderMode::MultiLineFirst {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.style(hl.style)
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
// we count 1 for the lbot char, and then a few more, the same number
|
||||||
|
// as we just repeated for. For each repeat we only add 1, even though
|
||||||
|
// due to ansi escape codes the number of bytes in the string could grow
|
||||||
|
// a lot each time.
|
||||||
|
gutter_cols += num_repeat + 1;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
} else {
|
} else {
|
||||||
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
gutter.push_str(&chars.vbar.style(hl.style).to_string());
|
||||||
|
|
@ -743,31 +916,33 @@ impl GraphicalReportHandler {
|
||||||
let byte_start = hl.offset();
|
let byte_start = hl.offset();
|
||||||
let byte_end = hl.offset() + hl.len();
|
let byte_end = hl.offset() + hl.len();
|
||||||
let start = self.visual_offset(line, byte_start, true).max(highest);
|
let start = self.visual_offset(line, byte_start, true).max(highest);
|
||||||
let end = self.visual_offset(line, byte_end, false).max(start + 1);
|
let end = if hl.len() == 0 {
|
||||||
|
start + 1
|
||||||
|
} else {
|
||||||
|
self.visual_offset(line, byte_end, false).max(start + 1)
|
||||||
|
};
|
||||||
|
|
||||||
let vbar_offset = (start + end) / 2;
|
let vbar_offset = (start + end) / 2;
|
||||||
let num_left = vbar_offset - start;
|
let num_left = vbar_offset - start;
|
||||||
let num_right = end - vbar_offset - 1;
|
let num_right = end - vbar_offset - 1;
|
||||||
if start < end {
|
underlines.push_str(
|
||||||
underlines.push_str(
|
&format!(
|
||||||
&format!(
|
"{:width$}{}{}{}",
|
||||||
"{:width$}{}{}{}",
|
"",
|
||||||
"",
|
chars.underline.to_string().repeat(num_left),
|
||||||
chars.underline.to_string().repeat(num_left),
|
if hl.len() == 0 {
|
||||||
if hl.len() == 0 {
|
chars.uarrow
|
||||||
chars.uarrow
|
} else if hl.label().is_some() {
|
||||||
} else if hl.label().is_some() {
|
chars.underbar
|
||||||
chars.underbar
|
} else {
|
||||||
} else {
|
chars.underline
|
||||||
chars.underline
|
},
|
||||||
},
|
chars.underline.to_string().repeat(num_right),
|
||||||
chars.underline.to_string().repeat(num_right),
|
width = start.saturating_sub(highest),
|
||||||
width = start.saturating_sub(highest),
|
)
|
||||||
)
|
.style(hl.style)
|
||||||
.style(hl.style)
|
.to_string(),
|
||||||
.to_string(),
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
highest = std::cmp::max(highest, end);
|
highest = std::cmp::max(highest, end);
|
||||||
|
|
||||||
(hl, vbar_offset)
|
(hl, vbar_offset)
|
||||||
|
|
@ -776,27 +951,40 @@ impl GraphicalReportHandler {
|
||||||
writeln!(f, "{}", underlines)?;
|
writeln!(f, "{}", underlines)?;
|
||||||
|
|
||||||
for hl in single_liners.iter().rev() {
|
for hl in single_liners.iter().rev() {
|
||||||
if let Some(label) = hl.label() {
|
if let Some(label) = hl.label_parts() {
|
||||||
self.write_no_linum(f, linum_width)?;
|
if label.len() == 1 {
|
||||||
self.render_highlight_gutter(f, max_gutter, line, all_highlights)?;
|
self.write_label_text(
|
||||||
let mut curr_offset = 1usize;
|
f,
|
||||||
for (offset_hl, vbar_offset) in &vbar_offsets {
|
line,
|
||||||
while curr_offset < *vbar_offset + 1 {
|
linum_width,
|
||||||
write!(f, " ")?;
|
max_gutter,
|
||||||
curr_offset += 1;
|
all_highlights,
|
||||||
}
|
chars,
|
||||||
if *offset_hl != hl {
|
&vbar_offsets,
|
||||||
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
hl,
|
||||||
curr_offset += 1;
|
&label[0],
|
||||||
} else {
|
LabelRenderMode::SingleLine,
|
||||||
let lines = format!(
|
)?;
|
||||||
"{}{} {}",
|
} else {
|
||||||
chars.lbot,
|
let mut first = true;
|
||||||
chars.hbar.to_string().repeat(2),
|
for label_line in &label {
|
||||||
label,
|
self.write_label_text(
|
||||||
);
|
f,
|
||||||
writeln!(f, "{}", lines.style(hl.style))?;
|
line,
|
||||||
break;
|
linum_width,
|
||||||
|
max_gutter,
|
||||||
|
all_highlights,
|
||||||
|
chars,
|
||||||
|
&vbar_offsets,
|
||||||
|
hl,
|
||||||
|
label_line,
|
||||||
|
if first {
|
||||||
|
LabelRenderMode::MultiLineFirst
|
||||||
|
} else {
|
||||||
|
LabelRenderMode::MultiLineRest
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
first = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -804,13 +992,80 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_multi_line_end(&self, f: &mut impl fmt::Write, hl: &FancySpan) -> fmt::Result {
|
// I know it's not good practice, but making this a function makes a lot of sense
|
||||||
writeln!(
|
// and making a struct for this does not...
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
fn write_label_text(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
line: &Line,
|
||||||
|
linum_width: usize,
|
||||||
|
max_gutter: usize,
|
||||||
|
all_highlights: &[FancySpan],
|
||||||
|
chars: &ThemeCharacters,
|
||||||
|
vbar_offsets: &[(&&FancySpan, usize)],
|
||||||
|
hl: &&FancySpan,
|
||||||
|
label: &str,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
self.write_no_linum(f, linum_width)?;
|
||||||
|
self.render_highlight_gutter(
|
||||||
f,
|
f,
|
||||||
"{} {}",
|
max_gutter,
|
||||||
self.theme.characters.hbar.style(hl.style),
|
line,
|
||||||
hl.label().unwrap_or_else(|| "".into()),
|
all_highlights,
|
||||||
|
LabelRenderMode::SingleLine,
|
||||||
)?;
|
)?;
|
||||||
|
let mut curr_offset = 1usize;
|
||||||
|
for (offset_hl, vbar_offset) in vbar_offsets {
|
||||||
|
while curr_offset < *vbar_offset + 1 {
|
||||||
|
write!(f, " ")?;
|
||||||
|
curr_offset += 1;
|
||||||
|
}
|
||||||
|
if *offset_hl != hl {
|
||||||
|
write!(f, "{}", chars.vbar.to_string().style(offset_hl.style))?;
|
||||||
|
curr_offset += 1;
|
||||||
|
} else {
|
||||||
|
let lines = match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => format!(
|
||||||
|
"{}{} {}",
|
||||||
|
chars.lbot,
|
||||||
|
chars.hbar.to_string().repeat(2),
|
||||||
|
label,
|
||||||
|
),
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
format!("{}{}{} {}", chars.lbot, chars.hbar, chars.rcross, label,)
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
format!(" {} {}", chars.vbar, label,)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
writeln!(f, "{}", lines.style(hl.style))?;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_multi_line_end_single(
|
||||||
|
&self,
|
||||||
|
f: &mut impl fmt::Write,
|
||||||
|
label: &str,
|
||||||
|
style: Style,
|
||||||
|
render_mode: LabelRenderMode,
|
||||||
|
) -> fmt::Result {
|
||||||
|
match render_mode {
|
||||||
|
LabelRenderMode::SingleLine => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.hbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineFirst => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.rcross.style(style), label)?;
|
||||||
|
}
|
||||||
|
LabelRenderMode::MultiLineRest => {
|
||||||
|
writeln!(f, "{} {}", self.theme.characters.vbar.style(style), label)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -889,6 +1144,16 @@ impl ReportHandler for GraphicalReportHandler {
|
||||||
Support types
|
Support types
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
enum LabelRenderMode {
|
||||||
|
/// we're rendering a single line label (or not rendering in any special way)
|
||||||
|
SingleLine,
|
||||||
|
/// we're rendering a multiline label
|
||||||
|
MultiLineFirst,
|
||||||
|
/// we're rendering the rest of a multiline label
|
||||||
|
MultiLineRest,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Line {
|
struct Line {
|
||||||
line_number: usize,
|
line_number: usize,
|
||||||
|
|
@ -956,7 +1221,10 @@ impl Line {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct FancySpan {
|
struct FancySpan {
|
||||||
label: Option<String>,
|
/// this is deliberately an option of a vec because I wanted to be very explicit
|
||||||
|
/// that there can also be *no* label. If there is a label, it can have multiple
|
||||||
|
/// lines which is what the vec is for.
|
||||||
|
label: Option<Vec<String>>,
|
||||||
span: SourceSpan,
|
span: SourceSpan,
|
||||||
style: Style,
|
style: Style,
|
||||||
}
|
}
|
||||||
|
|
@ -967,9 +1235,17 @@ impl PartialEq for FancySpan {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn split_label(v: String) -> Vec<String> {
|
||||||
|
v.split('\n').map(|i| i.to_string()).collect()
|
||||||
|
}
|
||||||
|
|
||||||
impl FancySpan {
|
impl FancySpan {
|
||||||
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
fn new(label: Option<String>, span: SourceSpan, style: Style) -> Self {
|
||||||
FancySpan { label, span, style }
|
FancySpan {
|
||||||
|
label: label.map(split_label),
|
||||||
|
span,
|
||||||
|
style,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn style(&self) -> Style {
|
fn style(&self) -> Style {
|
||||||
|
|
@ -979,7 +1255,15 @@ impl FancySpan {
|
||||||
fn label(&self) -> Option<String> {
|
fn label(&self) -> Option<String> {
|
||||||
self.label
|
self.label
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|l| l.style(self.style()).to_string())
|
.map(|l| l.join("\n").style(self.style()).to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label_parts(&self) -> Option<Vec<String>> {
|
||||||
|
self.label.as_ref().map(|l| {
|
||||||
|
l.iter()
|
||||||
|
.map(|i| i.style(self.style()).to_string())
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn offset(&self) -> usize {
|
fn offset(&self) -> usize {
|
||||||
|
|
|
||||||
19
src/lib.rs
19
src/lib.rs
|
|
@ -304,6 +304,23 @@
|
||||||
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
//! miette = { version = "X.Y.Z", features = ["fancy"] }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! Another way to display a diagnostic is by printing them using the debug formatter.
|
||||||
|
//! This is, in fact, what returning diagnostics from main ends up doing.
|
||||||
|
//! To do it yourself, you can write the following:
|
||||||
|
//!
|
||||||
|
//! ```rust
|
||||||
|
//! use miette::{IntoDiagnostic, Result};
|
||||||
|
//! use semver::Version;
|
||||||
|
//!
|
||||||
|
//! fn just_a_random_function() {
|
||||||
|
//! let version_result: Result<Version> = "1.2.x".parse().into_diagnostic();
|
||||||
|
//! match version_result {
|
||||||
|
//! Err(e) => println!("{:?}", e),
|
||||||
|
//! Ok(version) => println!("{}", version),
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ### ... diagnostic code URLs
|
//! ### ... diagnostic code URLs
|
||||||
//!
|
//!
|
||||||
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
//! `miette` supports providing a URL for individual diagnostics. This URL will
|
||||||
|
|
@ -593,6 +610,7 @@
|
||||||
//! .unicode(false)
|
//! .unicode(false)
|
||||||
//! .context_lines(3)
|
//! .context_lines(3)
|
||||||
//! .tab_width(4)
|
//! .tab_width(4)
|
||||||
|
//! .break_words(true)
|
||||||
//! .build(),
|
//! .build(),
|
||||||
//! )
|
//! )
|
||||||
//! }))
|
//! }))
|
||||||
|
|
@ -652,6 +670,7 @@
|
||||||
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
//! and some from [`thiserror`](https://github.com/dtolnay/thiserror), also
|
||||||
//! under the Apache License. Some code is taken from
|
//! under the Apache License. Some code is taken from
|
||||||
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
//! [`ariadne`](https://github.com/zesterer/ariadne), which is MIT licensed.
|
||||||
|
#[cfg(feature = "derive")]
|
||||||
pub use miette_derive::*;
|
pub use miette_derive::*;
|
||||||
|
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,190 @@ fn fmt_report(diag: Report) -> String {
|
||||||
out
|
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]
|
#[test]
|
||||||
fn empty_source() -> Result<(), MietteError> {
|
fn empty_source() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -587,6 +771,94 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_label() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here\nand\nthis\ntoo")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\ntext\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (7, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:1]
|
||||||
|
1 │ source
|
||||||
|
2 │ text
|
||||||
|
· ──┬─
|
||||||
|
· ╰─┤ this bit here
|
||||||
|
· │ and
|
||||||
|
· │ this
|
||||||
|
· │ too
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiple_multi_line_labels() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label = "x\ny"]
|
||||||
|
highlight1: SourceSpan,
|
||||||
|
#[label = "z\nw"]
|
||||||
|
highlight2: SourceSpan,
|
||||||
|
#[label = "a\nb"]
|
||||||
|
highlight3: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text text text text text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight1: (9, 4).into(),
|
||||||
|
highlight2: (14, 4).into(),
|
||||||
|
highlight3: (24, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ text text text text text
|
||||||
|
· ──┬─ ──┬─ ──┬─
|
||||||
|
· │ │ ╰─┤ a
|
||||||
|
· │ │ │ b
|
||||||
|
· │ ╰─┤ z
|
||||||
|
· │ │ w
|
||||||
|
· ╰─┤ x
|
||||||
|
· │ y
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -715,6 +987,43 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multiline_highlight_multiline_label() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label = "these two lines\nare the problem"]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (9, 11).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:3]
|
||||||
|
1 │ source
|
||||||
|
2 │ ╭─▶ text
|
||||||
|
3 │ ├─▶ here
|
||||||
|
· ╰──┤ these two lines
|
||||||
|
· │ are the problem
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
fn multiline_highlight_flyby() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -1367,3 +1676,40 @@ fn single_line_with_wide_char_unaligned_span_end() -> Result<(), MietteError> {
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_line_with_wide_char_unaligned_span_empty() -> Result<(), MietteError> {
|
||||||
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
#[error("oops!")]
|
||||||
|
#[diagnostic(code(oops::my::bad), help("try doing it better next time?"))]
|
||||||
|
struct MyBad {
|
||||||
|
#[source_code]
|
||||||
|
src: NamedSource,
|
||||||
|
#[label("this bit here")]
|
||||||
|
highlight: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n 👼🏼text\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight: (10, 0).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = r#"oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:2:4]
|
||||||
|
1 │ source
|
||||||
|
2 │ 👼🏼text
|
||||||
|
· ▲
|
||||||
|
· ╰── this bit here
|
||||||
|
3 │ here
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
"#
|
||||||
|
.trim_start()
|
||||||
|
.to_string();
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue