Expand description
A library to render snippets of source code with annotations.
This crate is meant to be used as a building block for compiler diagnostics (error reporting, warnings, lints, etc.).
This crate is #![no_std], but it depends on alloc.
§Spans and positions
Annotation spans are Range<usize> indices into the
snippet’s source unit sequence (see Snippet). The exact unit depends
on how the snippet was built:
Snippet::with_utf8()uses byte offsets into the original&str.Snippet::with_utf8_bytes()uses byte offsets into the original&[u8].Snippet::with_latin1()uses byte offsets into the original&[u8].Snippet::with_utf16_words()uses 16-bit word offsets into the originalu16sequence.Snippet::with_chars()usescharindices into the original character sequence.
These indices are not indices into the rendered output: some characters
will be replaced with some representation (for example, tabs are replaced
with spaces, some control characters are replaced, and invalid UTF-8 can
be represented as � or <XX>). The library keeps the mapping so that
spans still line up with what is shown.
§Output flexibility
Rendering is backend-agnostic: the library emits a stream of UTF-8 fragments
tagged with metadata, and an Output implementation decides what to do
with them.
This lets you render to plain text (e.g. a String
or PlainOutput), or integrate with your own styling system (terminal colors,
HTML, etc.).
§Cargo features
std(enabled by default): enables features that depend onstd, currentlyPlainOutputfor writing rendered annotations to anystd::io::Write.
When the std feature is disabled, this crate is #![no_std] but still
depends on alloc.
§Example
// Some source code
let source = indoc::indoc! {r#"
fn main() {
println!("Hello, world!");
}
"#};
// Create the snippet
let snippet = sourceannot::Snippet::with_utf8(
1,
source,
4,
sourceannot::ControlCharStyle::Codepoint,
true,
);
// Styles are generic over the type of the metadata that accompanies each
// chunk of rendered text. In this example, we will use the following enum:
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
enum Color {
Default,
Red,
Green,
Blue,
}
// If do not you need this per-chunk metadata, you can use `()` instead.
// Define the styles
// Use Unicode box drawing characters
let main_style = sourceannot::MainStyle {
margin: Some(sourceannot::MarginStyle {
line_char: '│',
discontinuity_chars: [' ', ' ', '·'],
meta: Color::Blue,
}),
horizontal_char: '─',
vertical_char: '│',
top_vertical_char: '╭',
top_corner_char: '╭',
bottom_corner_char: '╰',
spaces_meta: Color::Default,
text_normal_meta: Color::Default,
text_alt_meta: Color::Default,
};
// You can use a different style for each annotation, but in
// this example we will use the same style for all of them.
let annot_style = sourceannot::AnnotStyle {
caret: '^',
text_normal_meta: Color::Red,
text_alt_meta: Color::Red,
line_meta: Color::Red,
};
// Create the annotations
let mut annotations = sourceannot::Annotations::new(&snippet, &main_style);
annotations.add_annotation(
0..44,
&annot_style,
vec![("this is the `main` function".into(), Color::Red)],
);
annotations.add_annotation(
16..24,
&annot_style,
vec![("this is a macro invocation".into(), Color::Red)],
);
// Render the snippet with annotations. `PlainOutput` can write to any
// `std::io::Write` ignoring colors. But you could use your favorite terminal
// coloring library with a wrapper that implements the `Output` trait.
let max_line_no_width = annotations.max_line_no_width();
annotations
.render(
max_line_no_width,
0,
0,
sourceannot::PlainOutput(std::io::stderr().lock()),
)
.expect("failed to write to stderr");
// You can also render to a string, which also ignores colors.
let mut rendered = String::new();
annotations.render(max_line_no_width, 0, 0, &mut rendered);
The output will look like this:
1 │ ╭ fn main() {
2 │ │ println!("Hello, world!");
│ │ ^^^^^^^^ this is a macro invocation
3 │ │ }
│ ╰─^ this is the `main` functionWith an invalid UTF-8 source:
// Some source code
let source = indoc::indoc! {b"
fn main() {
println!(\"Hello, \xFFworld!\");
}
"};
// Create the snippet
let snippet = sourceannot::Snippet::with_utf8_bytes(
1,
source,
4,
sourceannot::ControlCharStyle::Codepoint,
true,
sourceannot::InvalidSeqStyle::Hexadecimal,
true,
);
// Assume styles from the previous example...
let mut annotations = sourceannot::Annotations::new(&snippet, &main_style);
annotations.add_annotation(
0..45,
&annot_style,
vec![("this is the `main` function".into(), Color::Red)],
);
// Add a span that points to the invalid UTF-8 byte.
annotations.add_annotation(
33..34,
&annot_style,
vec![("this an invalid UTF-8 sequence".into(), Color::Red)],
);
let max_line_no_width = annotations.max_line_no_width();
annotations
.render(
max_line_no_width,
0,
0,
sourceannot::PlainOutput(std::io::stderr().lock()),
)
.expect("failed to write to stderr");
The output will look like this:
1 │ ╭ fn main() {
2 │ │ println!("Hello, <FF>world!");
│ │ ^^^^ this an invalid UTF-8 sequence
3 │ │ }
│ ╰─^ this is the `main` functionStructs§
- Annot
Style - The style of a particular annotation.
- Annotations
- A collection of annotations attached to a
Snippet. - Main
Style - The general style of an annotated snippet.
- Margin
Style - The style of the margin of an annotated snippet.
- Plain
Output - An
Outputimplementor that writes to anystd::io::Writeignoring metadata. - Snippet
- A snippet of source code prepared for annotated rendering.
- Snippet
Builder - Incrementally constructs a
Snippet.
Enums§
- Control
Char Style - Style for how control characters should be represented in a snippet.
- Invalid
SeqStyle - Style for how invalid encoded sequences are represented.
Traits§
- Output
- Trait that consumes a rendered annotated snippet.
Functions§
- char_
should_ be_ replaced - Returns whether
chrshould be replaced/escaped when building aSnippet.