Skip to main content

AlacrittyRenderer - VTE Parsing

The AlacrittyRenderer wraps Alacritty’s battle-tested VTE parser to process ANSI escape sequences with fast native performance.

Overview

┌─────────────────────────────────────────────────────────────────────┐
│               ALACRITTY RENDERER IN MONOLEX                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   PTY Output (raw bytes + ANSI)                                     │
│       │                                                             │
│       ▼                                                             │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │  AlacrittyRenderer                                          │   │
│   │                                                             │   │
│   │  ┌──────────────────┐    ┌──────────────────────────────┐   │   │
│   │  │  AtomicParser    │───→│  alacritty_terminal::Term    │   │   │
│   │  │  BSU/ESU detect  │    │  VTE Parser + Grid State     │   │   │
│   │  └──────────────────┘    └──────────────────────────────┘   │   │
│   │                                   │                         │   │
│   │                                   ▼                         │   │
│   │                          ┌──────────────────┐               │   │
│   │                          │  CellConverter   │               │   │
│   │                          │  Alacritty→xterm │               │   │
│   │                          └──────────────────┘               │   │
│   │                                   │                         │   │
│   └───────────────────────────────────│─────────────────────────┘   │
│                                       ▼                             │
│   GridUpdate { viewport, scrollback, cursor, epoch }                │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Why Alacritty?

Aspectxterm.js ParserAlacritty Parser
LanguageJavaScriptRust
PerformanceInterpretedNative
MemoryGC overheadZero-copy
Test CoverageGoodExcellent (1000+)
ANSI SupportStandardExtensive
CJK SupportBasicFull

Core Components

AlacrittyRenderer

Wrapper around alacritty_terminal::Term:
pub struct AlacrittyRenderer {
    term: Term<EventProxy>,
    size: TermSize,
    dirty_lines: HashSet<usize>,
    request_full_update: bool,
}

impl AlacrittyRenderer {
    pub fn new(cols: u16, rows: u16) -> Self {
        let size = TermSize::new(cols as usize, rows as usize);
        let term = Term::new(config, &size, EventProxy::new());

        Self {
            term,
            size,
            dirty_lines: HashSet::new(),
            request_full_update: true,
        }
    }

    pub fn process(&mut self, data: &[u8]) -> Option<GridUpdate> {
        // 1. Feed data to Alacritty VTE parser
        for byte in data {
            self.term.input(*byte);
        }

        // 2. Mark dirty lines
        self.mark_dirty_lines();

        // 3. Convert to GridUpdate if needed
        if self.should_emit() {
            Some(self.to_grid_update())
        } else {
            None
        }
    }
}

AtomicParser

Detects BSU/ESU (Begin/End Synchronized Update) sequences:
pub struct AtomicParser {
    state: ParserState,
    buffer: Vec<u8>,
}

impl AtomicParser {
    pub fn process(&mut self, data: &[u8]) -> ProcessResult {
        for byte in data {
            match self.state {
                ParserState::Normal => {
                    if self.detect_bsu(byte) {
                        self.state = ParserState::InAtomicBlock;
                    }
                }
                ParserState::InAtomicBlock => {
                    if self.detect_esu(byte) {
                        self.state = ParserState::Normal;
                        return ProcessResult::AtomicBlockComplete;
                    }
                }
            }
            self.buffer.push(*byte);
        }

        ProcessResult::Continue
    }
}
BSU/ESU Sequences:
BSU: ESC [ ? 2026 h   # Begin Synchronized Update
ESU: ESC [ ? 2026 l   # End Synchronized Update

CellConverter

Converts Alacritty cells to xterm.js format:
pub fn convert_cell(cell: &AlacrittyCell) -> XtermCell {
    XtermCell {
        content: encode_content(cell.c, cell.flags),
        fg: encode_color(cell.fg, cell.flags),
        bg: encode_color(cell.bg, cell.flags),
    }
}

fn encode_content(c: char, flags: Flags) -> u32 {
    let codepoint = c as u32;
    let width = if flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };

    codepoint | (width << 22)
}

fn encode_color(color: Color, flags: Flags) -> u32 {
    let (value, mode) = match color {
        Color::Named(n) => (n as u32, CM_P16),
        Color::Indexed(i) => (i as u32, CM_P256),
        Color::Rgb(rgb) => (
            (rgb.r as u32) | ((rgb.g as u32) << 8) | ((rgb.b as u32) << 16),
            CM_RGB
        ),
    };

    value | (mode << 24) | encode_flags(flags)
}

GridUpdate Structure

The output of AlacrittyRenderer:
#[derive(Serialize)]
pub struct GridUpdate {
    // Viewport cells (visible area)
    pub viewport: Vec<Vec<XtermCell>>,

    // Scrollback cells (history)
    pub scrollback: Vec<Vec<XtermCell>>,

    // Cursor position
    pub cursor_x: u16,
    pub cursor_y: u16,
    pub cursor_visible: bool,

    // Synchronization
    pub epoch: u64,
    pub scrollback_lines: usize,

    // Dirty tracking
    pub dirty_lines: Vec<usize>,
    pub is_full_update: bool,
}

Dirty Tracking

Only emit changed lines for efficiency:
┌─────────────────────────────────────────────────────────────────────┐
│               DIRTY TRACKING                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   Full Grid (40 rows):                                              │
│   ┌─────────────────────────────────────────────────────────────┐   │
│   │ Row 0:  unchanged                                           │   │
│   │ Row 1:  unchanged                                           │   │
│   │ Row 2:  DIRTY ← cursor moved here                           │   │
│   │ Row 3:  DIRTY ← new text written                            │   │
│   │ Row 4:  unchanged                                           │   │
│   │ ...                                                         │   │
│   │ Row 39: unchanged                                           │   │
│   └─────────────────────────────────────────────────────────────┘   │
│                                                                     │
│   GridUpdate.dirty_lines = [2, 3]                                   │
│   GridUpdate.is_full_update = false                                 │
│                                                                     │
│   → Only rows 2, 3 sent to frontend                                 │
│   → Significant bandwidth reduction                                 │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Wide Character Handling

CJK characters (한글, 日本語, 中文) occupy 2 cells:
"한" (Korean character, width=2)

Cell Layout:
┌─────────────┬─────────────┐
│ Col 0       │ Col 1       │
│ "한"        │ (spacer)    │
│ width=2     │ WIDE_CHAR_  │
│ WIDE_CHAR   │ SPACER      │
└─────────────┴─────────────┘

Content Encoding:
Main cell:
  content = (0xD55C) | (0 << 21) | (2 << 22)
          = codepoint | combined_flag | width

Spacer cell:
  content = 0 (empty, WebGL handles spacing)

Performance Characteristics

OperationDescription
VTE parsingFast native Rust processing
Cell conversionDirect format conversion
Dirty trackingEfficient change detection
OverallFaster than JavaScript parser
The native Rust implementation provides faster parsing compared to JavaScript-based alternatives.

Key Files

FileLinesPurpose
alacritty_renderer.rs~599Term wrapper, dirty tracking
atomic_parser.rs~764BSU/ESU detection
cell_converter.rs~416Alacritty→xterm conversion

SMPC/OFAC Applied

PrincipleApplication
SMPCSingle purpose: VTE parsing only
Clean interface: process() → GridUpdate
No rendering logic (that’s xterm’s job)
OFACDirty tracking emerged from performance need
AtomicParser emerged from BSU/ESU requirement
Cell format emerged from xterm.js internals