Commit Graph

16457 Commits

Author SHA1 Message Date
qappell
751a60df61 macos: route IME preedit commits through key events 2026-07-07 13:00:39 -05:00
Mitchell Hashimoto
2da015cd6a terminal: various VT processing optimizations (~1.5x to ~6x throughput increase) (#13220)
This is a series of five commits that optimizes VT processing throughput
in various ways. Each commit is isolated, individually benchmarked, and
carries a detailed commit message so please read each for details about
each change.

After #13209 made IO fully parser-bound, these gains should translate
directly into end-to-end IO throughput (until some other stage becomes
the new bottleneck). Plain ASCII processing went from ~128 MB/s to ~725
MB/s. `time cat ascii_150MB.txt` went from 1.5s before 13209 to 1.2s on
main to 566ms on this branch.

## The changes

1. **batch printed codepoint runs into direct row fills**. Profiling
showed ~85% of plain-text time inside `Terminal.print`, re-answering the
same questions (margins, modes, width, charset, style) for every single
character. A new `print_slice` stream action delivers runs of decoded
codepoints to `Terminal.printSlice`, which hoists the invariants and
fills rows with a masked-compare + branch-free store loop, falling back
to `print()` for anything complex. **Result: 2.2x–5.7x on ascii plus
unicode text.**
2. **dispatch CSI finals directly from stream fast paths**. Every byte
through `Parser.next` copies a ~240 byte `[3]?Action` and a typical CSI
copied it twice. New `csi_entry/final` fast paths dispatch directly
without the action array. **Result: +17-18% on CSI streams.**
3. **bulk-parse CSI parameter bytes at the slice level**. Parameter
digits/separators are consumed in a tight slice loop with parser state
in locals instead of re-entering the per-byte path. **Result: +29-41% on
escape-heavy streams.**
4. **skip style map update when SGR leaves style unchanged**. Skip the
release/hash/probe/use churn when an SGR attribute is a no-op. **Result:
+4-7% on TUI-refresh patterns, -2-3% on adversarial random-color
streams** (tradeoff detailed in the commit message). This one is more
questionable, but willing to measure on real workloads.

## Benchmarks

Measured with `ghostty-bench +terminal-stream` (full terminal handler,
100 MB deterministic synthetic corpora, 120x80 terminal, M4 Max, macOS
26, ReleaseFast, hyperfine means of 10 runs, ~15 ms process startup
included in all numbers). These are parser-stage numbers, not end-to-end
app numbers.

| stream | before | after | throughput | change |

|----------------------------|--------|--------|------------------|--------|
| ascii (no newlines) | 784 ms | 138 ms | 128 → 725 MB/s | 5.7x |
| ascii lines | 833 ms | 198 ms | 120 → 505 MB/s | 4.2x |
| unicode mixed-script | 779 ms | 320 ms | 128 → 313 MB/s | 2.4x |
| CJK (all wide) | 424 ms | 126 ms | 236 → 794 MB/s | 3.4x |
| unicode, mode 2027 on | 807 ms | 367 ms | 124 → 273 MB/s | 2.2x |
| CJK, mode 2027 on | 495 ms | 198 ms | 202 → 505 MB/s | 2.5x |
| csi mix (SGR/CUP/EL/modes) | 648 ms | 414 ms | 154 → 242 MB/s | 1.6x |
| sgr fire (doom-fire-like) | 495 ms | 303 ms | 202 → 330 MB/s | 1.6x |
| TUI redraw (repeat styles) | 642 ms | 291 ms | 156 → 344 MB/s | 2.2x |
| osc | 8.26 s | 8.20 s | (untouched path) | ~1.0x |

**End-to-end note:** #13209 measured the parse thread pegged while the
gather thread used ~33% of a core, so parser gains of this size may make
gather (or the renderer lock) the new bottleneck for plain text before
the full 5.7x shows up end to end. I'll take a look at that soon...

## LLM Notes

These findings were almost all found by Fable 5. I went through each
change and simplified quite a lot, read every single line, re-ran
verifications by hand. Fable in particular isn't good at writing elegant
Zig code, so there's a lot of style stuff. Ultimately though, I
understand all of this and feel comfortable with the changes.
2026-07-06 09:07:57 -07:00
Mitchell Hashimoto
cee35cabf6 terminal: skip style map update when SGR leaves style unchanged
Profiling the csi benchmark showed ~20% of time in the style
ref-counted set (hash, probe, release/use churn) driven by
manualStyleUpdate, which runs after every SGR attribute even when
the attribute didn't actually change the cursor style. Real
programs re-assert the same style constantly (per span, per line,
or on every refresh of a mostly static screen), so a large share of
these updates are no-ops.

Screen.setAttribute already snapshots the old style to restore it
on failure, so this compares the style after applying the attribute
and returns early when it's unchanged: the current style ID is
already correct and no release/lookup/use is needed.

The tradeoff is one extra Style.eql on every style-changing
attribute. Measured with ghostty-bench terminal-stream (full
terminal handler, 100 MB deterministic corpora, 120x80, M4 Max,
ReleaseFast, hyperfine means of 10 runs) across corpora with
different repeated style rates (the csi/sgr corpora draw random
colors from a palette so nearly every SGR changes the style, which
is the worst case for this change; the redraw corpora model TUI
refreshes that re-assert the current style for 70% / 95% of SGRs):

| stream              | before | after  | change |
|---------------------|--------|--------|--------|
| redraw (95% same)   | 277 ms | 260 ms | +7%    |
| redraw (70% same)   | 302 ms | 291 ms | +4%    |
| csi (~0% same)      | 407 ms | 414 ms | -2%    |
| sgr (~0% same)      | 295 ms | 303 ms | -3%    |

Real-world SGR traffic is far closer to the redraw corpora than to
the adversarial random-color ones, so this trades a small worst
case regression for a solid win on the common pattern.
2026-07-06 08:51:52 -07:00
Mitchell Hashimoto
253e4f9c3c terminal: bulk-parse CSI parameter bytes at the slice level
After the CSI dispatch fast paths, profiling showed the remaining
escape-sequence cost was the per-byte plumbing itself: for every
parameter byte of a sequence like "ESC [ 38;2;10;20;30 m" the
stream re-entered nextNonUtf8, re-checked the parser state, and
re-dispatched through the fast-path switch, paying call and state
check overhead per digit.

consumeUntilGround now hands whole input slices to a new
consumeCsiParams loop whenever the parser is in the csi_param
state. It consumes runs of digits and separators with the parser
accumulator state held in locals, dispatches directly when it
reaches the final byte, and returns to the general path on the
first byte it doesn't understand (C0 controls, intermediates,
etc.), guaranteeing byte-for-byte identical semantics with the
per-byte fast path it hoists. Like the dispatch fast paths, this is
disabled at comptime for handlers that declare vtRaw so the
inspector continues to observe every action.

Throughput measured with ghostty-bench terminal-stream (full
terminal handler, 100 MB deterministic corpora, 120x80, M4 Max,
ReleaseFast, hyperfine means of 10 runs):

| stream | before | after  | change |
|--------|--------|--------|--------|
| csi    | 525 ms | 407 ms | +29%   |
| sgr    | 414 ms | 294 ms | +41%   |

Combined with the previous commit, CSI-heavy streams are 1.5-1.7x
faster end to end than before this series.
2026-07-06 08:51:51 -07:00
Mitchell Hashimoto
1a88f3622b terminal: dispatch CSI finals directly from stream fast paths
Profiling escape-heavy streams showed the dominant remaining cost
was Parser.next: every byte routed through it copies a [3]?Action
return value that is ~240 bytes (the action union is sized by
osc.Command). A typical CSI sequence paid this twice: once for the
first byte after "ESC [" (csi_entry has no fast path, so even the
first parameter digit went through the table machine) and once for
the final byte that dispatches the sequence.

This extends the existing stream fast paths to cover both. The
csi_param fast path now handles final bytes (0x40-0x7E) by
finalizing parameters and dispatching the CSI directly via a new
csiDispatchFinal, which replicates the parser's csi_dispatch action
(MAX_PARAMS overflow drop, trailing parameter finalization, and the
colon-separator validation for non-'m' finals) without constructing
the action array. A new csi_entry fast path handles the byte right
after "ESC [": first parameter digit, empty first parameter,
private markers (0x3C-0x3F), and parameterless finals. Everything
else (C0 controls, intermediates, the csi_entry colon edge case)
still defers to the state machine.

Because these paths dispatch without going through Parser.next,
they would bypass a handler's vtRaw hook, so they are disabled at
comptime for handlers that declare one (the inspector). Those
handlers keep the exact previous behavior.

Throughput measured with ghostty-bench terminal-stream (full
terminal handler, 100 MB deterministic corpora, 120x80, M4 Max,
ReleaseFast, hyperfine means of 10 runs). The csi corpus is a
realistic mix of SGR, cursor movement, erases, and mode changes
with short text runs; sgr is a doom-fire-like stream of truecolor
SGRs and cell pairs:

| stream | before | after  | change |
|--------|--------|--------|--------|
| csi    | 618 ms | 525 ms | +18%   |
| sgr    | 486 ms | 414 ms | +17%   |
2026-07-06 08:51:51 -07:00
Mitchell Hashimoto
47e26df60f terminal: batch printed codepoint runs into direct row fills
#13209

After #13209 the IO pipeline delivers the parse thread's full
measured capacity, so IO throughput is now bound by VT processing.
Profiling `terminal-stream` on plain text showed ~85% of wall time
inside Terminal.print: every printable codepoint paid the full
per-character cost (right margin computation, grapheme clustering
checks, width lookup, wrap/insert mode checks, charset mapping,
per-cell style bookkeeping, dirty marking, cursor advance) even
though for typical bulk output every one of those answers is the
same for thousands of consecutive characters.

This adds a new print_slice stream action carrying a run of
printable codepoints, emitted whenever the SIMD ground-state path
decodes multiple codepoints at once, plus Terminal.printSlice which
processes such runs in batch. Since action dispatch is comptime,
delivering a slice through the existing vt handler interface has
the same codegen as a dedicated entry point; handlers that don't
care about batching can simply loop and treat each codepoint as a
print action.

printSlice hoists all run-invariant checks (status display, insert
and wraparound modes, charset state, hyperlink state) out of the
loop and then fills cells row by row. A single masked u64 compare
classifies each destination cell as "simple" (plain codepoint cell,
narrow, no hyperlink, style already matching the cursor); runs of
simple cells are written with a branch-free store loop, style-only
mismatches are handled inline with the same ref-counting printCell
does, and anything needing real cleanup (wide spacers, grapheme
data, hyperlinks) exits the fast path with the cursor positioned on
the offending cell so print() handles that one codepoint with full
generality. Dirty marking, previous_char, and cursor advancement
happen once per row instead of once per character.

The fast path handles both narrow and wide codepoints (CJK/emoji are
written as wide+spacer_tail pair fills, including spacer-head
handling at the right edge) and stays exact under grapheme
clustering (mode 2027): a codepoint only joins a run if it is width
1 or 2 and is a grapheme break from the previously written
codepoint, so print() would never have attached it to the previous
cell. The first codepoint of a batch defers to print() whenever the
previous cell could carry cluster state we can't cheaply reason
about (including a pending wrap, where print attaches to the
pending cell instead of wrapping).

Correctness is verified by a new differential fuzz test that runs
the same operations through per-codepoint print and randomly
chunked printSlice, comparing full screen dumps, cursor state, and
page integrity (style refcounts, grapheme maps) after every
operation, across wraps, margins, mode toggles, hyperlinks,
charsets, and wide/combining/ZWJ/RI/jamo codepoints.

Throughput measured with ghostty-bench terminal-stream (full
terminal handler, 100 MB deterministic corpora, 120x80, M4 Max,
ReleaseFast, hyperfine means of 10 runs; ~15ms process startup
included in all numbers):

| stream                    | before | after  | change |
|---------------------------|--------|--------|--------|
| ascii (no newlines)       | 784 ms | 138 ms | 5.7x   |
| ascii lines               | 833 ms | 198 ms | 4.2x   |
| unicode mixed-script      | 779 ms | 320 ms | 2.4x   |
| CJK (all wide)            | 424 ms | 126 ms | 3.4x   |
| unicode, mode 2027 on     | 807 ms | 367 ms | 2.2x   |
| CJK, mode 2027 on         | 495 ms | 198 ms | 2.5x   |
2026-07-06 08:51:51 -07:00
Mitchell Hashimoto
258de36d15 benchmark: terminal-stream uses the full terminal handler
The terminal-stream benchmark previously used a simplified handler
that handles print actions and drops everything else. That was
originally intended to isolate parse and print throughput, but it
understates the cost of escape-heavy streams: no terminal state is
updated for CSI/OSC/ESC sequences, and because actions are
dispatched at comptime, the unhandled action arms are eliminated
entirely, so the benchmark measures dispatch code that doesn't
exist in the real app.

This switches the benchmark to the full readonly terminal stream
handler (terminal.TerminalStream). Every escape sequence now
updates real terminal state (styles, cursor movement, erases,
modes, etc.), closely mirroring the work the real IO thread does
per byte. This is the handler used to measure the VT throughput
changes in the following commits.

Parser-in-isolation measurement remains covered by the separate
terminal-parser and osc-parser benchmarks, and print throughput is
identical under both handlers since printing flows into the same
Terminal call either way.
2026-07-06 06:44:04 -07:00
Mitchell Hashimoto
0535770e35 termio: pipeline pty reads to overlap parsing with draining (25 - 55% IO throughput increase) (#13209)
This replaces the single-threaded pty read loop on posix systems with a
two-stage pipeline: a new `io-gather` thread drains the pty into a small
ring of large buffers while the existing `io-reader` thread parses the
previous batch concurrently.

After this, our IO throughput is now within noise (1 to 3%) of our VT
parsing and processing throughput. This means that any VT performance
improvements should raise IO throughput. We're completely bound by it
now.

> [!IMPORTANT]
> 
> **Against all odds, we've found a massive (25% to 55%) performance
improvement in Ghostty IO.** This is a subsystem we were happy to get 2%
improvement gains in recent years. And the change is relatively simple
and understandable, too.

The motivating discovery was actually found by Fable, but the resulting
code was hand-written and hand-verified (in addition to model-verified
as an extra check): on macOS the kernel tty output queue caps every read
on the pty master at about 1 KiB regardless of the read buffer size.
Instrumenting a pty with a 64 KB buffer while streaming a 6.49 MB file
produced 6,337 reads where every read was exactly 1024 bytes.

This immediately made me realize two things about the old loop that
we've had since like 2023 which called processOutput once per read: all
per-call overhead (terminal lock, render wakeup, cursor timestamp) was
paid per kilobyte of bulk output, and the child could never run more
than 1 KiB ahead of us, so while we parsed the child (e.g. `cat`) sat
blocked on a full kernel queue instead of producing.

In 2023, I justified this architecture by saying "reads are generally
small" but I didn't understand then that reads are generally small
because the kernel makes them small even if there is a lot of data.

To preserve latency for the more typical
small-reads-that-are-actually-small, sub-1 KB payloads deliver on the
first EAGAIN with no added latency.

## Benchmarks

End-to-end throughput was measured by timing `cat file > /dev/ttysN`
against a fresh app instance (M4 Max, macOS 26, ReleaseFast, medians of
repeated interleaved A/B runs):

| stream                  | before       | after         | change  |
|-------------------------|--------------|---------------|---------|
| ascii.txt (6.5 MB)      | 91-92 MB/s   | 114-123 MB/s  | +25-30% |
| unicode.txt (8 MB)      | 116-117 MB/s | 180-183 MB/s  | +55%    |
| DOOM-Fire-Zig           | 530 fps      | 770 fps       | +45%    |

The pipeline now delivers the parse stage's full measured capacity (the
parse thread is pegged while gather spends ~33% of a core, so any IO
throughput improvements are now fully parser-bound).

**Linux note:** This needs to be verified on Linux. I think broadly
architecture is better and should never be worse. But its possible some
of the magic constants need to be tuned differently. Would love more
testing there.

## Some Background on LLM Usage

As noted above, the original promising path was found by Fable 5. I
decided to throw some money at analyzing our IO throughput, and went
into it expecting minor improvements at the cost of unmaintainable
special case optimizations (typical LLM results when given such tasks on
an already-optimized path).

I spun off a Fable 5 session (API pricing) prior to some holiday weekend
(American, July 4th) festivities. When I came back late that night, it
pointed this path out with some pretty questionable code/results.

The frustrating thing is that _I swear I tried this years ago_ and it
didn't deliver results. Unfortunately, it was long enough ago (probably
2023) that I can't remember nor do I have any evidence. But, my brain
had already clocked this possibility and blocked it out as "already
tried and failed." The code in question that this PR ultimately touched
has been basically unchanged for 2+ years.

As a second point, this is a **great example of what I love about
LLMs**. I was not in a performance-hunting mood and I have other tasks I
need to get to while at the computer, so improving Ghostty performance
wasn't my current mode and I would not have worked on it anytime soon
while at the computer. But, I try to keep an agent running all the time,
and before my family came over for holiday afternoon, I decided today I
would try budgeting $100 to Fable to focus on Ghostty's IO performance.
Why not?

I came back, saw some questionable but interesting results, and decided
it was worth some human hours to validate, understand, and work on this
myself. It was worth it. 😄

Anyways, the total API cost of this if you're curious: **$88.28**.

Remember, I **hand-wrote the code** so thats basically what it cost me
to discover this path. Fable did produce its own solution (in about 3x
the LoC change of this diff with non-idiomatic Zig styles, overly
defensive guards, and simultaneously poor error handling). So, I guess I
did pay for a solution. A bad one. Haha. But the problem it found was
real and good.
2026-07-06 06:31:31 -07:00
Mitchell Hashimoto
2f0e6659dd termio: pipeline pty reads to overlap parsing with draining
This replaces the single-threaded pty read loop on posix systems with
a two-stage pipeline: a new `io-gather` thread drains the pty into a
small ring of large buffers while the existing `io-reader` thread
parses the previous batch concurrently.

The motivating discovery was actually found by Fable, but the
resulting code was hand-written and hand-verified (in addition to
model-verified as an extra check): on macOS the kernel tty output queue
caps every read on the pty master at about 1 KiB regardless of the
read buffer size. Instrumenting a pty with a 64 KB buffer while
streaming a 6.49 MB file produced 6,337 reads where every read was exactly 
1024 bytes. 

This immediately made me realize two things about the old loop that we've 
had since like 2023 which called processOutput once per read: all per-call 
overhead (terminal lock, render wakeup, cursor timestamp) was paid per
kilobyte of bulk output, and the child could never run more than 1 KiB
ahead of us, so while we parsed the child (e.g. `cat`) sat blocked on a full
kernel queue instead of producing. 

In 2023, I justified this architecture by saying "reads are generally small"
but I didn't understand then that reads are generally small because the
kernel makes them small even if there is a lot of data.

To preserve latency for the more typical
small-reads-that-are-actually-small, sub-1 KB payloads deliver on the first
EAGAIN with no added latency.

End-to-end throughput was measured by timing `cat file > /dev/ttysN`
against a fresh app instance (M4 Max, macOS 26, ReleaseFast, medians
of repeated interleaved A/B runs):

| stream                  | before       | after         | change  |
|-------------------------|--------------|---------------|---------|
| ascii.txt (6.5 MB)      | 91-92 MB/s   | 114-123 MB/s  | +25-30% |
| unicode.txt (8 MB)      | 116-117 MB/s | 180-183 MB/s  | +55%    |
| DOOM-Fire-Zig           | 530 fps      | 770 fps       | +45%    |

The pipeline now delivers the parse stage's full measured capacity
(the parse thread is pegged while gather spends ~33% of a core, so
any IO throughput improvements are now fully parser-bound).

**Linux note:** This needs to be verified on Linux. I think broadly
architecture is better and should never be worse. But its possible
some of the magic constants need to be tuned differently. Would love
more testing there.
2026-07-05 21:49:35 -07:00
Mitchell Hashimoto
b213a72c03 macOS: only read file urls for new-terminal services (#13169)
macOS is already guarding this in Services settings, but guarding what
we actually need anyway
2026-07-05 13:50:34 -07:00
Mitchell Hashimoto
8d83849132 macOS: read string contents per pasteboard item (#13170)
Fixes: https://github.com/ghostty-org/ghostty/pull/4956, regression
from: https://github.com/ghostty-org/ghostty/pull/4956


### AI Disclosure

Claude wrote the tests before I changed `getOpinionatedStringContents`
2026-07-05 13:50:19 -07:00
Mitchell Hashimoto
63e75e86c2 lib-vt: many more color utility APIs (#13206)
Embedders that render theme editors, palette pickers, or custom settings
UI need to use the same color semantics as Ghostty.

This moves the shared parsing paths into terminal/color and exposes them
through libghostty-vt. Config color and palette parsing now delegate to
the same helpers, so CLI/config behavior and the C ABI stay in lockstep.

From C:

    GhosttyColorRgb rgb;
    ghostty_color_parse("ForestGreen", 11, &rgb);

    uint8_t index;
    ghostty_color_parse_palette_entry(
        "0x10=#282c34", 12, &index, &rgb);

    const GhosttyColorX11Entry* names =
        ghostty_color_x11_names();

The exported color API is:

    ghostty_color_parse
    ghostty_color_parse_x11
    ghostty_color_parse_palette_entry
    ghostty_color_palette_default
    ghostty_color_palette_generate
    ghostty_color_luminance
    ghostty_color_perceived_luminance
    ghostty_color_contrast
    ghostty_color_x11_names
    ghostty_color_x11_name_count

The X11 name table is parsed once at comptime into null-terminated
entries in rgb.txt order. The existing case-insensitive map keeps the
same behavior for RGB.parse and +list-colors, while bindings can walk a
static table without allocations.

This doesn't add any more binary size since all of this was already used
by terminal internals.
2026-07-05 13:15:17 -07:00
Mitchell Hashimoto
2970e9a2a8 lib-vt: many more color utility APIs
Embedders that render theme editors, palette pickers, or custom
settings UI need to use the same color semantics as Ghostty.

This moves the shared parsing paths into terminal/color and exposes them
through libghostty-vt. Config color and palette parsing now delegate to
the same helpers, so CLI/config behavior and the C ABI stay in lockstep.

From C:

    GhosttyColorRgb rgb;
    ghostty_color_parse("ForestGreen", 11, &rgb);

    uint8_t index;
    ghostty_color_parse_palette_entry(
        "0x10=#282c34", 12, &index, &rgb);

    const GhosttyColorX11Entry* names =
        ghostty_color_x11_names();

The exported color API is:

    ghostty_color_parse
    ghostty_color_parse_x11
    ghostty_color_parse_palette_entry
    ghostty_color_palette_default
    ghostty_color_palette_generate
    ghostty_color_luminance
    ghostty_color_perceived_luminance
    ghostty_color_contrast
    ghostty_color_x11_names
    ghostty_color_x11_name_count

The X11 name table is parsed once at comptime into null-terminated
entries in rgb.txt order. The existing case-insensitive map keeps the
same behavior for RGB.parse and +list-colors, while bindings can walk a
static table without allocations.
2026-07-05 13:11:11 -07:00
Mitchell Hashimoto
4a7cabc4fe lib-vt: add color scheme report encoder (#13192)
Add a shared encoder for CSI ? 997 ; Ps n color scheme reports and use
it for both CSI ? 996 n replies and unsolicited Termio reports. Export
the same encoder through the libghostty-vt C API with docs and an
example.

This is a really light API, arguably easy for consumers to hardcode, but
it didn't match the rest of our style in the libghostty API so we should
expose it.
2026-07-05 12:53:13 -07:00
Jeffrey C. Ollie
715ef6c154 fix: set max window clamp to current monitor size (#13171)
This PR fixes #7984. The issue was that GTK would clamp the window
itself based on the display it was opened on. We fix this by computing
the size based on the current display and then implicitly setting the
window size instead of relying on GTK to do it.

Claude Code w/ Opus 4.7 was used to investigate, fix and explain some of
the Ghostty architecture to me.
2026-07-05 14:12:49 -05:00
Mitchell Hashimoto
f00e906949 lib-vt: add color scheme report encoder
Add a shared encoder for CSI ? 997 ; Ps n color scheme reports and use 
it for both CSI ? 996 n replies and unsolicited Termio reports. Export the 
same encoder through the libghostty-vt C API with docs and an example.

This is a really light API, arguably easy for consumers to hardcode,
but it didn't match the rest of our style in the libghostty API so we 
should expose it.

Example: GHOSTTY_COLOR_SCHEME_DARK encodes to ESC [ ? 997 ; 1 n,
while GHOSTTY_COLOR_SCHEME_LIGHT encodes to ESC [ ? 997 ; 2 n.
2026-07-05 10:51:39 -07:00
ghostty-vouch[bot]
243f7df7c1 Update VOUCHED list (#13202)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/13191#issuecomment-4887029233)
from @mitchellh.

Vouch: @tasselx

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-05 17:49:27 +00:00
yak
004c88e41e fix: set max window clamp to current monitor size 2026-07-05 10:41:06 -04:00
ghostty-vouch[bot]
131ca6d9eb Update VOUCHED list (#13199)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/13196#discussioncomment-17538577)
from @bo2themax.

Vouch: @Nagato-Yuzuru

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-05 14:26:54 +00:00
Jeffrey C. Ollie
8642142a3d Update iTerm2 colorschemes (#13190)
Upstream release:
https://github.com/mbadolato/iTerm2-Color-Schemes/releases/tag/release-20260629-161812-8c97c3c
2026-07-04 20:15:37 -05:00
mitchellh
02504bcce1 deps: Update iTerm2 color schemes 2026-07-05 00:33:57 +00:00
Mitchell Hashimoto
98df7efc83 lib-vt: add unicode grapheme width API (#13186)
Embedders that render text outside the terminal grid need to predict how
many cells text will occupy once it is written to the terminal. The
existing codepoint width API exposes the table used by print, but that
is not enough for mode 2027 grapheme clustering: VS15/VS16, ZWJ
sequences, skin tone modifiers, and other continuation codepoints can
change the width of the whole cluster.

This exposes a single segment-and-measure API so callers use Ghostty
segmentation and width folding together:

    uint8_t width;
    size_t n = ghostty_unicode_grapheme_width(cps, len, &width);

From the Zig module:

    const vt = @import("ghostty-vt");
    const result = vt.unicode.graphemeWidth(u21, cps);

Callers loop until their string is consumed. The API is intentionally
not streaming: input must contain a complete first cluster or the
logical string end, so chunked readers should keep buffering when the
function consumes all available codepoints and more may arrive.

The terminal hot path now shares the width-decision func with the API,
the helper is inline and preserves the old branch structure. So this
doesn't change codegen at all.
2026-07-04 14:10:31 -07:00
Mitchell Hashimoto
65e61282a6 lib-vt: add unicode grapheme width API
Embedders that render text outside the terminal grid need to predict
how many cells text will occupy once it is written to the terminal.
The existing codepoint width API exposes the table used by print, but
that is not enough for mode 2027 grapheme clustering: VS15/VS16, ZWJ
sequences, skin tone modifiers, and other continuation codepoints can
change the width of the whole cluster.

This exposes a single segment-and-measure API so callers use Ghostty
segmentation and width folding together:

    uint8_t width;
    size_t n = ghostty_unicode_grapheme_width(cps, len, &width);

From the Zig module:

    const vt = @import("ghostty-vt");
    const result = vt.unicode.graphemeWidth(u21, cps);

Callers loop until their string is consumed. The API is intentionally
not streaming: input must contain a complete first cluster or the
logical string end, so chunked readers should keep buffering when the
function consumes all available codepoints and more may arrive.

The terminal hot path now shares the width-decision func with the
API, the helper is inline and preserves the old branch structure. So
this doesn't change codegen at all.
2026-07-04 14:03:42 -07:00
ghostty-vouch[bot]
61ce641fca Update VOUCHED list (#13183)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/13182#issuecomment-4882909658)
from @trag1c.

Vouch: @pearkes

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-04 15:51:41 +00:00
Jeffrey C. Ollie
6887509035 Kitty dnd parser (#13029)
(#12852)
I opened a discussion to work on the new kitty dnd protocol and
implementing it for Ghostty. I was told to work on the parser but not to
hook up any actions to it yet. So, that's what I did! Largely based the
format on kitty_clipboard_protocol.zig, and used Claude Opus 4.8 (Claude
Code) for writing tests and some structural guidance early on. Would
love to get started on adding actions as well!
2026-07-04 02:21:56 -05:00
Mitchell Hashimoto
cca51729a1 lib-vt: add scroll-to-row viewport scrolling (#13179)
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.
2026-07-03 21:29:38 -07:00
Mitchell Hashimoto
8ef9193459 lib-vt: add unicode codepoint width API (#13178)
Embedders that render text outside the terminal grid need to predict how
many cells a codepoint will occupy once it is written to the terminal.
The immediate motivation is IME preedit overlay rendering: measuring
preedit text with font APIs (e.g. CoreText advances) can disagree with
the terminal's unicode table on ambiguous-width CJK and emoji, causing
the overlay to visibly jump when the composed text commits and reflows
through the real grid layout.

This exposes the exact width table the terminal print path already uses,
so overlays are column-accurate by construction. From C:

    uint8_t w = ghostty_unicode_codepoint_width(0x4E00); // 2

And from the Zig module:

    const vt = @import("ghostty-vt");
    const w = vt.unicode.codepointWidth(0x4E00); // 2

The function is total over its input: 0 for zero-width codepoints
(controls, combining marks, default-ignorables, surrogates), 2 for wide
codepoints (East Asian Wide/Fullwidth, regional indicators, clamped at
2), and 1 for everything else, including invalid values beyond U+10FFFF.

Perf: uses the LUT lookup we use for the main core terminal

Binary size: the width table was already linked into libghostty-vt via
the print path, so this adds only the exported wrapper.
2026-07-03 21:28:42 -07:00
Mitchell Hashimoto
3a2e28329c lib-vt: add scroll-to-row viewport scrolling
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.
2026-07-03 21:26:59 -07:00
Mitchell Hashimoto
fc5a727729 lib-vt: add unicode codepoint width API
Embedders that render text outside the terminal grid need to predict
how many cells a codepoint will occupy once it is written to the
terminal. The immediate motivation is IME preedit overlay rendering:
measuring preedit text with font APIs (e.g. CoreText advances) can
disagree with the terminal's unicode table on ambiguous-width CJK and
emoji, causing the overlay to visibly jump when the composed text
commits and reflows through the real grid layout.

This exposes the exact width table the terminal print path already
uses, so overlays are column-accurate by construction. From C:

    uint8_t w = ghostty_unicode_codepoint_width(0x4E00); // 2

And from the Zig module:

    const vt = @import("ghostty-vt");
    const w = vt.unicode.codepointWidth(0x4E00); // 2

The function is total over its input: 0 for zero-width codepoints
(controls, combining marks, default-ignorables, surrogates), 2 for
wide codepoints (East Asian Wide/Fullwidth, regional indicators,
clamped at 2), and 1 for everything else, including invalid values
beyond U+10FFFF.

Perf: uses the LUT lookup we use for the main core terminal

Binary size: the width table was already linked into libghostty-vt
via the print path, so this adds only the exported wrapper.
2026-07-03 21:20:54 -07:00
Mitchell Hashimoto
d560c64548 kitty/gfx: fix build on targets without 64-bit atomics (#13174)
The generation counter added in bdc0b6c19 is a process-global
std.atomic.Value(u64). The Zig compiler rejects 64-bit atomic operations
on targets whose largest supported atomic size is 32 bits (e.g.
arm-linux-androideabi), which broke the libghostty-vt Android build.
This slipped past other CI targets because they're either 64-bit or
compile kitty graphics out entirely (wasm32-freestanding).

The counter backing nextGeneration is now comptime-selected: 64-bit
targets keep the lock-free atomic counter, while 32-bit targets fall
back to a mutex-protected u64. This preserves the process-wide
uniqueness and monotonicity guarantees of generation stamps everywhere.
The mutex cost is irrelevant since this is a cold path, only invoked on
content mutations.
2026-07-03 12:05:41 -07:00
Mitchell Hashimoto
8fe5913f7e kitty/gfx: fix build on targets without 64-bit atomics
The generation counter added in bdc0b6c19 is a process-global
std.atomic.Value(u64). The Zig compiler rejects 64-bit atomic
operations on targets whose largest supported atomic size is 32 bits
(e.g. arm-linux-androideabi), which broke the libghostty-vt Android
build. This slipped past other CI targets because they're either
64-bit or compile kitty graphics out entirely (wasm32-freestanding).

The counter backing nextGeneration is now comptime-selected: 64-bit
targets keep the lock-free atomic counter, while 32-bit targets fall
back to a mutex-protected u64. This preserves the process-wide
uniqueness and monotonicity guarantees of generation stamps
everywhere. The mutex cost is irrelevant since this is a cold path,
only invoked on content mutations.
2026-07-03 11:40:56 -07:00
Mitchell Hashimoto
4812fcdc7d kitty/gfx: add generation stamps, delete transmit time (#13173)
Add a generation counter to the kitty graphics image storage. Every
content mutation (image transmit/replace, placement add, delete) assigns
the storage a fresh stamp, and every image is stamped when it is added
or replaced.

This solves two problems: 

First, a retransmission of the same image ID with identical dimensions
was previously undetectable by anything comparing width, height, format,
and data length; the per-image stamp changes on every add/replace, so
caches keyed on it always see the change. Second, the dirty flag was the
only storage-wide signal, and it is also set by scrolling and resizing,
which move placements without changing contents. The generation is only
bumped by content mutations, so an unchanged value means the placement
set and all image data are identical and consumers can skip re-reading
them, recomputing only placement geometry.

The generation replaces Image.transmit_time entirely: newest-image
lookup by number, eviction ordering, and the renderer's texture
staleness checks all key on it now. A monotonic counter strictly orders
transmissions where Instant-based times could collide within clock
resolution (the renderer previously assumed equal timestamps meant
identical images), and this removes a syscall and an error path per
transmission.

Delete commands now only mark a mutation (dirty flag and generation)
when they actually remove something. A delete-all runs on every screen
clear, so previously every ESC [ 2 J dirtied the image state even with
no images stored. Eviction via setLimit also now marks the state dirty,
which it previously did not.
2026-07-03 10:59:37 -07:00
Mitchell Hashimoto
bdc0b6c19c kitty/gfx: add generation stamps, delete transmit time
Add a generation counter to the kitty graphics image storage. Every
content mutation (image transmit/replace, placement add, delete)
assigns the storage a fresh stamp, and every image is stamped when
it is added or replaced. 

This solves two problems: 

First, a retransmission of the same image ID with identical dimensions 
was previously undetectable by anything comparing width, height, format, 
and data length; the per-image stamp changes on every add/replace, so caches 
keyed on it always see the change. Second, the dirty flag was the only 
storage-wide signal, and it is also set by scrolling and resizing, which move 
placements without changing contents. The generation is only bumped by content
mutations, so an unchanged value means the placement set and all
image data are identical and consumers can skip re-reading them,
recomputing only placement geometry.

The generation replaces Image.transmit_time entirely: newest-image
lookup by number, eviction ordering, and the renderer's texture
staleness checks all key on it now. A monotonic counter strictly
orders transmissions where Instant-based times could collide within
clock resolution (the renderer previously assumed equal timestamps
meant identical images), and this removes a syscall and an error
path per transmission.

Delete commands now only mark a mutation (dirty flag and
generation) when they actually remove something. A delete-all runs
on every screen clear, so previously every ESC [ 2 J dirtied the
image state even with no images stored. Eviction via setLimit also
now marks the state dirty, which it previously did not.

Both generations are exposed through libghostty-vt as
GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION and
GHOSTTY_KITTY_IMAGE_DATA_GENERATION (uint64_t), and the headers now
document that stored image data is always post-inflate/post-decode:
COMPRESSION always reports NONE, FORMAT is never PNG, and DATA_PTR
is raw pixels ready for GPU upload.

    uint64_t gen = 0;
    ghostty_kitty_graphics_get(
        graphics, GHOSTTY_KITTY_GRAPHICS_DATA_GENERATION, &gen);
    if (gen == last_gen) return; // nothing changed, skip re-reads
2026-07-03 10:55:10 -07:00
Lukas
10565995b9 macOS: only read file urls for new-terminal services
macOS is already guarding this system, but guarding what we actually need anyway
2026-07-03 13:24:55 +02:00
Lukas
49806fc4cc macOS: read string contents per pasteboard item in order
Pasteboards mixing file URLs with other items will now be pasted as joined string.
2026-07-03 12:53:34 +02:00
Claude Fable 5
acd09c0a6c macos: add tests for NSPasteboard.getOpinionatedStringContents 2026-07-03 12:53:34 +02:00
Leah Amelia Chen
bb7cd85b2e Manually vouch eunos-1128 & denounce VectorPeak (#13166)
See #13163

The `!denounce` command should ideally not ban the OP when a manual
target has been specified...
2026-07-03 13:31:26 +08:00
Leah Amelia Chen
3b63c6dc65 Manually vouch eunos-1128 & denounce VectorPeak
See #13163

The `!denounce` command should ideally not ban the OP when a manual
target has been specified...
2026-07-03 13:28:33 +08:00
ghostty-vouch[bot]
9314b2c559 Update VOUCHED list (#13165)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/13163#discussioncomment-17519109)
from @pluiedev.

Denounce: @eunos-1128

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-03 05:24:59 +00:00
Jeffrey C. Ollie
8252871b2b build(deps): bump docker/build-push-action from 7.2.0 to 7.3.0 (#13143)
Bumps
[docker/build-push-action](https://github.com/docker/build-push-action)
from 7.2.0 to 7.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/build-push-action/releases">docker/build-push-action's
releases</a>.</em></p>
<blockquote>
<h2>v7.3.0</h2>
<ul>
<li>Preserve names in esbuild bundle by <a
href="https://github.com/crazy-max"><code>@​crazy-max</code></a> in <a
href="https://redirect.github.com/docker/build-push-action/pull/1567">docker/build-push-action#1567</a></li>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.90.0 to 0.92.0 in
<a
href="https://redirect.github.com/docker/build-push-action/pull/1545">docker/build-push-action#1545</a>
<a
href="https://redirect.github.com/docker/build-push-action/pull/1572">docker/build-push-action#1572</a></li>
<li>Bump <code>@​sigstore/core</code> from 3.1.0 to 3.2.1 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1568">docker/build-push-action#1568</a></li>
<li>Bump js-yaml from 4.1.1 to 4.3.0 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1566">docker/build-push-action#1566</a></li>
<li>Bump tmp from 0.2.5 to 0.2.7 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1547">docker/build-push-action#1547</a></li>
<li>Bump undici from 6.24.1 to 6.27.0 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1564">docker/build-push-action#1564</a></li>
<li>Bump vite from 7.3.2 to 7.3.6 in <a
href="https://redirect.github.com/docker/build-push-action/pull/1563">docker/build-push-action#1563</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0">https://github.com/docker/build-push-action/compare/v7.2.0...v7.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="53b7df96c9"><code>53b7df9</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1572">#1572</a>
from docker/dependabot/npm_and_yarn/docker/actions-t...</li>
<li><a
href="154298c1ca"><code>154298c</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="cb1238b9c9"><code>cb1238b</code></a>
chore(deps): Bump <code>@​docker/actions-toolkit</code> from 0.91.0 to
0.92.0</li>
<li><a
href="24f845d5cb"><code>24f845d</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1566">#1566</a>
from docker/dependabot/npm_and_yarn/js-yaml-4.2.0</li>
<li><a
href="9c6973007b"><code>9c69730</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="bc3a3a5f72"><code>bc3a3a5</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1574">#1574</a>
from docker/dependabot/github_actions/aws-actions/co...</li>
<li><a
href="a82c504a23"><code>a82c504</code></a>
chore(deps): Bump js-yaml from 4.1.1 to 4.3.0</li>
<li><a
href="0285a75190"><code>0285a75</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1573">#1573</a>
from docker/dependabot/github_actions/actions/cache-...</li>
<li><a
href="c6ad2a3f96"><code>c6ad2a3</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1575">#1575</a>
from docker/dependabot/github_actions/actions/checko...</li>
<li><a
href="d37484fb97"><code>d37484f</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/build-push-action/issues/1564">#1564</a>
from docker/dependabot/npm_and_yarn/undici-6.27.0</li>
<li>Additional commits viewable in <a
href="f9f3042f7e...53b7df96c9">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=docker/build-push-action&package-manager=github_actions&previous-version=7.2.0&new-version=7.3.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>
2026-07-02 22:40:44 -05:00
Jeffrey C. Ollie
8a4d63c4ce build(deps): bump dorny/paths-filter from 4.0.1 to 4.0.2 (#13159)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from
4.0.1 to 4.0.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.2</h2>
<h2>What's Changed</h2>
<ul>
<li>fix warning message by <a
href="https://github.com/cgundy"><code>@​cgundy</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/282">dorny/paths-filter#282</a></li>
<li>chore: fix GitHub spelling in logs by <a
href="https://github.com/squat"><code>@​squat</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/278">dorny/paths-filter#278</a></li>
<li>fix: use rev-parse instead of branch --show-current for older git
compat by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/303">dorny/paths-filter#303</a></li>
<li>fix: work around git dubious ownership errors in container jobs by
<a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/317">dorny/paths-filter#317</a></li>
<li>docs: update changelog for v4.0.2 by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/318">dorny/paths-filter#318</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/cgundy"><code>@​cgundy</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/282">dorny/paths-filter#282</a></li>
<li><a href="https://github.com/squat"><code>@​squat</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/278">dorny/paths-filter#278</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v4.0.1...v4.0.2">https://github.com/dorny/paths-filter/compare/v4.0.1...v4.0.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/317">Work
around git dubious ownership errors in container jobs</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/303">Use
rev-parse instead of branch --show-current for older git compat</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/282">Fix
warning message</a></li>
</ul>
<h2>v4.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/255">Support
merge queue</a></li>
</ul>
<h2>v4.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/294">Update
action runtime to node24</a></li>
</ul>
<h2>v3.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/279">Add
missing predicate-quantifier</a></li>
</ul>
<h2>v3.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/224">Add
config parameter for predicate quantifier</a></li>
</ul>
<h2>v3.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/133">Compare
base and ref when token is empty</a></li>
</ul>
<h2>v3.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/210">Update to
Node.js 20</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/215">Update
all dependencies</a></li>
</ul>
<h2>v2.11.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/167">Update
@​actions/core to v1.10.0 - Fixes warning about deprecated
set-output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/168">Document
need for pull-requests: read permission</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/164">Updating
to actions/checkout@v3</a></li>
</ul>
<h2>v2.11.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/157">Set
list-files input parameter as not required</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/161">Update
Node.js</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/162">Fix
incorrect handling of Unicode characters in exec()</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/163">Use
Octokit pagination</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/160">Updates
real world links</a></li>
</ul>
<h2>v2.10.2</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/91">Fix
getLocalRef() returns wrong ref</a></li>
</ul>
<h2>v2.10.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/85">Improve
robustness of change detection</a></li>
</ul>
<h2>v2.10.0</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/82">Add
ref input parameter</a></li>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/83">Fix
change detection in PR when pullRequest.changed_files is
incorrect</a></li>
</ul>
<h2>v2.9.3</h2>
<ul>
<li><a href="https://redirect.github.com/dorny/paths-filter/pull/78">Fix
change detection when base is a tag</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="7b450fff21"><code>7b450ff</code></a>
docs: update changelog for v4.0.2 (<a
href="https://redirect.github.com/dorny/paths-filter/issues/318">#318</a>)</li>
<li><a
href="928037783a"><code>9280377</code></a>
fix: work around git dubious ownership errors in container jobs (<a
href="https://redirect.github.com/dorny/paths-filter/issues/317">#317</a>)</li>
<li><a
href="f3ceefdc7e"><code>f3ceefd</code></a>
fix: use rev-parse instead of branch --show-current for older git compat
(<a
href="https://redirect.github.com/dorny/paths-filter/issues/303">#303</a>)</li>
<li><a
href="61f87a10cd"><code>61f87a1</code></a>
chore: fix GitHub spelling in logs (<a
href="https://redirect.github.com/dorny/paths-filter/issues/278">#278</a>)</li>
<li><a
href="b82ff81ffb"><code>b82ff81</code></a>
fix warning message (<a
href="https://redirect.github.com/dorny/paths-filter/issues/282">#282</a>)</li>
<li>See full diff in <a
href="fbd0ab8f3e...7b450fff21">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dorny/paths-filter&package-manager=github_actions&previous-version=4.0.1&new-version=4.0.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>
2026-07-02 22:20:35 -05:00
dependabot[bot]
78733419a1 build(deps): bump dorny/paths-filter from 4.0.1 to 4.0.2
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from 4.0.1 to 4.0.2.
- [Release notes](https://github.com/dorny/paths-filter/releases)
- [Changelog](https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md)
- [Commits](fbd0ab8f3e...7b450fff21)

---
updated-dependencies:
- dependency-name: dorny/paths-filter
  dependency-version: 4.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-03 00:12:49 +00:00
Mitchell Hashimoto
842badca5f lib-vt: expose selection gesture to Zig (#13156)
Selection gestures are already part of the libghostty-vt C API, but the
native Zig module did not re-export the underlying terminal type. Zig
consumers that implement mouse selection had to reach into terminal
internals instead of using @import("ghostty-vt").

Re-export SelectionGesture from lib_vt alongside the other terminal
selection and screen types.

AI Disclosure: I used AI. Yes, for +1
2026-07-02 14:07:08 -07:00
Tim Culverhouse
f245cdc667 lib-vt: expose selection gesture to Zig
Selection gestures are already part of the libghostty-vt C API, but the
native Zig module did not re-export the underlying terminal type. Zig
consumers that implement mouse selection had to reach into terminal
internals instead of using @import("ghostty-vt").

Re-export SelectionGesture from lib_vt alongside the other terminal
selection and screen types.
2026-07-02 15:39:23 -05:00
Mitchell Hashimoto
c22df09da1 libghostty: fix utf-8 grapheme length overflow (#13145)
The GRAPHEMES_UTF8 row-cells getter inferred its required byte
accumulator from utf8CodepointSequenceLength, which stores the value in
u3. Multi-scalar clusters longer than seven UTF-8 bytes could overflow
that accumulator before the capacity check, causing wrong probe sizes
and allowing optimized builds to write past a caller-provided buffer.

Use usize for the required byte count so probing and capacity checks
match the later encode loop. Extend the render C API test to cover the
short combining cluster, an eight-byte flag cluster, a longer family
emoji, exact-size success, and the cap == needed - 1 no-write boundary.
2026-07-01 20:04:46 -07:00
Mitchell Hashimoto
aea63d71fe libghostty: fix utf-8 grapheme length overflow
The GRAPHEMES_UTF8 row-cells getter inferred its required byte
accumulator from utf8CodepointSequenceLength, which stores the
value in u3. Multi-scalar clusters longer than seven UTF-8 bytes
could overflow that accumulator before the capacity check, causing
wrong probe sizes and allowing optimized builds to write past a
caller-provided buffer.

Use usize for the required byte count so probing and capacity
checks match the later encode loop. Extend the render C API test
to cover the short combining cluster, an eight-byte flag cluster,
a longer family emoji, exact-size success, and the
cap == needed - 1 no-write boundary.
2026-07-01 18:49:25 -07:00
dependabot[bot]
5a699032ff build(deps): bump docker/build-push-action from 7.2.0 to 7.3.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 7.2.0 to 7.3.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](f9f3042f7e...53b7df96c9)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-version: 7.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-07-02 00:12:24 +00:00
ghostty-vouch[bot]
df5cee2382 Update VOUCHED list (#13141)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/13121#discussioncomment-17502740)
from @jcollie.

Vouch: @yak3d

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-07-01 22:28:24 +00:00
Jeffrey C. Ollie
8b71d7a03c ci: skip tip release when only non-artifact files change (#13130)
Ignore more files when releasing tip. Moved release-tip run to a
separate script so we can test against it.

### AI Disclosure

Claude did this, I reviewed the changes and asked it run the tests
locally before creating the pr.
2026-07-01 12:41:58 -05:00
Claude Opus 4.8
480edb45e3 ci: skip tip release when only non-artifact files change
Detect changes since the last tip with dorny/paths-filter (base: tip)
and skip the build when a push touches only files that never reach the
built artifact: all of .github (except release-tip.yml, which defines the
build/tag/publish jobs) plus docs and repo/lint/editor metadata.
2026-07-01 19:00:32 +02:00