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>
PageList nodes previously exposed Page directly, so introducing a
compressed representation would require every consumer to understand its
state and ownership.
Add resident and compressed node states behind a page access boundary.
Content access transparently recommits and restores retained mappings,
while metadata traversal stays compressed and lifecycle paths can
discard encoded contents without decoding. Compression borrows pool
memory for standard scratch and uses temporary aligned storage for
oversized pages.
Migrate terminal, rendering, search, formatting, and C API consumers to
the new boundary. Hot grow, scroll, and print paths reuse resolved
pages, with an explicit resident-only accessor where live cursor
pointers prove that the page cannot be compressed.
The compression entry point remains private with no production callers,
so normal terminal behavior and scrollback accounting are unchanged.
ReleaseFast terminal-stream comparisons across bulk output, scrolling,
redraw, and erase workloads remain within 2% of the parent revision.
Previously every file in the terminal package independently imported
build_options and ../lib/main.zig, then computed the same
lib_target constant. This was repetitive and meant each file needed
both imports just to get the target.
Introduce src/terminal/lib.zig which computes the target once and
re-exports the commonly used lib types (Enum, TaggedUnion, Struct,
String, checkGhosttyHEnum, structSizedFieldFits). All terminal
package files now import lib.zig and use lib.target instead of the
local lib_target constant, removing the per-file boilerplate.
The Discarding writer count field is u64, but several call sites
pass it where a usize is expected. On wasm32-freestanding, usize is
32-bit, so this caused compilation errors.
Use std.math.cast instead of a bare @intCast so that overflow is
handled gracefully, returning WriteFailed rather than triggering
safety-checked undefined behavior at runtime.
The Discarding writer count field is u64, but appendNTimes expects
usize which is u32 on 32-bit targets like arm-linux-androideabi.
Use std.math.cast instead of @intCast to safely handle the
conversion, returning WriteFailed on overflow rather than risking
undefined behavior.
The terminal.Stream next/nextSlice functions can now no longer fail.
All prior failure modes were fully isolated in the handler `vt`
callbacks. As such, vt callbacks are now required to not return an error
and handle their own errors somehow.
Allowing streams to be fallible before was an incorrect design. It
caused problematic scenarios like in `nextSlice` early terminating
processing due to handler errors. This should not be possible.
There is no safe way to bubble up vt errors through the stream because
if nextSlice is called and multiple errors are returned, we can't
coalesce them. We could modify that to return a partial result but its
just more work for stream that is unnecessary. The handler can do all of
this.
This work was discovered due to cleanups to prepare for more C APIs.
Less errors make C APIs easier to implement! And, it helps clean up our
Zig, too.
The VT formatter was treating cells without text as blank and emitting
them as plain spaces, losing any background color styling. This caused
TUIs like htop to lose their background colors when rehydrating terminal
state (e.g., after detach/reattach in zmx).
For styled formats (VT/HTML), cells with background colors or style_id
are now emitted with proper SGR sequences and a space character instead
of being accumulated as unstyled blanks.
Adds handling for bg_color_palette and bg_color_rgb content tags which
were previously unreachable.
Reference: https://ampcode.com/threads/T-019b7a35-c3f3-73fc-adfa-00bbe9dbda3c
The issue is in ghostty_src/src/terminal/formatter.zig#L1117-L1129:
- Cells without text are treated as "blank" (line 1117-1119) - this includes cells that only have background colors
- When blank cells are emitted, they're plain spaces (line 1129) - writer.splatByteAll(' ', blank_cells) outputs spaces without any SGR styling
- Background-only cells (bg_color_palette, bg_color_rgb) are marked unreachable (lines 1233-1235) because the code assumes hasText() already filtered them
This means when htop draws a row like:
`[green bg]CPU: 45%[red bg] [default]`
The trailing cells with red background but no text get accumulated as blanks and emitted as plain spaces - losing the background color.
Fixes#9426
Since we can't set the meta charset tag since we emit partial HTML, we
use codepoint entities like `{` for non-ASCII characters to
ensure proper rendering.
This adds HTML formatting capabilities to the formatter package. HTML is
emitted as inline styles. For palette indexes, direct RGB is emitted if
we have access to a palette; otherwise, we fall back to CSS variables.
This isn't exposed to end users yet, but will enable copy as html
features. This is available in libghostty.
Fixes#9395
**AI disclosure:** I used AI (Amp) to help me write tests, but the
implementation was done manually. I reviewed everything.
This replaces the logic of Screen.selectionString with calls to
ScreenFormatter.
This means that all our various selection-based features like copying to
clipboards now uses the new formatter. The formatter code is now
user-facing.
This forced us to pass all selectionString tests which revealed some
edge cases that were not handled correctly before in the formatter! The
formatter now handles:
- Plain text now emits `\n` instead of `\r\n`. VT emits `\r\n`
- Rectangular selections
- Various wide character edge cases
- Selection is now inclusive on the end, not exclusive
This adds the option `pin_map` or `point_map` (for pages) to formatter,
allowing callers to get a byte-by-byte mapping for where on the screen
each encoding maps to. This is used by search internals and hyperlinks.
I haven't hooked that all up yet. This diff was big enough I wanted this
as one.
Tests significantly cover the new feature.
Next up, we'll rip out `selectionString` and replace it with formatters!
This adds a new formatter that can be used with standard Zig `{f}`
formatting that emits any portion of the terminal screen as VT
sequences. In addition to simply styling, this can emit the entire
terminal/screen state such as cursor positions, active style, terminal
modes, etc.
To do this, I've extracted all formatting to a dedicated `formatter`
package within `terminal`. This handles all formatting types (currently
plaintext and VT formatting, but can imagine things like HTML in the
future). Presently, we have "formatting" split out across a variety of
places in Terminal, Screen, PageList, and Page. I didn't remove this
code yet but I intend to unify it all on formatter in the future.
This also doesn't expose this functionality in any user-facing way yet.
This PR just adds it to the ghostty-vt Zig module and unit tests it.
Ghostty app changes will come later.
**This also improves the readonly stream** to handle OSC color
operations for _setting_ but it doesn't emit any responses of course,
since its readonly.