← All libraries

CandyVt

🍬 CandyVt

In-memory virtual terminal emulator

port of x/vt vt500-parser cell-grid snapshot-tests no-pty

Feed an ANSI byte stream in, query a cell grid + cursor + mode state out — without ever spawning a real terminal. Built on a Paul-Williams VT500 state machine that mirrors charmbracelet/x/ansi/parser.

Install

composer require sugarcraft/candy-vt

Quickstart

use SugarCraft\Vt\Terminal\Terminal;

$term = Terminal::create(cols: 80, rows: 24);
$term->feed("\x1b[1;31mHello\x1b[0m world");

$screen = $term->screen();
$screen->cell(0, 0)->grapheme;       // 'H'
$screen->cell(0, 0)->sgr->bold;      // true
$screen->cell(0, 0)->foreground();   // 16-color red

$term->feed("\x1b]2;Demo\x07");
$term->windowTitle();                 // 'Demo'

$term->feed("\x1b[?1049h");
$term->mode()->altScreen;             // true

What's in the box

VT500 parser15-state Paul-Williams state machine. Direct port of charmbracelet/x/ansi/parser. Handles partial input across feed() calls and CSI subparameters (: separator per VT500 spec).
Full SGR16-color, bright, 256-color, and truecolor for fg + bg; bold / italic / underline / strikethrough / blink / reverse / dim / hidden toggles; SGR underline styles 4:0–4:5 (single / double / curly / dotted / dashed); BCE (CSI ?12 h) — erase fills inherit current background color.
Cursor + erase + scrollCUU/CUD/CUF/CUB/CUP/CHA/VPA, DECSC/DECRC, EL/ED/ECH/DCH/ICH, SU/SD, IND/RI/NEL.
DEC modes1049 alt screen + cursor save, 25 cursor visibility, 1000/1002/1003/1006 mouse, 2004 bracketed paste, 2026 synchronized output (batch queue + flush on disable), 7 auto-wrap (DECAWM).
OSC dispatch0/1/2 window title, 4 palette set, 8 hyperlinks (per-cell attribution), 52 clipboard write/read events.
Width-aware + combiningCJK + most emoji take 2 cells with a continuation marker. Zero-width combining marks (U+0300–U+036F) attach to the preceding cell via Cell::$combining so composed graphemes render correctly in a single column.
Scrollback bufferRing buffer (default 1000 rows) captures scrolled-off content. Access via Screen::scrollback(). Configure size with Terminal::withScrollbackSize(). Erased by CSI 3 J.

Use it for

Source & demos

Try the quickstart →

API

ClassMethodDescription
Terminalcreate(cols, rows)Create a virtual terminal
Terminalfeed(bytes)Feed ANSI bytes to terminal
Terminalscreen()Get current cell grid
Terminalcursor()Get cursor position
Terminalmode()Get terminal mode state (includes autoWrap)
TerminalwindowTitle()Get window title from OSC
TerminalwithScrollbackSize(n)New terminal with scrollback buffer size N
Screencell(col, row)Get cell at position
Screenscrollback()Get scrollback buffer (ring buffer of scrolled-off rows)
Cellgrapheme, sgr, combining, foreground(), background()Cell content, style, and combining marks
CellwithCombining(string)New cell with additional combining marks appended
ModeautoWrap, syncUpdate, withAutoWrap(bool)DECAWM auto-wrap and synchronized-output (DEC 2026) state
SgrunderlineStyle, withUnderlineStyle(UnderlineStyle)SGR underline style (None/Single/Double/Curly/Dotted/Dashed)
UnderlineStyleNone / Single / Double / Curly / Dotted / DashedEnum — CSI 4:N map (4:0–4:5)

Demos.

VHS-recorded GIFs of every example shipped with the library. Regenerated automatically on every push that touches the source.

Feed and screenshot

Feed and screenshot

An ANSI byte stream feeds into a Terminal; the cell grid prints back as a screenshot, with window title, cursor position, and hyperlink span.