mirror of https://github.com/zkat/miette.git
fix(graphical) Align highlights correctly with tab characters (#87)
Fixes: https://github.com/zkat/miette/issues/87 BREAKING CHANGE: Tabs are always expanded to spaces by the graphical handler, and `tab_width` now defaults to 4. Instead of replacing every tab with a fixed number of spaces, spaces are used to align to the next tabstop. `tab_width` controls the space between tabstops rather than the fixed width of each tab character.
This commit is contained in:
parent
17ed8ba34e
commit
9e5872bab0
|
|
@ -1,7 +1,7 @@
|
|||
use std::fmt::{self, Write};
|
||||
|
||||
use owo_colors::{OwoColorize, Style};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
|
||||
use crate::diagnostic_chain::DiagnosticChain;
|
||||
use crate::handlers::theme::*;
|
||||
|
|
@ -28,7 +28,7 @@ pub struct GraphicalReportHandler {
|
|||
pub(crate) theme: GraphicalTheme,
|
||||
pub(crate) footer: Option<String>,
|
||||
pub(crate) context_lines: usize,
|
||||
pub(crate) tab_width: Option<usize>,
|
||||
pub(crate) tab_width: usize,
|
||||
pub(crate) with_cause_chain: bool,
|
||||
}
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ impl GraphicalReportHandler {
|
|||
theme: GraphicalTheme::default(),
|
||||
footer: None,
|
||||
context_lines: 1,
|
||||
tab_width: None,
|
||||
tab_width: 4,
|
||||
with_cause_chain: true,
|
||||
}
|
||||
}
|
||||
|
|
@ -62,14 +62,14 @@ impl GraphicalReportHandler {
|
|||
theme,
|
||||
footer: None,
|
||||
context_lines: 1,
|
||||
tab_width: None,
|
||||
tab_width: 4,
|
||||
with_cause_chain: true,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the displayed tab width in spaces.
|
||||
pub fn tab_width(mut self, width: usize) -> Self {
|
||||
self.tab_width = Some(width);
|
||||
self.tab_width = width;
|
||||
self
|
||||
}
|
||||
|
||||
|
|
@ -442,12 +442,7 @@ impl GraphicalReportHandler {
|
|||
self.render_line_gutter(f, max_gutter, line, &labels)?;
|
||||
|
||||
// And _now_ we can print out the line text itself!
|
||||
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)?;
|
||||
};
|
||||
self.render_line_text(f, &line.text)?;
|
||||
|
||||
// Next, we write all the highlights that apply to this particular line.
|
||||
let (single_line, multi_line): (Vec<_>, Vec<_>) = labels
|
||||
|
|
@ -605,15 +600,44 @@ impl GraphicalReportHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns an iterator over the visual width of each character in a line.
|
||||
fn line_visual_char_width<'a>(&self, text: &'a str) -> impl Iterator<Item = usize> + 'a {
|
||||
let mut column = 0;
|
||||
let tab_width = self.tab_width;
|
||||
text.chars().map(move |c| {
|
||||
let width = if c == '\t' {
|
||||
// Round up to the next multiple of tab_width
|
||||
tab_width - column % tab_width
|
||||
} else {
|
||||
c.width().unwrap_or(0)
|
||||
};
|
||||
column += width;
|
||||
width
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the visual column position of a byte offset on a specific line.
|
||||
fn visual_offset(&self, line: &Line, offset: usize) -> usize {
|
||||
let line_range = line.offset..(line.offset + line.length);
|
||||
assert!(line_range.contains(&offset));
|
||||
|
||||
let text = &line.text[..offset - line.offset];
|
||||
let tab_count = text.matches('\t').count();
|
||||
let tab_width = self.tab_width.unwrap_or(1);
|
||||
text.width() + tab_count * tab_width
|
||||
self.line_visual_char_width(text).sum()
|
||||
}
|
||||
|
||||
/// Renders a line to the output formatter, replacing tabs with spaces.
|
||||
fn render_line_text(&self, f: &mut impl fmt::Write, text: &str) -> fmt::Result {
|
||||
for (c, width) in text.chars().zip(self.line_visual_char_width(text)) {
|
||||
if c == '\t' {
|
||||
for _ in 0..width {
|
||||
f.write_char(' ')?
|
||||
}
|
||||
} else {
|
||||
f.write_char(c)?
|
||||
}
|
||||
}
|
||||
f.write_char('\n')?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn render_single_line_highlights(
|
||||
|
|
|
|||
|
|
@ -157,10 +157,10 @@ fn single_line_with_tab_in_middle() -> Result<(), MietteError> {
|
|||
|
||||
std::env::set_var("REPLACE_TABS", "4");
|
||||
|
||||
let src = "source\ntext\ttext\n here".to_string();
|
||||
let src = "source\ntext =\ttext\n here".to_string();
|
||||
let err = MyBad {
|
||||
src: NamedSource::new("bad_file.rs", src),
|
||||
highlight: (12, 4).into(),
|
||||
highlight: (14, 4).into(),
|
||||
};
|
||||
let out = fmt_report(err.into());
|
||||
println!("Error: {}", out);
|
||||
|
|
@ -169,7 +169,7 @@ fn single_line_with_tab_in_middle() -> Result<(), MietteError> {
|
|||
× oops!
|
||||
╭─[bad_file.rs:1:1]
|
||||
1 │ source
|
||||
2 │ text text
|
||||
2 │ text = text
|
||||
· ──┬─
|
||||
· ╰── this bit here
|
||||
3 │ here
|
||||
|
|
|
|||
Loading…
Reference in New Issue