Use inclusive image ID bounds for the Kitty graphics protocol range
delete operation.
Range deletion previously joined the lower and upper bound checks with or,
which matched every placement for any valid range. A targeted delete could
therefore remove every graphics placement.
Join the bounds with and and update the lowercase and uppercase range tests
to keep placements below and above the selected interval.
`printRepeat` (CSI `b`, repeat the previous character N times) calls
`print()` once per repeat, so something like `\x1b[2000b` ran grapheme
checks, width lookups, wrap handling, and the integrity assert 2000
times for what is usually the same character on the same row.
`Terminal.print` was 24% of samples on a REP-heavy micro benchmark.
This PR just aims to add a fast path by introducing a chunking
mechanism. anything that needs care (insert mode, grapheme clustering,
hyperlinks) still falls back to per-codepoint print() inside printSlice,
so behavior *should* stay unchanged.
Some profiling data:
Generated with some plain stupid logic:
```py
D = "benchdata"
parts, total = [], 0
while total < 40_000_000:
line = "x" + "\x1b[80b" + "y" + "\x1b[35b" + "\r\n"
parts.append(line); total += len(line)
open(f"{D}/rep.bin", "wb").write("".join(parts).encode())
```
**macOS (hyperfine, 15 runs, warmup 3):**
| | mean |
|---|---|
| before | 2.360 s |
| after | 1.166 s |
And now the really interesting and promising stuff
**Linux, 24-core NixOS x86_64 (poop, 6s sampling):**
| | wall_time | instructions | branch_misses | peak_rss |
|---|---|---|---|---|
| before | 1.51 s | 50.9 G | 9.41 M | 6.82 MB |
| after | 562 ms | 9.07 G | 114 K | 6.74 MB |
While doing some work on my tmux fork I noticed multiple parts of
libghostty-vt was slower than tmux equivalents(isolated). Turns out they
do some smart stuff there.
printRepeat called print() once per repeat, so something like \x1b[2000b
ran grapheme checks, width lookups, wrap handling, etc etc 2000 times.
printSlice is already documented as semantically identical to
calling print per codepoint, so this just feeds the repeated
codepoint through it in 4096-entry stack chunks. Simple runs take
the batched fast path, and anything that needs care falls back to the
previous behaviour.
Fixes#13614
Only translate the shared default commands when building the GTK
runtime. macOS now use the source strings until we do broader
localization.
Fixes#13614
Only translate the shared default commands when building the GTK runtime.
macOS now use the source strings until we do broader localization.
Fix for Issue #12940
I actually do not know if this has already been resolved and the issue
is just still open. Either way, here's a fix. Now we run a check to see
if the current program is accepting mouse events before discarding the
middle click.
Fixes#13386, based on
https://github.com/mustafa0x/ghostty/commit/a8c090
Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.
Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar
and layer state, increasing memory use and WindowServer CPU with tab
churn.
Validated with an AppDelegate change that sat and created/closed tabs in
a loop, then counted weak controllers/windows/nsapp window.
Most obvious next step in translating Ghostty is the command palette.
Added support for i18n.N_ (https://docs.gtk.org/glib/i18n.html#macros).
Made a Latvian translation for the command palette to test. Codex did
bulk of the translations but I verified them.
Fixes#13386
Defer transparent-titlebar KVO rebinding to the next main-queue turn.
Track the observed tab group so unchanged bindings are preserved.
Previously, a tab-group callback could invalidate and recreate its own
observation before returning, leaving closed terminal windows registered
with AppKit after the undo timeout. These windows accumulated titlebar and
layer state, increasing memory use and WindowServer CPU with tab churn.
Validated with an AppDelegate change that sat and created/closed tabs
in a loop, then counted weak controllers/windows/nsapp window.
Co-authored-by: Mustafa J <mustafa.0x@gmail.com>
the original wording is a bit confusing; I thought cursor-click-to-move
required shell-integration to be enabled, and was confused when the
mouse was still moving my cursor in fish even with
shell-integration=none.
#11799
Creating a CGEventTap without Accessibility permission leaks a Mach port
inside CoreGraphics on every failed attempt. The global keybind listener
retried this once per second while waiting for permission, so Ghostty
eventually exhausted the process port limit.
Request Accessibility access once, poll AXIsProcessTrusted while access
is denied, and create the event tap only after access is granted. Stop
polling before creation so an unrelated tap failure cannot restart the
leaking retry loop.
Tested this with various settings and global keys working fine.
This PR speeds up our formatting (plain text, html, and VT) by anywhere
from ~1.5x to ~8x.
The formatter is the hot path behind multiple features in Ghostty GUI:
clipboard copy (plain/VT/HTML), `write_screen_file`, `selectionString`,
and terminal search sliding window. It's also the hot path for
libghostty users, namely people like
[zmx](https://github.com/neurosnap/zmx) which utilize the VT formatter
to restore a terminal.
This PR also adds the benchmarking infrastructure for the formatter.
## How
- **Fast cell-run optimization.** For simple cells (single codepoint, no
style/hyperlink) we encode them as a single run rather than one at a
time.
- **Make some arguments comptime.** Generates more code but benchmarks
show it improves things, specifically for per-format switches that we do
a LOT.
- **Interned style id fast path.** Styles are interned per page, so id
equality implies style equality. We track the id of the active style and
skip the per-cell `Style` copy + `eql` when it matches.
- **Fast printing.** Avoid `std.fmt` where possible and assemble
integers, RGB colors, codepoints in fixed-width buffers with a single
memcpy. This was extracted partially to `fastprint.zig` so we can reuse
it.
- **Avoid double-formatting for tracked pins.** Previously we formatted
twice (once through a `Discarding` writer to count bytes) for pin maps.
Now I'm smarter about it and do a single pass.
## Performance
All on my machine, 80x24 terminal, 10K lines of scrollback.
| workload | main | this PR | speedup | throughput |
| --------------------- | -------- | -------- | ------- | ---------- |
| plain / plain | 5.74 ms | 1.67 ms | 3.4x | 364 MB/s |
| plain / vt | 6.46 ms | 1.04 ms | 6.2x | 596 MB/s |
| plain / html | 7.39 ms | 2.32 ms | 3.2x | 308 MB/s |
| unicode / plain | 9.74 ms | 5.42 ms | 1.8x | 276 MB/s |
| unicode / vt | 10.42 ms | 5.53 ms | 1.9x | 275 MB/s |
| unicode / html | 12.53 ms | 7.35 ms | 1.7x | 509 MB/s |
| styled / plain | 5.65 ms | 1.69 ms | 3.4x | 360 MB/s |
| styled / vt | 9.07 ms | 4.20 ms | 2.2x | 409 MB/s |
| styled / html | 10.78 ms | 6.64 ms | 1.6x | 740 MB/s |
| mixed / plain | 8.59 ms | 4.81 ms | 1.8x | 226 MB/s |
| mixed / vt | 11.25 ms | 6.65 ms | 1.7x | 250 MB/s |
| mixed / html | 14.47 ms | 10.52 ms | 1.4x | 414 MB/s |
| wrapped / plain | 7.30 ms | 1.11 ms | 6.6x | 733 MB/s |
| wrapped / vt | 8.14 ms | 1.04 ms | 7.8x | 789 MB/s |
| wrapped / html | 9.00 ms | 2.12 ms | 4.2x | 465 MB/s |
| pin-map / plain | 12.51 ms | 3.80 ms | 3.3x | |
| pin-map / vt | 13.12 ms | 2.99 ms | 4.4x | |
| active screen / plain | 12.5 µs | 2.7 µs | 4.6x | |
| active screen / vt | 18.4 µs | 6.8 µs | 2.7x | |
Workloads:
- `plain` is ASCII lines
- `unicode` is 2/3/4-byte codepoints with 10% grapheme clusters
- `styled` is heavy SGR churn
- `mixed` is styles + Unicode + hyperlinks
- `wrapped` is a continuous soft-wrapped stream
- `pin-map`/`active screen` are the selectionString/search-style and
visible-screen-only cases respectively.
Finally, what was previously thought impossible, is now possible.
The blur region itself is far more accurate than what we can conjure up
on our own, and in a much more finetuned and detailed way too.
Thank you, GTK devs!
Closes#13581
#11799
Creating a CGEventTap without Accessibility permission leaks a Mach
port inside CoreGraphics on every failed attempt. The global keybind
listener retried this once per second while waiting for permission, so
Ghostty eventually exhausted the process port limit.
Request Accessibility access once, poll AXIsProcessTrusted while
access is denied, and create the event tap only after access is
granted. Stop polling before creation so an unrelated tap failure
cannot restart the leaking retry loop.
Finally, what was previously thought impossible, is now possible.
The blur region itself is far more accurate than what we can conjure up
on our own, and in a much more finetuned and detailed way too.
Thank you, GTK devs!
GTK 4.23.3 added its own (much smarter) implementation of background blur,
which means our implementation is not only redundant, it also crashes the
program because a surface cannot have multiple associated blur objects.
Ergo, don't do custom blur on newer GTK versions.
See #13578
RunIterator allocated a list of font candidates for every
multi-codepoint grapheme, then scanned it for the first font covering
the entire cluster.
Instead, check the primary and additional font candidates as they're
discovered. This preserves their order while removing the temporary
array and avoids additional lookups when the primary font supports the
full grapheme.