Agent skill
alacritty-terminal
Terminal emulation library from Alacritty for VT100/xterm compatible terminal embedding
Install this agent skill to your Project
npx add-skill https://github.com/johnlindquist/script-kit-next/tree/main/.opencode/skill/alacritty-terminal
SKILL.md
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<T> - The Terminal Emulator
The main terminal type, generic over an EventListener:
use alacritty_terminal::term::{Term, Config as TermConfig};
use alacritty_terminal::event::EventListener;
// Create a terminal with event listener
let config = TermConfig {
scrolling_history: 10_000, // scrollback lines
..TermConfig::default()
};
let term: Term<EventProxy> = Term::new(config, &size, event_proxy);
Key fields:
is_focused: bool- Controls cursor appearanceselection: Option<Selection>- Current text selectionvi_mode_cursor: ViModeCursor- Vi mode cursor position
Key methods:
grid()/grid_mut()- Access the underlying gridresize(size)- Resize terminal dimensionsscroll_display(Scroll)- Scroll the viewportselection_to_string()- Get selected textmode()- Get current terminal modes (TermMode)renderable_content()- Get content optimized for rendering
Grid<T> - 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();
// Access by Line (row)
let row = &grid[Line(0)]; // First visible row
// Access by Point (row + column)
let cell = &grid[Point::new(Line(0), Column(5))];
// Cursor position
let cursor_point = grid.cursor.point;
Scroll enum variants:
use alacritty_terminal::grid::Scroll;
Scroll::Delta(i32) // Relative scroll by lines
Scroll::PageUp // Scroll one page up
Scroll::PageDown // Scroll one page down
Scroll::Top // Scroll to top of history
Scroll::Bottom // Scroll to bottom (latest)
Cell - Single Character with Attributes
use alacritty_terminal::term::cell::{Cell, Flags};
use vte::ansi::Color;
// Cell fields
let cell: &Cell = &grid[point];
cell.c // char - the character
cell.fg // Color - foreground color
cell.bg // Color - background color
cell.flags // Flags - attributes (bold, italic, etc.)
cell.hyperlink() // Option<&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 // Swap fg/bg
Flags::HIDDEN
Flags::DIM
Flags::WIDE_CHAR // Part of wide character
Flags::WIDE_CHAR_SPACER // Spacer after wide char
Index Types - Line, Column, Point
use alacritty_terminal::index::{Line, Column, Point, Direction};
// Line uses i32 (can be negative for scrollback)
let line = Line(0); // First visible line
let scrollback = Line(-100); // 100 lines into scrollback
// Column uses usize
let col = Column(10);
// Point combines both
let point = Point::new(Line(0), Column(5));
// Or use AlacPoint alias for disambiguation
let point = AlacPoint::new(Line(0), Column(5));
// Direction for navigation/selection
Direction::Left
Direction::Right
Selection - Text Selection State
use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::index::{Point, Direction};
// Create selection
let selection = Selection::new(
SelectionType::Simple, // Normal character selection
point, // Starting point
Direction::Left, // Initial direction
);
// Selection types
SelectionType::Simple // Character-by-character
SelectionType::Semantic // Word selection (double-click)
SelectionType::Lines // Line selection (triple-click)
SelectionType::Block // Block/rectangular selection
// Update selection endpoint
selection.update(new_point, Direction::Right);
// Get selection range for a term
if let Some(range) = selection.to_range(&term) {
for point in range.iter() {
// Process selected points
}
}
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 => { /* Terminal bell */ }
Event::Title(title) => { /* Window title change */ }
Event::ResetTitle => { /* Reset title */ }
Event::Exit => { /* Terminal requested exit */ }
Event::ChildExit(code) => { /* Child process exited */ }
Event::Wakeup => { /* New content available */ }
Event::PtyWrite(text) => { /* Data to write to PTY */ }
Event::MouseCursorDirty => { /* Cursor changed */ }
Event::CursorBlinkingChange => { /* Blink state changed */ }
Event::ClipboardStore(clipboard, data) => { /* Store to clipboard */ }
Event::ClipboardLoad(clipboard, format) => { /* Load from clipboard */ }
Event::ColorRequest(index, format) => { /* Query color */ }
Event::TextAreaSizeRequest(format) => { /* Query size */ }
}
}
}
TermMode - Terminal State Flags
use alacritty_terminal::term::TermMode;
let mode = term.mode();
// Check modes
mode.contains(TermMode::BRACKETED_PASTE) // Wrap paste in escape sequences
mode.contains(TermMode::SHOW_CURSOR) // Cursor visible
mode.contains(TermMode::APP_CURSOR) // Application cursor keys
mode.contains(TermMode::APP_KEYPAD) // Application keypad mode
mode.contains(TermMode::MOUSE_REPORT_CLICK) // Mouse click reporting
mode.contains(TermMode::ALT_SCREEN) // Alternate screen buffer
Usage in script-kit-gpui
Terminal Creation Pattern
// From src/terminal/alacritty.rs
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(),
}
}
// Process PTY output through VTE parser
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
// Iterate visible lines
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)];
// Get character
let c = cell.c;
// Resolve colors
let fg = resolve_color(&cell.fg, theme);
let bg = resolve_color(&cell.bg, theme);
// Check attributes
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],
// ... etc for all 16 ANSI colors
NamedColor::BrightBlack => theme.ansi[8],
// ... etc for bright variants
},
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 => {
// 6x6x6 color cube
let idx = idx - 16;
let r = (idx / 36) % 6;
let g = (idx / 6) % 6;
let b = idx % 6;
let to_val = |v| if v == 0 { 0 } else { 55 + v * 40 };
Rgb { r: to_val(r), g: to_val(g), b: to_val(b) }
}
232..=255 => {
// Grayscale ramp
let gray = 8 + (idx - 232) * 10;
Rgb { r: gray, g: gray, b: gray }
}
}
}
Scroll Operations
use alacritty_terminal::grid::Scroll;
// Scroll by lines
term.scroll_display(Scroll::Delta(-5)); // Up 5 lines
term.scroll_display(Scroll::Delta(5)); // Down 5 lines
// Page scroll
term.scroll_display(Scroll::PageUp);
term.scroll_display(Scroll::PageDown);
// Jump to ends
term.scroll_display(Scroll::Top); // Top of scrollback
term.scroll_display(Scroll::Bottom); // Latest output
// Get current scroll position
let offset = term.grid().display_offset(); // 0 = at bottom
Selection Handling
use alacritty_terminal::selection::{Selection, SelectionType};
use alacritty_terminal::index::{Point, Line, Column, Direction};
// Start selection on mouse down
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,
));
}
// Double-click for word selection
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, // Word boundaries
point,
Direction::Left,
));
}
// Update on mouse drag
fn update_selection(term: &mut Term<T>, col: usize, row: usize) {
if let Some(ref mut sel) = term.selection {
let point = Point::new(Line(row as i32), Column(col));
sel.update(point, Direction::Right);
}
}
// Get selected text
fn get_selection(term: &Term<T>) -> Option<String> {
term.selection_to_string()
}
// Clear selection
term.selection = None;
Bracketed Paste Mode
// Check if bracketed paste is enabled
if term.mode().contains(TermMode::BRACKETED_PASTE) {
// Wrap pasted text in escape sequences
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
// WRONG: Using usize directly
let row = &grid[5]; // Won't compile - needs Line type
// CORRECT: Use Line wrapper
let row = &grid[Line(5)];
Don't Ignore Wide Characters
// WRONG: Assuming all cells are single-width
for col in 0..width {
render_cell(&grid[Line(row)][Column(col)]);
}
// CORRECT: Check for wide char spacers
for col in 0..width {
let cell = &grid[Line(row)][Column(col)];
if cell.flags.contains(Flags::WIDE_CHAR_SPACER) {
continue; // Skip spacer cells
}
let width = if cell.flags.contains(Flags::WIDE_CHAR) { 2 } else { 1 };
render_cell(cell, width);
}
Don't Forget to Process VTE
// WRONG: Writing to term directly won't parse escape sequences
term.input("Hello"); // This method doesn't exist!
// CORRECT: Use VTE processor to parse PTY output
let mut processor = Processor::new();
processor.advance(&mut term, pty_bytes);
Don't Hold Lock During PTY I/O
// WRONG: Blocking I/O with lock held
let mut state = terminal_state.lock().unwrap();
let bytes = pty.read_blocking(&mut buffer)?; // Blocks!
state.process_bytes(&bytes);
// CORRECT: Read in background, process with brief lock
// Background thread:
let bytes = pty.read_blocking(&mut buffer)?;
tx.send(bytes)?;
// Main thread:
while let Ok(bytes) = rx.try_recv() {
let mut state = terminal_state.lock().unwrap();
state.process_bytes(&bytes);
} // Lock released quickly
Don't Assume Positive Line Numbers
// WRONG: Assuming lines are always positive
let line = row as i32; // May need negative for scrollback
// CORRECT: Account for display offset
let display_offset = grid.display_offset() as i32;
let line = Line(row as i32 - display_offset);
Related Dependencies
vte(0.13) - Virtual Terminal Emulator parservte::ansi::Processor- Parses escape sequencesvte::ansi::Color,Rgb,NamedColor- Color typesvte::ansi::Handler- Trait implemented by Term
References
- docs.rs/alacritty_terminal/0.25.0
- Alacritty GitHub
- VTE crate
- Script-kit-gpui usage:
src/terminal/alacritty.rs
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
Generate Component Documentation
Based on existing docs styles and specific API implementations, and referencing same name stories, generate comprehensive documentation for the new component.
Generate Component Story
Generate a comprehensive story for a new component for as example.
new-component
How to write a new component of GPUI Component.
troubleshooting
Diagnose and fix common Script Kit issues. Use when the user reports bugs, crashes, missing features, or unexpected behavior in Script Kit GPUI.
script-authoring
Create and manage TypeScript scripts for Script Kit. Use when the user wants to write a new script, edit an existing script, or understand Script Kit's SDK and metadata system.
agents
Create mdflow-backed agent files for Script Kit. Use when the user wants to create AI agents, configure agent backends (Claude, Gemini, Codex), or manage agent metadata.
Didn't find tool you were looking for?