bittty
bittty: A fast, pure Python terminal emulator library.
bittty (bitplane-tty) is a high-performance terminal emulator engine that provides comprehensive ANSI sequence parsing and terminal state management.
Vocabulary: the Board is the machine (devices, registers, video memory, the
child process). A terminal (bittty.terminals) is the chrome a human looks at,
plugged into the board’s display port. Terminal is deliberately not exported
here — import it from bittty.terminals.
bittty.terminals.probe
Capability probing for the stdio terminal.
Ask the real outer terminal what it can do, then hand a TerminalCaps up to the backend. Uses a DA1-terminated handshake: fire all the queries plus a Primary DA request, then read until the (universally answered) DA reply arrives, so optional queries that go unanswered never make us hang. Non-tty or timeout ⇒ env/config caps only.
Nothing here reads capabilities the backend then trusts blindly — TerminalCaps is purely physical facts (colour depth, pixel geometry, background). Graphics-mode reconciliation is deferred with the graphics families.
color_depth_from_env
def color_depth_from_env(env) -> str
Best-effort colour depth from COLORTERM / TERM (no query exists for this).
parse_probe_replies
def parse_probe_replies(buf: str, env) -> TerminalCaps
Build TerminalCaps from a probe-reply buffer, unioned with env (probe wins).
probe_caps
def probe_caps(stdin_fd,
write,
env=None,
timeout: float = 0.5) -> TerminalCaps
Query the real terminal and return TerminalCaps; env-only on a non-tty/timeout.
write is a callable that writes a str to the outer terminal (and flushes).
bittty.terminals.base
The Terminal abstract base: the chrome a human looks at.
A concrete terminal is-a Terminal and has-a Board (composition). It plugs itself into the board’s display port, pushes TerminalCaps up, and receives discrete side-effects through present(), which dispatches each PresentEvent to a typed hook. Every hook defaults to a no-op, so adding a new event type can never break an existing terminal — it just grows the surface with another optional override.
The board never imports this module: the boundary only ever runs terminal -> board, never the reverse.
Terminal Objects
class Terminal()
Abstract base for terminals (chrome). Compose a Board; override the hooks you need.
attach
def attach() -> None
Plug this terminal into its board’s display port.
detach
def detach() -> None
Unplug from the board’s display port.
set_caps
def set_caps(caps: TerminalCaps) -> None
Push the real terminal’s capabilities down to the board.
present
def present(event: PresentEvent) -> None
Route a present event to its typed hook (unknown types are ignored).
bittty.terminals.stdio
StdioTerminal: the reference terminal, whose venue is this process’s stdio.
Composes a Board (never subclasses it) and drives the real outer terminal: raw-mode stdin, ANSI rendering to stdout, resize handling, and mouse mirroring. Discrete side-effects arrive through the Terminal hooks (on_bell/on_title/ on_mouse_mode).
HostInputSink Objects
class HostInputSink()
Sink for the input-direction parser: host bytes in, board events out.
Every operation carries its raw bytes, so anything we don’t intercept is forwarded to the child verbatim and in order — arrow keys, control chars, pastes, whole unknown sequences. Interception is by raw prefix, not operation name: on the input direction CSI I is a focus report, never CHT.
StdioTerminal Objects
class StdioTerminal(Terminal)
Render a bittty Board to the real terminal this program is running in.
get_default_shell
def get_default_shell() -> str
Get the default shell command for the current platform.
on_bell
def on_bell() -> None
Ring the outer terminal’s bell.
on_title
def on_title(title: str, icon_title: str) -> None
Mirror the window title onto the outer terminal.
on_mouse_mode
def on_mouse_mode(mode: str, sgr: bool) -> None
Mirror the child’s requested mouse-tracking mode onto the outer terminal.
disable_host_mouse
def disable_host_mouse() -> None
Turn off host-terminal mouse reporting.
setup_terminal
def setup_terminal() -> None
Put the host terminal into raw mode, clear it, and ask for focus events.
restore_terminal
def restore_terminal() -> None
Restore the host terminal to its original state.
probe_capabilities
def probe_capabilities() -> None
Ask the outer terminal what it can do and push TerminalCaps to the backend.
render_screen
def render_screen() -> None
Render the current board state to stdout, then place the host’s hardware cursor.
The cursor is the host terminal’s own: position it and show it when the child wants it visible (DECTCEM). The host hollows it on unfocus by itself, exactly like a real terminal.
handle_pty_data
def handle_pty_data(data: str) -> None
Feed child output into the emulator and mark the screen dirty.
Rendering happens on the run loop’s tick, not per PTY chunk — a repaint
per chunk backpressures a flooding child (it blocks writing to the PTY
while we paint), turning a 66ms find into a 750ms one.
handle_sgr_mouse_sequence
def handle_sgr_mouse_sequence(sequence: str) -> bool
Parse a host SGR mouse report and re-inject it through bittty.
handle_focus
def handle_focus(focused: bool) -> None
A host focus event: the backend owns the state; we just repaint.
handle_input
def handle_input(data: str) -> None
Feed host input through the input-direction parser.
The parser reassembles sequences split across reads; HostInputSink intercepts SGR mouse reports and focus events and forwards everything else to the child verbatim.
flush_pending_input
def flush_pending_input() -> None
Release a held incomplete sequence that never completed.
A lone ESC keypress looks like a sequence prefix, so the parser holds it; when no follow-up arrives within an input-loop tick it was a real ESC and must reach the child.
handle_resize
def handle_resize() -> None
Re-read the host size and resize the emulator (called from a SIGWINCH handler).
input_loop
async def input_loop() -> None
Read host input and forward it.
run
async def run() -> None
Main loop: start the shell, pump input, render until it exits.
cleanup
def cleanup() -> None
Tear down the child and restore the host terminal.
bittty.terminals
Terminals: the chrome a human looks at, named by venue.
Kept in its own package so the board never imports terminal code — the “board is never subclassed by a terminal” rule, enforced structurally.
bittty.video
Video memory: the 2D cell grid the blitter writes and terminals render.
A Board has two pages of it (primary and alternate).
Video Objects
class Video()
A 2D grid that stores terminal content.
__init__
def __init__(width: int, height: int) -> None
Initialize buffer with given dimensions.
observe
def observe() -> int
Snapshot for dirty tracking: close the current epoch, open a new one.
Returns the new epoch; rows stamped at or after it are dirty relative to this observation. Each reader keeps its own returned value.
dirty_rows
def dirty_rows(seen: int) -> List[int]
Rows changed since seen (a value returned by observe()).
set_line_attribute
def set_line_attribute(y: int, attribute: str) -> None
Set a line’s DECDHL/DECDWL/DECSWL attribute.
get_line_attribute
def get_line_attribute(y: int) -> str
Return a line’s width/height attribute (single by default).
reset_line_attributes
def reset_line_attributes() -> None
Return every line to single-width, single-height (RIS).
get_content
def get_content() -> List[List[Cell]]
Get buffer content as a 2D grid.
get_cell
def get_cell(x: int, y: int) -> Cell
Get cell at position.
set_cell
def set_cell(x: int, y: int, char: str, style_or_ansi=None) -> None
Set a single cell at position.
Arguments:
x, y: Position
char- Character to storestyle_or_ansi- Either a Style object or ANSI string (for backward compatibility)
set
def set(x: int, y: int, text: str, style_or_ansi=None) -> None
Set text at position, overwriting existing content.
insert
def insert(x: int, y: int, text: str, style_or_ansi=None) -> None
Insert text at position, shifting existing content right.
delete
def delete(x: int, y: int, count: int = 1) -> None
Delete characters at position.
clear_region
def clear_region(x1: int,
y1: int,
x2: int,
y2: int,
style_or_ansi=None) -> None
Clear a rectangular region.
clear_line
def clear_line(y: int,
mode: int = constants.ERASE_FROM_CURSOR_TO_END,
cursor_x: int = 0,
style_or_ansi=None) -> None
Clear line content.
scroll_up
def scroll_up(count: int) -> None
Scroll content up, removing top lines and adding blank lines at bottom.
scroll_down
def scroll_down(count: int) -> None
Scroll content down, removing bottom lines and adding blank lines at top.
scroll_region_up
def scroll_region_up(top: int, bottom: int, count: int) -> None
Scroll a specific region up by count lines. BLAZING FAST bulk operation!
scroll_region_down
def scroll_region_down(top: int, bottom: int, count: int) -> None
Scroll a specific region down by count lines. BLAZING FAST bulk operation!
resize
def resize(width: int, height: int) -> None
Resize buffer to new dimensions.
link_extent
def link_extent(x: int, y: int) -> tuple | None
The contiguous same-link run containing (x, y) on its row.
Returns (uri, link_id, x0, x1) with an inclusive column span, or None if the cell isn’t a link. Segments split across rows share a link_id; grouping those into one hover is the chrome’s job.
get_line_text
def get_line_text(y: int) -> str
Get plain text content of a line (for debugging/testing).
get_line
def get_line(y: int, width: int = None) -> str
Get full ANSI sequence for a line — a pure read of video memory.
No cursor or pointer is composited in; those are chrome concerns, rendered by the terminal from the board’s registers.
get_line_tuple
def get_line_tuple(y: int, width: int = None) -> tuple
Get line as a hashable tuple for caching — a pure read of video memory.
bittty.connections
Connections and the board-side ports they plug into.
A port is a jack on the board, and both are full-duplex. The host port carries bytes both ways (a serial line: PTY, pipe, socket). The display port carries typed events both ways: present events down to the terminal (chrome), input events up from it.
Connection Objects
@runtime_checkable
class Connection(Protocol)
A cable implementation (PTY, pipe, socket) that accepts terminal input/reply data.
write
def write(data: str)
Write data to the connected host.
Presentable Objects
@runtime_checkable
class Presentable(Protocol)
A terminal (chrome) that receives discrete present events from the board.
present
def present(event: "PresentEvent") -> None
Handle one present event.
HostPort Objects
class HostPort()
The board’s jack toward the child program; a Connection (PTY, pipe) plugs in.
Full duplex: write() is the transmit pin (replies and encoded input toward the child); connect() starts the receive pump, feeding the child’s output into a sink — the board wires it to its parser.
attach
def attach(connection: Connection) -> None
Attach a connection to this host port (transmit side only).
detach
def detach() -> None
Detach the current connection.
connect
def connect(connection: Connection,
on_data: Callable[[str], None],
on_idle: Optional[Callable[[], bool]] = None,
on_closed: Optional[Callable[[], None]] = None) -> None
Plug in a duplex connection and start pumping its receive side.
on_data receives each decoded chunk. on_idle fires when a read returns nothing — return True to stop the pump (the board reaps its dead child there). on_closed fires when the connection errors out.
disconnect
def disconnect() -> None
Stop the receive pump and unplug the connection.
connected
@property
def connected() -> bool
Whether a connection is attached.
write
def write(data: str, flush: bool = False)
Write data to the attached connection.
DisplayPort Objects
class DisplayPort()
The board’s jack toward the terminal (chrome); mirrors HostPort.
The name is the video-connector pun, kept on purpose: the one place “display” survives in board vocabulary. Full duplex: the board pushes discrete present events down; the terminal sends input events, focus changes, and capability reports up. When no terminal is attached present() is a no-op, so the board runs headless exactly as before.
attach
def attach(terminal: Presentable) -> None
Attach a terminal (chrome) to receive present events.
detach
def detach() -> None
Detach the current terminal.
connected
@property
def connected() -> bool
Whether a terminal (chrome) is attached.
present
def present(event: "PresentEvent") -> None
Forward a present event to the attached terminal, if any.
input
def input(data: str) -> None
Keystrokes from the terminal, translated per keyboard modes.
input_key
def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None
A key + modifier from the terminal.
input_fkey
def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None
A function key + modifier from the terminal.
input_numpad_key
def input_numpad_key(key: str) -> None
A numpad key from the terminal.
input_paste
def input_paste(text: str) -> None
Pasted text from the terminal, bracketed per mode 2004.
input_mouse
def input_mouse(x: int, y: int, button: int, event_type: str,
modifiers: set[str]) -> None
A mouse event from the terminal.
focus_in
def focus_in() -> None
The box gained focus.
focus_out
def focus_out() -> None
The box lost focus.
set_caps
def set_caps(caps: "TerminalCaps") -> None
The terminal reports what its venue can do.
bittty.parser.dcs
DCS (Device Control String) operation parser.
parse_dcs_operation
def parse_dcs_operation(string_buffer: str, raw: str = "") -> Operation
Return an operation for a DCS sequence.
bittty.parser.csi
CSI (Control Sequence Introducer) operation parser.
param
def param(params, index=0, default=None)
Return params[index] when present and not None, else default.
parse_csi_params
@lru_cache(maxsize=1000)
def parse_csi_params(data)
Parse CSI parameters when actually needed.
Arguments:
data- Complete CSI sequence like ‘[1;2H’ or ‘[?25h’
Returns:
tuple- (params_list, intermediate_chars, final_char)
parse_csi_operation
@lru_cache(maxsize=4096)
def parse_csi_operation(raw_csi_data: str) -> Operation | None
Return a semantic operation for CSI sequences migrated to the operation layer.
Cached: real terminal traffic repeats a small CSI vocabulary (a full-screen TUI session uses a few hundred distinct sequences), and Operation is frozen with immutable args, so one instance per distinct sequence is safe to share.
bittty.parser.osc
OSC (Operating System Command) operation parser.
parse_osc_operation
def parse_osc_operation(string_buffer: str, raw: str = "") -> Operation | None
Return a semantic operation for an OSC sequence.
bittty.parser.core
Core Parser class with state machine and sequence dispatching (fast state-specific scanners).
parse_string_sequence
@lru_cache(maxsize=300)
def parse_string_sequence(data: str, sequence_type: str) -> str
Strip prefix and terminator for OSC/DCS/APC/PM/SOS.
Parser Objects
class Parser()
State machine: GROUND → (CSI | STRING[osc|dcs|apc|pm|sos]) → GROUND Uses small, state-specific scanners for speed.
flush_trailing
def flush_trailing() -> None
Emit any held incomplete sequence as plain text and reset to ground.
For an input-direction parser: a lone ESC (or a dangling introducer) that never completed within the caller’s patience was a keypress, not a sequence prefix, and must reach the sink as-is. Never called on the output direction, where waiting for the rest of the sequence is right.
bittty.parser.escape
Simple escape sequence operation parser.
parse_escape_operation
def parse_escape_operation(data: str) -> Operation | None
Return a semantic operation for a simple ESC sequence.
parse_hash_operation
def parse_hash_operation(data: str) -> Operation | None
Return a semantic operation for an ESC # n line-size / alignment sequence.
parse_charset_operation
def parse_charset_operation(data: str) -> Operation | None
Return a semantic operation for a charset designation sequence.
bittty.parser
Parser module for terminal escape sequence processing.
This module provides a modular parser system that processes terminal escape sequences through specialized handlers:
- CSI sequences (cursor movement, styling, modes)
- OSC sequences (window titles, colors)
- Simple escape sequences (cursor save/restore, etc.)
- DCS sequences (device control strings)
The main Parser class coordinates all these handlers and maintains the state machine.
bittty.operations
Parser operation model.
Operation Objects
@dataclass(frozen=True)
class Operation()
A parsed terminal operation.
OperationSink Objects
class OperationSink(Protocol)
Receives operations emitted by the parser.
handle_operation
def handle_operation(operation: Operation) -> None
Handle one parsed operation.
control_name
def control_name(ch: str) -> str
Return a stable operation name for a C0 control character.
bittty.keymap
Keyboard encoding as model data.
Function keys are where real terminals diverge most: a VT100 has only PF1-PF4
and no keyboard-modifier encoding, while xterm sends F1-F12 and folds shift/alt/
ctrl into a ;mod parameter. A KeyMap captures that difference as data.
KeyMap Objects
@dataclass(frozen=True)
class KeyMap()
How a terminal encodes its keys (and whether it encodes modifiers).
cursor_keys
“up”/”down”/”left”/”right” -> CSI/SS3 final byte
nav_keys
“home”/”end”/… -> CSI body
modifiers
whether shift/alt/ctrl are folded into the sequence
apply_modifier
def apply_modifier(sequence: str, modifier: int) -> str
Fold an xterm-style modifier into a function-key sequence.
ESC O X becomes ESC [ 1 ; mod X; ESC [ n ~ becomes ESC [ n ; mod ~.
bittty.model
Terminal models: the emulation profile as data.
A model captures the constants that distinguish one real terminal from another — starting with the Device Attributes (DA) responses used to identify the terminal to the host. Over time this grows to carry charset repertoire, colour depth, and which capabilities a board assembles.
Model Objects
@dataclass(frozen=True)
class Model()
A terminal type expressed as data.
da1_response
Primary Device Attributes (answer to CSI c)
da2_response
Secondary DA (CSI > c); None if unsupported
da3_response
Tertiary DA (CSI = c); None if unsupported
get_model
def get_model(term_name: str | None, default: Model = DEFAULT) -> Model
Resolve a $TERM name to a model, falling back through shorter prefixes.
So “xterm-kitty” or “screen.xterm-256color” degrade gracefully to the nearest known family, and an unknown or empty TERM yields the default (xterm).
bittty.pty.base
Base PTY interface for terminal emulation.
This module provides a concrete base class that works with file-like objects, with platform-specific subclasses overriding only the byte-level I/O methods.
PTY Objects
class PTY()
A generic PTY that lacks OS integration.
Uses StringIO if no file handles are provided, and subprocess to handle its children.
If you use this then you’ll have to
__init__
def __init__(from_process: Optional[BinaryIO] = None,
to_process: Optional[BinaryIO] = None,
rows: int = constants.DEFAULT_TERMINAL_HEIGHT,
cols: int = constants.DEFAULT_TERMINAL_WIDTH)
Initialize PTY with file-like input/output sources.
Arguments:
from_process- File-like object to read process output from (or None)to_process- File-like object to write user input to (or None)rows- Terminal heightcols- Terminal width
read_bytes
def read_bytes(size: int) -> bytes
Read raw bytes. Override in subclasses for platform-specific I/O.
write_bytes
def write_bytes(data: bytes) -> int
Write raw bytes. Override in subclasses for platform-specific I/O.
read
def read(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str
Read data using the C incremental UTF-8 decoder (buffers split code points).
write
def write(data: str) -> int
Write string as UTF-8 bytes.
resize
def resize(rows: int, cols: int) -> None
Resize the terminal (base implementation just updates dimensions).
close
def close() -> None
Close the PTY streams.
closed
@property
def closed() -> bool
Check if PTY is closed.
spawn_process
def spawn_process(command: str, env: dict[str, str] = ENV) -> subprocess.Popen
Spawn a process connected to PTY streams.
read_async
async def read_async(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str
Async read using thread pool executor.
Uses loop.run_in_executor() as a generic cross-platform approach. Unix PTY overrides this with more efficient file descriptor monitoring. Windows and other platforms use this thread pool implementation.
flush
def flush() -> None
Flush output.
bittty.pty.unix
Unix/Linux/macOS PTY implementation.
UnixPTY Objects
class UnixPTY(PTY)
Unix/Linux/macOS PTY implementation.
resize
def resize(rows: int, cols: int) -> None
Resize the terminal using TIOCSWINSZ ioctl.
close
def close() -> None
Close the PTY file descriptors.
spawn_process
def spawn_process(command: str,
env: dict[str, str] = UNIX_ENV) -> subprocess.Popen
Spawn a process attached to this PTY.
flush
def flush() -> None
PTY master file descriptors don’t support fsync - data flows immediately.
For Unix PTYs, data written to the master side appears immediately on the slave side, so no explicit flushing is needed.
read_async
async def read_async(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str
Async read from PTY using efficient file descriptor monitoring.
Uses loop.add_reader() with file descriptors for maximum efficiency on Unix. This is the most performant approach since Unix supports select/poll on PTY fds.
bittty.pty.windows
Windows PTY implementation using pywinpty.
WinptyFileWrapper Objects
class WinptyFileWrapper()
File-like wrapper for winpty.PTY to work with base PTY class.
read
def read(size: int = -1) -> str
Read data as strings.
write
def write(data: str) -> int
Write string data.
close
def close() -> None
Close the PTY.
closed
@property
def closed() -> bool
Check if closed.
flush
def flush() -> None
Flush - no-op for winpty.
WinptyProcessWrapper Objects
class WinptyProcessWrapper()
Wrapper to provide subprocess.Popen-like interface for winpty PTY.
poll
def poll()
Check if process is still running.
wait
def wait()
Wait for process to complete.
returncode
@property
def returncode()
Get the return code.
pid
@property
def pid()
Get the process ID.
WindowsPTY Objects
class WindowsPTY(PTY)
Windows PTY implementation using pywinpty.
Note: This PTY operates in text mode - winpty handles UTF-8 internally. The read/write methods work directly with strings for performance, with bytes conversion only when needed for compatibility.
read
def read(size: int = constants.DEFAULT_PTY_BUFFER_SIZE) -> str
Read data directly from winpty (text mode, no UTF-8 splitting needed).
write
def write(data: str) -> int
Write string data directly to winpty (text mode).
resize
def resize(rows: int, cols: int) -> None
Resize the terminal.
spawn_process
def spawn_process(command: str,
env: Optional[Dict[str, str]] = ENV) -> subprocess.Popen
Spawn a process attached to this PTY.
bittty.pty
PTY implementations for terminal emulation.
bittty.constants
Constants for the terminal parser and emulator.
This module contains constants used in the terminal parsing and emulation logic, following standards like VT100, VT220, and xterm.
ENQ
Enquiry (triggers answerback)
BEL
Bell
BS
Backspace
HT
Horizontal Tab
LF
Line Feed
VT
Vertical Tab
FF
Form Feed
CR
Carriage Return
SO
Shift Out (activate G1)
SI
Shift In (activate G0)
ESC
Escape
DEL
Delete
LINE_SINGLE
DECSWL — normal single-width, single-height
LINE_DOUBLE_WIDTH
DECDWL
LINE_DOUBLE_TOP
DECDHL top half
LINE_DOUBLE_BOTTOM
DECDHL bottom half
DECKBUM_KEYBOARD_USAGE
DECKBUM is mode 68; mode 69 is DECLRMM (left/right margins)
DA1_132_COLUMNS
132 column mode
DA1_PRINTER_PORT
Printer port
DA1_REGIS_GRAPHICS
ReGIS graphics
DA1_SIXEL_GRAPHICS
Sixel graphics
DA1_SELECTIVE_ERASE
Selective erase
DA1_USER_DEFINED_KEYS
User-defined keys (UDKs)
DA1_NATIONAL_REPLACEMENT_CHARSETS
National replacement character sets
DA1_TECH_CHARACTERS
Technical characters
DA1_LOCATOR_PORT
Locator port
DA1_TERMINAL_STATE_INTERROGATION
Terminal state interrogation
DA1_USER_WINDOWS
User windows
DA1_DUAL_SESSIONS
Dual sessions
DA1_HORIZONTAL_SCROLLING
Horizontal scrolling
DA1_ANSI_COLOR
ANSI color
DA1_GREEK_CHARSET
Greek character set
DA1_TURKISH_CHARSET
Turkish character set
DA1_ISO_LATIN2_CHARSET
ISO Latin-2 character set
DA1_PC_TERM
PC Term
DA1_SOFT_KEY_MAP
Soft key map
DA1_ASCII_EMULATION
ASCII emulation
EBADF
Bad file descriptor
EINVAL
Invalid argument
bittty.charsets
Character set mappings for terminal emulation.
get_charset
def get_charset(designator: str) -> dict
Get character set mapping for a designator.
bittty.devices.query
Query operation handler for the current board state.
QueryDevice Objects
class QueryDevice(Device)
Applies terminal query operations to the current board implementation.
handle_cwd
def handle_cwd(operation: Operation) -> None
OSC 7 — record the reported working directory.
handle_notify
def handle_notify(operation: Operation) -> None
OSC 9 / 777 / 99 — a desktop notification.
handle_shell_mark
def handle_shell_mark(operation: Operation) -> None
OSC 133 — a shell-integration prompt/command mark.
handle_pointer_shape
def handle_pointer_shape(operation: Operation) -> None
OSC 22 — the requested mouse-pointer shape.
handle_font
def handle_font(operation: Operation) -> None
OSC 50 — set the font, or answer a query (data == ‘?’) with the current one.
report_version
def report_version(operation: Operation) -> None
| XTVERSION (CSI > q) — reply DCS > | name version ST. |
report_status_string
def report_status_string(operation: Operation) -> None
DECRQSS — answer a request for the current value of a setting.
handle_clipboard
def handle_clipboard(operation: Operation) -> None
OSC 52 — set the clipboard, or answer a query with its current contents.
handle_window_op
def handle_window_op(operation: Operation) -> None
XTWINOPS — window manipulation requests and reports; a terminal (chrome) actuates them.
set_conformance_level
def set_conformance_level(operation: Operation) -> None
DECSCL — record the requested conformance level (behaviourally a no-op).
handle_setterm
def handle_setterm(operation: Operation) -> None
linux setterm CSI…] — update the board’s hardware registers.
request_checksum
def request_checksum(operation: Operation) -> None
DECRQCRA — reply DCS Pid ! ~ HHHH ST with a 16-bit checksum of a rectangle.
This is the DEC character-value form: the negated sum of the codepoints in the area, masked to 16 bits. (xterm can fold SGR attributes in too; that is a model detail we can add when a terminal needs it.)
request_termcap
def request_termcap(operation: Operation) -> None
XTGETTCAP — answer hex-encoded termcap/terminfo capability requests.
bittty.devices.board
The board: the whole terminal emulator machine.
Hosts the devices and registers, owns the child process and its PTY, and routes parser operations to device handlers. A terminal (chrome) (bittty.terminals) plugs into the display port; the child program is wired to the host port via a PTY.
Board Objects
class Board()
The terminal emulator: devices, registers, and process/PTY lifecycle.
get_pty_handler
@staticmethod
def get_pty_handler(rows: int = constants.DEFAULT_TERMINAL_HEIGHT,
cols: int = constants.DEFAULT_TERMINAL_WIDTH,
stdin=None,
stdout=None)
Create a platform-appropriate PTY handler.
print_text
def print_text(text: str) -> None
Write printable text (the parser’s fast path — no Operation wrapper).
resize
def resize(width: int, height: int) -> None
Resize the terminal, including buffers and the attached PTY.
bell
def bell() -> None
Ring the terminal bell: pushed to the terminal (chrome) as a present event.
present
def present(event: PresentEvent) -> None
Push a discrete side-effect to the attached terminal (no-op if none).
set_caps
def set_caps(caps: TerminalCaps) -> None
Record what the real terminal can do (a terminal (chrome) pushes this after probing).
set_focus
def set_focus(focused: bool) -> None
Record the box’s focus state and report it to the child (DECSET 1004).
reset
def reset(hard: bool = True) -> None
Reset the terminal. hard is RIS (full power-on); soft is DECSTR.
get_device
def get_device(name: str)
Return a plugged-in device by slot name.
get_content
def get_content()
Get current screen content as raw buffer data.
capture_pane
def capture_pane() -> str
Capture screen content: a pure pull of video memory as ANSI lines.
No cursor or pointer is composited in — the chrome renders those from the board’s registers (cursor.x/y, modes.cursor_visible, mouse.x/y).
link_at
def link_at(x: int, y: int) -> tuple | None
The hyperlink under a cell: (uri, link_id) or None.
The chrome pulls this for hover and click arbitration — link data is model state; the hover effect and the click-through are chrome.
attach_display
def attach_display(display) -> None
Attach a terminal (chrome) to receive present events (mirrors the host/PTY cable).
detach_display
def detach_display() -> None
Detach the current terminal.
input_key
def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None
Convert key + modifier to standard control codes, then send to the host.
input_fkey
def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None
Convert function key + modifier to standard control codes, then send to the host.
input_numpad_key
def input_numpad_key(key: str) -> None
Convert numpad key to appropriate sequence based on DECNKM mode.
input
def input(data: str) -> None
Translate control codes based on terminal modes and send to the host.
input_paste
def input_paste(text: str) -> None
Pasted text from the terminal: bracketed when mode 2004 is on, else raw.
Bypasses keyboard translation — a paste is data, not keystrokes.
input_mouse
def input_mouse(x: int, y: int, button: int, event_type: str,
modifiers: set[str]) -> None
Handle mouse input, cache position, and send appropriate sequence to the host.
Arguments:
x- 1-based mouse column.y- 1-based mouse row.button- The button that was pressed/released.event_type- “press”, “release”, or “move”.modifiers- A set of active modifiers (“shift”, “meta”, “ctrl”).
focus_in
def focus_in() -> None
The box gained focus: record it and report to the child if DECSET 1004 is on.
focus_out
def focus_out() -> None
The box lost focus: record it and report to the child if DECSET 1004 is on.
pty
@property
def pty() -> Optional[Any]
Attached PTY connection.
set_pty_data_callback
def set_pty_data_callback(callback: Callable[[str], None]) -> None
Swap the host port’s receive sink (a terminal uses this to add render throttling).
start_process
async def start_process() -> None
Start the child process with PTY.
stop_process
def stop_process() -> None
Stop the child process and clean up.
bittty.devices.charset
Charset operation handler for the current board state.
CharsetDevice Objects
class CharsetDevice(Device)
Owns charset state and applies charset operations.
designate
def designate(index: int, designator: str) -> None
Apply an SCS G-set designation, ignoring charsets the terminal lacks.
translate
def translate(text: str) -> str
Translate text through the invoked G-sets: GL for 0x20-0x7F, GR for 0xA0-0xFF.
set_g0_charset
def set_g0_charset(charset: str) -> None
Set the G0 character set.
set_g1_charset
def set_g1_charset(charset: str) -> None
Set the G1 character set.
set_g2_charset
def set_g2_charset(charset: str) -> None
Set the G2 character set.
set_g3_charset
def set_g3_charset(charset: str) -> None
Set the G3 character set.
shift_in
def shift_in() -> None
Shift In (SI / LS0) - invoke G0 into GL.
shift_out
def shift_out() -> None
Shift Out (SO / LS1) - invoke G1 into GL.
locking_shift
def locking_shift(gset: int, right: bool = False) -> None
Persistently invoke a G-set: LS2/LS3 into GL, LS1R/LS2R/LS3R into GR.
single_shift_2
def single_shift_2() -> None
Single Shift 2 (SS2) - use G2 for next character only.
single_shift_3
def single_shift_3() -> None
Single Shift 3 (SS3) - use G3 for next character only.
reset
def reset() -> None
Reset charset selections to US ASCII.
bittty.devices.style
Style operation handler for the current board state.
StyleDevice Objects
class StyleDevice(Device)
Owns current style state and applies style operations.
pop_sgr
def pop_sgr() -> None
XTPOPSGR — restore the SGR attributes saved by the matching XTPUSHSGR.
current_ansi_code
@property
def current_ansi_code() -> str
The active style as an ANSI SGR string (boundary/compat accessor).
apply_sgr
def apply_sgr(style: Style | None, reset: bool = False) -> None
Apply an SGR style update (API/testing surface over the hot handler).
set_default
def set_default() -> None
ESC [ 8 ] — make the current attributes the default (the SGR 0 target).
set_hyperlink
def set_hyperlink(uri: str, link_id: str = "") -> None
OSC 8 — start (non-empty URI) or end (empty) the active hyperlink.
The id= param groups link segments a layout has split (wrapped lines, columns); the chrome uses it to hover them as one link.
set_protected
def set_protected(mode: int) -> None
DECSCA — 1 protects subsequent characters from selective erase; 0/2 clears it.
reset
def reset() -> None
Reset to the default style (and clear the ESC[8] default register and SGR stack).
background_ansi
def background_ansi() -> str
Return the active background style as ANSI.
bittty.devices.base
Base class shared by board devices.
Device Objects
class Device()
A board device: dispatches an operation to its name -> handler table.
bittty.devices.blitter
The blitter: writes video memory. Screen and editing operation handlers.
Blitter Objects
class Blitter(Device)
Owns the video pages and applies screen/editing operations.
set_line_attribute
def set_line_attribute(attribute: str) -> None
DECDHL/DECDWL/DECSWL — set the cursor line’s width/height attribute.
write_text
def write_text(text: str, ansi_code: str = "") -> None
Write printable text at the cursor position, wrapping runs longer than the line.
A single PRINT run can be arbitrarily long (a shell line wider than the screen arrives as one chunk), so write it line-sized piece by line-sized piece; prepare_for_text_write supplies the wrap (or the clip, with autowrap off) between pieces.
repeat_last_character
def repeat_last_character(count: int) -> None
Repeat the last printed character count times.
resize
def resize(width: int, height: int) -> None
Resize terminal dimensions and screen buffers.
clear_screen
def clear_screen(mode: int = constants.ERASE_FROM_CURSOR_TO_END) -> None
Clear screen.
clear_line
def clear_line(mode: int = constants.ERASE_FROM_CURSOR_TO_END) -> None
Clear line.
clear_rect
def clear_rect(x1: int,
y1: int,
x2: int,
y2: int,
ansi_code: str = "") -> None
Clear a rectangular region.
selective_erase_display
def selective_erase_display(mode: int) -> None
DECSED — erase in display, leaving DECSCA-protected characters.
selective_erase_line
def selective_erase_line(mode: int) -> None
DECSEL — erase in line, leaving DECSCA-protected characters.
fill_rectangle
def fill_rectangle(params) -> None
DECFRA — fill a rectangle with a character (Pch;Pt;Pl;Pb;Pr).
erase_rectangle
def erase_rectangle(params) -> None
DECERA — erase a rectangle (Pt;Pl;Pb;Pr).
selective_erase_rectangle
def selective_erase_rectangle(params) -> None
DECSERA — erase a rectangle, leaving DECSCA-protected characters.
copy_rectangle
def copy_rectangle(params) -> None
DECCRA — copy a rectangle to another origin (Pts;Pls;Pbs;Prs;Pps;Ptd;Pld;Ppd).
set_attr_change_extent
def set_attr_change_extent(ps: int) -> None
DECSACE — 1 = stream (wrapping run), else rectangle (default).
change_attributes_rectangle
def change_attributes_rectangle(params) -> None
DECCARA — merge SGR attributes into every cell of the area (rectangle or stream).
reverse_attributes_rectangle
def reverse_attributes_rectangle(params) -> None
DECRARA — toggle the given attributes (1/4/5/7) across the area (rectangle or stream).
switch_screen
def switch_screen(alt: bool) -> None
Switch between primary and alternate screen.
alignment_test
def alignment_test() -> None
Fill the screen with ‘E’ characters for alignment testing.
set_scroll_region
def set_scroll_region(top: int, bottom: int) -> None
Set scroll region.
set_top_and_bottom_margins
def set_top_and_bottom_margins(top: int, bottom: int | None) -> None
DECSTBM — set the scroll region and home the cursor (origin-aware).
insert_lines
def insert_lines(count: int) -> None
Insert blank lines at cursor position.
delete_lines
def delete_lines(count: int) -> None
Delete lines at cursor position.
insert_characters
def insert_characters(count: int, ansi_code: str = "") -> None
Insert blank characters at cursor position.
delete_characters
def delete_characters(count: int) -> None
Delete characters at cursor position.
scroll
def scroll(lines: int) -> None
Scroll content within the active scroll region.
pan
def pan(columns: int) -> None
SL/SR — pan the scroll-region rows horizontally within the left/right margins.
shift_columns
def shift_columns(count: int) -> None
DECIC (count > 0) / DECDC (count < 0) — insert/delete columns at the cursor.
Confined to the left/right margin box; a cursor outside it is a no-op.
set_left_right_margins
def set_left_right_margins(left: int | None, right: int | None) -> None
DECSLRM — set the left/right margins (1-based; None/0 = extremes) and home the cursor.
reset_left_right_margins
def reset_left_right_margins() -> None
Restore the margins to the full screen width.
apply_left_right_margins
def apply_left_right_margins(operation: Operation) -> None
CSI Pl ; Pr s — DECSLRM when margin mode is on, else SCOSC (save cursor).
scroll_up
def scroll_up(count: int) -> None
Scroll content up within scroll region.
scroll_down
def scroll_down(count: int) -> None
Scroll content down within scroll region.
reset
def reset(hard: bool = True) -> None
Restore the full scroll region; a hard reset also clears both buffers to primary.
set_column_mode
def set_column_mode(columns: int) -> None
DECCOLM — switch 80/132 columns; always clears the screen and homes the cursor.
erase_characters
def erase_characters(count: int) -> None
Erase count characters from the cursor with the current style; the cursor stays (ECH).
bittty.devices.control
Control operation handler for the current board state.
ControlDevice Objects
class ControlDevice(Device)
Applies C0 and simple control operations to the current board implementation.
carriage_return_line_feed
def carriage_return_line_feed() -> None
CR+LF as one fused token (the parser batches the pair).
next_line
def next_line() -> None
NEL — carriage return followed by line feed.
answerback
def answerback() -> None
ENQ — transmit the programmed answerback string, if any is set.
bittty.devices.cursor
Cursor operation handler for the current board state.
CursorDevice Objects
class CursorDevice(Device)
Owns cursor state and applies cursor operations.
set_position
def set_position(x: int | None, y: int | None) -> None
Move cursor to an absolute, clamped terminal position.
move_to
def move_to(x: int | None, y: int | None) -> None
Apply a CUP/HVP/VPA move, honouring origin mode (DECOM).
Under origin mode the row is relative to the scroll region’s top and the column to the left margin, each clamped within its margins.
clamp_to_terminal
def clamp_to_terminal() -> None
Clamp the current position after terminal dimensions change.
carriage_return
def carriage_return() -> None
Move cursor to the beginning of the current line.
line_feed
def line_feed(is_wrapped: bool = False) -> None
Move down one line, scrolling the active scroll region if needed.
forward_index
def forward_index() -> None
DECFI — move right; at the right margin, pan the margin box one column left.
back_index
def back_index() -> None
DECBI — move left; at the left margin, pan the margin box one column right.
reverse_index
def reverse_index() -> None
Move up one line, scrolling down at the top of the scroll region.
backspace
def backspace() -> None
Move cursor back one position, wrapping to the previous line if needed.
set_tab_stop
def set_tab_stop(x: int | None = None) -> None
Set a horizontal tab stop at the given column.
next_tab_stop
def next_tab_stop() -> int
Return the next horizontal tab stop, clamped to the last column.
horizontal_tab
def horizontal_tab() -> None
Advance to the next horizontal tab stop.
previous_tab_stop
def previous_tab_stop() -> int
Return the nearest tab stop left of the cursor, or column 0.
forward_tab
def forward_tab(count: int) -> None
CHT — advance count tab stops.
backward_tab
def backward_tab(count: int) -> None
CBT — retreat count tab stops.
clear_tab_stop
def clear_tab_stop(mode: int) -> None
TBC — clear the tab stop at the cursor (0) or all tab stops (3).
tab_control
def tab_control(mode: int) -> None
CTC — set (0) or clear (2) a tab stop at the cursor, or clear all (5).
reset_tab_stops
def reset_tab_stops() -> None
DECST8C — reset to a tab stop every 8 columns.
next_line
def next_line(count: int) -> None
CNL — move to the first column, count lines down (no scroll).
previous_line
def previous_line(count: int) -> None
CPL — move to the first column, count lines up (no scroll).
set_cursor_style
def set_cursor_style(style: int) -> None
DECSCUSR — set cursor shape and blink from the style parameter.
prepare_for_text_write
def prepare_for_text_write() -> None
Apply wrapping or clipping before writing at the cursor.
advance_after_text_write
def advance_after_text_write(character_count: int) -> None
Advance after printable text, preserving existing autowrap behavior.
save
def save() -> None
Save cursor position and attributes.
restore
def restore() -> None
Restore cursor position and attributes.
reset
def reset(hard: bool = True) -> None
Home the cursor and clear saved state; a hard reset restores default tab stops.
bittty.devices.palette
Palette device: owns the live colour palette and the OSC colour surface.
PaletteDevice Objects
class PaletteDevice(Device)
Holds the current 256-colour table plus fg/bg/cursor, and applies OSC colour ops.
special_color
def special_color(operation: Operation) -> None
OSC 5 — set or (spec == ‘?’) query a special colour (0=bold, 1=underline, …).
special_color_enable
def special_color_enable(operation: Operation) -> None
OSC 6 — enable or disable a special colour.
reset
def reset() -> None
Restore the model’s default colours plus construction overrides (RIS).
dynamic_color
def dynamic_color(operation: Operation, slot: str, cmd: int,
fallback: str) -> None
OSC 13/14/17/19 — set, or (data == ‘?’) query a dynamic colour with a fg/bg fallback.
push_colors
def push_colors() -> None
XTPUSHCOLORS — save the whole palette (256 entries plus fg/bg/cursor).
pop_colors
def pop_colors() -> None
XTPOPCOLORS — restore the palette saved by the matching XTPUSHCOLORS.
resolve
def resolve(color) -> RGB | None
Resolve a Style colour to concrete RGB. None means “use the default fg/bg”.
set_palette
def set_palette(operation: Operation) -> None
OSC 4 ; n ; spec [; n ; spec …] — set or (spec == ‘?’) query palette entries.
set_or_query_special
def set_or_query_special(operation: Operation, slot: str, cmd: int) -> None
OSC 10/11/12 — set or (data == ‘?’) query the fg/bg/cursor colour.
reset_palette
def reset_palette(operation: Operation) -> None
OSC 104 — reset all palette entries, or just the listed indices.
reset_special
def reset_special(slot: str) -> None
OSC 110/111/112 — reset the fg/bg/cursor colour to the model default.
set_linux_palette
def set_linux_palette(operation: Operation) -> None
ESC ] P nrrggbb — the linux console’s own single-entry palette set.
bittty.devices.printer
Printer device: the terminal’s aux printer port (Media Copy / MC).
Historically a terminal had a printer hanging off its aux port; Media Copy routed screen data to it. Here the printer is a board device with an attachable sink — a callable taking a str, or any object with a write() method (a file, an io.StringIO, a real printer driver). Unattached, printed output is simply discarded, exactly like a terminal with no printer connected.
PrinterDevice Objects
class PrinterDevice(Device)
Owns printer state (controller/auto-print modes) and the output sink.
attach
def attach(sink) -> None
Attach a printer sink: a callable(str), or an object with a write(str) method.
emit
def emit(text: str) -> None
Send text to the attached sink, if any.
media_copy
def media_copy(operation: Operation) -> None
MC (CSI Ps i) — ANSI media copy.
dec_media_copy
def dec_media_copy(operation: Operation) -> None
DEC MC (CSI ? Ps i) — auto-print and DEC print variants.
print_screen
def print_screen() -> None
Send the whole current screen to the printer, one line per row.
print_line
def print_line(y: int) -> None
Send a single row to the printer.
reset
def reset(hard: bool = True) -> None
Leave printer controller/auto-print modes; the attached sink is config, so it stays.
bittty.devices.mouse
Mouse input encoder: xterm mouse reports and the DEC locator protocol.
MouseDevice Objects
class MouseDevice(Device)
Owns mouse presentation state and emits mouse input reports.
enable_locator
def enable_locator(operation: Operation) -> None
DECELR — enable/disable locator reporting; ps2==1 selects pixel coordinates.
select_locator_events
def select_locator_events(operation: Operation) -> None
DECSLE — choose whether button presses/releases trigger reports.
set_filter_rectangle
def set_filter_rectangle(operation: Operation) -> None
DECEFR — report once the locator leaves this rectangle.
request_locator_position
def request_locator_position(operation: Operation) -> None
DECRQLP — report the locator position now (or that it is unavailable).
input_mouse
def input_mouse(x: int, y: int, button: int, event_type: str,
modifiers: set[str]) -> None
Handle mouse input, cache position, and send appropriate sequence to the host.
Arguments:
x- 1-based mouse column.y- 1-based mouse row.button- The button that was pressed/released.event_type- “press”, “release”, or “move”.modifiers- A set of active modifiers (“shift”, “meta”, “ctrl”).
bittty.devices.keyboard
Keyboard input encoder for terminal key events.
KeyboardDevice Objects
class KeyboardDevice(Device)
Encodes keyboard input into terminal control sequences.
set_user_keys
def set_user_keys(operation: Operation) -> None
DECUDK — install user-defined strings for function keys.
set_modify_keys
def set_modify_keys(operation: Operation) -> None
XTMODKEYS (CSI > Pp ; Pv m) — set a key-modifier resource; Pp 4 is modifyOtherKeys.
kitty_push
def kitty_push(operation: Operation) -> None
CSI > flags u — save the current flags and adopt new ones.
kitty_pop
def kitty_pop(operation: Operation) -> None
CSI < n u — pop n saved flag-states off the stack.
kitty_set
def kitty_set(operation: Operation) -> None
CSI = flags ; mode u — set (1), add (2) or remove (3) flag bits.
kitty_query
def kitty_query(operation: Operation) -> None
CSI ? u — report the current Kitty flags as CSI ? flags u.
report_focus
def report_focus(focused: bool) -> None
Focus reporting (DECSET 1004) — send CSI I on focus in, CSI O on focus out.
reset
def reset(hard: bool = True) -> None
RIS clears the modern-keyboard negotiation state.
input_key
def input_key(char: str, modifier: int = constants.KEY_MOD_NONE) -> None
Convert key + modifier to standard control codes, then send to input().
input_fkey
def input_fkey(num: int, modifier: int = constants.KEY_MOD_NONE) -> None
Encode a function key using any user-defined string, else the keymap.
input_numpad_key
def input_numpad_key(key: str) -> None
Convert numpad key to the sequence for the current keypad mode.
input
def input(data: str) -> None
Translate control codes based on terminal modes and send to the host.
translate_application_cursor_keys
def translate_application_cursor_keys(data: str) -> str
Translate embedded normal cursor-key CSI sequences to application mode.
bittty.devices.modes
Terminal modes as a declarative capability table.
Each mode is a small Mode capability that claims a number (ANSI or DEC
private), knows how to apply itself and how to report its DECRQM status, and
can be omitted by a model (so, e.g., a terminal that predates bracketed
paste simply does not recognise mode 2004). The boolean flags themselves stay
as attributes on the device: they are read widely across the emulator, and
several modes legitimately share one flag (mode 7 and 1000 both drive
mouse_tracking).
Mode Objects
@dataclass(frozen=True)
class Mode()
One terminal mode: which flag it backs and how it answers DECRQM.
attr
device flag this mode drives, if any
invert
“set” stores the negation (modes 12, 66)
queryable
DECRQM reports this mode’s state
apply_fn
side effect
status_fn
custom DECRQM status
peripheral
“mouse”/”cursor”/”sync”: emit a present event on change
status
def status(device: ModeDevice) -> int
DECRQM status: 1 = set, 2 = reset, 0 = not recognised.
ModeDevice Objects
class ModeDevice(Device)
Owns terminal mode state and applies mode operations via the mode table.
reset
def reset(hard: bool = True) -> None
Reset modes. hard restores every flag (RIS); soft is the DECSTR subset.
set_mode
def set_mode(mode: int, value: bool = True, private: bool = False) -> None
Set a single terminal mode.
clear_mode
def clear_mode(mode: int, private: bool = False) -> None
Clear a single terminal mode.
bittty.devices.title
Title operation handler for the current board state.
TitleDevice Objects
class TitleDevice(Device)
Owns terminal title state and applies title operations.
set_title
def set_title(title: str) -> None
Set terminal title.
set_icon_title
def set_icon_title(icon_title: str) -> None
Set terminal icon title.
set_both
def set_both(title: str) -> None
Set both the window title and the icon title.
push
def push() -> None
XTWINOPS 22 — save the current window and icon titles.
pop
def pop() -> None
XTWINOPS 23 — restore the most recently saved titles.
bittty.devices
Device adapters for applying parser operations.
bittty.style
CURSOR_CODE
Reverse video for cursor display
RESET_CODE
Reset all formatting
Style Objects
class Style()
Immutable text style. The keyword surface matches the old dataclass field per field (None = inherit); internally the tri-state attributes are packed so merge/eq/hash cost a couple of int ops instead of a 20-field walk.
merge
def merge(other: Style) -> Style
Merge another style into this one; the other’s set attributes win.
replace
def replace(**kwargs) -> Style
A new Style with the given fields replaced (None = back to inherit).
diff
def diff(other: "Style") -> str
Generate minimal ANSI sequence to transition to another style.
parse_sgr_with_reset
@lru_cache(maxsize=10000)
def parse_sgr_with_reset(ansi: str) -> Tuple[Optional[Style], bool]
Parse an SGR sequence into (style, reset): reset means “clear, then apply style”.
A reset token (0, 00, or an empty parameter) anywhere in the sequence discards everything before it, so ESC[0;31m is “reset, then red” — not a red merge into the current attributes. A pure reset returns (None, True) so the hot path can skip the merge without comparing 20 Style fields.
interpret
@lru_cache(maxsize=10000)
def interpret(tokens: Tuple[str, ...]) -> Style
Interpret SGR tokens into a Style, accumulating the packed masks directly.
get_background
@lru_cache(maxsize=10000)
def get_background(ansi: str) -> str
Extract just the background color as an ANSI sequence.
Arguments:
ansi- ANSI escape sequence
Returns:
ANSI sequence with just the background color, or empty string
merge_ansi_styles
@lru_cache(maxsize=10000)
def merge_ansi_styles(base: str, new: str) -> str
Merge two ANSI style sequences, returning a new ANSI sequence.
Arguments:
base- Base ANSI sequencenew- New ANSI sequence to merge
Returns:
Merged ANSI sequence
style_to_ansi
@lru_cache(maxsize=10000)
def style_to_ansi(style: Style) -> str
Convert a Style object back to an ANSI escape sequence.
Arguments:
style- Style object to convert
Returns:
ANSI escape sequence string
bittty.caps
TerminalCaps: physical facts about the real terminal, pushed up by the chrome.
The backend answers the child’s physical-fact queries (window/cell pixel size, background colour) from these when a terminal (chrome) has supplied them. Every field defaults to “unknown” (None / “unknown”), meaning “change nothing” — so a board with no attached terminal behaves exactly as it does today.
Graphics-capability flags (sixel / kitty / iTerm images) are deliberately absent until graphics modes are on the table; there is nothing to reconcile without them.
TerminalCaps Objects
@dataclass(frozen=True)
class TerminalCaps()
What the real terminal can actually do (terminal -> board).
color_depth
“monochrome” / “16” / “256” / “truecolor” / “unknown”
cell_px
character cell size in pixels (CSI 16 t)
window_px
window size in pixels (CSI 14 t)
background
actual background colour (OSC 11)
unknown
@classmethod
def unknown(cls) -> "TerminalCaps"
Caps that assert nothing — the backend keeps its current behaviour.
bittty.palette
Colour palettes: the index -> RGB mapping a terminal presents.
bittty stores colours symbolically (a Style holds an indexed or rgb Color);
the palette is the authoritative map an indexed colour resolves through. It is
seeded from the model, mutated by OSC colour sequences, and queried by
their ? forms. Rendering to real pixels stays a terminal (chrome) concern — the
terminal calls PaletteDevice.resolve() when it wants RGB.
build_256
def build_256(base16: tuple[RGB, ...]) -> list[RGB]
Build the full 256-colour table: 16 base + 216 colour cube + 24 greys.
parse_color_spec
def parse_color_spec(spec: str) -> RGB | None
Parse an X11 colour spec: rgb:R/G/B or RGB/RRGGBB/… .
format_rgb
def format_rgb(rgb: RGB) -> str
Format an RGB triple as an X11 rgb:rrrr/gggg/bbbb reply string.
PaletteDefaults Objects
@dataclass(frozen=True)
class PaletteDefaults()
A terminal’s default colours: the 16 ANSI colours plus fg/bg/cursor.
bittty.present
Present events: discrete side-effects the board pushes to the attached terminal (chrome).
Screen content stays pull (a terminal (chrome) reads capture_pane()/get_line on its own cadence). Only these discrete events are pushed, through the board’s DisplayPort. Each is a plain frozen dataclass — pure data, no imports from board/devices — so board.py can depend on this module without a cycle.
Bell Objects
@dataclass(frozen=True)
class Bell()
The terminal bell rang (C0 BEL).
TitleChanged Objects
@dataclass(frozen=True)
class TitleChanged()
Window and/or icon title changed (OSC 0/1/2, XTWINOPS title stack).
Notification Objects
@dataclass(frozen=True)
class Notification()
A desktop notification was posted (OSC 9 / 777 / 99).
ClipboardChanged Objects
@dataclass(frozen=True)
class ClipboardChanged()
A clipboard selection was set (OSC 52 write).
PromptMark Objects
@dataclass(frozen=True)
class PromptMark()
A shell-integration prompt/command mark (OSC 133).
PointerShapeChanged Objects
@dataclass(frozen=True)
class PointerShapeChanged()
The mouse-pointer shape was requested (OSC 22).
FontChanged Objects
@dataclass(frozen=True)
class FontChanged()
The font was set (OSC 50).
CwdChanged Objects
@dataclass(frozen=True)
class CwdChanged()
The reported working directory changed (OSC 7).
WindowRequest Objects
@dataclass(frozen=True)
class WindowRequest()
A window action was requested: “raise” / “lower” / “refresh” (XTWINOPS 5/6/7).
WindowStateChanged Objects
@dataclass(frozen=True)
class WindowStateChanged()
Window iconify/maximize/fullscreen/position state changed (XTWINOPS).
ConsoleRequest Objects
@dataclass(frozen=True)
class ConsoleRequest()
A virtual-console switch was requested (linux setterm 12/15).
kind
“switch” / “previous”
MouseModeChanged Objects
@dataclass(frozen=True)
class MouseModeChanged()
The requested mouse-tracking mode changed (derived from modes 9/1000/1002/1003).
mode
“off” / “basic” / “button” / “any”
sgr
SGR (1006) encoding requested
CursorVisibilityChanged Objects
@dataclass(frozen=True)
class CursorVisibilityChanged()
Text-cursor visibility changed (DECTCEM, mode 25).
SyncOutputChanged Objects
@dataclass(frozen=True)
class SyncOutputChanged()
Synchronized-output mode toggled (mode 2026); a terminal (chrome) gates repaint on it.