Commit Graph

16856 Commits

Author SHA1 Message Date
Mitchell Hashimoto
d7bb4b8639 libghostty-vt: add C API for snapshotting functions
Expose terminal snapshot through the libghostty-vt C API and add
a new C example that runs in CI to verify this stuff works!

## Example

```c
size_t continuation_limit = 1024;
assert(ghostty_terminal_set(
    terminal,
    GHOSTTY_TERMINAL_OPT_CONTINUATION_MAX_BYTES,
    &continuation_limit) == GHOSTTY_SUCCESS);

uint8_t *bytes = NULL;
size_t len = 0;
assert(ghostty_snapshot_encode_alloc(
    terminal, NULL, &bytes, &len) == GHOSTTY_SUCCESS);

GhosttySnapshotDecoder decoder = NULL;
assert(ghostty_snapshot_decoder_new_buf(
    NULL, &decoder, bytes, len) == GHOSTTY_SUCCESS);

GhosttyTerminal restored = NULL;
assert(ghostty_snapshot_decoder_decode(
    decoder, &restored) == GHOSTTY_SUCCESS);

ghostty_snapshot_decoder_free(decoder);
ghostty_free(NULL, bytes, len);
```

Streaming decode:

```c
GhosttyReader reader = {
    .read = read_snapshot,
    .userdata = source,
};
GhosttySnapshotDecoder decoder = NULL;
assert(ghostty_snapshot_decoder_new(
    NULL, &decoder, reader) == GHOSTTY_SUCCESS);

GhosttyTerminal terminal = NULL;
assert(ghostty_snapshot_decoder_ready(
    decoder, &terminal) == GHOSTTY_SUCCESS);

GhosttyResult result;
while ((result = ghostty_snapshot_decoder_next(decoder)) ==
       GHOSTTY_SUCCESS) {
  size_t rows = 0;
  assert(ghostty_snapshot_decoder_get(
      decoder,
      GHOSTTY_SNAPSHOT_DECODER_DATA_PROGRESS_ROWS,
      &rows) == GHOSTTY_SUCCESS);
  render(terminal);
}
assert(result == GHOSTTY_NO_VALUE);
```
2026-08-03 13:09:04 -07:00
Mitchell Hashimoto
7d748097a0 core: free allocated writes in read-only mode (#13574)
Read-only filtering happens in Surface.queueIo after callers construct
the message. This early return leaked write_alloc payloads because the
IO thread never receives them and therefore does not perform its normal
cleanup.
2026-08-03 09:14:59 -07:00
Jon Parise
957ed21d5c core: free allocated writes in read-only mode
Read-only filtering happens in Surface.queueIo after callers construct
the message. This early return leaked write_alloc payloads because the
IO thread never receives them and therefore does not perform its normal
cleanup.
2026-08-03 11:42:11 -04:00
Mitchell Hashimoto
863fc9531a terminal/snapshot: more misc bugs (#13573)
Again nothing critical, just some polish around the edges.
2026-08-03 08:20:43 -07:00
Mitchell Hashimoto
5c65304a27 crash: do not use global state (#13567)
This removes use of global state from the crash reporting functionality
(everything in `src/crash`).

This particularly ensures that there are no races on the system
environment during the execution of the initialization thread that would
possibly cause crashes, particularly in any (albeit unsupported) 3rd
party integrations of libghostty-internal.

Ultimately, this pushes any coupling of I/O and environment to places
that would more correctly interface with global state, such as the
same-thread `global.init`, and the crash report CLI.

Note that similar de-coupling actions have been taken on XDG and home
directory functionality, pushing their coupling points up the stack in a
similar way.
2026-08-03 08:13:05 -07:00
Mitchell Hashimoto
7d9aaa2970 terminal/snapshot: clarify incremental history errors
Document why incremental history decoding exposes native page finalization 
errors and intentionally bypasses the one-shot ExistingHistory guard after READY.
2026-08-03 08:11:01 -07:00
Mitchell Hashimoto
cafe7d5da4 terminal/snapshot: clamp decoded saved cursors
SCREEN decoding restored saved cursor coordinates directly from the wire 
even when they exceeded the current terminal dimensions, unlike the live 
cursor restoration path.
2026-08-03 08:10:45 -07:00
Mitchell Hashimoto
f99896bf8c terminal/snapshot: preserve style reader errors
Lenient style decoding previously caught every error, so PAGE and SCREEN 
could treat truncation or an I/O failure as an invalid semantic style and 
continue from a corrupted stream position.

Add a nullable decoder that discards only invalid style contents while 
propagating reader failures. Update snapshot callers and cover both semantic 
fallback and structural failure behavior.
2026-08-03 08:10:31 -07:00
Mitchell Hashimoto
b5290e74c4 terminal/snapshot: release decoded hyperlink table refs
Decoded hyperlink table entries retained their insertion reference after 
the grid added its per-cell references. Overwriting all linked cells could 
therefore leave unused entries alive indefinitely.

Release each accepted wire table entry after grid decoding, including 
duplicate values that map to one native ID. Regression coverage verifies 
exact cell ownership and reaping after overwrite.
2026-08-03 08:08:51 -07:00
Mitchell Hashimoto
0b940ed589 terminal/snapshot: misc bugs (#13572)
Misc bugs related to snapshotting. Nothing critical. Each backed by a
failed test w/o the change that passes with it.
2026-08-03 07:00:04 -07:00
Mitchell Hashimoto
418b5d1805 terminal/snapshot: harden grapheme suffix decode
Grapheme suffix decoding accepted U+0000 even though zero is the native
empty-cell sentinel. It also appended codepoints one at a time and, when
page capacity failed after a prefix had been stored, left that truncated
prefix attached to the cell. Hostile snapshots could therefore introduce
invalid cluster data or render a partial cluster depending on allocator
capacity.

Ignore NUL alongside invalid scalar values. If any append runs out of
native capacity, remove the prefix already attached and consume the rest
of the declared suffix without applying it, making delivery atomic at the
cluster level. Cover NUL input and a failure after 128 accepted suffix
codepoints.
2026-08-03 06:45:09 -07:00
Mitchell Hashimoto
9a5279db68 terminal/snapshot: release decoded style table refs
PAGE decoding inserted every valid style table entry into the native
ref-counted set before decoding cells. That insertion contributed one
reference in addition to every cell reference, unlike organically built
pages where the initial add belongs to the first cell. An unused encoded
style therefore remained live with refcount one and was emitted again on
every re-encode; used styles were also permanently over-counted.

After the grid has installed all cell references, release the temporary
table-owned reference once per distinct live style. Unused styles become
dead immediately and used styles retain exactly their cell count. Cover
used reference counts, unordered sparse IDs, and canonical first
re-encoding of an unused entry.
2026-08-03 06:45:09 -07:00
Mitchell Hashimoto
e89ff37aa8 terminal/snapshot: encode screen pages safely
SCREEN encoding assumed every page from the active boundary onward was
resident. A debug assertion guarded that PageList policy invariant, but
release builds immediately used pageAssumeResident. If compression policy
ever allowed a SCREEN suffix page to remain compressed, the encoder would
read an inactive union field, causing undefined behavior and potentially
a crash or corrupt snapshot.

Use pagePreservingState for every SCREEN suffix page, as HISTORY already
does, and include allocation failure in EncodeError. Resident pages remain
a zero-allocation borrow while compressed pages decode into temporary
read-only storage without changing the source representation. Exercise the
path with an explicitly compressed active suffix page.
2026-08-03 06:45:09 -07:00
Mitchell Hashimoto
cbc9f360b1 terminal/snapshot: report invalid decoder states
Decoder.next treated calls before READY and calls after any prior decode
error as unreachable. Network or mux glue that retried after a truncated
history record, or invoked next before setup completed, could therefore
turn a recoverable protocol misuse into a process panic.

Add DecoderNotReady and DecoderFailed to NextError and return them for
the start and failed states. Keep finished calls idempotent, and cover
both an early call and a retry after FINISH truncation.
2026-08-03 06:45:09 -07:00
Mitchell Hashimoto
d148471838 terminal/snapshot: preserve mixed-width pending wrap
SCREEN decode clamped the cursor x coordinate to the physical page
width, but validated pending wrap against the terminal-wide column
count. A lazily reflowed page narrower than the current terminal could
therefore lose a valid pending-wrap state at its last physical column.
The next write would continue on the same row instead of wrapping.

Validate pending wrap against the cursor page width, matching the clamp
and the page-local cursor pin. Add a mixed-width decode regression that
places the cursor at the narrow page boundary.
2026-08-02 20:46:38 -07:00
Chris Marchesi
15c50c1db1 crash: do not use global state
This removes use of global state from the crash reporting functionality
(everything in src/crash).

This particularly ensures that there are no races on the system
environment during the execution of the initialization thread that would
possibly cause crashes, particularly in any (albeit unsupported) 3rd
party integrations of libghostty-internal.

Ultimately, this pushes any coupling of I/O and environment to places
that would more correctly interface with global state, such as the
same-thread global.init, and the crash report CLI.

Note that similar de-coupling actions have been taken on XDG and home
directory functionality, pushing their coupling points up the stack in a
similar way.
2026-08-02 20:24:30 -07:00
Mitchell Hashimoto
b4592eefd2 terminal/snapshot: add incremental decoder (#13569)
This adds a new `terminal.snapshot.Decoder` that allows for incremental
decoding of a snapshot stream. There are two methods: `ready` builds up
the entire terminal up to READY. Then `next` acts like a Zig iterator
and applies incremental history as it becomes available. In between
calls to `ready` and `next` the caller can do whatever.

The use case for this: with a 1MB ascii stream, the time to decode to
READY is ~40us on my machine, versus 1.5ms for the entire history. This
means that a terminal could be rendered and visible after 40us rather
than waiting for the full terminal. This isn't a large terminal, but
that READY time should be pretty standard since screens don't get that
big, but history is unbounded.
2026-08-02 20:07:40 -07:00
Mitchell Hashimoto
e37865bedc terminal/snapshot: add incremental decoder
This adds a new `terminal.snapshot.Decoder` that allows for incremental
decoding of a snapshot stream. There are two methods: `ready` builds up
the entire terminal up to READY. Then `next` acts like a Zig iterator
and applies incremental history as it becomes available. In between
calls to `ready` and `next` the caller can do whatever.
2026-08-02 19:54:36 -07:00
Mitchell Hashimoto
d351d9ce07 terminal/snapshot: more efficient binary form, optimize wire size + encode/decode speeds (#13566)
Reworks the terminal PAGE grid wire format and optimize both
encode/decode. Example improvements for 1MB of VT input w/ full
scrollback: ~30x smaller wire size, ~45x faster encoding and decoding.

> [!IMPORTANT]
>
> **Snapshot version 1 is still explicitly a work-in-progress format, so
this breaks wire compatibility**.

The original snapshot version I merged favored simplicity over
optimization. This was the format used a proof-of-concept in my own
projects, but I knew it wasn't what I wanted to ship. This PR looks at
the record formats and trades simplicity for performance, a fair trade
for a performance-sensitive binary format.

Overview of changes:

- **8-byte grid cells.** Cells are now one 64-bit word whose layout
deliberately coincides with the native cell. Previously, cells were 16
bytes each and in our 1MB corpus 97% of the data was `0`. Lol.
- **Blank trailing cells are not written.** Rows declare an encoded cell
count so trailing blank cells cost nothing.
- **Hardware CRC32C.** Added `src/crc32c.zig` that uses inline-asm on
aarch64/x86_64 to get hardware speeds for CRC32. Zig's stdlib is 0.56
GB/s, aarch64 hardware is 10 GB/s on my computer.
- **Variable-width cells.** Each row declares how many bytes transport
each cell word: 1, 2, 4, or 8 depending on the widest row cell.

## Format

Grid layout, per PAGE record:

```
   old                                new
   +--------------------------+      +--------------------------+
   | row 0                    |      | row 0                    |
   |   flags (1)              |      |   flags + width (1)      |
   |   cols * 16B cells with  |      |   encoded cell count (2) |
   |   inline suffixes        |      |   count * width cells    |
   +--------------------------+      +--------------------------+
   | ...                      |      | ...                      |
   +--------------------------+      +--------------------------+
   | row (rows - 1)           |      | row (rows - 1)           |
   +--------------------------+      +--------------------------+
                                     | grapheme suffix section  |
                                     +--------------------------+
```

Every row previously carried exactly `cols` cells; now it carries cells
only through its last non-default cell, and the cells past the count are
implicitly zero. The row flag byte gains the encoded cell width in its
previously reserved bits:

```
   bit 0 wrap                 bit 2-3 semantic prompt
   bit 1 wrap continuation    bit 4-5 encoded cell width (log2 bytes)
```

The cell itself, old fixed 16-byte header versus the new single word:

```
   old (16 bytes + inline suffixes)     new (one u64 word)
   +--------+---------+--------+        bit  0 +------------------+
   | kind 1 | width 1 | flags 1|               | content kind  2b |
   +--------+---------+--------+        bit  2 +------------------+
   | zero 1 | style id 2       |               | content      24b |
   +--------+------------------+        bit 26 +------------------+
   | hyperlink id 2            |               | style ID     16b |
   +---------------------------+        bit 42 +------------------+
   | value 4                   |               | width kind    2b |
   +---------------------------+        bit 44 +------------------+
   | grapheme count 4          |               | protected     1b |
   +---------------------------+        bit 45 +------------------+
   | grapheme cps 4 * count    |               | hyperlink     1b |
   +---------------------------+        bit 46 +------------------+
                                               | semantic      2b |
                                        bit 48 +------------------+
                                               | hyperlink ID 16b |
                                        bit 64 +------------------+
```

The word's bit layout intentionally matches the native cell (with the
wire hyperlink ID in the native padding), so full-width rows are a
straight copy of page memory. The row's encoded width then transports
each word truncated, and decode is the matching zero-extension:

```
   width | bytes | admitted cells
   ------+-------+------------------------------------------------
     0   |   1   | codepoint <= U+00FF, nothing else set
     1   |   2   | codepoint <= U+FFFF, nothing else set
     2   |   4   | any content kind/codepoint, style IDs 1-63,
         |       | narrow, no flags, no hyperlink
     3   |   8   | everything
```

Grapheme suffixes were inline after each cell, which forced per-cell
framing decisions; they are now one section after the rows, so a
grapheme-free page (the overwhelming case) pays 4 bytes total:

```
   old: ... | cell | cp cp | cell | ...      (inline, per cell)

   new: +----------------+----------------------------------+
        | entry count 4  | entries: row 2, col 2, count 2,  |
        |                |          count * codepoint 4     |
        +----------------+----------------------------------+
```

## Performance

Setup: `ghostty-bench +terminal-snapshot`, 80x24 terminal with unlimited
scrollback fed 1 MB of VT input.

Per-commit improvements:

| change                        | wire size | encode  | decode   |
|-------------------------------|-----------|---------|----------|
| baseline (v1 before this PR)  | 34.16 MB  | 92.8 ms | 119.8 ms |
| 8-byte cells + blank elision  | 7.66 MB   | 18.2 ms | 28.0 ms  |
| hardware CRC32C               | 7.66 MB   | 5.8 ms  | 15.5 ms  |
| gate page verification        | 7.66 MB   | 5.8 ms  | 12.2 ms  |
| staged PAGE payload decoding  | 7.66 MB   | 5.8 ms  | 8.1 ms   |
| variable-width cells          | 1.03 MB   | 2.0 ms  | 2.7 ms   |

Final result across various inputs:

| corpus | wire size | encode | decode |

|----------------------------|------------------------|----------------------|-----------------------|
| ascii lines 1-70 | 34.16 -> 1.03 MB (33x) | 92.8 -> 2.0 ms (46x) |
119.8 -> 2.7 ms (44x) |
| ascii full-width wrap | 16.01 -> 1.04 MB (15x) | 43.6 -> 1.3 ms (34x)
| 56.2 -> 1.8 ms (31x) |
| utf8 (wide/grapheme heavy) | 4.33 -> 1.89 MB (2.3x) | 12.4 -> 1.7 ms
(7x) | 19.0 -> 2.2 ms (9x) |

### Relationship with Compression

I expect that users of this will wrap everything in compression, so I
also benchmarked all my changes against a caller-owned zstd compressor
to ensure we're making the write tradeoffs. Less bytes means less time
in a compressor, even if a ton of 0s compresses really well.

My results: `zstd -1` over the `lines` snapshot drops from 12.7 ms to
0.8 ms, and the compressed artifact shrinks from 1.35 MB to 0.86 MB. So
the end state is a win-win.
2026-08-02 15:01:00 -07:00
Mitchell Hashimoto
9e3019f190 terminal/snapshot: variable-width grid cell encoding
Add a per-row encoded cell width to the PAGE grid format. Rows
previously always spent eight bytes per cell, but a plain text cell
carries only a codepoint: on line-shaped scrollback most encoded
bytes were predictable zeros that still had to pass through CRC32C,
BLAKE3, both codecs, and any transport compression the caller
applies.

Each row now declares one of four widths in previously reserved row
flag bits, chosen canonically as the smallest width admitted by the
bitwise OR of the row cell words: one or two bytes transport a bare
codepoint, four bytes transport the low word half (any content kind,
style IDs up to sixty-three, no wide or flag or hyperlink bits), and
eight bytes remain the full word. Every width is a truncation on
encode and a zero-extension on decode, so narrow rows encode and
decode as vectorizable integer loops, one and two byte rows need at
most surrogate replacement and skip cell normalization entirely, and
full-width rows keep the existing bulk copy. Decoders use the
declared width for framing and accept rows encoded wider than
necessary. Rows containing wide characters, hyperlinks, semantic
content, or large style IDs still use the full width, which leaves
CJK-heavy content unchanged.

Benchmark deltas at this commit (terminal-snapshot, M-series,
ReleaseFast, 1 MB corpora):

  ascii lines 1-70:  7.66 MB -> 1.03 MB (7.4x)
                     encode 5.8 -> 2.0 ms, decode 8.1 -> 2.7 ms
  ascii full-wrap:   8.04 MB -> 1.04 MB (7.7x)
                     encode 5.4 -> 1.3 ms, decode 7.2 -> 1.8 ms
  utf8:              unchanged (wide cells keep rows at full width)

For a caller compressing the stream, the lines snapshot end to end
with zstd -1: encode plus compress 18.5 -> 2.8 ms, decompress plus
decode 15.8 -> 3.6 ms, and the compressed size itself drops from
1.35 MB to 0.86 MB because the packed stream is denser for the
entropy coder.
2026-08-02 14:32:48 -07:00
Mitchell Hashimoto
3e5d128353 terminal/snapshot: stage PAGE payloads while decoding
PAGE payloads were decoded through a stack of stream adapters:
a CRC32C-hashing reader over a length-limited reader over the
BLAKE3-hashing snapshot reader. Every row paid several adapter
crossings and both hashes were fed row-sized chunks, which kept
BLAKE3 out of its efficient many-block path and made adapter
overhead about a quarter of decode time.

Decode now reads the remaining payload into a scratch buffer with
one bulk read, so each hash sees the payload as a single update, and
then parses the tables and grid from a flat in-memory reader. Row
headers are also read as one three-byte read instead of two calls.
Staging is capped at 8 MiB, far above any standard-capacity page
payload, so a hostile declared length cannot force a large
allocation; larger payloads fall back to the streaming path. CRC
validation and exact-exhaustion checks are unchanged, with the
staged reader checked for leftover bytes to preserve
PayloadNotExhausted semantics.

Benchmark deltas at this commit (terminal-snapshot, 1 MB corpora):

  ascii lines 1-70:  decode 12.2 -> 8.1 ms (encode unchanged)
  ascii full-wrap:   decode 11.1 -> 7.2 ms
  utf8:              decode  3.1 -> 2.1 ms

Relative to the previous wire format and codecs, the series is a
16.0x encode and 14.8x decode improvement on line-shaped scrollback
at 4.5x smaller wire size.
2026-08-02 14:32:27 -07:00
Mitchell Hashimoto
9f66563479 terminal/snapshot: gate page verification on slow runtime safety
PAGE decoding verified the complete native integrity of every decoded
page unconditionally, building per-cell reference maps that accounted
for roughly a fifth of decode time. The decoder normalizes every
semantic value while decoding, so a completed decode upholds page
invariants by construction and the verification only defends against
decoder bugs. Follow the native page policy instead: assertIntegrity
and friends run full verification only when slow runtime safety is
enabled, which keeps the check in debug and test builds where those
bugs are caught.

Benchmark deltas at this commit (terminal-snapshot, 1 MB corpora):

  ascii lines 1-70:  decode 15.5 -> 12.2 ms (encode unchanged)
2026-08-02 14:31:31 -07:00
Mitchell Hashimoto
9cc061c28c terminal/snapshot: hardware-accelerated CRC32C 2026-08-02 14:31:31 -07:00
Mitchell Hashimoto
ed0f54fb8c terminal/snapshot: 8-byte grid cells with blank elision
Rework the PAGE grid encoding for codec speed and size. This is a
breaking change to the work-in-progress version 1 wire format.

Cells were previously a fixed 16-byte header plus inline grapheme
suffixes: one byte each for content kind, width, and flags, a
reserved byte, 16-bit style and hyperlink IDs, a 32-bit value, and an
always-present 32-bit suffix count that was almost always zero. Cells
are now one 64-bit little-endian word with a documented bit registry
that carries the hyperlink ID in its high bits. The layout
deliberately coincides with the native cell so clean rows encode as a
straight copy of page memory and decode as one bulk read plus an
in-place normalization pass; a comptime check falls back to a
portable field-by-field codec if the native layout ever diverges.

Each row header also gains an encoded cell count so trailing default
cells are elided instead of spending 16 bytes apiece encoding
nothing: on typical shell output most of every row is blank, and
measurement showed 97% of encoded snapshot bytes were zero. Grapheme
suffixes move out of the cell stream into a per-grid section of
(row, column, codepoints) entries, which keeps row decoding
fixed-stride and bulk-copyable.

Decode ID remapping switches from hash maps to direct-indexed tables
sized by the 16-bit encoded ID space, removing per-styled-cell hash
lookups.

Benchmark deltas at this commit (terminal-snapshot, M-series,
ReleaseFast, 1 MB corpora):

  ascii lines 1-70:  34.16 MB -> 7.66 MB (4.5x)
                     encode 92.8 -> 18.2 ms, decode 119.8 -> 28.0 ms
  ascii full-wrap:   16.01 MB -> 8.04 MB (2.0x)
                     encode 43.6 -> 18.4 ms, decode  56.2 -> 25.6 ms
  utf8:               4.33 MB -> 1.90 MB (2.3x)
                     encode 12.4 ->  4.9 ms, decode  19.0 ->  9.6 ms
2026-08-02 14:31:31 -07:00
Mitchell Hashimoto
f424b20589 macOS: install update with same code path (#13562)
Previously if you select "**Install and Relaunch**" in the update pill,
there's still a confirmation alert about killing active process, but it
will just relaunch regardlessly(which is the intended behaviour) when
you do it in the command palette.

Using UpdateState to check so Ghostty will relaunch immediately when
user chooses "Install and Relaunch".

> For the auto update case, this will be handled a bit differently in
the future. If an update is already installed and waiting for relaunch
(that the user is not aware of), quitting Ghostty will still remind them
if there's active processes.
2026-08-02 14:28:37 -07:00
Mitchell Hashimoto
39799a61ce terminal/osc: decode OSC 52 base64 with the SIMD decoder (#13565)
use simd decoder for OSC 52 clipboard instead of the scalar impl.
2026-08-02 14:28:08 -07:00
Uzair Aftab
c992658b29 terminal/osc: decode OSC 52 base64 with the SIMD decoder
OSC 52 clipboard writes decoded their base64 payload with the scalar
std implementation while Kitty graphics payloads already used the SIMD
decoder in src/simd.

Move clipboard path to use the same SIMD decoder. The encode side
of the read reply stays scalar since the codebase has no SIMD
encoder. 3.3x faster on a 4KB-payload decode micro-benchmark.
2026-08-02 23:16:34 +02:00
ghostty-vouch[bot]
a4f9b9cea2 Update VOUCHED list (#13564)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/13563#issuecomment-5160200376)
from @trag1c.

Denounce: @guysoft

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-02 20:29:16 +00:00
Mitchell Hashimoto
0b5e12453b benchmark: add terminal-snapshot benchmark
Measures the terminal binary snapshot codecs in both directions
against the same terminal state. Setup feeds a pre-generated VT
stream (for example from ghostty-gen ascii) to a terminal outside
the timed region. 

Baseline measurements at this commit (M-series, ReleaseFast, 80x24,
unlimited scrollback, 1 MB corpora, per-loop time with setup
subtracted):

  ascii lines 1-70:  34.16 MB  encode  92.8 ms  decode 119.8 ms
  ascii full-wrap:   16.01 MB  encode  43.6 ms  decode  56.2 ms
  utf8:               4.33 MB  encode  12.4 ms  decode  19.0 ms
2026-08-02 12:36:18 -07:00
Lukas
3a606c6c41 macOS: install update with same code path 2026-08-02 21:21:51 +02:00
Mitchell Hashimoto
7031c892b2 synthetic: line length options for the ascii generator
The ascii generator emits an unbroken stream of printable bytes, which
exercises terminal wrapping but produces only full-width rows. Add
line-min and line-max options that emit CR LF-terminated lines with a
uniformly distributed printable length so generated corpora can also
model shell-like output where most rows end well before the last
column. The default behavior is unchanged.
2026-08-02 09:32:54 -07:00
Mitchell Hashimoto
bab076c1a2 build: lower iOS deployment target version (#13539)
Reopening #13535.
2026-08-02 07:03:52 -07:00
Mitchell Hashimoto
915496c221 libghostty(formatter): fix superfluous newline in html formatting (#13543)
In the html formatter every page is formatted in a div. When the div
closes it causes a newline in the html rendering. In order to fix this a
newline is now removed whenever the div is closed (if there are any
newlines waiting to be rendered - as far as I could see in my testing
there was always one).

I thought of trying to add a test but could not think of a way to do so
without adding a massive blob of html into the file.

Before (orange was added by me to show where the div ends):
<img width="1278" height="586" alt="image"
src="https://github.com/user-attachments/assets/473ba28d-bec0-481f-9a89-a6a72c9a3657"
/>

After:
<img width="959" height="439" alt="image"
src="https://github.com/user-attachments/assets/27bae0d3-cc82-4dc8-aa72-c4a8b0f7d424"
/>

No AI was used in this pr.
2026-08-02 07:03:40 -07:00
Mitchell Hashimoto
f6830420ce termio: fix doc comment grammar and PTY casing in message.zig (#13549)
### What
Comment-only cleanup in `src/termio/message.zig`:

- Fixes a subject-verb agreement error in the `Message` union's doc
  comment: "the number of messages ... are also very few" -> "is also
  very small"
- Capitalizes "pty" to "PTY" in several doc comments 

### Why
Caught while reading through `message.zig`. No functional changes,
doc comments only.
2026-08-02 07:03:19 -07:00
Mitchell Hashimoto
322636bfb0 config: update scrollbar doc per current implementation (#13553)
We should also update the doc after #9865, which is discussed in
https://github.com/ghostty-org/ghostty/discussions/9610
2026-08-02 07:02:19 -07:00
Mitchell Hashimoto
e85bf9fb2d terminal/snapshot: pty continuation record (#13556)
Builds on #13544

This adds a new CONTINUATION record type that is sent before READY.
CONTINUATION contains the bytes (if any) that will bring a ground-state
VT state machine up to the same state.

This allows snapshotting a terminal instance that is, for example,
blocked waiting for a caller to complet an in-flight Kitty graphics
protocol send. In practice, I think this will be rare. But in theory, it
avoids a DoS-type attack.

The continuation state must be the MINIMAL set of bytes that will move
the virtual terminal state from a ground to non-ground state. The reason
it must be minimal is because any extra bytes can duplicate work into
the terminal that might already exist.
2026-08-02 07:01:56 -07:00
Mitchell Hashimoto
70e41e96d3 terminal/snapshot: pty continuation
Builds on #13544

This adds a new CONTINUATION record type that is sent before READY.
CONTINUATION contains the bytes (if any) that will bring a ground-state
VT state machine up to the same state.

This allows snapshotting a terminal instance that is, for example,
blocked waiting for a caller to complet an in-flight Kitty graphics
protocol send. In practice, I think this will be rare. But in theory, it
avoids a DoS-type attack.

The continuation state must be the MINIMAL set of bytes that will move
the virtual terminal state from a ground to non-ground state. The reason
it must be minimal is because any extra bytes can duplicate work into the 
terminal that might already exist.
2026-08-02 06:41:42 -07:00
Lukas
a7cfa6fc23 config: update scrollbar doc per current implementation 2026-08-02 14:45:55 +02:00
ghostty-vouch[bot]
6837d7027f Update VOUCHED list (#13550)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/13549#issuecomment-5157211534)
from @trag1c.

Vouch: @12ya

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-02 10:32:48 +00:00
Iliya Lyan
f5911d6964 comment: fix grammar and acronym casing in message.zig doc comments
- "the number of messages we send to the IO thread are also very few"
  had a subject-verb agreement issue; reworded to "is also very small"
- Capitalized "pty" -> "PTY"
2026-08-02 19:27:31 +09:00
Roni Jacobson
75302feda4 Add test for superfluous newline in html formatting 2026-08-02 02:21:12 +03:00
Mitchell Hashimoto
46edeee407 macOS: fix update error pill is not showing properly (#13540)
`acknowledgement` will call `dismissUpdateInstallation` so the error
state will never happen.
2026-08-01 14:28:06 -07:00
Mitchell Hashimoto
f5880782fe terminal: add stream continuation tracking for replay (#13544)
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. :)

**LLM usage:** I wrote the continuation tracker myself, used a mix of
5.6+Fable to review it for me, applied their feedback directly. Only
place with predominantly AI code are tests, which I reviewed. Commit and
PR message written myself.

## 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

No real impact.

Via `ghostty-bench +terminal-stream`.

Corpus | Main | PR w/ Tracking Off | PR w/ Tracking On
-- | -- | -- | --
Plain ASCII (256 MiB) | 175.4 ms | 175.8 ms | 175.5 ms
UTF-8 (32 MiB) | 268.4 ms | 270.0 ms | 269.9 ms
5% invalid UTF-8 (32 MiB) | 316.4 ms | 317.8 ms | 320.2 ms
CSI-heavy (32 MiB) | 145.0 ms | 146.3 ms | 145.9 ms
OSC (32 MiB) | 1621.0 ms | 1627.5 ms | 1638.3 ms
Kitty APC (128 MiB) | 95.5 ms | 96.9 ms | 96.3 ms
Mixed traffic (32 MiB) | 172.4 ms | 172.0 ms | 172.3 ms
Giant APC (128 MiB) | 38.3 ms | 38.3 ms | 40.8 ms
2026-08-01 14:16:20 -07:00
Mitchell Hashimoto
68beeeb3f6 terminal: add stream continuation tracking for replay
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
2026-08-01 13:39:21 -07:00
Roni Jacobson
f024d21fc4 Fix superfluous newline in html formatting
Every page is formatted in a div, and when the div closes it creates a newline in the html rendering.
In order to fix this a newline is now removed whenever the div is closed (if there are any newlines waiting to be rendered).
2026-08-01 21:36:58 +03:00
Jeffrey C. Ollie
2ee42adc76 datastruct/circ_buf: fix metadata after shrinking (#13515)
## Summary

- normalize circular-buffer metadata after every resize
- retain the oldest values when shrinking below the current length
- cover partial shrink, exact-length shrink, and empty-to-zero
boundaries

## Root cause

`resize` rotated live values to index zero before reallocating, but only
repaired `head` and `full` when capacity grew. Shrinking a partially
filled buffer could therefore leave `head` beyond the new allocation and
report a length greater than capacity. A later append could index
outside the resized storage.

## Validation

- `zig fmt --check src/datastruct/circ_buf.zig`
- `zig test src/circ_buf_test.zig --test-filter 'CircBuf resize'` using
a temporary import harness: 8 tests passed
2026-08-01 12:19:05 -05:00
trag1c
b2d4462590 gtk: fix capitalization of banner title (#10642) 2026-08-01 18:58:27 +02:00
Jeffrey C. Ollie
e9e7864b43 remove fuzze entries in *.po files 2026-08-01 11:28:10 -05:00
ghostty-vouch[bot]
74ad15c104 Update VOUCHED list (#13542)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/13527#issuecomment-5152299257)
from @jcollie.

Vouch: @gadgetman6

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-01 16:23:13 +00:00
Jeffrey C. Ollie
7a512c3125 gtk: fix capitalization of banner title 2026-08-01 11:03:10 -05:00