diff --git a/Cargo.toml b/Cargo.toml index 61793fb7..2a692b5d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ exclude = ["images/", "tests/", "miette-derive/"] thiserror = "1.0.26" miette-derive = { path = "miette-derive", version = "=3.3.1-alpha.0"} once_cell = "1.8.0" +unicode-width = "0.1.9" owo-colors = { version = "3.0.0", optional = true } atty = { version = "0.2.14", optional = true } @@ -45,7 +46,7 @@ fancy = [ "supports-hyperlinks", "supports-color", "supports-unicode", - "backtrace" + "backtrace", ] [workspace] diff --git a/src/handlers/narratable.rs b/src/handlers/narratable.rs index 0581e0ac..31e75cf7 100644 --- a/src/handlers/narratable.rs +++ b/src/handlers/narratable.rs @@ -1,5 +1,7 @@ use std::fmt; +use unicode_width::{UnicodeWidthChar, UnicodeWidthStr}; + use crate::chain::Chain; use crate::protocol::{Diagnostic, Severity}; use crate::{LabeledSpan, MietteError, ReportHandler, SourceCode, SourceSpan, SpanContents}; @@ -208,23 +210,44 @@ impl NarratableReportHandler { writeln!(f)?; for line in &lines { writeln!(f, "snippet line {}: {}", line.line_number, line.text)?; - let relevant = labels.iter().filter(|l| line.span_starts(l.inner())); - for label in relevant { - let contents = source - .read_span(label.inner(), self.context_lines, self.context_lines) - .map_err(|_| fmt::Error)?; - if contents.line() + 1 == line.line_number { - write!( - f, - " label starting at line {}, column {}", - contents.line() + 1, - contents.column() + 1 - )?; - if let Some(label) = label.label() { - write!(f, ": {}", label)?; + let relevant = labels + .iter() + .filter_map(|l| line.span_attach(l.inner()).map(|a| (a, l))); + for (attach, label) in relevant { + match attach { + SpanAttach::Contained { col_start, col_end } if col_start == col_end => { + write!( + f, + " label at line {}, column {}", + line.line_number, col_start, + )?; + } + SpanAttach::Contained { col_start, col_end } => { + write!( + f, + " label at line {}, columns {} to {}", + line.line_number, col_start, col_end, + )?; + } + SpanAttach::Starts { col_start } => { + write!( + f, + " label starting at line {}, column {}", + line.line_number, col_start, + )?; } - writeln!(f)?; + SpanAttach::Ends { col_end } => { + write!( + f, + " label ending at line {}, column {}", + line.line_number, col_end, + )?; + } + } + if let Some(label) = label.label() { + write!(f, ": {}", label)?; } + writeln!(f)?; } } Ok(()) @@ -281,6 +304,7 @@ impl NarratableReportHandler { line_number: line, offset: line_offset, text: line_str.clone(), + at_end_of_file, }); line_str.clear(); line_offset = offset; @@ -308,12 +332,62 @@ struct Line { line_number: usize, offset: usize, text: String, + at_end_of_file: bool, +} + +enum SpanAttach { + Contained { col_start: usize, col_end: usize }, + Starts { col_start: usize }, + Ends { col_end: usize }, +} + +/// Returns column at offset, and nearest boundary if offset is in the middle of the character +fn safe_get_column(text: &str, offset: usize, start: bool) -> usize { + let mut column = text.get(0..offset).map(|s| s.width()).unwrap_or_else(|| { + let mut column = 0; + for (idx, c) in text.char_indices() { + if offset <= idx { + break; + } + column += c.width().unwrap_or(0); + } + column + }); + if start { + // Offset are zero-based, so plus one + column += 1; + } // On the other hand for end span, offset refers for the next column + // So we should do -1. column+1-1 == column + column } impl Line { - // Does this line contain the *beginning* of this multiline span? - // This assumes self.span_applies() is true already. - fn span_starts(&self, span: &SourceSpan) -> bool { - span.offset() >= self.offset + fn span_attach(&self, span: &SourceSpan) -> Option { + let span_end = span.offset() + span.len(); + let line_end = self.offset + self.text.len(); + + let start_after = span.offset() >= self.offset; + let end_before = self.at_end_of_file || span_end <= line_end; + + if start_after && end_before { + let col_start = safe_get_column(&self.text, span.offset() - self.offset, true); + let col_end = if span.is_empty() { + col_start + } else { + // span_end refers to the next character after token + // while col_end refers to the exact character, so -1 + safe_get_column(&self.text, span_end - self.offset, false) + }; + return Some(SpanAttach::Contained { col_start, col_end }); + } + if start_after && span.offset() <= line_end { + let col_start = safe_get_column(&self.text, span.offset() - self.offset, true); + return Some(SpanAttach::Starts { col_start }); + } + if end_before && span_end >= self.offset { + let col_end = safe_get_column(&self.text, span_end - self.offset, false); + return Some(SpanAttach::Ends { col_end }); + } + None } } diff --git a/tests/narrated.rs b/tests/narrated.rs index 6e906c6c..5d2eff1d 100644 --- a/tests/narrated.rs +++ b/tests/narrated.rs @@ -46,8 +46,8 @@ fn single_line_with_wide_char() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here snippet line 2: 👼🏼text + label at line 2, columns 3 to 6: this bit here snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -82,8 +82,8 @@ fn single_line_highlight() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here snippet line 2: text + label at line 2, columns 3 to 6: this bit here snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -118,7 +118,7 @@ fn single_line_highlight_offset_zero() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here + label at line 1, column 1: this bit here snippet line 2: text diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -153,8 +153,8 @@ fn single_line_highlight_with_empty_span() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here snippet line 2: text + label at line 2, column 3: this bit here snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -189,8 +189,8 @@ fn single_line_highlight_no_label() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1 snippet line 2: text + label at line 2, columns 3 to 6 snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -225,8 +225,8 @@ fn single_line_highlight_at_line_start() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here snippet line 2: text + label at line 2, columns 1 to 4: this bit here snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -267,10 +267,10 @@ fn multiple_same_line_highlights() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: x - label starting at line 1, column 1: y - label starting at line 1, column 1: z snippet line 2: text text text text text + label at line 2, columns 3 to 6: x + label at line 2, columns 8 to 11: y + label at line 2, columns 18 to 21: z snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -305,9 +305,10 @@ fn multiline_highlight_adjacent() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: these two lines snippet line 2: text + label starting at line 2, column 3: these two lines snippet line 3: here + label ending at line 3, column 6: these two lines diagnostic help: try doing it better next time? diagnostic code: oops::my::bad "# @@ -352,11 +353,13 @@ Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: line1 label starting at line 1, column 1: block 1 - label starting at line 1, column 1: block 2 snippet line 2: line2 + label starting at line 2, column 5: block 2 snippet line 3: line3 snippet line 4: line4 + label ending at line 4, column 1: block 2 snippet line 5: line5 + label ending at line 5, column 5: block 1 diagnostic help: try doing it better next time? diagnostic code: oops::my::bad "# @@ -418,11 +421,13 @@ Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: line1 label starting at line 1, column 1: block 1 - label starting at line 1, column 1 snippet line 2: line2 + label starting at line 2, column 5 snippet line 3: line3 snippet line 4: line4 + label ending at line 4, column 1 snippet line 5: line5 + label ending at line 5, column 5: block 1 diagnostic help: try doing it better next time? diagnostic code: oops::my::bad " @@ -461,9 +466,11 @@ Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source label starting at line 1, column 1: this bit here snippet line 2: text - label starting at line 2, column 1: also this bit + label ending at line 2, column 3: this bit here snippet line 3: here + label starting at line 3, column 7: also this bit snippet line 4: more here + label ending at line 4, column 3: also this bit diagnostic help: try doing it better next time? diagnostic code: oops::my::bad " @@ -575,8 +582,8 @@ fn related() -> Result<(), MietteError> { Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here snippet line 2: text + label at line 2, columns 3 to 6: this bit here snippet line 3: here diagnostic help: try doing it better next time? diagnostic code: oops::my::bad @@ -587,7 +594,7 @@ Error: oops! Begin snippet for bad_file.rs starting at line 1, column 1 snippet line 1: source - label starting at line 1, column 1: this bit here + label at line 1, columns 1 to 6: this bit here snippet line 2: text diagnostic help: try doing it better next time? diagnostic code: oops::my::bad