Skip to content

Commit

Permalink
feat(tabs): Add replace tabs with spaces option (#82)
Browse files Browse the repository at this point in the history
Fixes: #73
  • Loading branch information
jlkiri authored and zkat committed Oct 6, 2021
1 parent cb5a919 commit 1f70140
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 7 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ miette::set_hook(Box::new(|_| {
.terminal_links(true)
.unicode(false)
.context_lines(3)
.tab_width(4)
.build())
}))

Expand Down
10 changes: 10 additions & 0 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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),
}
Expand Down
41 changes: 35 additions & 6 deletions src/handlers/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -37,6 +38,7 @@ impl GraphicalReportHandler {
theme: GraphicalTheme::default(),
footer: None,
context_lines: 1,
tab_width: None,
}
}

Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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() {
Expand Down
131 changes: 131 additions & 0 deletions tests/graphical.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)]
Expand Down Expand Up @@ -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)]
Expand Down
1 change: 0 additions & 1 deletion tests/test_location.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}
Expand Down

0 comments on commit 1f70140

Please sign in to comment.