From c943dbc7539148e3eb78d5beda68ac0372568c2a Mon Sep 17 00:00:00 2001 From: cad97 Date: Sun, 20 Oct 2019 22:01:06 -0400 Subject: [PATCH 1/4] take-three API --- benches/simple.rs | 148 ++++++--- examples/format.rs | 57 ++-- src/annotation.rs | 25 -- src/display_list/annotation.rs | 8 - src/display_list/line.rs | 51 --- src/display_list/list.rs | 147 --------- src/display_list/mod.rs | 5 - src/formatter/mod.rs | 151 +++++++++ src/formatter/types.rs | 170 ++++++++++ src/input.rs | 226 +++++++++++++ src/lib.rs | 17 +- src/renderer/mod.rs | 323 +++++++++++++++++++ src/renderers/ascii_default/mod.rs | 226 ------------- src/renderers/ascii_default/styles/color.rs | 34 -- src/renderers/ascii_default/styles/color2.rs | 42 --- src/renderers/ascii_default/styles/mod.rs | 29 -- src/renderers/ascii_default/styles/plain.rs | 16 - src/renderers/mod.rs | 21 -- src/slice.rs | 9 - src/snippet.rs | 9 - src/span.rs | 185 +++++++++++ 21 files changed, 1193 insertions(+), 706 deletions(-) delete mode 100644 src/annotation.rs delete mode 100644 src/display_list/annotation.rs delete mode 100644 src/display_list/line.rs delete mode 100644 src/display_list/list.rs delete mode 100644 src/display_list/mod.rs create mode 100644 src/formatter/mod.rs create mode 100644 src/formatter/types.rs create mode 100644 src/input.rs create mode 100644 src/renderer/mod.rs delete mode 100644 src/renderers/ascii_default/mod.rs delete mode 100644 src/renderers/ascii_default/styles/color.rs delete mode 100644 src/renderers/ascii_default/styles/color2.rs delete mode 100644 src/renderers/ascii_default/styles/mod.rs delete mode 100644 src/renderers/ascii_default/styles/plain.rs delete mode 100644 src/renderers/mod.rs delete mode 100644 src/slice.rs delete mode 100644 src/snippet.rs create mode 100644 src/span.rs diff --git a/benches/simple.rs b/benches/simple.rs index 35da8d6..afd2d00 100644 --- a/benches/simple.rs +++ b/benches/simple.rs @@ -4,70 +4,122 @@ extern crate criterion; use criterion::black_box; use criterion::Criterion; -use annotate_snippets::DisplayList; -use annotate_snippets::{Annotation, AnnotationType, SourceAnnotation}; -use annotate_snippets::{Slice, Snippet}; - -use annotate_snippets::renderers::ascii_default::get_renderer; -use annotate_snippets::renderers::Renderer; +use annotate_snippets::*; +use std::ops::Range; const SOURCE: &'static str = r#") -> Option { -for ann in annotations { - match (ann.range.0, ann.range.1) { - (None, None) => continue, - (Some(start), Some(end)) if start > end_index => continue, - (Some(start), Some(end)) if start >= start_index => { - let label = if let Some(ref label) = ann.label { - format!(" {}", label) - } else { - String::from("") - }; + for ann in annotations { + match (ann.range.0, ann.range.1) { + (None, None) => continue, + (Some(start), Some(end)) if start > end_index => continue, + (Some(start), Some(end)) if start >= start_index => { + let label = if let Some(ref label) = ann.label { + format!(" {}", label) + } else { + String::from("") + }; - return Some(format!( - "{}{}{}", - " ".repeat(start - start_index), - "^".repeat(end - start), - label - )); + return Some(format!( + "{}{}{}", + " ".repeat(start - start_index), + "^".repeat(end - start), + label + )); + } + _ => continue, } - _ => continue, + }"#; + +fn source_snippet() -> Snippet<'static, WithLineNumber<&'static str>> { + Snippet { + title: Some(Title { + code: Some(&"E0308"), + message: Message { + text: &"mismatched types", + level: Level::Error, + }, + }), + slices: &[Slice { + span: WithLineNumber { + line_num: 51, + data: SOURCE, + }, + origin: Some(&"src/format.rs"), + annotations: &[ + Annotation { + span: 5..19, + message: Some(Message { + text: &"expected `Option` because of return type", + level: Level::Warning, + }), + }, + Annotation { + span: 26..725, + message: Some(Message { + text: &"expected enum `std::option::Option`", + level: Level::Error, + }), + }, + ], + footer: &[], + }], } -}"#; +} -fn create_snippet() { - let snippet = Snippet { - title: Some(Annotation { - id: Some("E0308"), - label: Some("mismatched types"), - annotation_type: AnnotationType::Error, +fn range_snippet() -> Snippet<'static, Range> { + Snippet { + title: Some(Title { + code: Some(&"E0308"), + message: Message { + text: &"mismatched types", + level: Level::Error, + }, }), - footer: &[], slices: &[Slice { - source: SOURCE, - line_start: Some(51), - origin: Some("src/format.rs"), + span: 0..725, + origin: Some(&"src/format.rs"), annotations: &[ - SourceAnnotation { - label: "expected `Option` because of return type", - annotation_type: AnnotationType::Warning, - range: 5..19, + Annotation { + span: 5..19, + message: Some(Message { + text: &"expected `Option` because of return type", + level: Level::Warning, + }), }, - SourceAnnotation { - label: "expected enum `std::option::Option`", - annotation_type: AnnotationType::Error, - range: 23..725, + Annotation { + span: 26..725, + message: Some(Message { + text: &"expected enum `std::option::Option`", + level: Level::Error, + }), }, ], + footer: &[], }], - }; - let r = get_renderer(); - let dl: DisplayList = (&snippet).into(); - let mut result: Vec = Vec::new(); - r.fmt(&mut result, &dl).unwrap(); + } } pub fn criterion_benchmark(c: &mut Criterion) { - c.bench_function("format", |b| b.iter(|| black_box(create_snippet()))); + c.bench_function("format [&str]", |b| { + b.iter(|| { + black_box({ + let snippet = source_snippet(); + let formatted = format(&snippet, &()); + let mut out: Vec = Vec::new(); + renderer::Ascii::plain().render(&formatted, &(), &mut out) + }) + }) + }); + c.bench_function("format [Range]", |b| { + b.iter(|| { + black_box({ + let snippet = range_snippet(); + let formatted = format(&snippet, &SOURCE); + let mut out: Vec = Vec::new(); + renderer::Ascii::plain().render(&formatted, &SOURCE, &mut out) + }) + }) + }); } criterion_group!(benches, criterion_benchmark); diff --git a/examples/format.rs b/examples/format.rs index c59bb09..e5b16e6 100644 --- a/examples/format.rs +++ b/examples/format.rs @@ -1,9 +1,5 @@ -use annotate_snippets::DisplayList; -use annotate_snippets::{Annotation, AnnotationType, SourceAnnotation}; -use annotate_snippets::{Slice, Snippet}; - -use annotate_snippets::renderers::get_renderer; -use annotate_snippets::renderers::Renderer; +use annotate_snippets::*; +use std::io; fn main() { let source = r#") -> Option { @@ -30,34 +26,41 @@ fn main() { }"#; let snippet = Snippet { - title: Some(Annotation { - id: Some("E0308"), - label: Some("mismatched types"), - annotation_type: AnnotationType::Error, + title: Some(Title { + code: Some(&"E0308"), + message: Message { + text: &"mismatched types", + level: Level::Error, + }, }), - footer: &[], slices: &[Slice { - source, - line_start: Some(51), - origin: Some("src/format.rs"), + span: WithLineNumber { + line_num: 51, + data: source, + }, + origin: Some(&"src/format.rs"), annotations: &[ - SourceAnnotation { - label: "expected `Option` because of return type", - annotation_type: AnnotationType::Warning, - range: 5..19, + Annotation { + span: 5..19, + message: Some(Message { + text: &"expected `Option` because of return type", + level: Level::Warning, + }), }, - SourceAnnotation { - label: "expected enum `std::option::Option`", - annotation_type: AnnotationType::Error, - range: 23..725, + Annotation { + span: 26..725, + message: Some(Message { + text: &"expected enum `std::option::Option`", + level: Level::Error, + }), }, ], + footer: &[], }], }; - let dl = DisplayList::from(&snippet); - let r = get_renderer(); - let mut s: Vec = Vec::new(); - r.fmt(&mut s, &dl).unwrap(); - println!("{}", std::str::from_utf8(&s).unwrap()); + let formatted = format(&snippet, &()); + renderer::Ascii::ansi() + .render(&formatted, &(), &mut io::stdout().lock()) + .unwrap(); } diff --git a/src/annotation.rs b/src/annotation.rs deleted file mode 100644 index b1ac52d..0000000 --- a/src/annotation.rs +++ /dev/null @@ -1,25 +0,0 @@ -use std::ops::Range; - -#[derive(Debug, Clone)] -pub struct Annotation<'s> { - pub id: Option<&'s str>, - pub label: Option<&'s str>, - pub annotation_type: AnnotationType, -} - -#[derive(Debug, Clone)] -pub enum AnnotationType { - None, - Error, - Warning, - Info, - Note, - Help, -} - -#[derive(Debug, Clone)] -pub struct SourceAnnotation<'s> { - pub range: Range, - pub label: &'s str, - pub annotation_type: AnnotationType, -} diff --git a/src/display_list/annotation.rs b/src/display_list/annotation.rs deleted file mode 100644 index a9da08c..0000000 --- a/src/display_list/annotation.rs +++ /dev/null @@ -1,8 +0,0 @@ -use crate::annotation::AnnotationType; - -#[derive(Debug, Clone)] -pub struct Annotation<'d> { - pub annotation_type: AnnotationType, - pub id: Option<&'d str>, - pub label: &'d str, -} diff --git a/src/display_list/line.rs b/src/display_list/line.rs deleted file mode 100644 index f0774b7..0000000 --- a/src/display_list/line.rs +++ /dev/null @@ -1,51 +0,0 @@ -use super::annotation::Annotation; -use crate::annotation::AnnotationType; -use std::ops::Range; - -#[derive(Debug, Clone)] -pub enum DisplayLine<'d> { - Source { - lineno: Option, - inline_marks: Vec, - line: DisplaySourceLine<'d>, - }, - Raw(DisplayRawLine<'d>), -} - -#[derive(Debug, Clone)] -pub enum DisplaySourceLine<'d> { - Content { - text: &'d str, - }, - Annotation { - annotation: Annotation<'d>, - range: Range, - }, - Empty, -} - -#[derive(Debug, Clone)] -pub enum DisplayRawLine<'d> { - Origin { - path: &'d str, - pos: (Option, Option), - }, - Annotation { - annotation: Annotation<'d>, - source_aligned: bool, - continuation: bool, - }, -} - -#[derive(Debug, Clone)] -pub struct DisplayMark { - pub mark_type: DisplayMarkType, - pub annotation_type: AnnotationType, -} - -#[derive(Debug, Clone)] -pub enum DisplayMarkType { - AnnotationThrough, - AnnotationStart, - AnnotationEnd, -} diff --git a/src/display_list/list.rs b/src/display_list/list.rs deleted file mode 100644 index 3b18248..0000000 --- a/src/display_list/list.rs +++ /dev/null @@ -1,147 +0,0 @@ -use super::annotation::Annotation; -use super::line::{DisplayLine, DisplayMark, DisplayMarkType, DisplayRawLine, DisplaySourceLine}; -use crate::{Slice, Snippet, SourceAnnotation}; - -#[derive(Debug, Clone)] -pub struct DisplayList<'d> { - pub body: Vec>, -} - -fn get_header_pos(slice: &Slice) -> (Option, Option) { - let line = slice.line_start; - (line, None) -} - -impl<'d> From<&Snippet<'d>> for DisplayList<'d> { - fn from(snippet: &Snippet<'d>) -> Self { - let mut body = vec![]; - - if let Some(annotation) = &snippet.title { - let label = annotation.label.unwrap_or_default(); - body.push(DisplayLine::Raw(DisplayRawLine::Annotation { - annotation: Annotation { - annotation_type: annotation.annotation_type.clone(), - id: annotation.id, - label: &label, - }, - source_aligned: false, - continuation: false, - })); - } - - for slice in snippet.slices { - let slice_dl: DisplayList = slice.into(); - body.extend(slice_dl.body); - } - DisplayList { body } - } -} - -impl<'d> From<&Slice<'d>> for DisplayList<'d> { - fn from(slice: &Slice<'d>) -> Self { - let mut body = vec![]; - - if let Some(path) = slice.origin { - body.push(DisplayLine::Raw(DisplayRawLine::Origin { - path, - pos: get_header_pos(slice), - })); - } - - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - - let mut annotations: Vec<&SourceAnnotation> = slice.annotations.iter().collect(); - - // let mut current_annotation = annotations.next(); - let mut line_start_pos = 0; - - let mut i = slice.line_start.unwrap_or(1); - for line in slice.source.lines() { - let line_range = line_start_pos..(line_start_pos + line.chars().count() + 1); - - let mut current_annotations = vec![]; - let mut inline_marks = vec![]; - - annotations.retain(|ann| { - if line_range.contains(&ann.range.start) && line_range.contains(&ann.range.end) { - // Annotation in this line - current_annotations.push(*ann); - false - } else if line_range.contains(&ann.range.start) - && !line_range.contains(&ann.range.end) - { - // Annotation starts in this line - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationStart, - annotation_type: ann.annotation_type.clone(), - }); - true - } else if ann.range.start < line_range.start && ann.range.end > line_range.end { - // Annotation goes through this line - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: ann.annotation_type.clone(), - }); - true - } else if line_range.contains(&ann.range.end) { - // Annotation ends on this line - inline_marks.push(DisplayMark { - mark_type: DisplayMarkType::AnnotationThrough, - annotation_type: ann.annotation_type.clone(), - }); - current_annotations.push(*ann); - false - } else { - true - } - }); - - body.push(DisplayLine::Source { - lineno: Some(i), - inline_marks, - line: DisplaySourceLine::Content { text: line }, - }); - for ann in current_annotations { - let start = if ann.range.start >= line_start_pos { - ann.range.start - line_start_pos - } else { - 0 - }; - let inline_marks = if ann.range.start < line_start_pos { - vec![DisplayMark { - mark_type: DisplayMarkType::AnnotationEnd, - annotation_type: ann.annotation_type.clone(), - }] - } else { - vec![] - }; - body.push(DisplayLine::Source { - lineno: None, - inline_marks, - line: DisplaySourceLine::Annotation { - annotation: Annotation { - annotation_type: ann.annotation_type.clone(), - id: None, - label: ann.label, - }, - range: start..(ann.range.end - line_start_pos), - }, - }); - } - line_start_pos += line_range.len(); - i += 1; - } - - body.push(DisplayLine::Source { - lineno: None, - inline_marks: vec![], - line: DisplaySourceLine::Empty, - }); - - DisplayList { body } - } -} diff --git a/src/display_list/mod.rs b/src/display_list/mod.rs deleted file mode 100644 index 49ff81a..0000000 --- a/src/display_list/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod annotation; -pub mod line; -pub mod list; - -pub use list::DisplayList; diff --git a/src/formatter/mod.rs b/src/formatter/mod.rs new file mode 100644 index 0000000..693185d --- /dev/null +++ b/src/formatter/mod.rs @@ -0,0 +1,151 @@ +use crate::{Annotation, Level, Slice, Snippet, Span as _, SpanFormatter, WithLineNumber}; +use std::cmp; + +pub fn format<'d, Span: crate::Span>( + snippet: &'d Snippet<'d, Span>, + f: &dyn SpanFormatter, +) -> FormattedSnippet<'d, Span> { + let mut lines = vec![]; + + if let Some(title) = snippet.title { + lines.push(DisplayLine::Raw(RawLine::Title { title })) + } + + for slice in snippet.slices { + format_into(&mut lines, slice, f); + } + + FormattedSnippet { lines } +} + +fn format_into<'d, Span: crate::Span>( + lines: &mut Vec>, + slice: &'d Slice<'d, Span>, + f: &dyn SpanFormatter, +) { + let mut this_line = f.first_line(&slice.span); + + if let Some(origin) = slice.origin { + lines.push(DisplayLine::Raw(RawLine::Origin { + path: origin, + pos: Some(( + this_line.line_num, + f.count_columns( + &slice.span, + &slice.span.slice(this_line.data.start()..slice.span.start()), + ), + )), + })); + + // spacing line iff origin line present + lines.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: SourceLine::Empty, + }) + } + + // TODO: benchmark whether `retain` here benefits more than the allocation overhead + let mut annotations: Vec<&Annotation<'_, _>> = slice.annotations.iter().collect(); + + let mut process_line = |line: &WithLineNumber| { + let WithLineNumber { + data: line, + line_num, + } = line; + + let mut annotations_here = vec![]; + let mut marks_here = vec![]; + + annotations.retain(|&ann| { + let level = ann.message.map(|m| m.level).unwrap_or(Level::Info); + + if line.start() <= ann.span.start() && ann.span.end() <= line.end() { + // Annotation in this line + annotations_here.push(ann); + false + } else if line.start() <= ann.span.start() && ann.span.start() <= line.end() { + // Annotation starts in this line + marks_here.push(Mark { + kind: MarkKind::Start, + level, + }); + true + } else if ann.span.start() < line.start() && line.end() < ann.span.end() { + // Annotation goes through this line + marks_here.push(Mark { + kind: MarkKind::Continue, + level, + }); + true + } else if ann.span.start() < line.start() && ann.span.end() <= line.end() { + // Annotation ends on this line + marks_here.push(Mark { + kind: MarkKind::Continue, + level, + }); + annotations_here.push(ann); + false + } else { + // Annotation starts on later line + true + } + }); + + lines.push(DisplayLine::Source { + lineno: Some(*line_num), + inline_marks: marks_here, + line: SourceLine::Content { span: &slice.span, subspan: line.clone() }, + }); + + for ann in annotations_here { + let level = ann.message.map(|m| m.level).unwrap_or(Level::Info); + + let start_pos = cmp::max(ann.span.start(), line.start()); + let start = f.count_columns(&slice.span, &slice.span.slice(line.start()..start_pos)); + let len = f.count_columns(&slice.span, &slice.span.slice(start_pos..ann.span.end())); + + let marks_here = if ann.span.start() < line.start() { + vec![Mark { + kind: MarkKind::Here, + level, + }] + } else { + vec![] + }; + + lines.push(DisplayLine::Source { + lineno: None, + inline_marks: marks_here, + line: SourceLine::Annotation { + message: ann.message, + underline: (start, len), + }, + }) + } + }; + + process_line(&this_line); + while let Some(line) = f.next_line(&slice.span, &this_line) { + this_line = line; + process_line(&this_line); + } + + if !slice.footer.is_empty() { + // spacing line iff footer lines follow + lines.push(DisplayLine::Source { + lineno: None, + inline_marks: vec![], + line: SourceLine::Empty, + }); + + for &message in slice.footer { + lines.push(DisplayLine::Raw(RawLine::Message { message })) + } + } +} + +mod types; +pub use types::{ + DisplayLine, Mark, MarkKind, RawLine, SourceLine, FormattedSnippet, +}; diff --git a/src/formatter/types.rs b/src/formatter/types.rs new file mode 100644 index 0000000..87a7174 --- /dev/null +++ b/src/formatter/types.rs @@ -0,0 +1,170 @@ +use crate::{DebugAndDisplay, Level, Message, Title}; +use std::fmt; + +// Cannot derive Debug, Clone because we need to bound Span::Subspan +// so #[derive(Debug, Clone)] is manually expanded here (ugh) + +pub struct FormattedSnippet<'d, Span: crate::Span> { + pub lines: Vec>, +} + +impl fmt::Debug for FormattedSnippet<'_, Span> +where + Span: fmt::Debug, + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("FormattedSnippet") + .field("inner", &self.lines) + .finish() + } +} + +impl Clone for FormattedSnippet<'_, Span> +where + Span::Subspan: Clone, +{ + fn clone(&self) -> Self { + FormattedSnippet { + lines: self.lines.clone(), + } + } +} + +pub enum DisplayLine<'d, Span: crate::Span> { + Source { + lineno: Option, + inline_marks: Vec, + line: SourceLine<'d, Span>, + }, + Raw(RawLine<'d>), +} + +// #[derive(Debug)] +impl fmt::Debug for DisplayLine<'_, Span> +where + Span: fmt::Debug, + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + DisplayLine::Source { + lineno, + inline_marks, + line, + } => f + .debug_struct("Source") + .field("lineno", lineno) + .field("inline_marks", inline_marks) + .field("line", line) + .finish(), + DisplayLine::Raw(raw) => f.debug_tuple("Raw").field(raw).finish(), + } + } +} + +// #[derive(Clone)] +impl Clone for DisplayLine<'_, Span> +where + Span::Subspan: Clone, +{ + fn clone(&self) -> Self { + match self { + DisplayLine::Source { + lineno, + inline_marks, + line, + } => DisplayLine::Source { + lineno: *lineno, + inline_marks: inline_marks.clone(), + line: (*line).clone(), + }, + DisplayLine::Raw(raw) => DisplayLine::Raw(*raw), + } + } +} + +pub enum SourceLine<'d, Span: crate::Span> { + Content { + span: &'d Span, + subspan: Span::Subspan, + }, + Annotation { + message: Option>, + underline: (usize, usize), + }, + Empty, +} + +// #[derive(Debug)] +impl fmt::Debug for SourceLine<'_, Span> +where + Span: fmt::Debug, + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SourceLine::Content { span, subspan } => f + .debug_struct("Content") + .field("span", span) + .field("subspan", subspan) + .finish(), + SourceLine::Annotation { message, underline } => f + .debug_struct("Annotation") + .field("message", message) + .field("underline", underline) + .finish(), + SourceLine::Empty => f.debug_struct("Empty").finish(), + } + } +} + +// #[derive(Copy)] +impl Copy for SourceLine<'_, Span> where Span::Subspan: Copy {} + +// #[derive(Clone)] +impl Clone for SourceLine<'_, Span> +where + Span::Subspan: Clone, +{ + fn clone(&self) -> Self { + match self { + SourceLine::Content { span, subspan } => SourceLine::Content { + span: span.clone(), + subspan: subspan.clone(), + }, + SourceLine::Annotation { message, underline } => SourceLine::Annotation { + message: *message, + underline: *underline, + }, + SourceLine::Empty => SourceLine::Empty, + } + } +} + +#[derive(Debug, Copy, Clone)] +pub enum RawLine<'d> { + Origin { + path: &'d dyn DebugAndDisplay, + pos: Option<(usize, usize)>, + }, + Title { + title: Title<'d>, + }, + Message { + message: Message<'d>, + }, +} + +#[derive(Debug, Copy, Clone)] +pub struct Mark { + pub kind: MarkKind, + pub level: Level, +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub enum MarkKind { + Start, + Continue, + Here, +} diff --git a/src/input.rs b/src/input.rs new file mode 100644 index 0000000..527f1b0 --- /dev/null +++ b/src/input.rs @@ -0,0 +1,226 @@ +use std::fmt; + +pub trait DebugAndDisplay: fmt::Debug + fmt::Display {} +impl DebugAndDisplay for T {} + +// Cannot derive Debug because we need to bound Span::Subspan +// so #[derive(Debug)] is manually expanded here (ugh) + +/// Primary structure for annotation formatting. +/// +/// # Examples +/// +/// To produce the error annotation +/// +/// ```text +/// error[E0277]: `std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely +/// --> examples/nonsend_future.rs:23:5 +/// | +/// 5 | fn is_send(t: T) { +/// | ------- ---- required by this bound in `is_send` +/// ... +/// 23 | is_send(foo()); +/// | ^^^^^^^ `std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely +/// | +/// = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, u32>` +/// note: future does not implement `std::marker::Send` as this value is used across an await +/// --> examples/nonsend_future.rs:15:3 +/// | +/// 14 | let g = x.lock().unwrap(); +/// | - has type `std::sync::MutexGuard<'_, u32>` +/// 15 | baz().await; +/// | ^^^^^^^^^^^ await occurs here, with `g` maybe used later +/// 16 | } +/// | - `g` is later dropped here +/// ``` +/// +/// two snippets are used: +/// +/// ```rust +/// # use annotate_snippets::*; +/// let first_snippet = Snippet { +/// title: Some(Title { +/// code: Some(&"E0277"), +/// message: Message { +/// text: &"`std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely", +/// level: Level::Error, +/// }, +/// }), +/// slices: &[Slice { +/// span: WithLineNumber { +/// data: "fn is_send(t: T) {\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n is_send(foo());", +/// line_num: 5, +/// }, +/// origin: Some(&"examples/nonsend_future.rs"), +/// annotations: &[ +/// Annotation { +/// span: 4..11, +/// message: None, +/// }, +/// Annotation { +/// span: 14..18, +/// message: Some(Message { +/// text: &"required by this bound in `is_send`", +/// level: Level::Info, +/// }) +/// }, +/// Annotation { +/// span: 67..74, +/// message: Some(Message { +/// text: &"`std::sync::MutexGuard<'_, u32>` cannot be sent between threads safely", +/// level: Level::Error, +/// }) +/// }, +/// ], +/// footer: &[Message { +/// text: &"within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::sync::MutexGuard<'_, u32>`", +/// level: Level::Help, +/// }], +/// }], +/// }; +/// let second_snippet = Snippet { +/// title: Some(Title { +/// code: None, +/// message: Message { +/// text: &"future does not implement `std::marker::Send` as this value is used across an await", +/// level: Level::Note, +/// }, +/// }), +/// slices: &[Slice { +/// span: WithLineNumber { +/// data: " let g = x.lock().unwrap();\n baz().await;\n}", +/// line_num: 14, +/// }, +/// origin: Some(&"examples/nonsend_future.rs"), +/// annotations: &[ +/// Annotation { +/// span: 8..9, +/// message: Some(Message { +/// text: &"has type `std::sync::MutexGuard<'_, u32>`", +/// level: Level::Info, +/// }), +/// }, +/// Annotation { +/// span: 36..47, +/// message: Some(Message { +/// text: &"await occurs here, with `g` maybe used later", +/// level: Level::Error, +/// }) +/// }, +/// Annotation { +/// span: 50..51, +/// message: Some(Message { +/// text: &"`g` is later dropped here", +/// level: Level::Info, +/// }) +/// }, +/// ], +/// footer: &[], +/// }], +/// }; +/// ``` +#[derive(Copy, Clone)] +pub struct Snippet<'s, Span: crate::Span> { + pub title: Option>, + pub slices: &'s [Slice<'s, Span>], +} + +// #[derive(Debug)] +impl fmt::Debug for Snippet<'_, Span> +where + Span: fmt::Debug, + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Snippet") + .field("title", &self.title) + .field("slices", &self.slices) + .finish() + } +} + +/// Title line for an annotation snippet. +#[derive(Debug, Copy, Clone)] +pub struct Title<'s> { + pub code: Option<&'s dyn DebugAndDisplay>, + pub message: Message<'s>, +} + +/// A slice of text with annotations. +#[derive(Copy, Clone)] +pub struct Slice<'s, Span: crate::Span> { + pub span: Span, + pub origin: Option<&'s dyn DebugAndDisplay>, + pub annotations: &'s [Annotation<'s, Span>], + pub footer: &'s [Message<'s>], +} + +// #[derive(Debug)] +impl fmt::Debug for Slice<'_, Span> +where + Span: fmt::Debug, + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Slice") + .field("span", &self.span) + .field("origin", &self.origin) + .field("annotations", &self.annotations) + .field("footer", &self.footer) + .finish() + } +} + +/// An annotation for some span. +pub struct Annotation<'s, Span: crate::Span> { + pub span: Span::Subspan, + pub message: Option>, +} + +// #[derive(Debug)] +impl fmt::Debug for Annotation<'_, Span> +where + Span::Subspan: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("Annotation") + .field("span", &self.span) + .field("message", &self.message) + .finish() + } +} + +// #[derive(Copy)] +impl Copy for Annotation<'_, Span> where Span::Subspan: Copy {} + +// #[derive(Clone)] +impl Clone for Annotation<'_, Span> +where + Span::Subspan: Clone, +{ + fn clone(&self) -> Self { + Annotation { + span: self.span.clone(), + message: self.message, + } + } +} + +/// A message with an associated level. +#[derive(Debug, Copy, Clone)] +pub struct Message<'s> { + pub text: &'s dyn DebugAndDisplay, + pub level: Level, +} + +/// A level of severity for an annotation message. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] +pub enum Level { + /// Typically displayed using a red color. + Error, + /// Typically displayed using a blue color. + Warning, + Info, + Note, + Help, +} diff --git a/src/lib.rs b/src/lib.rs index 9848c43..da57daf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,9 @@ -pub mod annotation; -mod display_list; -pub mod renderers; -pub mod slice; -pub mod snippet; +pub mod formatter; +mod input; +pub mod renderer; +mod span; -pub use annotation::{Annotation, AnnotationType, SourceAnnotation}; -pub use display_list::DisplayList; -pub use slice::Slice; -pub use snippet::Snippet; +pub use formatter::format; +pub use input::{Annotation, DebugAndDisplay, Level, Message, Slice, Snippet, Title}; +pub use renderer::Renderer; +pub use span::{Span, SpanFormatter, SpanWriter, WithLineNumber}; diff --git a/src/renderer/mod.rs b/src/renderer/mod.rs new file mode 100644 index 0000000..a3357e1 --- /dev/null +++ b/src/renderer/mod.rs @@ -0,0 +1,323 @@ +use crate::{ + formatter::{DisplayLine, FormattedSnippet, Mark, MarkKind, RawLine, SourceLine}, + DebugAndDisplay, Level, SpanWriter, +}; +use std::io; + +pub trait Renderer { + fn render<'a, Span: crate::Span>( + &self, + snippet: &FormattedSnippet<'a, Span>, + f: &dyn SpanWriter, + w: &mut dyn io::Write, + ) -> io::Result<()>; +} + +pub struct Ascii { + use_ansi: bool, +} + +impl Ascii { + pub fn plain() -> Self { + Ascii { use_ansi: false } + } + + pub fn ansi() -> Self { + Ascii { use_ansi: true } + } +} + +impl Ascii { + #[inline(always)] + fn reset(&self, w: &mut dyn io::Write) -> io::Result<()> { + if self.use_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") + } else { + Ok(()) + } + } + + // bold + fg(Fixed(12)) + #[inline(always)] + fn bold_bright_blue(&self, w: &mut dyn io::Write) -> io::Result<()> { + if self.use_ansi { + write!(w, "\x1B[1;34;1m") + } else { + Ok(()) + } + } + + // fg(Fixed(14)) + #[inline(always)] + fn bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> { + if self.use_ansi { + write!(w, "\x1B[0;36;1m") + } else { + Ok(()) + } + } + + // bold + fg(Fixed(14)) + #[inline(always)] + fn bold_bright_cyan(&self, w: &mut dyn io::Write) -> io::Result<()> { + if self.use_ansi { + 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_line( + &self, + line: &DisplayLine<'_, Span>, + line_num_width: usize, + marks_width: usize, + f: &dyn SpanWriter, + w: &mut dyn io::Write, + ) -> io::Result<()> { + match line { + DisplayLine::Source { + lineno, + inline_marks, + line, + } => { + self.bold_bright_blue(w)?; + if let Some(lineno) = lineno { + write!(w, "{:>width$} | ", lineno, width = line_num_width)?; + } else { + write!(w, "{:>width$} | ", "", width = line_num_width)?; + } + self.reset(w)?; + write!( + w, + "{:>width$}", + "", + width = marks_width - inline_marks.len() + )?; + self.render_marks(inline_marks, w)?; + self.render_source_line(line, f, w)?; + writeln!(w) + } + DisplayLine::Raw(line) => self.render_raw_line(line, line_num_width, w), + } + } + + 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 => '\\', + }; + write!(w, "{}", c)?; + } + self.reset(w) + } + + fn render_source_line( + &self, + line: &SourceLine<'_, Span>, + f: &dyn SpanWriter, + w: &mut dyn io::Write, + ) -> io::Result<()> { + match line { + SourceLine::Content { span, subspan } => 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)?; + } else { + write!(w, "{:->width$} ", "", width = underline.1)?; + } + write!( + w, + "{}", + message.map_or(&"" as &dyn DebugAndDisplay, |message| message.text) + )?; + self.reset(w) + } + SourceLine::Empty => Ok(()), + } + } + + fn render_raw_line( + &self, + line: &RawLine<'_>, + line_num_width: usize, + w: &mut dyn io::Write, + ) -> io::Result<()> { + match line { + &RawLine::Origin { path, pos } => { + write!(w, "{:>width$}", "", width = line_num_width)?; + self.bold_bright_blue(w)?; + write!(w, "-->")?; + self.reset(w)?; + write!(w, " {}", path)?; + if let Some((line, column)) = pos { + write!(w, ":{}:{}", line, column)?; + } + writeln!(w) + } + RawLine::Message { message } => { + self.style_for(message.level, w)?; + let cta = match message.level { + Level::Error => "error", + Level::Warning => "warning", + Level::Info => "info", + Level::Note => "note", + Level::Help => "help", + }; + write!(w, "{:>width$} = {}", "", cta, width = line_num_width)?; + writeln!(w, ": {}", message.text) + } + RawLine::Title { title } => { + self.style_bold_for(title.message.level, w)?; + let cta = match title.message.level { + Level::Error => "error", + Level::Warning => "warning", + Level::Info => "info", + Level::Note => "note", + Level::Help => "help", + }; + write!(w, "{}", cta)?; + if let Some(code) = title.code { + write!(w, "[{}]", code)?; + } + self.bold(w)?; + writeln!(w, ": {}", title.message.text) + } + } + } +} + +impl Renderer for Ascii { + fn render<'a, Span: crate::Span>( + &self, + snippet: &FormattedSnippet<'a, Span>, + f: &dyn SpanWriter, + w: &mut dyn io::Write, + ) -> io::Result<()> { + // note that the line numbers of multiple slices might not be in order + let max_line_num = snippet + .lines + .iter() + .filter_map(|line| match *line { + DisplayLine::Source { lineno, .. } => lineno, + _ => None, + }) + .max() + .unwrap_or(0); + + let marks_width = snippet + .lines + .iter() + .filter_map(|line| match line { + DisplayLine::Source { inline_marks, .. } => Some(inline_marks.len()), + _ => None, + }) + .max() + .unwrap_or(0); + + for line in &snippet.lines { + self.render_line(line, log10usize(max_line_num), marks_width, f, w)?; + } + + self.reset(w) + } +} + +fn log10usize(mut n: usize) -> usize { + let mut sum = 0; + while n != 0 { + n /= 10; + sum += 1; + } + sum +} diff --git a/src/renderers/ascii_default/mod.rs b/src/renderers/ascii_default/mod.rs deleted file mode 100644 index deb3d76..0000000 --- a/src/renderers/ascii_default/mod.rs +++ /dev/null @@ -1,226 +0,0 @@ -mod marks; -mod styles; - -#[cfg(feature = "ansi_term")] -use crate::renderers::ascii_default::styles::color::Style; -#[cfg(feature = "termcolor")] -use crate::renderers::ascii_default::styles::color2::Style; -#[cfg(all(not(feature = "ansi_term"), not(feature = "termcolor")))] -use crate::renderers::ascii_default::styles::plain::Style; - -use super::Renderer as RendererTrait; -use crate::annotation::AnnotationType; -use crate::display_list::line::DisplayLine; -use crate::display_list::line::DisplayMark; -use crate::display_list::line::DisplayMarkType; -use crate::display_list::line::DisplayRawLine; -use crate::display_list::line::DisplaySourceLine; -use crate::DisplayList; -use marks::MarkKind; -use std::cmp; -use std::io::Write; -use std::iter::repeat; -use std::marker::PhantomData; -use styles::Style as StyleTrait; -use styles::StyleType; - -fn digits(n: usize) -> usize { - let mut n = n; - let mut sum = 0; - while n != 0 { - n /= 10; - sum += 1; - } - sum -} - -pub struct Renderer { - style: PhantomData, -} - -pub fn get_renderer() -> impl RendererTrait { - Renderer::