From 4091ae5b830eafe11b8dca8fb8b77785bcc1f9fb Mon Sep 17 00:00:00 2001 From: cad97 Date: Mon, 21 Oct 2019 20:48:44 -0400 Subject: [PATCH] Support box drawing characters --- benches/simple.rs | 29 +++--- examples/format.rs | 4 +- src/input.rs | 3 +- src/renderer/default.rs | 213 +++++++++++++++++++--------------------- 4 files changed, 119 insertions(+), 130 deletions(-) diff --git a/benches/simple.rs b/benches/simple.rs index afd2d00..e11e9e9 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -1,8 +1,7 @@ #[macro_use] extern crate criterion; -use criterion::black_box; -use criterion::Criterion; +use criterion::{BatchSize, Criterion}; use annotate_snippets::*; use std::ops::Range; @@ -101,24 +100,26 @@ fn range_snippet() -> Snippet<'static, Range> { pub fn criterion_benchmark(c: &mut Criterion) { c.bench_function("format [&str]", |b| { - b.iter(|| { - black_box({ + b.iter_batched_ref( + || Vec::::with_capacity(1100), + |out| { let snippet = source_snippet(); let formatted = format(&snippet, &()); - let mut out: Vec = Vec::new(); - renderer::Ascii::plain().render(&formatted, &(), &mut out) - }) - }) + renderer::Ascii::new().render(&formatted, &(), out) + }, + BatchSize::SmallInput, + ) }); c.bench_function("format [Range]", |b| { - b.iter(|| { - black_box({ + b.iter_batched_ref( + || Vec::::with_capacity(1100), + |out| { let snippet = range_snippet(); let formatted = format(&snippet, &SOURCE); - let mut out: Vec = Vec::new(); - renderer::Ascii::plain().render(&formatted, &SOURCE, &mut out) - }) - }) + renderer::Ascii::new().render(&formatted, &SOURCE, out) + }, + BatchSize::SmallInput, + ) }); } diff --git a/examples/format.rs b/examples/format.rs index e5b16e6..4519750 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -60,7 +60,9 @@ fn main() { }; let formatted = format(&snippet, &()); - renderer::Ascii::ansi() + renderer::Ascii::new() + .ansi(true) + .box_drawing(true) .render(&formatted, &(), &mut io::stdout().lock()) .unwrap(); } diff --git a/src/input.rs b/src/input.rs index 527f1b0..a855838 100644 --- a/src/input.rs +++ b/src/input.rs @@ -218,8 +218,9 @@ pub struct Message<'s> { pub enum Level { /// Typically displayed using a red color. Error, - /// Typically displayed using a blue color. + /// Typically displayed using a yellow color. Warning, + /// Typically displayed using a blue color. Info, Note, Help, diff --git a/src/renderer/default.rs b/src/renderer/default.rs index a38165f..675e6d7 100644 --- a/src/renderer/default.rs +++ b/src/renderer/default.rs @@ -5,83 +5,46 @@ use crate::{ }; use std::io; +#[derive(Debug, Copy, Clone, Default)] pub struct Ascii { - use_ansi: bool, + pub ansi: bool, + #[allow(unused)] // TODO + pub fold: bool, + pub box_drawing: bool, + #[doc(hidden)] // to allow structural creation with `Ascii { ..Default::default() }` + pub __non_exhaustive: (), } impl Ascii { - pub fn plain() -> Self { - Ascii { use_ansi: false } + pub fn new() -> Self { + Default::default() } - pub fn ansi() -> Self { - Ascii { use_ansi: true } + pub fn ansi(&mut self, b: bool) -> &mut Self { + self.ansi = b; + self + } + + pub fn box_drawing(&mut self, b: bool) -> &mut Self { + self.box_drawing = b; + self } } impl Ascii { #[inline(always)] - fn reset(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { + fn reset(self, w: &mut dyn io::Write) -> io::Result<()> { + if self.ansi { write!(w, "\x1B[0m") } else { Ok(()) } } - fn bold(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[0;1m") - } else { - Ok(()) - } - } - - // fg(Fixed(9)) #[inline(always)] - fn bright_red(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[0;31;1m") - } else { - Ok(()) - } - } - - // bold + fg(Fixed(9)) - #[inline(always)] - fn bold_bright_red(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[1;31;1m") - } else { - Ok(()) - } - } - - // fg(Fixed(11)) - #[inline(always)] - fn bright_yellow(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[0;33;1m") - } else { - Ok(()) - } - } - - // bold + fg(Fixed(11)) - #[inline(always)] - fn bold_bright_yellow(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[1;33;1m") - } else { - Ok(()) - } - } - - // fg(Fixed(12)) - #[inline(always)] - fn bright_blue(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[0;34;1m") + fn bold(self, w: &mut dyn io::Write) -> io::Result<()> { + if self.ansi { + write!(w, "\x1B[0;1m") } else { Ok(()) } @@ -89,67 +52,63 @@ impl Ascii { // bold + fg(Fixed(12)) #[inline(always)] - fn bold_bright_blue(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { + fn bold_bright_blue(self, w: &mut dyn io::Write) -> io::Result<()> { + if self.ansi { write!(w, "\x1B[1;34;1m") } else { Ok(()) } } - // fg(Fixed(14)) + // FIXME: emitted ANSI codes are highly redundant when repeated #[inline(always)] - fn bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[0;36;1m") + fn style_for(self, level: Level, w: &mut dyn io::Write) -> io::Result<()> { + if self.ansi { + match level { + Level::Error => write!(w, "\x1B[0;31;1m"), + Level::Warning => write!(w, "\x1B[0;33;1m"), + Level::Info => write!(w, "\x1B[0;34;1m"), + Level::Note => self.reset(w), + Level::Help => write!(w, "\x1B[0;36;1m"), + } } else { Ok(()) } } - // bold + fg(Fixed(14)) + // FIXME: emitted ANSI codes are highly redundant when repeated #[inline(always)] - fn bold_bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> { - if self.use_ansi { - write!(w, "\x1B[1;36;1m") + fn style_bold_for(self, level: Level, w: &mut dyn io::Write) -> io::Result<()> { + if self.ansi { + match level { + Level::Error => write!(w, "\x1B[1;31;1m"), + Level::Warning => write!(w, "\x1B[1;33;1m"), + Level::Info => write!(w, "\x1B[1;34;1m"), + Level::Note => self.reset(w), + Level::Help => write!(w, "\x1B[1;36;1m"), + } } else { Ok(()) } } - - // FIXME: emitted ANSI codes are highly redundant when repeated - #[inline(always)] - fn style_for(&self, level: Level, w: &mut dyn io::Write) -> io::Result<()> { - match level { - Level::Error => self.bright_red(w), - Level::Warning => self.bright_yellow(w), - Level::Info => self.bright_blue(w), - Level::Note => self.reset(w), - Level::Help => self.bright_cyan(w), - } - } - - // FIXME: emitted ANSI codes are highly redundant when repeated - #[inline(always)] - fn style_bold_for(&self, level: Level, w: &mut dyn io::Write) -> io::Result<()> { - match level { - Level::Error => self.bold_bright_red(w), - Level::Warning => self.bold_bright_yellow(w), - Level::Info => self.bold_bright_blue(w), - Level::Note => self.reset(w), - Level::Help => self.bold_bright_cyan(w), - } - } } impl Ascii { - fn render_marks(&self, marks: &[Mark], w: &mut dyn io::Write) -> io::Result<()> { + fn render_marks(self, marks: &[Mark], w: &mut dyn io::Write) -> io::Result<()> { for mark in marks { self.style_for(mark.level, w)?; - let c = match mark.kind { - MarkKind::Start => '/', - MarkKind::Continue => '|', - MarkKind::Here => '\\', + let c = if self.box_drawing { + match mark.kind { + MarkKind::Start => '┌', + MarkKind::Continue => '│', + MarkKind::Here => '└', + } + } else { + match mark.kind { + MarkKind::Start => '/', + MarkKind::Continue => '|', + MarkKind::Here => '\\', + } }; write!(w, "{}", c)?; } @@ -157,35 +116,53 @@ impl Ascii { } fn render_source_line( - &self, + self, line: &SourceLine<'_, Span>, + is_long: bool, f: &dyn SpanWriter, w: &mut dyn io::Write, ) -> io::Result<()> { match line { - SourceLine::Content { span, subspan } => f.write(w, span, subspan), + SourceLine::Content { span, subspan } => { + write!(w, " ")?; + f.write(w, span, subspan) + } SourceLine::Annotation { message, underline } => { - write!(w, "{:>width$}", "", width = underline.0)?; - self.style_bold_for(message.map_or(Level::Info, |message| message.level), w)?; - // FIXME: respect level for pointer character - if underline.0 == 0 { - write!(w, "{:_>width$} ", "^", width = underline.1)?; + let (indent, len) = if is_long { + (0, underline.0 + underline.1 + 1) + } else { + (underline.0 + 1, underline.1) + }; + write!(w, "{:>width$}", "", width = indent)?; + let level = message.map_or(Level::Info, |message| message.level); + self.style_bold_for(level, w)?; + if is_long { + if self.box_drawing { + write!(w, "{:─>width$} ", "┘", width = len)?; + } else { + write!(w, "{:_>width$} ", "^", width = len)?; + } } else { - write!(w, "{:->width$} ", "", width = underline.1)?; + match level { + Level::Error => write!(w, "{:^>width$} ", "", width = len)?, + Level::Warning => write!(w, "{:~>width$} ", "", width = len)?, + Level::Info | Level::Help | Level::Note => { + write!(w, "{:->width$} ", "", width = len)? + } + } } write!( w, "{}", message.map_or(&"" as &dyn DebugAndDisplay, |message| message.text) - )?; - self.reset(w) + ) } SourceLine::Empty => Ok(()), } } fn render_raw_line( - &self, + self, line: &RawLine<'_>, line_num_width: usize, w: &mut dyn io::Write, @@ -194,7 +171,11 @@ impl Ascii { &RawLine::Origin { path, pos } => { write!(w, "{:>width$}", "", width = line_num_width)?; self.bold_bright_blue(w)?; - write!(w, "-->")?; + if self.box_drawing { + write!(w, "═╦═")?; + } else { + write!(w, "-->")?; + } self.reset(w)?; write!(w, " {}", path)?; if let Some((line, column)) = pos { @@ -228,7 +209,7 @@ impl Ascii { write!(w, "[{}]", code)?; } self.bold(w)?; - writeln!(w, ": {}", title.message.text) + writeln!(w, ": {}", title.message.text) } } } @@ -266,10 +247,11 @@ impl Renderer for Ascii { line, } => { self.bold_bright_blue(w)?; + let sep = if self.box_drawing { '║' } else { '|' }; if let Some(lineno) = lineno { - write!(w, "{:>width$} | ", lineno, width = line_num_width)?; + write!(w, "{:>width$} {} ", lineno, sep, width = line_num_width)?; } else { - write!(w, "{:>width$} | ", "", width = line_num_width)?; + write!(w, "{:>width$} {} ", "", sep, width = line_num_width)?; } self.reset(w)?; write!( @@ -279,7 +261,10 @@ impl Renderer for Ascii { width = marks_width - inline_marks.len() )?; self.render_marks(inline_marks, w)?; - self.render_source_line(line, f, w)?; + let is_long = inline_marks + .last() + .map_or(false, |mark| mark.kind == MarkKind::Here); + self.render_source_line(line, is_long, f, w)?; writeln!(w) } DisplayLine::Raw(line) => self.render_raw_line(line, line_num_width, w),