| name | alacritty-terminal |
| description | Terminal emulation library from Alacritty for VT100/xterm compatible terminal embedding |
| version | 0.25 |
alacritty-terminal
alacritty_terminal is the terminal emulation library extracted from the Alacritty GPU-accelerated terminal emulator. Script-kit-gpui uses version 0.25 (matching Zed's version) to provide full VT100/xterm/ANSI terminal emulation.
Architecture Overview
PTY Output --> VTE Parser --> Term Grid --> Render
| |
v v
Escape Seq Cell Storage
Processing (scrollback)
The library handles:
- Escape sequence parsing via VTE (Virtual Terminal Emulator)
- Terminal grid management with scrollback
- Cell attributes (colors, bold, italic, etc.)
- Selection handling
- Terminal modes (application cursor, bracketed paste, etc.)
Key Types
Term - The Terminal Emulator
The main terminal type, generic over an EventListener:
use alacritty_terminal::term::{Term, Config as TermConfig};
use alacritty_terminal::event::EventListener;
let config = TermConfig {
scrolling_history: 10_000,
..TermConfig::default()
};
let term: Term<EventProxy> = Term::new(config, &size, event_proxy);
Key fields:
is_focused: bool - Controls cursor appearance
selection: Option<Selection> - Current text selection
vi_mode_cursor: ViModeCursor - Vi mode cursor position
Key methods:
grid() / grid_mut() - Access the underlying grid
resize(size) - Resize terminal dimensions
scroll_display(Scroll) - Scroll the viewport
selection_to_string() - Get selected text
mode() - Get current terminal modes (TermMode)
renderable_content() - Get content optimized for rendering
Grid - 2D Cell Storage
Optimized storage for terminal content:
use alacritty_terminal::grid::{Grid, Dimensions, Scroll};
use alacritty_terminal::index::{Line, Column, Point};
let grid = term.grid();
let row = &grid[Line(0)];
let cell = &grid[Point::new(Line(0), Column(5))];
let cursor_point = grid.cursor.point;
Scroll enum variants:
use alacritty_terminal::grid::Scroll;
Scroll::Delta(i32)
Scroll::PageUp
Scroll::PageDown
Scroll::Top
Scroll::Bottom
Cell - Single Character with Attributes
use alacritty_terminal::term::cell::{Cell, Flags};
use vte::ansi::Color;
let cell: &Cell = &grid[point];
cell.c
cell.fg
cell.bg
cell.flags
cell.hyperlink()
Cell Flags (bitflags):
use alacritty_terminal::term::cell::Flags;
Flags::BOLD
Flags::ITALIC
Flags::UNDERLINE
Flags::DOUBLE_UNDERLINE
Flags::UNDERCURL
Flags::DOTTED_UNDERLINE
Flags::DASHED_UNDERLINE
Flags::STRIKEOUT
Flags::INVERSE
Flags::HIDDEN
Flags::DIM
Flags::WIDE_CHAR
Flags::WIDE_CHAR_SPACER
Index Types - Line, Column, Point
use alacritty_terminal::index::{Line, Column, Point, Direction};
let line = Line(0);
let scrollback = Line(-100);
let col = Column(10);
let point = Point::new(Line(0), Column(5));
let point = AlacPoint::new(Line(0), Column(5));
Direction::Left
Direction::Right
Selection - Text Selection State
use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::index::{Point, Direction};
let selection = Selection::new(
SelectionType::Simple,
point,
Direction::Left,
);
SelectionType::Simple
SelectionType::Semantic
SelectionType::Lines
SelectionType::Block
selection.update(new_point, Direction::Right);
if let Some(range) = selection.to_range(&term) {
for point in range.iter() {
}
}
EventListener - Terminal Events
use alacritty_terminal::event::{Event, EventListener};
struct MyEventProxy { }
impl EventListener for MyEventProxy {
fn send_event(&self, event: Event) {
match event {
Event::Bell => { }
Event::Title(title) => { }
Event::ResetTitle => { }
Event::Exit => { }
Event::ChildExit(code) => { }
Event::Wakeup => { }
Event::PtyWrite(text) => { }
Event::MouseCursorDirty => { }
Event::CursorBlinkingChange => { }
Event::ClipboardStore(clipboard, data) => { }
Event::ClipboardLoad(clipboard, format) => { }
Event::ColorRequest(index, format) => { }
Event::TextAreaSizeRequest(format) => { }
}
}
}
TermMode - Terminal State Flags
use alacritty_terminal::term::TermMode;
let mode = term.mode();
mode.contains(TermMode::BRACKETED_PASTE)
mode.contains(TermMode::SHOW_CURSOR)
mode.contains(TermMode::APP_CURSOR)
mode.contains(TermMode::APP_KEYPAD)
mode.contains(TermMode::MOUSE_REPORT_CLICK)
mode.contains(TermMode::ALT_SCREEN)
Usage in script-kit-gpui
Terminal Creation Pattern
use alacritty_terminal::term::{Term, Config as TermConfig};
use vte::ansi::Processor;
struct TerminalState {
term: Term<EventProxy>,
processor: Processor,
}
impl TerminalState {
fn new(config: TermConfig, size: &TerminalSize, event_proxy: EventProxy) -> Self {
Self {
term: Term::new(config, size, event_proxy),
processor: Processor::new(),
}
}
fn process_bytes(&mut self, bytes: &[u8]) {
self.processor.advance(&mut self.term, bytes);
}
}
Implementing Dimensions Trait
Required for Term::new() and Term::resize():
use alacritty_terminal::grid::Dimensions;
struct TerminalSize {
cols: usize,
rows: usize,
}
impl Dimensions for TerminalSize {
fn total_lines(&self) -> usize { self.rows }
fn screen_lines(&self) -> usize { self.rows }
fn columns(&self) -> usize { self.cols }
}
Reading Grid Content for Rendering
for line_idx in 0..term.screen_lines() {
let row = &grid[Line(line_idx as i32)];
for col_idx in 0..term.columns() {
let cell = &row[Column(col_idx)];
let c = cell.c;
let fg = resolve_color(&cell.fg, theme);
let bg = resolve_color(&cell.bg, theme);
if cell.flags.contains(Flags::BOLD) { }
if cell.flags.contains(Flags::WIDE_CHAR) { }
}
}
Color Resolution
Colors can be Named, Indexed (0-255), or Spec (direct RGB):
use vte::ansi::{Color, NamedColor, Rgb};
fn resolve_color(color: &Color, theme: &Theme) -> Rgb {
match color {
Color::Named(named) => match named {
NamedColor::Foreground => theme.foreground,
NamedColor::Background => theme.background,
NamedColor::Black => theme.ansi[0],
NamedColor::Red => theme.ansi[1],
NamedColor::BrightBlack => theme.ansi[8],
},
Color::Indexed(idx) => resolve_indexed(*idx, theme),
Color::Spec(rgb) => *rgb,
}
}
fn resolve_indexed(idx: u8, theme: &Theme) -> Rgb {
match idx {
0..=15 => theme.ansi[idx as usize],
16..=231 => {
let idx = idx - 16;
let r = (idx / 36) % 6;
let g = (idx / 6) % 6;
let b = idx % 6;
let to_val = |v| if v == { } { + v * };
Rgb { r: (r), g: (g), b: (b) }
}
..= => {
= + (idx - ) * ;
Rgb { r: gray, g: gray, b: gray }
}
}
}
Scroll Operations
use alacritty_terminal::grid::Scroll;
term.scroll_display(Scroll::Delta(-5));
term.scroll_display(Scroll::Delta(5));
term.scroll_display(Scroll::PageUp);
term.scroll_display(Scroll::PageDown);
term.scroll_display(Scroll::Top);
term.scroll_display(Scroll::Bottom);
let offset = term.grid().display_offset();
Selection Handling
use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::index::{Point, Line, Column, Direction};
fn start_selection(term: &mut Term<T>, col: usize, row: usize) {
let point = Point::new(Line(row as i32), Column(col));
term.selection = Some(Selection::new(
SelectionType::Simple,
point,
Direction::Left,
));
}
fn start_word_selection(term: &mut Term<T>, col: usize, row: usize) {
let point = Point::new(Line(row as i32), Column(col));
term.selection = Some(Selection::new(
SelectionType::Semantic,
point,
Direction::Left,
));
}
fn update_selection(term: &mut Term<T>, col: usize, row: usize) {
if let Some(ref mut sel) = term.selection {
= Point::((row ), (col));
sel.(point, Direction::Right);
}
}
(term: &Term<T>) <> {
term.()
}
term.selection = ;
Bracketed Paste Mode
if term.mode().contains(TermMode::BRACKETED_PASTE) {
let wrapped = format!("\x1b[200~{}\x1b[201~", text);
pty.write_all(wrapped.as_bytes())?;
} else {
pty.write_all(text.as_bytes())?;
}
Anti-patterns
Don't Mix Line Index Types
let row = &grid[5];
let row = &grid[Line(5)];
Don't Ignore Wide Characters
for col in 0..width {
render_cell(&grid[Line(row)][Column(col)]);
}
for col in 0..width {
let cell = &grid[Line(row)][Column(col)];
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
continue;
}
let width = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
render_cell(cell, width);
}
Don't Forget to Process VTE
term.input("Hello");
let mut processor = Processor::new();
processor.advance(&mut term, pty_bytes);
Don't Hold Lock During PTY I/O
let mut state = terminal_state.lock().unwrap();
let bytes = pty.read_blocking(&mut buffer)?;
state.process_bytes(&bytes);
let bytes = pty.read_blocking(&mut buffer)?;
tx.send(bytes)?;
while let Ok(bytes) = rx.try_recv() {
let mut state = terminal_state.lock().unwrap();
state.process_bytes(&bytes);
}
Don't Assume Positive Line Numbers
let line = row as i32;
let display_offset = grid.display_offset() as i32;
let line = Line(row as i32 - display_offset);
Related Dependencies
vte (0.13) - Virtual Terminal Emulator parser
vte::ansi::Processor - Parses escape sequences
vte::ansi::Color, Rgb, NamedColor - Color types
vte::ansi::Handler - Trait implemented by Term
References