avt is asciinema's implementation of virtual terminal emulator written in Rust.
It is used by asciinema CLI, asciinema player, asciinema server and asciinema gif generator.
avt parses text and control sequences, maintains virtual screen state, and provides synchronous observers of terminal operations.
avt consists of:
- parser for ANSI-compatible video terminal based on excellent state diagram by Paul Williams,
- virtual screen buffers (primary/alternate) in a form of character grid with additional color/styling attributes,
- API for feeding text or parsed actions and querying screen buffers and cursor position,
- composable observers for dirty rows, primary-text transcripts, and OSC 133 shell integration.
avt could serve as the emulation component of a full terminal emulator, but the following are outside its scope:
- user input handling, such as keyboard and mouse events,
- rendering (see asciinema player and agg).
avt doesn't try to 100% replicate any specific terminal variant like VT102 or VT520, instead it implements most control sequences supported by modern terminal emulators like Gnome Console, WezTerm, Alacritty, iTerm2, Ghostty, mosh etc.
Requires Rust 1.86 or later:
[dependencies]
avt = "0.19"The library is unconditionally no_std with alloc, requiring an allocator and
pointer-width atomics. Supported targets are Linux x86-64/ARM64, macOS ARM64,
and WebAssembly (wasm32-unknown-unknown).
use avt::Terminal;
fn main() -> Result<(), avt::InvalidSize> {
let mut terminal = Terminal::new(80, 24)?;
terminal.feed("hello\r\nworld");
let line = terminal.screen().line(1).unwrap();
assert_eq!(line.text().trim_end(), "world");
assert_eq!(terminal.cursor().position(), Some((5, 1)));
Ok(())
}Terminal::new retains no scrollback. Use
Terminal::with_scrollback(80, 24, Some(1_000)) for bounded primary history, or
None for unlimited retention. Both constructors reject zero dimensions.
Caller-requested resizes clamp dimensions to at least one.
Plain feed, execute, and resize return (). Their _with variants return
the observer's report. Feed accepts strings, characters, and flat iterables;
Parser can also parse independently for Terminal::execute.
For incremental rendering, use DirtyRows to identify rows to repaint:
use avt::observer::DirtyRows;
use avt::Terminal;
fn main() -> Result<(), avt::InvalidSize> {
let mut terminal = Terminal::new(80, 24)?;
let mut dirty = DirtyRows::new();
let rows = terminal.feed_with("hello\r\nworld", &mut dirty);
for row in rows {
let line = terminal.screen().line(row).unwrap();
// Render line.cells(), or inspect line.text().
}
let cursor = terminal.cursor().position();
Ok(())
}Keep observers across calls and pass them through every mutation, including
resizes. DirtyRows reports a full repaint on its first call; cursor-only changes
must be tracked separately. Observers compose as tuples:
use avt::observer::{DirtyRows, Transcript};
use avt::Terminal;
fn main() -> Result<(), avt::InvalidSize> {
let mut terminal = Terminal::new(80, 24)?;
let mut dirty = DirtyRows::new();
let mut transcript = Transcript::new();
let (rows, mut lines) = terminal.feed_with("hello\r\n", (&mut dirty, &mut transcript));
lines.extend(transcript.finish(&terminal));
Ok(())
}Transcript collects plain-text output from the primary screen, joining
soft-wrapped lines and preserving text as it scrolls out of view. Call finish
to collect the remaining content. Alternate-screen content is excluded.
avt is still pre-1.0. The API is settling, but breaking changes remain possible. See the migration guide if you're migrating from an earlier version.
Run an example with cargo run --example NAME:
| Example | Purpose |
|---|---|
text |
Read text and control sequences from stdin and print a transcript |
render |
Repaint dirty rows and cursor positions |
dump_restore |
Initialize a second terminal from a state dump and continue processing input |
history |
Reconstruct submitted commands from OSC 133 markers |
prompt_markers |
Locate prompt annotations and track their outcomes after reflow |
command_timestamps |
Associate submitted commands with application-provided prompt-start times |
index_text |
Compose a custom primary-text indexer with shell tracking |
slice |
Keep operations needed to reproduce selected screen cells and the cursor |
To work on avt, first download the source code:
git clone https://github.com/asciinema/avt
cd avtThe recommended way to get the toolchain is the Nix dev shell, which provides everything needed and just works:
nix developIf you don't use Nix, you need the Rust toolchain (1.86 or later) with Cargo, installed via your system package manager or rustup.
Then build the library with:
cargo buildTo run the test suite:
cargo test
cargo fmt --check
cargo clippy --all-targets --all-featuresTo run the benchmarks:
cargo benchFor benchmark smoke checks without timing, use cargo bench --bench NAME -- --test
with parser, terminal, or charset.
./fuzz.sh repeatedly runs property tests.
If you'd like to propose or submit any changes, please read the contribution guidelines first.
Sustainability of asciinema development relies on donations and sponsorships.
Please help the software project you use and love. Become a supporter or a corporate sponsor.
asciinema is sponsored by:
If you're interested in customization of avt or any other asciinema component to suit your corporate needs, check asciinema consulting services.
© 2019 Marcin Kulik.
All code is licensed under the Apache License, Version 2.0. See LICENSE file for details.