ABI BREAKING: This removes `ghostty_terminal_mode_get` and `_mode_set`.
We can now represent these operations completely with standard
`ghostty_terminal_get` and `ghostty_terminal_set`, which makes it much
more flexible to preserve ABI in the future.
This is all centered around a new `GhosttyTerminalModeConfig` structure
that is an in or out parameter depending on use case.
This also adds a new `GHOSTTY_TERMINAL_OPT_MODE_DEFAULT` option that
can be used to set the _default_ value of mode that happens when a RIS
event (full reset) is sent.
Track each image's placement count in its existing metadata. This lets
us use constant-time usage checks (rather than scans) during eviction.
Select the best candidate directly from storage on each eviction, preserving
the existing priority order: unused status, transient hint, generation, then
ID.
Since eviction no longer allocates, it can't fail, so callers no longer
need to handle out-of-memory conditions.
Add an explicit libghostty-vt title-report option and keep CSI 21 t
disabled unless an embedder enables it.
Previously, registering the general PTY write callback also caused the
terminal to echo attacker-controlled window titles. This exposed
embedders to command injection after user interaction.
Gate the response in the shared terminal stream, append the C API
option without renumbering existing values, and cover the default,
opt-in, and reset behavior in Zig and C API tests.
This adds opt-in continuation tracking to `terminal.Stream` that allows
any caller to call `writeContinuation` in order to get the minimum bytes
necessary from a grounded parser state to the identical state.
This enables reliable stream restart across serialization states, which
could be used for local restart, networked terminals, etc. For me, this
is used for multiplexers. :)
## Implementation
The implementation of this was really carefully done to avoid any
negative performance impact particularly when continuation tracking is
_off_.
The way this work is simple:
1. ESC is the only char that leaves the ground state and most
ESC sequences are short. So if we're in a non-ground state, we
do a backwards vectorized search to find the last `ESC` in the
input slice. If one doesn't exist, we assume we found it previously
and store the whole slice (rare, since ESC sequences are usually
short like I said).
2. If we're in the ground state that means we only have a potential
incomplete UTF-8 codepoint, so we find the lead UTF-8 byte.
3. When writing, we normalize the suffix to drop things like BEL
commands that would've already been handled to avoid
double-calling.
## Performance
Via `ghostty-bench +terminal-stream`
Corpus main tracking off tracking on
plain ASCII (256 MiB) 175.4ms 175.8ms 175.5ms
UTF-8 (32 MiB) 268.4ms 270.0ms 269.9ms
5% invalid UTF-8 (32 MiB) 316.4ms 317.8ms 320.2ms
CSI-heavy (32 MiB) 145.0ms 146.3ms 145.9ms
OSC (32 MiB) 1621.0ms 1627.5ms 1638.3ms
Kitty APC (128 MiB) 95.5ms 96.9ms 96.3ms
mixed traffic (32 MiB) 172.4ms 172.0ms 172.3ms
giant APC (128 MiB) 38.3ms 38.3ms 40.8ms
C API callers could configure runtime scrollback limits but could not
read them back. Add terminal data keys for the primary screen byte and
line configurations.
Return GHOSTTY_NO_VALUE for unlimited limits and keep reads stable while
an alternate screen is active. Document the configured-value semantics
and add focused coverage for defaults, updates, and unlimited values.
Terminal construction previously accepted GhosttyTerminalOptions with
dimensions and one scrollback byte limit. Remove the options struct from
the ABI and make ghostty_terminal_new accept columns and rows directly.
Add byte and line limit options to ghostty_terminal_set and forward them
to the runtime Terminal setters. NULL removes a limit, while zero bytes
disables scrollback. Update type metadata, tests, and all API examples.
This adds a new config `scrollback-limit-lines` to limit scrollback by
lines instead of bytes. This also renames `scrollback-limit` to
`scrollback-limit-bytes` to make it clear what it does but we have a
compatibility entry so old configurations will continue to work, so its
not breaking.
**This is NOT exclusive to `scrollback-limit-bytes`**. When both are
set, then the _first limit reached_ is used. Since lines is affected by
viewport size and bytes are affected by entries (more styles, more
graphemes, etc.), they serve somewhat different purposes and it might be
useful to set both.
The default remains 50MB of bytes, unlimited lines.
This is not exposed to libghostty yet. I have that coming as a follow up
change.
Move DECRQSS response encoding into the terminal DCS handler so both
the full termio path and libghostty-vt terminal stream emit the same
replies. The C API stream now maintains and releases DCS parser state
and forwards responses through write_pty.
The C API is assumed to be single-threaded per VT instance.
Additionally, using fully-threaded I/O instances registers signal
handlers, and would do a pair of registrations once per instance, which
could easily get out of hand (and is not really what we intend anyway).
init_single_threaded does not register signal handlers, so it does not
have this issue, and matches the execution model of the C VT API
(single-threaded/not thread-safe within a single VT instance).
This also fixes an initialization issue with the threaded I/O instance
in general (needs allocation as the memory location would have gone out
of scope before).
This commit represents the majority of the work necessary to upgrade
Ghostty to use Zig 0.16.0.
Key parts:
* In addition to its previous responsibilities, the global state now
houses state for global I/O implementations and the process
environment. It is now also utilized in the main application along
with the C library. Where necessary, global state is isolated from key
parts of the implementation (e.g., in libghostty subsystems), and it's
expected that this list will grow.
* We currently manage our own C translation layer where necessary. In
these cases, cImport has been removed in favor of the new external
translate-c package. Due to fixes that have needed be made to properly
translate the dependencies that were swapped out, as mentioned, we
have had to backport fixes from the current translate-c package (and
the upstream Arocc dependency). We will host this ourselves until Zig
0.17.0 is released with these fixes.
* Where necessary (only a small number of cases), some stdlib code from
0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in
lib/compat.
Co-authored-by: Leah Amelia Chen <hi@pluie.me>
Cursor defaults were duplicated across stream handlers and it was a
pretty significant amount of simple and yet non-trivial logic to
understand.
Store these on Terminal itself and have methods to route things like
DECSCUSR through for consistent behaviors.
The libghostty-vt stream is made to be infallible: in the case of any error
it just logs and moves on. That's because a terminal can't really... stop,
under normal operations. But, under special operations (fuzzing, replays,
etc.) it can and should stop!
Rather than make the operation fallible, its simply enough for me at least
to know that something went wrong. This is a simple change that adds a simple
flag that is flagged to true when such a scenario happens.
For normal Ghostty GUI operations, this isn't used at all. For libghostty
consumers they can choose to read it if they want, but don't have to.
This also adds a C API to read it.
This makes the Terminal.resize handle more of the common elements that
a core terminal emulator should: cell geoemtry handling (if exists),
updates synchronized output modes.
This adds a new TerminalStream.resize that also handles the side effects
for more easy integration into downstream libghostty-vt consumers, namely
mode 2048 in-band signaling handling.
libghostty-vt was already parsing OSC 52 into the clipboard_contents
action but the stream handler dropped, so there was no way to observe
clipboard writes (in this case, a program using go-libghostty). This
adds a clipboard_set effect following the existing bell/title_changed
pattern and expose it through the C API as
`GHOSTTY_TERMINAL_OPT_CLIPBOARD_SET`.
The callback receives the OSC 52 kind byte and the base64 payload
exactly as received; decoding and kind interpretation are left to the
embedder, matching how decoding is typically deferred.
Note this intentionally does not deal with clipboard read requests given
the security implications.
AI disclosure: Fable (via Claude Code) did the majority of the work
here, I validated it on the client side and fully understand the pattern
we're fitting into.
#13182
Replace the OSC 52-specific kind and encoded payload callback with an
atomic clipboard write containing a normalized destination and decoded
MIME representations. This keeps protocol details out of embedders and
lets iTerm2 Copy use the same semantic path.
Represent clears with an empty content list, preserve binary payloads,
and return a generic result for protocols that acknowledge writes. Add
the C ABI descriptors, layout metadata, and effects example so future
multipart protocols can reuse the callback without another API break.
Scrollback compression scheduling was only available to Zig callers that
used Terminal directly, leaving C embedders unable to drive the same idle
compression policy.
Define ABI-aware mode and result enums on Terminal and export activity
and compression operations through the C API. Keep scheduling
caller-owned, validate C inputs, and document the incremental contract
with a complete example.
Report unsupported reclamation consistently for full passes so callers
can disable compression on targets that cannot retain decommitted
mappings.
libghostty-vt already tracked OSC color state but ignored color queries in the standalone stream handler. This meant embedders that installed write_pty still received no response for OSC 4/10/11/12 or Kitty OSC 21 queries.
Resolve the current terminal colors through shared Terminal helpers and encode replies through the write_pty effect. Xterm queries use the fixed 16-bit rgb form, preserve the request terminator, and fall back from cursor to foreground when no cursor color is set. Kitty color queries now report supported terminal-backed keys and return empty values for unset dynamic colors.
Add RGB wire encoders and tests covering the stream handler and C API. The OSC parser now releases color operation request lists during reset, fixing an allocation leak exposed by multi-query OSC color tests.
This adds a GHOSTTY_SCROLL_VIEWPORT_ROW tag with a `size_t row` member
in the value union. The row is an absolute offset from the top of the
scrollable area, clamped to the active area, in the same row space as
the scrollbar offset so thumb positions round-trip cleanly:
ghostty_terminal_scroll_viewport(term,
(GhosttyTerminalScrollViewport){
.tag = GHOSTTY_SCROLL_VIEWPORT_ROW,
.value = {.row = 42},
});
The tag is appended to the existing enum and the union fits within the
reserved padding, so this is ABI compatible.
This also corrects the docs on GHOSTTY_TERMINAL_DATA_SCROLLBAR: the
getter is amortized O(1) (total is maintained incrementally, the offset
is cached), not "expensive". Since there is intentionally no change
callback, the docs now bless polling per frame or per write batch and
diffing, which is what Ghostty's own renderer does.
Motivation: Embedders building native scrollbars can already read scroll state via
GHOSTTY_TERMINAL_DATA_SCROLLBAR, but the write side only exposed
top/bottom/delta scrolling. Mapping a scrollbar thumb drag to an
absolute position required reading the current offset and computing a
delta, which is two calls that must be sequenced atomically by the
caller.
The core already supports absolute positioning and the macOS
app uses it for scroller drags via the scroll_to_row keybinding; this
exposes the same operation through the libghostty C API.
libghostty-vt already parses OSC 52 into the clipboard_contents action but
the stream handler dropped it in the no-effect list, so embedders had no way
to observe a program's clipboard writes. Add a clipboard_set effect following
the existing bell/title_changed pattern and expose it through the C API as
GHOSTTY_TERMINAL_OPT_CLIPBOARD_SET.
The callback receives the OSC 52 kind byte and the base64 payload exactly as
received; decoding and kind interpretation are left to the embedder, matching
how ghostty itself defers decoding to the apprt layer.
Clipboard read requests ("?") are never forwarded: answering one would let
any program running in the terminal silently read the user's clipboard, and
a VT state library cannot mediate that with user consent. Empty payloads are
also ignored rather than inventing clear semantics.
Previously the libghostty-vt stream handler dropped .report_pwd as a
no-op, so embedders never saw shell-reported cwd changes and the
terminal's pwd field was never populated from escape sequences.
Wire the action to setPwd and expose a pwd_changed callback analogous
to title_changed via GHOSTTY_TERMINAL_OPT_PWD_CHANGED. The payload is
passed through unparsed; embedders read it with ghostty_terminal_get
and decode any URI scheme themselves.
This hooks up the glyph protocol glossary to the terminal state. This
effectively makes us handle the APC protocol for it both in Ghostty GUI
and libghostty, although we didn't implement the renderer yet.
The Zig/C libghostty API also has a way to disable the protocol but it is
enabled by default. The memory usage is bound by the specification.
For dirty tracking for the renderer, we're going with the simple route that
any glyph change marks a coarse grained dirty flag and we'll [in the future]
rebuild the entire state in the renderer. I think this will be fine for
realistic workloads, but we can reassess in the future when we have
real workloads.
This PR adds 2 options to `libghostty-vt` to configure the style and
blink status of the default cursor. They control how the terminal
renders the cursor when a program doesn't request any explicit style or
when it resets it to the terminal's default state by sending a DECSCUSR
reset sequence (`CSI 0 q`).
Adds a PageList regression test exercising the underflow path fixed in
7fa6fffbc, and a libghostty-vt C API test mirroring the original repro
through ghostty_terminal_resize.
Adds an option to `libghostty-vt` to configure the default cursor style
that should be displayed when an app sends a DECSCUSR reset sequence
(`CSI 0 q`).
Expose whether the terminal viewport is currently pinned to the active
area through the libghostty-vt terminal data API. Previously embedders
could only infer this from scrollbar geometry, which was indirect and
could require the more expensive scrollbar calculation.
The new GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE value returns the exact
PageList viewport state as a bool. The scroll viewport test now verifies
the value while moving between the active area and scrollback.
Tracked grid references previously held a raw terminal wrapper pointer
and were required to be freed before the terminal. If callers kept one
past terminal destruction, later tracked-ref calls could dereference
freed terminal or page-list memory before detecting that the reference
was no longer meaningful.
Track live C tracked-grid-ref handles from the terminal wrapper and
detach them before tearing down terminal storage. Detached refs now
report no value through the tracked-ref APIs and can still be freed by
the caller. Update the C API docs to describe this lifetime behavior and
add a regression test for using a tracked ref after terminal free.
This introduces some overhead but tracked pins shouldn't be numerous and
this dramatically improves safety.
No API changes due to this (just more safety).
Tracked grid references previously held a raw terminal wrapper pointer and
were required to be freed before the terminal. If callers kept one past
terminal destruction, later tracked-ref calls could dereference freed
terminal or page-list memory before detecting that the reference was no
longer meaningful.
Track live C tracked-grid-ref handles from the terminal wrapper and detach
them before tearing down terminal storage. Detached refs now report no
value through the tracked-ref APIs and can still be freed by the caller.
Update the C API docs to describe this lifetime behavior and add a
regression test for using a tracked ref after terminal free.
This introduces some overhead but tracked pins shouldn't be numerous
and this dramatically improves safety.
Expose a C API for checking whether a GhosttyPoint is inside a
GhosttySelection. The new terminal helper validates the selection snapshot
against the active screen, resolves the point to a grid pin, and delegates
to the internal Selection.contains implementation so C consumers get the
same linear and rectangular selection semantics as Ghostty.
Wire the symbol through the C API exports and public headers, and add a
focused test covering linear containment and rectangular selection behavior.
Expose selection endpoint ordering through the libghostty-vt C API so
embedders can safely normalize selections whose start and end refs may be
reversed. The new APIs report the current order and return a fresh
untracked selection with forward or reverse bounds.
Selection.Order now uses lib.Enum, matching the existing adjustment enum
pattern and keeping the C ABI enum generated from the same Zig source of
truth. The new functions are wired through the C API re-export and lib-vt
export paths, with coverage for mirrored rectangular selection ordering.
Add terminal set/get support for the active screen selection through the
existing option and data APIs. Setting a selection copies the C snapshot
into terminal-owned tracked state, while passing NULL clears the current
selection.
Getting the selection now returns an untracked GhosttySelection snapshot
or GHOSTTY_NO_VALUE when there is no selection. The C header documents
the different lifetimes for set and get so embedders know when input and
returned grid references remain valid.
Add a C API for tracked pins, known as a tracked grid ref in C.
The new API can create tracked refs from terminal points, snapshot them
back to regular grid refs for cell access, convert them to coordinates,
move them to a new point, report when their semantic location was lost,
and free the tracked pin bookkeeping. This is backed by PageList tracked
pins and exposed through the libghostty-vt export layer and headers.
This PR fixes an issue where a zero-width combining mark could attach to
the wrong cell when the preceding character was written in the final
column and the cursor had a pending wrap.