mirror of https://github.com/zkat/miette.git
Merge branch 'main' into feature/syntect
This commit is contained in:
commit
8a7c0ddf99
|
|
@ -58,6 +58,7 @@ pub struct MietteHandlerOpts {
|
||||||
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) break_words: Option<bool>,
|
||||||
|
pub(crate) wrap_lines: Option<bool>,
|
||||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
pub(crate) highlighter: Option<MietteHighlighter>,
|
pub(crate) highlighter: Option<MietteHighlighter>,
|
||||||
|
|
@ -129,6 +130,16 @@ impl MietteHandlerOpts {
|
||||||
self
|
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 true, long words can be broken when wrapping.
|
||||||
///
|
///
|
||||||
/// If false, long words will not be broken when they exceed the width.
|
/// If false, long words will not be broken when they exceed the width.
|
||||||
|
|
@ -138,7 +149,6 @@ impl MietteHandlerOpts {
|
||||||
self.break_words = Some(break_words);
|
self.break_words = Some(break_words);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
/// Sets the `textwrap::WordSeparator` to use when determining wrap points.
|
||||||
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
pub fn word_separator(mut self, word_separator: textwrap::WordSeparator) -> Self {
|
||||||
self.word_separator = Some(word_separator);
|
self.word_separator = Some(word_separator);
|
||||||
|
|
@ -299,7 +309,7 @@ impl MietteHandlerOpts {
|
||||||
MietteHighlighter::nocolor()
|
MietteHighlighter::nocolor()
|
||||||
};
|
};
|
||||||
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
let theme = self.theme.unwrap_or(GraphicalTheme { characters, styles });
|
||||||
let mut handler = GraphicalReportHandler::new()
|
let mut handler = GraphicalReportHandler::new_themed(theme)
|
||||||
.with_width(width)
|
.with_width(width)
|
||||||
.with_links(linkify)
|
.with_links(linkify)
|
||||||
.with_theme(theme);
|
.with_theme(theme);
|
||||||
|
|
@ -323,6 +333,9 @@ impl MietteHandlerOpts {
|
||||||
if let Some(b) = self.break_words {
|
if let Some(b) = self.break_words {
|
||||||
handler = handler.with_break_words(b)
|
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 {
|
if let Some(s) = self.word_separator {
|
||||||
handler = handler.with_word_separator(s)
|
handler = handler.with_word_separator(s)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ use crate::diagnostic_chain::{DiagnosticChain, ErrorKind};
|
||||||
use crate::handlers::theme::*;
|
use crate::handlers::theme::*;
|
||||||
use crate::highlighters::{Highlighter, MietteHighlighter};
|
use crate::highlighters::{Highlighter, MietteHighlighter};
|
||||||
use crate::protocol::{Diagnostic, Severity};
|
use crate::protocol::{Diagnostic, Severity};
|
||||||
use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
use crate::{LabeledSpan, ReportHandler, SourceCode, SourceSpan, SpanContents};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a
|
A [`ReportHandler`] that displays a given [`Report`](crate::Report) in a
|
||||||
|
|
@ -31,6 +31,7 @@ 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) wrap_lines: bool,
|
||||||
pub(crate) break_words: bool,
|
pub(crate) break_words: bool,
|
||||||
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
pub(crate) word_separator: Option<textwrap::WordSeparator>,
|
||||||
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
pub(crate) word_splitter: Option<textwrap::WordSplitter>,
|
||||||
|
|
@ -56,6 +57,7 @@ impl GraphicalReportHandler {
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
|
wrap_lines: true,
|
||||||
break_words: true,
|
break_words: true,
|
||||||
word_separator: None,
|
word_separator: None,
|
||||||
word_splitter: None,
|
word_splitter: None,
|
||||||
|
|
@ -72,6 +74,7 @@ impl GraphicalReportHandler {
|
||||||
footer: None,
|
footer: None,
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
tab_width: 4,
|
tab_width: 4,
|
||||||
|
wrap_lines: true,
|
||||||
with_cause_chain: true,
|
with_cause_chain: true,
|
||||||
break_words: true,
|
break_words: true,
|
||||||
word_separator: None,
|
word_separator: None,
|
||||||
|
|
@ -135,6 +138,12 @@ impl GraphicalReportHandler {
|
||||||
self
|
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.
|
/// Enables or disables breaking of words during wrapping.
|
||||||
pub fn with_break_words(mut self, break_words: bool) -> Self {
|
pub fn with_break_words(mut self, break_words: bool) -> Self {
|
||||||
self.break_words = break_words;
|
self.break_words = break_words;
|
||||||
|
|
@ -218,7 +227,7 @@ impl GraphicalReportHandler {
|
||||||
opts = opts.word_splitter(word_splitter);
|
opts = opts.word_splitter(word_splitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(footer, opts))?;
|
writeln!(f, "{}", self.wrap(footer, opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -279,7 +288,7 @@ impl GraphicalReportHandler {
|
||||||
opts = opts.word_splitter(word_splitter);
|
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 {
|
if !self.with_cause_chain {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -329,16 +338,17 @@ impl GraphicalReportHandler {
|
||||||
ErrorKind::Diagnostic(diag) => {
|
ErrorKind::Diagnostic(diag) => {
|
||||||
let mut inner = String::new();
|
let mut inner = String::new();
|
||||||
|
|
||||||
// Don't print footer for inner errors
|
|
||||||
let mut inner_renderer = self.clone();
|
let mut inner_renderer = self.clone();
|
||||||
|
// Don't print footer for inner errors
|
||||||
inner_renderer.footer = None;
|
inner_renderer.footer = None;
|
||||||
|
// Cause chains are already flattened, so don't double-print the nested error
|
||||||
inner_renderer.with_cause_chain = false;
|
inner_renderer.with_cause_chain = false;
|
||||||
inner_renderer.render_report(&mut inner, diag)?;
|
inner_renderer.render_report(&mut inner, diag)?;
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(&inner, opts))?;
|
writeln!(f, "{}", self.wrap(&inner, opts))?;
|
||||||
}
|
}
|
||||||
ErrorKind::StdError(err) => {
|
ErrorKind::StdError(err) => {
|
||||||
writeln!(f, "{}", textwrap::fill(&err.to_string(), opts))?;
|
writeln!(f, "{}", self.wrap(&err.to_string(), opts))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -362,7 +372,7 @@ impl GraphicalReportHandler {
|
||||||
opts = opts.word_splitter(word_splitter);
|
opts = opts.word_splitter(word_splitter);
|
||||||
}
|
}
|
||||||
|
|
||||||
writeln!(f, "{}", textwrap::fill(&help.to_string(), opts))?;
|
writeln!(f, "{}", self.wrap(&help.to_string(), opts))?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
@ -374,6 +384,9 @@ impl GraphicalReportHandler {
|
||||||
parent_src: Option<&dyn SourceCode>,
|
parent_src: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if let Some(related) = diagnostic.related() {
|
if let Some(related) = diagnostic.related() {
|
||||||
|
let mut inner_renderer = self.clone();
|
||||||
|
// Re-enable the printing of nested cause chains for related errors
|
||||||
|
inner_renderer.with_cause_chain = true;
|
||||||
writeln!(f)?;
|
writeln!(f)?;
|
||||||
for rel in related {
|
for rel in related {
|
||||||
match rel.severity() {
|
match rel.severity() {
|
||||||
|
|
@ -381,12 +394,12 @@ impl GraphicalReportHandler {
|
||||||
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
Some(Severity::Warning) => write!(f, "Warning: ")?,
|
||||||
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
Some(Severity::Advice) => write!(f, "Advice: ")?,
|
||||||
};
|
};
|
||||||
self.render_header(f, rel)?;
|
inner_renderer.render_header(f, rel)?;
|
||||||
self.render_causes(f, rel)?;
|
inner_renderer.render_causes(f, rel)?;
|
||||||
let src = rel.source_code().or(parent_src);
|
let src = rel.source_code().or(parent_src);
|
||||||
self.render_snippets(f, rel, src)?;
|
inner_renderer.render_snippets(f, rel, src)?;
|
||||||
self.render_footer(f, rel)?;
|
inner_renderer.render_footer(f, rel)?;
|
||||||
self.render_related(f, rel, src)?;
|
inner_renderer.render_related(f, rel, src)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
@ -398,66 +411,58 @@ impl GraphicalReportHandler {
|
||||||
diagnostic: &(dyn Diagnostic),
|
diagnostic: &(dyn Diagnostic),
|
||||||
opt_source: Option<&dyn SourceCode>,
|
opt_source: Option<&dyn SourceCode>,
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
if let Some(source) = opt_source {
|
let source = match opt_source {
|
||||||
if let Some(labels) = diagnostic.labels() {
|
Some(source) => source,
|
||||||
let mut labels = labels.collect::<Vec<_>>();
|
None => return Ok(()),
|
||||||
labels.sort_unstable_by_key(|l| l.inner().offset());
|
};
|
||||||
if !labels.is_empty() {
|
let labels = match diagnostic.labels() {
|
||||||
let contents = labels
|
Some(labels) => labels,
|
||||||
.iter()
|
None => return Ok(()),
|
||||||
.map(|label| {
|
};
|
||||||
source.read_span(label.inner(), self.context_lines, self.context_lines)
|
|
||||||
})
|
let mut labels = labels.collect::<Vec<_>>();
|
||||||
.collect::<Result<Vec<Box<dyn SpanContents<'_>>>, MietteError>>()
|
labels.sort_unstable_by_key(|l| l.inner().offset());
|
||||||
.map_err(|_| fmt::Error)?;
|
|
||||||
let mut contexts = Vec::with_capacity(contents.len());
|
let mut contexts = Vec::with_capacity(labels.len());
|
||||||
for (right, right_conts) in labels.iter().cloned().zip(contents.iter()) {
|
for right in labels.iter().cloned() {
|
||||||
if contexts.is_empty() {
|
let right_conts = source
|
||||||
contexts.push((right, right_conts));
|
.read_span(right.inner(), self.context_lines, self.context_lines)
|
||||||
} else {
|
.map_err(|_| fmt::Error)?;
|
||||||
let (left, left_conts) = contexts.last().unwrap().clone();
|
|
||||||
let left_end = left.offset() + left.len();
|
if contexts.is_empty() {
|
||||||
let right_end = right.offset() + right.len();
|
contexts.push((right, right_conts));
|
||||||
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
|
continue;
|
||||||
// The snippets will overlap, so we create one Big Chunky Boi
|
}
|
||||||
let new_span = LabeledSpan::new(
|
|
||||||
left.label().map(String::from),
|
let (left, left_conts) = contexts.last().unwrap();
|
||||||
left.offset(),
|
if left_conts.line() + left_conts.line_count() >= right_conts.line() {
|
||||||
if right_end >= left_end {
|
// The snippets will overlap, so we create one Big Chunky Boi
|
||||||
// Right end goes past left end
|
let left_end = left.offset() + left.len();
|
||||||
right_end - left.offset()
|
let right_end = right.offset() + right.len();
|
||||||
} else {
|
let new_end = std::cmp::max(left_end, right_end);
|
||||||
// right is contained inside left
|
|
||||||
left.len()
|
let new_span = LabeledSpan::new(
|
||||||
},
|
left.label().map(String::from),
|
||||||
);
|
left.offset(),
|
||||||
if source
|
new_end - left.offset(),
|
||||||
.read_span(
|
);
|
||||||
new_span.inner(),
|
// Check that the two contexts can be combined
|
||||||
self.context_lines,
|
if let Ok(new_conts) =
|
||||||
self.context_lines,
|
source.read_span(new_span.inner(), self.context_lines, self.context_lines)
|
||||||
)
|
{
|
||||||
.is_ok()
|
contexts.pop();
|
||||||
{
|
// We'll throw the contents away later
|
||||||
contexts.pop();
|
contexts.push((new_span, new_conts));
|
||||||
contexts.push((
|
continue;
|
||||||
// We'll throw this away later
|
|
||||||
new_span, left_conts,
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
contexts.push((right, right_conts));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
contexts.push((right, right_conts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ctx, _) in contexts {
|
|
||||||
self.render_context(f, source, &ctx, &labels[..])?;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contexts.push((right, right_conts));
|
||||||
}
|
}
|
||||||
|
for (ctx, _) in contexts {
|
||||||
|
self.render_context(f, source, &ctx, &labels[..])?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -470,10 +475,16 @@ impl GraphicalReportHandler {
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let (contents, lines) = self.get_lines(source, context.inner())?;
|
let (contents, lines) = self.get_lines(source, context.inner())?;
|
||||||
|
|
||||||
let primary_label = labels
|
// only consider labels from the context as primary label
|
||||||
.iter()
|
let ctx_labels = labels.iter().filter(|l| {
|
||||||
|
context.inner().offset() <= l.inner().offset()
|
||||||
|
&& l.inner().offset() + l.inner().len()
|
||||||
|
<= context.inner().offset() + context.inner().len()
|
||||||
|
});
|
||||||
|
let primary_label = ctx_labels
|
||||||
|
.clone()
|
||||||
.find(|label| label.primary())
|
.find(|label| label.primary())
|
||||||
.or_else(|| labels.first());
|
.or_else(|| ctx_labels.clone().next());
|
||||||
|
|
||||||
// sorting is your friend
|
// sorting is your friend
|
||||||
let labels = labels
|
let labels = labels
|
||||||
|
|
@ -835,6 +846,41 @@ impl GraphicalReportHandler {
|
||||||
Ok(())
|
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 {
|
fn write_linum(&self, f: &mut impl fmt::Write, width: usize, linum: usize) -> fmt::Result {
|
||||||
write!(
|
write!(
|
||||||
f,
|
f,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
use miette::{Diagnostic, MietteHandler, MietteHandlerOpts, ReportHandler, RgbColors};
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
use std::ffi::OsString;
|
||||||
use std::fmt::{self, Debug};
|
use std::fmt::{self, Debug};
|
||||||
use std::sync::Mutex;
|
use std::sync::Mutex;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
@ -42,16 +43,29 @@ fn color_format(handler: MietteHandler) -> ColorFormat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a function with an environment variable set to a specific value, then
|
/// Store the current value of an environment variable on construction, and then
|
||||||
/// sets it back to it's original value once completed.
|
/// restore that value when the guard is dropped.
|
||||||
fn with_env_var<F: FnOnce()>(var: &str, value: &str, body: F) {
|
struct EnvVarGuard<'a> {
|
||||||
let old_value = std::env::var_os(var);
|
var: &'a str,
|
||||||
std::env::set_var(var, value);
|
old_value: Option<OsString>,
|
||||||
body();
|
}
|
||||||
if let Some(old_value) = old_value {
|
|
||||||
std::env::set_var(var, old_value);
|
impl EnvVarGuard<'_> {
|
||||||
} else {
|
fn new(var: &str) -> EnvVarGuard<'_> {
|
||||||
std::env::remove_var(var);
|
EnvVarGuard {
|
||||||
|
var,
|
||||||
|
old_value: std::env::var_os(var),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for EnvVarGuard<'_> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some(old_value) = &self.old_value {
|
||||||
|
std::env::set_var(self.var, old_value);
|
||||||
|
} else {
|
||||||
|
std::env::remove_var(self.var);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,22 +86,33 @@ fn check_colors<F: Fn(MietteHandlerOpts) -> MietteHandlerOpts>(
|
||||||
//
|
//
|
||||||
// Since environment variables are shared for the entire process, we need
|
// Since environment variables are shared for the entire process, we need
|
||||||
// to ensure that only one test that modifies these env vars runs at a time.
|
// to ensure that only one test that modifies these env vars runs at a time.
|
||||||
let guard = COLOR_ENV_VARS.lock().unwrap();
|
let lock = COLOR_ENV_VARS.lock().unwrap();
|
||||||
|
|
||||||
with_env_var("NO_COLOR", "1", || {
|
let guards = (
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
EnvVarGuard::new("NO_COLOR"),
|
||||||
assert_eq!(color_format(handler), no_support);
|
EnvVarGuard::new("FORCE_COLOR"),
|
||||||
});
|
);
|
||||||
with_env_var("FORCE_COLOR", "1", || {
|
// Clear color environment variables that may be set outside of 'cargo test'
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
std::env::remove_var("NO_COLOR");
|
||||||
assert_eq!(color_format(handler), ansi_support);
|
std::env::remove_var("FORCE_COLOR");
|
||||||
});
|
|
||||||
with_env_var("FORCE_COLOR", "3", || {
|
|
||||||
let handler = make_handler(MietteHandlerOpts::new()).build();
|
|
||||||
assert_eq!(color_format(handler), rgb_support);
|
|
||||||
});
|
|
||||||
|
|
||||||
drop(guard);
|
std::env::set_var("NO_COLOR", "1");
|
||||||
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
|
assert_eq!(color_format(handler), no_support);
|
||||||
|
std::env::remove_var("NO_COLOR");
|
||||||
|
|
||||||
|
std::env::set_var("FORCE_COLOR", "1");
|
||||||
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
|
assert_eq!(color_format(handler), ansi_support);
|
||||||
|
std::env::remove_var("FORCE_COLOR");
|
||||||
|
|
||||||
|
std::env::set_var("FORCE_COLOR", "3");
|
||||||
|
let handler = make_handler(MietteHandlerOpts::new()).build();
|
||||||
|
assert_eq!(color_format(handler), rgb_support);
|
||||||
|
std::env::remove_var("FORCE_COLOR");
|
||||||
|
|
||||||
|
drop(guards);
|
||||||
|
drop(lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
||||||
|
|
@ -220,6 +220,50 @@ fn word_wrap_options() -> Result<(), MietteError> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn empty_source() -> Result<(), MietteError> {
|
fn empty_source() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -1811,4 +1855,95 @@ fn syntax_highlighter_on_real_file() {
|
||||||
);
|
);
|
||||||
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
assert!(out.contains("\u{1b}[38;2;180;142;173m"));
|
||||||
assert_eq!(expected, strip_ansi_escapes::strip_str(out));
|
assert_eq!(expected, strip_ansi_escapes::strip_str(out));
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn triple_adjacent_highlight() -> 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"]
|
||||||
|
highlight1: SourceSpan,
|
||||||
|
#[label = "also this bit"]
|
||||||
|
highlight2: SourceSpan,
|
||||||
|
#[label = "finally we got"]
|
||||||
|
highlight3: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n\n\n text\n\n\n here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight1: (0, 6).into(),
|
||||||
|
highlight2: (11, 4).into(),
|
||||||
|
highlight3: (22, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = "oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:1:1]
|
||||||
|
1 │ source
|
||||||
|
· ───┬──
|
||||||
|
· ╰── this bit here
|
||||||
|
2 │
|
||||||
|
3 │
|
||||||
|
4 │ text
|
||||||
|
· ──┬─
|
||||||
|
· ╰── also this bit
|
||||||
|
5 │
|
||||||
|
6 │
|
||||||
|
7 │ here
|
||||||
|
· ──┬─
|
||||||
|
· ╰── finally we got
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
";
|
||||||
|
assert_eq!(expected, &out);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_adjacent_highlight() -> 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"]
|
||||||
|
highlight1: SourceSpan,
|
||||||
|
#[label = "also this bit"]
|
||||||
|
highlight2: SourceSpan,
|
||||||
|
}
|
||||||
|
|
||||||
|
let src = "source\n\n\n\n text here".to_string();
|
||||||
|
let err = MyBad {
|
||||||
|
src: NamedSource::new("bad_file.rs", src),
|
||||||
|
highlight1: (0, 6).into(),
|
||||||
|
highlight2: (12, 4).into(),
|
||||||
|
};
|
||||||
|
let out = fmt_report(err.into());
|
||||||
|
println!("Error: {}", out);
|
||||||
|
let expected = "oops::my::bad
|
||||||
|
|
||||||
|
× oops!
|
||||||
|
╭─[bad_file.rs:1:1]
|
||||||
|
1 │ source
|
||||||
|
· ───┬──
|
||||||
|
· ╰── this bit here
|
||||||
|
2 │
|
||||||
|
╰────
|
||||||
|
╭─[bad_file.rs:5:3]
|
||||||
|
4 │
|
||||||
|
5 │ text here
|
||||||
|
· ──┬─
|
||||||
|
· ╰── also this bit
|
||||||
|
╰────
|
||||||
|
help: try doing it better next time?
|
||||||
|
";
|
||||||
|
assert_eq!(expected, &out);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -194,3 +194,85 @@ fn test_nested_diagnostic_source_is_output() {
|
||||||
|
|
||||||
assert_eq!(expected, out);
|
assert_eq!(expected, out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, miette::Diagnostic, thiserror::Error)]
|
||||||
|
#[error("A multi-error happened")]
|
||||||
|
struct MultiError {
|
||||||
|
#[related]
|
||||||
|
related_errs: Vec<Box<dyn Diagnostic>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "fancy-no-backtrace")]
|
||||||
|
#[test]
|
||||||
|
fn test_nested_cause_chains_for_related_errors_are_output() {
|
||||||
|
let inner_error = TestStructError {
|
||||||
|
asdf_inner_foo: SourceError {
|
||||||
|
code: String::from("This is another error"),
|
||||||
|
help: String::from("You should fix this"),
|
||||||
|
label: (3, 4),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let first_error = NestedError {
|
||||||
|
code: String::from("right here"),
|
||||||
|
label: (6, 4),
|
||||||
|
the_other_err: Box::new(inner_error),
|
||||||
|
};
|
||||||
|
let second_error = SourceError {
|
||||||
|
code: String::from("You're actually a mess"),
|
||||||
|
help: String::from("Get a grip..."),
|
||||||
|
label: (3, 4),
|
||||||
|
};
|
||||||
|
let multi_error = MultiError {
|
||||||
|
related_errs: vec![Box::new(first_error), Box::new(second_error)],
|
||||||
|
};
|
||||||
|
let diag = NestedError {
|
||||||
|
code: String::from("the outside world"),
|
||||||
|
label: (6, 4),
|
||||||
|
the_other_err: Box::new(multi_error),
|
||||||
|
};
|
||||||
|
let mut out = String::new();
|
||||||
|
miette::GraphicalReportHandler::new_themed(miette::GraphicalTheme::unicode_nocolor())
|
||||||
|
.with_width(80)
|
||||||
|
.with_footer("Yooo, a footer".to_string())
|
||||||
|
.render_report(&mut out, &diag)
|
||||||
|
.unwrap();
|
||||||
|
println!("{}", out);
|
||||||
|
|
||||||
|
let expected = r#" × A nested error happened
|
||||||
|
╰─▶ × A multi-error happened
|
||||||
|
|
||||||
|
Error: × A nested error happened
|
||||||
|
├─▶ × TestError
|
||||||
|
│
|
||||||
|
╰─▶ × A complex error happened
|
||||||
|
╭────
|
||||||
|
1 │ This is another error
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
help: You should fix this
|
||||||
|
|
||||||
|
╭────
|
||||||
|
1 │ right here
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
Error: × A complex error happened
|
||||||
|
╭────
|
||||||
|
1 │ You're actually a mess
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
help: Get a grip...
|
||||||
|
|
||||||
|
╭────
|
||||||
|
1 │ the outside world
|
||||||
|
· ──┬─
|
||||||
|
· ╰── here
|
||||||
|
╰────
|
||||||
|
|
||||||
|
Yooo, a footer
|
||||||
|
"#;
|
||||||
|
|
||||||
|
assert_eq!(expected, out);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue