Skip to content
asciinemaPublic

About

asciinema virtual terminal

Topics

Resources

Contributing

Stars

233 stars

Watchers

2 watching

Forks

Repository files navigation

avt - asciinema virtual terminal

Test Crates.io

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.

Usage

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.

Examples

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

Development

To work on avt, first download the source code:

git clone https://github.com/asciinema/avt
cd avt

The recommended way to get the toolchain is the Nix dev shell, which provides everything needed and just works:

nix develop

If 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 build

To run the test suite:

cargo test
cargo fmt --check
cargo clippy --all-targets --all-features

To run the benchmarks:

cargo bench

For 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.

Donations

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:

Consulting

If you're interested in customization of avt or any other asciinema component to suit your corporate needs, check asciinema consulting services.

License

© 2019 Marcin Kulik.

All code is licensed under the Apache License, Version 2.0. See LICENSE file for details.

About

asciinema virtual terminal

Topics

Resources

Contributing

Stars

233 stars

Watchers

2 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages