diff options
Diffstat (limited to 'src/term/renderer.rs')
-rw-r--r-- | src/term/renderer.rs | 149 |
1 files changed, 84 insertions, 65 deletions
diff --git a/src/term/renderer.rs b/src/term/renderer.rs index ab5656e..cd82e88 100644 --- a/src/term/renderer.rs +++ b/src/term/renderer.rs @@ -1,9 +1,9 @@ use std::io::{self, Write}; -use std::ops::{Range, RangeTo}; +use std::ops::Range; use termcolor::{ColorSpec, WriteColor}; use crate::diagnostic::{LabelStyle, Severity}; -use crate::files::Location; +use crate::files::{Error, Location}; use crate::term::{Chars, Config, Styles}; /// The 'location focus' of a source code snippet. @@ -23,20 +23,22 @@ pub type SingleLabel<'diagnostic> = (LabelStyle, Range<usize>, &'diagnostic str) /// A multi-line label to render. /// -/// Locations are relative to the start of where the source cord is rendered. +/// Locations are relative to the start of where the source code is rendered. pub enum MultiLabel<'diagnostic> { - /// Left top corner for multi-line labels. - /// - /// ```text - /// ╭ - /// ``` - TopLeft, /// Multi-line label top. + /// The contained value indicates where the label starts. /// /// ```text /// ╭────────────^ /// ``` - Top(RangeTo<usize>), + /// + /// Can also be rendered at the beginning of the line + /// if there is only whitespace before the label starts. + /// + /// /// ```text + /// ╭ + /// ``` + Top(usize), /// Left vertical labels for multi-line labels. /// /// ```text @@ -44,11 +46,12 @@ pub enum MultiLabel<'diagnostic> { /// ``` Left, /// Multi-line label bottom, with an optional message. + /// The first value indicates where the label ends. /// /// ```text /// ╰────────────^ blah blah /// ``` - Bottom(RangeTo<usize>, &'diagnostic str), + Bottom(usize, &'diagnostic str), } #[derive(Copy, Clone)] @@ -138,7 +141,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, code: Option<&str>, message: &str, - ) -> io::Result<()> { + ) -> Result<(), Error> { // Write locus // // ```text @@ -168,7 +171,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { // ```text // [E0001] // ``` - if let Some(code) = &code { + if let Some(code) = &code.filter(|code| !code.is_empty()) { write!(self, "[{}]", code)?; } @@ -181,15 +184,14 @@ impl<'writer, 'config> Renderer<'writer, 'config> { write!(self, ": {}", message)?; self.reset()?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } /// Empty line. - pub fn render_empty(&mut self) -> io::Result<()> { - write!(self, "\n")?; - + pub fn render_empty(&mut self) -> Result<(), Error> { + writeln!(self)?; Ok(()) } @@ -198,7 +200,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { /// ```text /// ┌─ test:2:9 /// ``` - pub fn render_snippet_start(&mut self, outer_padding: usize, locus: &Locus) -> io::Result<()> { + pub fn render_snippet_start( + &mut self, + outer_padding: usize, + locus: &Locus, + ) -> Result<(), Error> { self.outer_gutter(outer_padding)?; self.set_color(&self.styles().source_border)?; @@ -209,7 +215,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { write!(self, " ")?; self.snippet_locus(&locus)?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } @@ -229,7 +235,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { single_labels: &[SingleLabel<'_>], num_multi_labels: usize, multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], - ) -> io::Result<()> { + ) -> Result<(), Error> { // Trim trailing newlines, linefeeds, and null chars from source, if they exist. // FIXME: Use the number of trimmed placeholders when rendering single line carets let source = source.trim_end_matches(['\n', '\r', '\0'].as_ref()); @@ -250,7 +256,9 @@ impl<'writer, 'config> Renderer<'writer, 'config> { match multi_labels_iter.peek() { Some((label_index, label_style, label)) if *label_index == label_column => { match label { - MultiLabel::TopLeft => { + MultiLabel::Top(start) + if *start <= source.len() - source.trim_start().len() => + { self.label_multi_top_left(severity, *label_style)?; } MultiLabel::Top(..) => self.inner_gutter_space()?, @@ -276,9 +284,9 @@ impl<'writer, 'config> Renderer<'writer, 'config> { }) || multi_labels.iter().any(|(_, ls, label)| { *ls == LabelStyle::Primary && match label { - MultiLabel::Top(range) => column_range.start >= range.end, - MultiLabel::TopLeft | MultiLabel::Left => true, - MultiLabel::Bottom(range, _) => column_range.end <= range.end, + MultiLabel::Top(start) => column_range.start >= *start, + MultiLabel::Left => true, + MultiLabel::Bottom(start, _) => column_range.end <= *start, } }); @@ -299,7 +307,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { if in_primary { self.reset()?; } - write!(self, "\n")?; + writeln!(self)?; } // Write single labels underneath source @@ -456,7 +464,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { write!(self, "{}", message)?; self.reset()?; } - write!(self, "\n")?; + writeln!(self)?; // Write hanging labels pointing to carets // @@ -483,7 +491,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { trailing_label, source.char_indices(), )?; - write!(self, "\n")?; + writeln!(self)?; // Write hanging labels pointing to carets // @@ -511,7 +519,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { self.set_color(self.styles().label(severity, *label_style))?; write!(self, "{}", message)?; self.reset()?; - write!(self, "\n")?; + writeln!(self)?; } } } @@ -524,7 +532,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { // ``` for (multi_label_index, (_, label_style, label)) in multi_labels.iter().enumerate() { let (label_style, range, bottom_message) = match label { - MultiLabel::TopLeft | MultiLabel::Left => continue, // no label caret needed + MultiLabel::Left => continue, // no label caret needed + // no label caret needed if this can be started in front of the line + MultiLabel::Top(start) if *start <= source.len() - source.trim_start().len() => { + continue + } MultiLabel::Top(range) => (*label_style, range, None), MultiLabel::Bottom(range, message) => (*label_style, range, Some(message)), }; @@ -543,7 +555,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { match multi_labels_iter.peek() { Some((i, (label_index, ls, label))) if *label_index == label_column => { match label { - MultiLabel::TopLeft | MultiLabel::Left => { + MultiLabel::Left => { self.label_multi_left(severity, *ls, underline.map(|(s, _)| s))?; } MultiLabel::Top(..) if multi_label_index > *i => { @@ -571,11 +583,10 @@ impl<'writer, 'config> Renderer<'writer, 'config> { } // Finish the top or bottom caret - let range = range.clone(); match bottom_message { - None => self.label_multi_top_caret(severity, label_style, source, range)?, + None => self.label_multi_top_caret(severity, label_style, source, *range)?, Some(message) => { - self.label_multi_bottom_caret(severity, label_style, source, range, message)? + self.label_multi_bottom_caret(severity, label_style, source, *range, message)? } } } @@ -594,11 +605,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, num_multi_labels: usize, multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], - ) -> io::Result<()> { + ) -> Result<(), Error> { self.outer_gutter(outer_padding)?; self.border_left()?; self.inner_gutter(severity, num_multi_labels, multi_labels)?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } @@ -613,11 +624,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, num_multi_labels: usize, multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], - ) -> io::Result<()> { + ) -> Result<(), Error> { self.outer_gutter(outer_padding)?; self.border_left_break()?; self.inner_gutter(severity, num_multi_labels, multi_labels)?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } @@ -627,7 +638,11 @@ impl<'writer, 'config> Renderer<'writer, 'config> { /// = expected type `Int` /// found type `String` /// ``` - pub fn render_snippet_note(&mut self, outer_padding: usize, message: &str) -> io::Result<()> { + pub fn render_snippet_note( + &mut self, + outer_padding: usize, + message: &str, + ) -> Result<(), Error> { for (note_line_index, line) in message.lines().enumerate() { self.outer_gutter(outer_padding)?; match note_line_index { @@ -639,8 +654,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { _ => write!(self, " ")?, } // Write line of message - write!(self, " {}", line)?; - write!(self, "\n")?; + writeln!(self, " {}", line)?; } Ok(()) @@ -674,25 +688,29 @@ impl<'writer, 'config> Renderer<'writer, 'config> { } /// Location focus. - fn snippet_locus(&mut self, locus: &Locus) -> io::Result<()> { + fn snippet_locus(&mut self, locus: &Locus) -> Result<(), Error> { write!( self, "{name}:{line_number}:{column_number}", name = locus.name, line_number = locus.location.line_number, column_number = locus.location.column_number, - ) + )?; + Ok(()) } /// The outer gutter of a source line. - fn outer_gutter(&mut self, outer_padding: usize) -> io::Result<()> { - write!(self, "{space: >width$}", space = "", width = outer_padding)?; - write!(self, " ")?; + fn outer_gutter(&mut self, outer_padding: usize) -> Result<(), Error> { + write!(self, "{space: >width$} ", space = "", width = outer_padding)?; Ok(()) } /// The outer gutter of a source line, with line number. - fn outer_gutter_number(&mut self, line_number: usize, outer_padding: usize) -> io::Result<()> { + fn outer_gutter_number( + &mut self, + line_number: usize, + outer_padding: usize, + ) -> Result<(), Error> { self.set_color(&self.styles().line_number)?; write!( self, @@ -706,7 +724,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { } /// The left-hand border of a source line. - fn border_left(&mut self) -> io::Result<()> { + fn border_left(&mut self) -> Result<(), Error> { self.set_color(&self.styles().source_border)?; write!(self, "{}", self.chars().source_border_left)?; self.reset()?; @@ -714,7 +732,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { } /// The broken left-hand border of a source line. - fn border_left_break(&mut self) -> io::Result<()> { + fn border_left_break(&mut self) -> Result<(), Error> { self.set_color(&self.styles().source_border)?; write!(self, "{}", self.chars().source_border_left_break)?; self.reset()?; @@ -729,7 +747,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { single_labels: &[SingleLabel<'_>], trailing_label: Option<(usize, &SingleLabel<'_>)>, char_indices: impl Iterator<Item = (usize, char)>, - ) -> io::Result<()> { + ) -> Result<(), Error> { for (metrics, ch) in self.char_metrics(char_indices) { let column_range = metrics.byte_index..(metrics.byte_index + ch.len_utf8()); let label_style = hanging_labels(single_labels, trailing_label) @@ -765,7 +783,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, label_style: LabelStyle, underline: Option<LabelStyle>, - ) -> io::Result<()> { + ) -> Result<(), Error> { match underline { None => write!(self, " ")?, // Continue an underline horizontally @@ -790,7 +808,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { &mut self, severity: Severity, label_style: LabelStyle, - ) -> io::Result<()> { + ) -> Result<(), Error> { write!(self, " ")?; self.set_color(self.styles().label(severity, label_style))?; write!(self, "{}", self.chars().multi_top_left)?; @@ -807,7 +825,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { &mut self, severity: Severity, label_style: LabelStyle, - ) -> io::Result<()> { + ) -> Result<(), Error> { write!(self, " ")?; self.set_color(self.styles().label(severity, label_style))?; write!(self, "{}", self.chars().multi_bottom_left)?; @@ -825,13 +843,13 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, label_style: LabelStyle, source: &str, - range: RangeTo<usize>, - ) -> io::Result<()> { + start: usize, + ) -> Result<(), Error> { self.set_color(self.styles().label(severity, label_style))?; for (metrics, _) in self .char_metrics(source.char_indices()) - .take_while(|(metrics, _)| metrics.byte_index < range.end + 1) + .take_while(|(metrics, _)| metrics.byte_index < start + 1) { // FIXME: improve rendering of carets between character boundaries (0..metrics.unicode_width) @@ -844,7 +862,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { }; write!(self, "{}", caret_start)?; self.reset()?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } @@ -858,14 +876,14 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, label_style: LabelStyle, source: &str, - range: RangeTo<usize>, + start: usize, message: &str, - ) -> io::Result<()> { + ) -> Result<(), Error> { self.set_color(self.styles().label(severity, label_style))?; for (metrics, _) in self .char_metrics(source.char_indices()) - .take_while(|(metrics, _)| metrics.byte_index < range.end) + .take_while(|(metrics, _)| metrics.byte_index < start) { // FIXME: improve rendering of carets between character boundaries (0..metrics.unicode_width) @@ -881,7 +899,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { write!(self, " {}", message)?; } self.reset()?; - write!(self, "\n")?; + writeln!(self)?; Ok(()) } @@ -890,7 +908,7 @@ impl<'writer, 'config> Renderer<'writer, 'config> { &mut self, severity: Severity, underline: Option<Underline>, - ) -> io::Result<()> { + ) -> Result<(), Error> { match underline { None => self.inner_gutter_space(), Some((label_style, vertical_bound)) => { @@ -907,8 +925,9 @@ impl<'writer, 'config> Renderer<'writer, 'config> { } /// Writes an empty gutter space. - fn inner_gutter_space(&mut self) -> io::Result<()> { - write!(self, " ") + fn inner_gutter_space(&mut self) -> Result<(), Error> { + write!(self, " ")?; + Ok(()) } /// Writes an inner gutter, with the left lines if necessary. @@ -917,12 +936,12 @@ impl<'writer, 'config> Renderer<'writer, 'config> { severity: Severity, num_multi_labels: usize, multi_labels: &[(usize, LabelStyle, MultiLabel<'_>)], - ) -> io::Result<()> { + ) -> Result<(), Error> { let mut multi_labels_iter = multi_labels.iter().peekable(); for label_column in 0..num_multi_labels { match multi_labels_iter.peek() { Some((label_index, ls, label)) if *label_index == label_column => match label { - MultiLabel::TopLeft | MultiLabel::Left | MultiLabel::Bottom(..) => { + MultiLabel::Left | MultiLabel::Bottom(..) => { self.label_multi_left(severity, *ls, None)?; multi_labels_iter.next(); } |