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)
|
.terminal_links(true)
|
||||||
.unicode(false)
|
.unicode(false)
|
||||||
.context_lines(3)
|
.context_lines(3)
|
||||||
|
.tab_width(4)
|
||||||
.build())
|
.build())
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,6 +39,7 @@ pub struct MietteHandlerOpts {
|
||||||
pub(crate) unicode: Option<bool>,
|
pub(crate) unicode: Option<bool>,
|
||||||
pub(crate) footer: Option<String>,
|
pub(crate) footer: Option<String>,
|
||||||
pub(crate) context_lines: Option<usize>,
|
pub(crate) context_lines: Option<usize>,
|
||||||
|
pub(crate) tab_width: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MietteHandlerOpts {
|
impl MietteHandlerOpts {
|
||||||
|
|
@ -119,6 +120,12 @@ impl MietteHandlerOpts {
|
||||||
self
|
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.
|
/// Builds a [MietteHandler] from this builder.
|
||||||
pub fn build(self) -> MietteHandler {
|
pub fn build(self) -> MietteHandler {
|
||||||
let graphical = self.is_graphical();
|
let graphical = self.is_graphical();
|
||||||
|
|
@ -167,6 +174,9 @@ impl MietteHandlerOpts {
|
||||||
if let Some(context_lines) = self.context_lines {
|
if let Some(context_lines) = self.context_lines {
|
||||||
handler = handler.with_context_lines(context_lines);
|
handler = handler.with_context_lines(context_lines);
|
||||||
}
|
}
|
||||||
|
if let Some(w) = self.tab_width {
|
||||||
|
handler = handler.tab_width(w);
|
||||||
|
}
|
||||||
MietteHandler {
|
MietteHandler {
|
||||||
inner: Box::new(handler),
|
inner: Box::new(handler),
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@ pub struct GraphicalReportHandler {
|
||||||
pub(crate) theme: GraphicalTheme,
|
pub(crate) theme: GraphicalTheme,
|
||||||
pub(crate) footer: Option<String>,
|
pub(crate) footer: Option<String>,
|
||||||
pub(crate) context_lines: usize,
|
pub(crate) context_lines: usize,
|
||||||
|
pub(crate) tab_width: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GraphicalReportHandler {
|
impl GraphicalReportHandler {
|
||||||
|
|
@ -37,6 +38,7 @@ impl GraphicalReportHandler {
|
||||||
theme: GraphicalTheme::default(),
|
theme: GraphicalTheme::default(),
|
||||||
footer: None,
|
footer: None,
|
||||||
context_lines: 1,
|
context_lines: 1,
|
||||||
|
tab_width: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,9 +50,16 @@ impl GraphicalReportHandler {
|
||||||
theme,
|
theme,
|
||||||
footer: None,
|
footer: None,
|
||||||
context_lines: 1,
|
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].
|
/// Whether to enable error code linkification using [Diagnostic::url].
|
||||||
pub fn with_links(mut self, links: bool) -> Self {
|
pub fn with_links(mut self, links: bool) -> Self {
|
||||||
self.linkify_code = links;
|
self.linkify_code = links;
|
||||||
|
|
@ -376,7 +385,12 @@ impl GraphicalReportHandler {
|
||||||
self.render_line_gutter(f, max_gutter, line, &labels)?;
|
self.render_line_gutter(f, max_gutter, line, &labels)?;
|
||||||
|
|
||||||
// And _now_ we can print out the line text itself!
|
// 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.
|
// Next, we write all the highlights that apply to this particular line.
|
||||||
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
|
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
|
||||||
|
|
@ -545,10 +559,20 @@ impl GraphicalReportHandler {
|
||||||
) -> fmt::Result {
|
) -> fmt::Result {
|
||||||
let mut underlines = String::new();
|
let mut underlines = String::new();
|
||||||
let mut highest = 0;
|
let mut highest = 0;
|
||||||
|
|
||||||
let chars = &self.theme.characters;
|
let chars = &self.theme.characters;
|
||||||
for hl in single_liners {
|
for hl in single_liners {
|
||||||
let hl_len = std::cmp::max(1, hl.len());
|
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 vbar_offset = local_offset + (hl_len / 2);
|
||||||
let num_left = vbar_offset - local_offset;
|
let num_left = vbar_offset - local_offset;
|
||||||
let num_right = local_offset + hl_len - vbar_offset - 1;
|
let num_right = local_offset + hl_len - vbar_offset - 1;
|
||||||
|
|
@ -581,10 +605,15 @@ impl GraphicalReportHandler {
|
||||||
let vbar_offsets: Vec<_> = single_liners
|
let vbar_offsets: Vec<_> = single_liners
|
||||||
.iter()
|
.iter()
|
||||||
.map(|hl| {
|
.map(|hl| {
|
||||||
(
|
let local_offset = if let Some(w) = self.tab_width {
|
||||||
hl,
|
// Only count tabs that affect the position of the highlighted line and ignore tabs past the span.
|
||||||
(hl.offset() - line.offset) + (std::cmp::max(1, hl.len()) / 2),
|
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();
|
.collect();
|
||||||
for hl in single_liners.iter().rev() {
|
for hl in single_liners.iter().rev() {
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ fn fmt_report(diag: Report) -> String {
|
||||||
NarratableReportHandler::new()
|
NarratableReportHandler::new()
|
||||||
.render_report(&mut out, diag.as_ref())
|
.render_report(&mut out, diag.as_ref())
|
||||||
.unwrap();
|
.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 {
|
} else {
|
||||||
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
GraphicalReportHandler::new_themed(GraphicalTheme::unicode_nocolor())
|
||||||
.with_width(80)
|
.with_width(80)
|
||||||
|
|
@ -65,6 +71,84 @@ fn single_line_with_wide_char() -> Result<(), MietteError> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn single_line_highlight() -> Result<(), MietteError> {
|
fn single_line_highlight() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
@ -293,6 +377,53 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> {
|
||||||
Ok(())
|
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]
|
#[test]
|
||||||
fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
fn multiline_highlight_adjacent() -> Result<(), MietteError> {
|
||||||
#[derive(Debug, Diagnostic, Error)]
|
#[derive(Debug, Diagnostic, Error)]
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ impl miette::ReportHandler for LocationHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn track_caller(&mut self, location: &'static Location<'static>) {
|
fn track_caller(&mut self, location: &'static Location<'static>) {
|
||||||
dbg!(location);
|
|
||||||
self.actual = Some(location.file());
|
self.actual = Some(location.file());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue