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");