Ghostty.Terminal (Ghostty v0.5.0)

Copy Markdown View Source

A managed terminal emulator backed by libghostty-vt.

Each terminal is a GenServer that owns a libghostty-vt terminal instance. All operations are serialized through the GenServer to ensure thread safety (libghostty-vt terminal instances are not thread-safe).

Examples

{:ok, term} = Ghostty.Terminal.start_link(cols: 120, rows: 40)
Ghostty.Terminal.write(term, File.read!("recording.vt"))
{:ok, html} = Ghostty.Terminal.snapshot(term, :html)

Supervision

children = [
  {Ghostty.Terminal, name: :main, cols: 80, rows: 24, max_scrollback: 50_000}
]

Effects

Terminal programs can trigger side effects via VT sequences (query responses, bell, title changes). These are forwarded as messages to the process that called start_link/1:

  • {:pty_write, binary} — query responses to write back to the PTY
  • :bell — BEL character
  • :title_changed — title change via OSC 2

Snapshot formats

  • :plain — plain text with all styling stripped
  • :html — HTML with inline styles preserving all colors and attributes
  • :vt — raw VT escape sequences (round-trippable)

Summary

Functions

Returns the terminal screen as a grid of cells.

Returns a child spec for use in supervision trees.

Returns the current cursor position as {col, row} (0-indexed).

Returns the current render-state cursor metadata for the visible viewport.

Encodes a focus event into a terminal escape sequence.

Returns whether focus reporting (DEC mode 1004) is enabled.

Encodes a key event into a terminal escape sequence.

Encodes a mouse event into a terminal escape sequence.

Returns the current terminal mouse reporting mode state.

Returns the current visible render-state cells together with cursor metadata.

Performs a full terminal reset (RIS — Reset to Initial State).

Resizes the terminal. Text reflow is handled automatically.

Scrolls the terminal viewport.

Returns the scrollbar state for the terminal viewport.

Returns the current terminal dimensions as {cols, rows}.

Returns a snapshot of the terminal screen content.

Starts a terminal process linked to the caller.

Returns the terminal's effective color theme.

Writes VT-encoded data to the terminal.

Types

cell()

@type cell() :: {binary(), rgb() | nil, rgb() | nil, non_neg_integer()}

cursor_state()

@type cursor_state() :: %{
  x: non_neg_integer() | nil,
  y: non_neg_integer() | nil,
  visible: boolean(),
  blinking: boolean(),
  style: cursor_style(),
  wide_tail: boolean(),
  color: rgb() | nil
}

cursor_style()

@type cursor_style() :: :bar | :block | :underline | :block_hollow

format()

@type format() :: :plain | :html | :vt

mouse_modes()

@type mouse_modes() :: %{
  tracking: boolean(),
  x10: boolean(),
  normal: boolean(),
  button: boolean(),
  any: boolean(),
  sgr: boolean()
}

option()

@type option() ::
  {:cols, pos_integer()}
  | {:rows, pos_integer()}
  | {:max_scrollback, non_neg_integer()}
  | {:foreground, rgb()}
  | {:background, rgb()}
  | {:cursor_color, rgb()}
  | {:palette, palette()}
  | {:name, GenServer.name()}

palette()

@type palette() :: [rgb()] | %{optional(0..255) => rgb()}

render_state()

@type render_state() :: %{
  cells: [[cell()]],
  cursor: cursor_state(),
  mouse: mouse_modes(),
  scrollbar: scrollbar(),
  focus_reporting: boolean(),
  foreground: rgb() | nil,
  background: rgb() | nil
}

rgb()

@type rgb() :: {byte(), byte(), byte()}

scrollbar()

@type scrollbar() :: %{
  total: non_neg_integer(),
  offset: non_neg_integer(),
  len: non_neg_integer()
}

theme()

@type theme() :: %{
  foreground: rgb() | nil,
  background: rgb() | nil,
  cursor: rgb() | nil,
  palette: [rgb()]
}

Functions

cells(terminal)

@spec cells(GenServer.server()) :: [[cell()]]

Returns the terminal screen as a grid of cells.

Each cell is {grapheme, fg, bg, flags} where:

  • grapheme — UTF-8 binary (empty for blank cells)
  • fg / bg{r, g, b} tuples or nil
  • flags — bitmask (see Ghostty.Terminal.Cell for helpers)

Examples

rows = Ghostty.Terminal.cells(term)

for row <- rows, {char, fg, _bg, flags} <- row, char != "" do
  if Ghostty.Terminal.Cell.bold?({char, fg, nil, flags}), do: IO.write("*")
  IO.write(char)
end

child_spec(init_arg)

Returns a child spec for use in supervision trees.

cursor(terminal)

Returns the current cursor position as {col, row} (0-indexed).

cursor_state(terminal)

@spec cursor_state(GenServer.server()) :: cursor_state()

Returns the current render-state cursor metadata for the visible viewport.

encode_focus(gained?)

@spec encode_focus(boolean()) :: {:ok, binary()} | :none

Encodes a focus event into a terminal escape sequence.

Examples

{:ok, seq} = Ghostty.Terminal.encode_focus(true)   # => "\e[I"
{:ok, seq} = Ghostty.Terminal.encode_focus(false)  # => "\e[O"

focus_reporting?(terminal)

@spec focus_reporting?(GenServer.server()) :: boolean()

Returns whether focus reporting (DEC mode 1004) is enabled.

input_key(terminal, event)

@spec input_key(GenServer.server(), Ghostty.KeyEvent.t()) :: {:ok, binary()} | :none

Encodes a key event into a terminal escape sequence.

Returns {:ok, sequence} with the bytes to send to the PTY, or :none if the key event produces no output.

Examples

Ghostty.Terminal.input_key(term, %Ghostty.KeyEvent{key: :enter})
# => {:ok, "\r"}

Ghostty.Terminal.input_key(term, %Ghostty.KeyEvent{key: :c, mods: [:ctrl]})
# => {:ok, <<3>>}

input_mouse(terminal, event)

@spec input_mouse(GenServer.server(), Ghostty.MouseEvent.t()) ::
  {:ok, binary()} | :none

Encodes a mouse event into a terminal escape sequence.

Returns {:ok, sequence} or :none if mouse tracking is not enabled by the running program.

mouse_modes(terminal)

@spec mouse_modes(GenServer.server()) :: mouse_modes()

Returns the current terminal mouse reporting mode state.

render_state(terminal)

@spec render_state(GenServer.server()) :: render_state()

Returns the current visible render-state cells together with cursor metadata.

reset(terminal)

@spec reset(GenServer.server()) :: :ok

Performs a full terminal reset (RIS — Reset to Initial State).

resize(terminal, cols, rows)

@spec resize(GenServer.server(), pos_integer(), pos_integer()) :: :ok

Resizes the terminal. Text reflow is handled automatically.

Examples

Ghostty.Terminal.resize(term, 120, 40)

scroll(terminal, delta)

@spec scroll(GenServer.server(), integer()) :: :ok

Scrolls the terminal viewport.

Positive delta scrolls down (towards newer content), negative scrolls up (towards scrollback history).

scrollbar(terminal)

@spec scrollbar(GenServer.server()) :: scrollbar()

Returns the scrollbar state for the terminal viewport.

size(terminal)

@spec size(GenServer.server()) :: {pos_integer(), pos_integer()}

Returns the current terminal dimensions as {cols, rows}.

snapshot(terminal, format \\ :plain)

@spec snapshot(GenServer.server(), format()) :: {:ok, binary()}

Returns a snapshot of the terminal screen content.

Formats

  • :plain — plain text, stripped of all styling (default)
  • :html — HTML with inline styles preserving colors and attributes
  • :vt — raw VT escape sequences

Examples

{:ok, text} = Ghostty.Terminal.snapshot(term)
{:ok, html} = Ghostty.Terminal.snapshot(term, :html)
{:ok, vt}   = Ghostty.Terminal.snapshot(term, :vt)

start_link(opts \\ [])

@spec start_link([option()]) :: GenServer.on_start()

Starts a terminal process linked to the caller.

Effect messages (:bell, :title_changed, {:pty_write, data}) are sent to the calling process.

Options

  • :cols - number of columns (default: 80)
  • :rows - number of rows (default: 24)
  • :max_scrollback - maximum scrollback lines (default: 10_000)
  • :foreground - default foreground as {red, green, blue}
  • :background - default background as {red, green, blue}
  • :cursor_color - default cursor color as {red, green, blue}
  • :palette - palette colors as a list starting at index 0, or a map of palette indices (0..255) to RGB tuples; unspecified entries retain libghostty-vt's built-in defaults
  • :name - GenServer name registration

theme(terminal)

@spec theme(GenServer.server()) :: theme()

Returns the terminal's effective color theme.

Values include defaults configured at startup plus any overrides applied by terminal programs through OSC color sequences. The palette always contains 256 entries.

write(terminal, data)

@spec write(GenServer.server(), iodata()) :: :ok

Writes VT-encoded data to the terminal.

The terminal's VT parser processes escape sequences and updates the internal screen, cursor, and style state. Accepts iodata.

Examples

Ghostty.Terminal.write(term, "hello world")
Ghostty.Terminal.write(term, "\e[31mred\e[0m")
Ghostty.Terminal.write(term, ["line 1\r\n", "line 2\r\n"])