Skip to main content

Crate sourceannot

Crate sourceannot 

Source
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:

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 on std, currently PlainOutput for writing rendered annotations to any std::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` function

With 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` function

Structs§

AnnotStyle
The style of a particular annotation.
Annotations
A collection of annotations attached to a Snippet.
MainStyle
The general style of an annotated snippet.
MarginStyle
The style of the margin of an annotated snippet.
PlainOutput
An Output implementor that writes to any std::io::Write ignoring metadata.
Snippet
A snippet of source code prepared for annotated rendering.
SnippetBuilder
Incrementally constructs a Snippet.

Enums§

ControlCharStyle
Style for how control characters should be represented in a snippet.
InvalidSeqStyle
Style for how invalid encoded sequences are represented.

Traits§

Output
Trait that consumes a rendered annotated snippet.

Functions§

char_should_be_replaced
Returns whether chr should be replaced/escaped when building a Snippet.