Skip to main content

Crate codesnake

Crate codesnake 

Source
Expand description

Pretty printer for non-overlapping code spans.

This crate aids you in creating output like the following, both for the terminal (ANSI) as well as for the web (HTML):

  ╭─[fac.lisp]
  │
1 │   (defun factorial (n) (if (zerop n) 1────┬────          ╰───────────────────────── this function ...
  ┆ ╭──────────────────────╯
2 │          (* n (factorial (1- n)))))                                                                    ╰─────────────────────────────────┴─── ... is defined by this
  ┆                                     ╰─ (and here is EOF)
──╯

This example has been created with cargo run --example example -- --html. To see its console output, run cargo run --example example.

§Usage

Suppose that we have a source file and a list of byte ranges that we want to annotate. For example:

let src = r#"if true { 42 } else { "42" }"#;
let labels = [
    (8..14, "this is of type Nat"),
    (20..28, "this is of type String"),
];

First, we have to create a LineIndex. This splits the source into lines, so that further functions can quickly find in which line a byte is situated.

use codesnake::LineIndex;
let idx = LineIndex::new(src);

Next, we create a code Block from our index and the Labels:

use codesnake::{Block, Label};
let block = Block::new(&idx, labels.map(|(range, text)| Label::<_, _, ()>::new(range).with_text(text))).unwrap();

This will fail if your labels refer to bytes outside the range of your source.

Finally, we can print our code block:

use codesnake::CodeWidth;
let block = block.map_code(|c| CodeWidth::new(c, c.len()));
// yield "  ╭─[main.rs]"
println!("{}{}", block.prologue(), "[main.rs]");
print!("{block}");
// yield "──╯"
println!("{}", block.epilogue());

§Colors

To color the output on a terminal, you can use a crate like yansi. This allows you to color labels as follows:

use codesnake::{Block, CodeWidth, Label, LineIndex};
use yansi::{Color, Paint};
let label = Label::<_, &str, _>::new(range).with_style(Color::Red);
let block = block.with_paint(|f, style, code| {
    if *style == Color::default() {
        write!(f, "{code}")
    } else {
        write!(f, "{}", code.fg(*style))
    }
});
assert_eq!(block.to_string(), "1 │ if true \u{1b}[31m{ 42 }\u{1b}[0m else { \"42\" }\n");

For HTML, you can use something like:

use codesnake::{Block, CodeWidth, Label, LineIndex};
let label = Label::<_, &str, _>::new(range).with_style("color:red");
let block = block.with_paint(|f, style, code| {
    if style.is_empty() {
        write!(f, "{code}")
    } else {
        write!(f, "<span style=\"{style}\">{code}</span>")
    }
});
assert_eq!(block.to_string(), "1 │ if true <span style=\"color:red\">{ 42 }</span> else { \"42\" }\n");

Structs§

Block
Sequence of lines, containing code C, (label) text T, and style S.
CodeWidth
Piece of code together with its display width.
Label
Code label with text and style.
LineIndex
Associate byte offsets with line numbers.