mirror of https://github.com/zkat/miette.git
feat(tabs): Add replace tabs with spaces option (#82)
Fixes: https://github.com/zkat/miette/issues/73
This commit is contained in:
parent
cb5a919deb
commit
1f70140c2e
|
|
@ -389,6 +389,7 @@ miette::set_hook(Box::new(|_| {
|
|||
.terminal_links(true)
|
||||
.unicode(false)
|
||||
.context_lines(3)
|
||||
.tab_width(4)
|
||||
.build())
|
||||
}))
|
||||
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ pub struct MietteHandlerOpts {
|
|||
pub(crate) unicode: Option<bool>,
|
||||
pub(crate) footer: Option<String>,
|
||||
pub(crate) context_lines: Option<usize>,
|
||||
pub(crate) tab_width: Option<usize>,
|
||||
}
|
||||
|
||||
impl MietteHandlerOpts {
|
||||
|
|
@ -119,6 +120,12 @@ impl MietteHandlerOpts {
|
|||
self
|
||||
}
|
||||
|
||||
/// Set the displayed tab width in spaces.
|
||||
pub fn tab_width(mut self, width: usize) -> Self {
|
||||
self.tab_width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
/// Builds a [MietteHandler] from this builder.
|
||||
pub fn build(self) -> MietteHandler {
|
||||
let graphical = self.is_graphical();
|
||||
|
|
@ -167,6 +174,9 @@ impl MietteHandlerOpts {
|
|||
if let Some(context_lines) = self.context_lines {
|
||||
handler = handler.with_context_lines(context_lines);
|
||||
}
|
||||
if let Some(w) = self.tab_width {
|
||||
handler = handler.tab_width(w);
|
||||
}
|
||||
MietteHandler {
|
||||
inner: Box::new(handler),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ pub struct GraphicalReportHandler {
|
|||
pub(crate) theme: GraphicalTheme,
|
||||
pub(crate) footer: Option<String>,
|
||||
pub(crate) context_lines: usize,
|
||||
pub(crate) tab_width: Option<usize>,
|
||||
}
|
||||
|
||||
impl GraphicalReportHandler {
|
||||
|
|
@ -37,6 +38,7 @@ impl GraphicalReportHandler {
|
|||
theme: GraphicalTheme::default(),
|
||||
footer: None,
|
||||
context_lines: 1,
|
||||
tab_width: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,9 +50,16 @@ impl GraphicalReportHandler {
|
|||
theme,
|
||||
footer: None,
|
||||
context_lines: 1,
|
||||
tab_width: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the displayed tab width in spaces.
|
||||
pub fn tab_width(mut self, width: usize) -> Self {
|
||||
self.tab_width = Some(width);
|
||||
self
|
||||
}
|
||||
|
||||
/// Whether to enable error code linkification using [Diagnostic::url].
|
||||
pub fn with_links(mut self, links: bool) -> Self {
|
||||
self.linkify_code = links;
|
||||
|
|
@ -376,7 +385,12 @@ impl GraphicalReportHandler {
|
|||
self.render_line_gutter(f, max_gutter, line, &labels)?;
|
||||
|
||||
// And _now_ we can print out the line text itself!
|
||||
writeln!(f, "{}", line.text)?;
|
||||
if let Some(w) = self.tab_width {
|
||||
let text = line.text.replace("\t", " ".repeat(w).as_str());
|
||||
writeln!(f, "{}", text)?;
|
||||
} else {
|
||||
writeln!(f, "{}", line.text)?;
|
||||
};
|
||||
|
||||
// Next, we write all the highlights that apply to this particular line.
|
||||
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
|
||||
|
|
@ -545,10 +559,20 @@ impl GraphicalReportHandler {
|
|||
) -> fmt::Result {
|
||||
let mut underlines = String::new();
|
||||
let mut highest = 0;
|
||||
|
||||
let chars = &self.theme.characters;
|
||||
for hl in single_liners {
|
||||
let hl_len = std::cmp::max(1, hl.len());
|
||||
let local_offset = hl.offset() - line.offset;
|
||||
|
||||
let local_offset = if let Some(w) = self.tab_width {
|
||||
// Only count tabs that affect the position of the highlighted line and ignore tabs past the span.
|
||||
let tab_count = &line.text[..hl.offset() - line.offset].matches('\t').count();
|
||||
let tabs_as_spaces = tab_count * w - tab_count;
|
||||
hl.offset() - line.offset + tabs_as_spaces
|
||||
} else {
|
||||
hl.offset() - line.offset
|
||||
};
|
||||
|
||||
let vbar_offset = local_offset + (hl_len / 2);
|
||||
let num_left = vbar_offset - local_offset;
|
||||
let num_right = local_offset + hl_len - vbar_offset - 1;
|
||||
|
|
@ -581,10 +605,15 @@ impl GraphicalReportHandler {
|
|||
let vbar_offsets: Vec<_> = single_liners
|
||||
.iter()
|
||||
.map(|hl| {
|
||||
(
|
||||
hl,
|
||||
(hl.offset() - line.offset) + (std::cmp::max(1, hl.len()) / 2),
|
||||
)
|
||||
let local_offset = if let Some(w) = self.tab_width {
|
||||
// Only count tabs that affect the position of the highlighted line and ignore tabs past the span.
|
||||
let tab_count = &line.text[..hl.offset() - line.offset].matches('\t').count();
|
||||
let tabs_as_spaces = tab_count * w - tab_count;
|
||||
hl.offset() - line.offset + tabs_as_spaces
|
||||
} else {
|
||||
hl.offset() - line.offset
|
||||
};
|
||||
(hl, local_offset + (std::cmp::max(1, hl.len()) / 2))
|
||||
})
|
||||
.collect();
|
||||
for hl in single_liners.iter().rev() {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,12 @@ fn fmt_report(diag: Report) -> String {
|
|||
NarratableReportHandler::new()
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
} else if let Ok(w) = std::env::var("REPLACE_TABS") {
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
.tab_width(w.parse().expect("Invalid tab width."))
|
||||
.render_report(&mut out, diag.as_ref())
|
||||
.unwrap();
|
||||
} else {
|
||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||
.with_width(80)
|
||||
|
|
@ -65,6 +71,84 @@ fn single_line_with_wide_char() -> Result<(), MietteError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_with_two_tabs() -> 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,
|
||||
}
|
||||
|
||||
std::env::set_var("REPLACE_TABS", "4");
|
||||
|
||||
let src = "source\n\t\ttext\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (9, 4).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected = r#"oops::my::bad
|
||||
|
||||
× oops!
|
||||
╭─[bad_file.rs:1:1]
|
||||
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(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_with_tab_in_middle() -> 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,
|
||||
}
|
||||
|
||||
std::env::set_var("REPLACE_TABS", "4");
|
||||
|
||||
let src = "source\ntext\ttext\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (12, 4).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
let expected = r#"oops::my::bad
|
||||
|
||||
× oops!
|
||||
╭─[bad_file.rs:1:1]
|
||||
1 │ source
|
||||
2 │ text text
|
||||
· ──┬─
|
||||
· ╰── this bit here
|
||||
3 │ here
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn single_line_highlight() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
@ -293,6 +377,53 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_same_line_highlights_with_tabs_in_middle() -> 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"]
|
||||
highlight1: SourceSpan,
|
||||
#[label = "y"]
|
||||
highlight2: SourceSpan,
|
||||
#[label = "z"]
|
||||
highlight3: SourceSpan,
|
||||
}
|
||||
|
||||
std::env::set_var("REPLACE_TABS", "4");
|
||||
|
||||
let src = "source\n text text text\ttext 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:1:1]
|
||||
1 │ source
|
||||
2 │ text text text text text
|
||||
· ──┬─ ──┬─ ──┬─
|
||||
· │ │ ╰── z
|
||||
· │ ╰── y
|
||||
· ╰── x
|
||||
3 │ here
|
||||
╰────
|
||||
help: try doing it better next time?
|
||||
"#
|
||||
.trim_start()
|
||||
.to_string();
|
||||
assert_eq!(expected, out);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||
#[derive(Debug, Diagnostic, Error)]
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ impl miette::ReportHandler for LocationHandler {
|
|||
}
|
||||
|
||||
fn track_caller(&mut self, location: &'static Location<'static>) {
|
||||
dbg!(location);
|
||||
self.actual = Some(location.file());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue