Commit Graph

16277 Commits (c09ade225acb0abfea2f845197b227086a76922f)

Author SHA1 Message Date
Uzair Aftab c09ade225a
agents: symlink CLAUDE.md to AGENTS.md 2026-05-29 21:11:09 +02:00
Mitchell Hashimoto 90175950d5
libghostty-vt: preserve shell prompts on resize by default (#12653)
This PR makes libghostty-vt preserve shell prompts across resize unless
the shell explicitly opts into prompt clearing/redraw with `redraw=1`.
2026-05-29 06:41:23 -07:00
Mitchell Hashimoto cb36966a75
libghostty: add utf-8 grapheme cell getter to C API (#12847)
Add a render-state row-cells getter that encodes the current cell's full
grapheme cluster directly as UTF-8 into a caller-provided GhosttyBuffer.
The getter writes the base codepoint first, followed by any extra
grapheme codepoints, and follows the existing buffer-writer convention
where len is bytes written on success or required capacity on
GHOSTTY_OUT_OF_SPACE.

Previously C consumers could query grapheme codepoints, but bindings
that needed UTF-8 text had to reconstruct and encode the cluster
themselves. That duplicated terminal internals in downstream bindings
and made users pay for awkward cross-language struct handling. By owning
the UTF-8/grapheme behavior in libghostty, bindings can use one stable C
API and optionally wrap it with small binding-local helpers.
2026-05-28 13:02:11 -07:00
Mitchell Hashimoto 519a612beb
libghostty: fix wasm build for selection gesture 2026-05-28 13:00:49 -07:00
Mitchell Hashimoto 3cf01e8445
libghostty: add utf-8 grapheme cell getter to C API
Add a render-state row-cells getter that encodes the current cell's
full grapheme cluster directly as UTF-8 into a caller-provided
GhosttyBuffer. The getter writes the base codepoint first, followed by
any extra grapheme codepoints, and follows the existing buffer-writer
convention where len is bytes written on success or required capacity
on GHOSTTY_OUT_OF_SPACE.

Previously C consumers could query grapheme codepoints, but bindings
that needed UTF-8 text had to reconstruct and encode the cluster
themselves. That duplicated terminal internals in downstream bindings
and made users pay for awkward cross-language struct handling. By
owning the UTF-8/grapheme behavior in libghostty, bindings can use one
stable C API and optionally wrap it with small binding-local helpers.
2026-05-28 12:33:36 -07:00
Mitchell Hashimoto 54ac5fd21e
libghostty: expose row cell styling bit (#12837)
Add a render row-cells data key for querying whether the current cell
has explicit styling. This lets consumers avoid fetching a raw cell or
full style snapshot when all they need is the cell's HasStyling bit.

The new key is appended to the existing enum for ABI safety and is
served by the existing row-cells getter path. Existing data keys and
function exports are unchanged.

This was identified as an allocation hot-spot in Go renderers.
2026-05-27 21:11:56 -07:00
Mitchell Hashimoto 8beea5f92d
libghostty: expose row cell styling bit
Add a render row-cells data key for querying whether the current cell has
explicit styling. This lets consumers avoid fetching a raw cell or full style
snapshot when all they need is the cell's HasStyling bit.

The new key is appended to the existing enum for ABI safety and is served by
the existing row-cells getter path. Existing data keys and function exports are
unchanged.
2026-05-27 21:10:26 -07:00
Mitchell Hashimoto 15264856f6
libghostty: expose viewport active state (#12836)
Expose whether the terminal viewport is currently pinned to the active
area through the libghostty-vt terminal data API. Previously embedders
could only infer this from scrollbar geometry, which was indirect and
could require the more expensive scrollbar calculation.

The new GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE value returns the exact
PageList viewport state as a bool. The scroll viewport test now verifies
the value while moving between the active area and scrollback.
2026-05-27 15:28:49 -07:00
Mitchell Hashimoto f730ee0557
libghostty: expose viewport active state
Expose whether the terminal viewport is currently pinned to the active
area through the libghostty-vt terminal data API. Previously embedders
could only infer this from scrollbar geometry, which was indirect and
could require the more expensive scrollbar calculation.

The new GHOSTTY_TERMINAL_DATA_VIEWPORT_ACTIVE value returns the exact
PageList viewport state as a bool. The scroll viewport test now verifies
the value while moving between the active area and scrollback.
2026-05-27 15:24:49 -07:00
Mitchell Hashimoto 6d089a544d
libghostty: C API for SelectionGesture (#12833)
C API side of https://github.com/ghostty-org/ghostty/pull/12830
2026-05-27 11:11:14 -07:00
Mitchell Hashimoto 4e2d7c314b
libghostty: optimize bits for selection gesture validation fields 2026-05-27 11:05:33 -07:00
Mitchell Hashimoto 3e0477a14a
example/c-vt-selection-gesture 2026-05-27 11:01:25 -07:00
Mitchell Hashimoto f0fcb10406
libghostty: selection gesture deep press 2026-05-27 10:57:50 -07:00
Mitchell Hashimoto 603684ba11
libghostty: selection gesture autotick 2026-05-27 10:55:15 -07:00
Mitchell Hashimoto 90fd1ec2e7
libghostty: selection gesture drag events 2026-05-27 10:53:10 -07:00
Mitchell Hashimoto 3fd2c66a04
libghostty: selection gesture release event 2026-05-27 10:49:18 -07:00
Mitchell Hashimoto 5ac8e6569a
libghostty: add ghostty_selection_gesture_event 2026-05-27 10:47:26 -07:00
Mitchell Hashimoto bbfa984aec
libghostty: GhosttySelectionGestureEvent 2026-05-27 10:42:29 -07:00
Mitchell Hashimoto 2f61ba036e
libghostty: starting the SelectionGesture API, just init/get 2026-05-27 09:02:25 -07:00
Mitchell Hashimoto 3103ae8838
macos: avoid duplicate appearance sync on tab focus (#12828)
Close #12825

Skip the initial emissions from the focused surface appearance
publishers after a tab focus change. The focused surface is already
synced immediately, so the initial Combine values only repeat the same
titlebar and background updates. Subsequent derived config and OSC
background changes still resync the window appearance.



https://github.com/user-attachments/assets/f229fb95-4b4c-4040-85ac-0acfcc54ca82



Assigned to Codex GPT 5.5(medium)
PS: Sry for I don't write zig and let AI write this.
2026-05-27 07:52:42 -07:00
Mitchell Hashimoto 756fda776b
cli: rework +ssh-cache internals and user interface (#12814)
This change primarily focused on a revised +ssh-cache user interface,
but it also reworks a bunch of the internals.

The primary CLI improvement is support for positional arguments and a
consistent list output format that includes both the ISO-formatted
timestamp and relative age.

ghostty +ssh-cache # List all cached destinations
    ghostty +ssh-cache user@example.com          # Show that destination
ghostty +ssh-cache example.com # Show all users on that host
ghostty +ssh-cache --add=user@example.com # Manually add a destination
    ghostty +ssh-cache --remove=user@example.com # Remove a destination
ghostty +ssh-cache --prune=30d # Remove entries older than 30 days
    ghostty +ssh-cache --clear                   # Clear entire cache

Notable, we now support a --prune operation that replaces the previous
--expire-days flag that was never actually hooked up to anything (!!).
--prune also supports a wider range of Duration-based values.

We're also much more consistent with error codes: 0=success, 1=failure,
2=usage.

While working on those changes, I also reworked the cache internals,
particularly the code around timestamp handling and errors. For example,
I dropped the explicit error sets because they were growing unwieldy,
and in practice we only matched on a subset of those errors.

Lastly, overall test coverage should be much improved, especially around
the time- and allocation-related operations.

---

*AI Disclosure:* I made a lot of iterative, AI-assisted (Claude Opus
4.7) correctness passes over this work. It was particularly helpful in
tracing through the various failure modes, and it wrote those unit tests
in the process.
2026-05-27 07:52:00 -07:00
Mitchell Hashimoto 8518986b1e
macOS: clear stale OSC 11 background cache on config change (#12822)
## Summary

`SurfaceView` caches the background color set by OSC 11 in
`backgroundColor`. `TerminalWindow.preferredBackgroundColor` consults
that cache before falling back to `derivedConfig.backgroundColor`, so
once OSC 11 has fired the cached value masks any later config change.

After a light/dark theme auto-switch (e.g. `theme =
light:my-light,dark:my-dark`) this leaves the window chrome on the
previous theme's color until the application next emits OSC 11.

In `ghosttyConfigDidChange`, after updating `derivedConfig`, drop the
cache when it no longer matches the new config-derived background. A
subsequent `ghosttyColorDidChange` repopulates it as before, so
within-config OSC 11 behavior is unchanged.

## Reproduction

1. Configure `theme = light:SomeLight,dark:SomeDark` where the two
themes have visibly different background colors.
2. Open a terminal session where any application (e.g. a shell startup
script) has sent OSC 11 to set a custom background color.
3. Switch macOS appearance (System Settings → Appearance).
4. **Before**: window chrome stays the previous theme's color until the
terminal next emits OSC 11.
5. **After**: window chrome immediately updates to the new theme's
background color.

## Changes

- `SurfaceView_AppKit.swift` — one guard: if the cached
`backgroundColor` disagrees with the new
`derivedConfig.backgroundColor`, set it to `nil`.
2026-05-27 07:51:12 -07:00
Mitchell Hashimoto c343c5a67a
Extract click/drag selection handling into SelectionGesture (#12830)
Refactor terminal text selection into a reusable `SelectionGesture`
state machine. Most importantly, this means our click+drag logic around
selection is now fully unit tested! And we found bugs! And fixed them!

The large line increase in this diff is mainly comments + tests.

I've wanted to do this forever so we can unit test this, but I was
kicked in the butt to do it recently because reimplementing selection
logic in libghostty consumers turns out to be complex and error prone
and we have a perfectly battle tested logic machine here so why not
extract it?

Behavioral changes from main surfaced via unit testing:

- Dragging now drags by output across semantic output blocks when the
initial press was an output selection. This matches the behavior of
dragging continuing whatever the initial selection logic was.
- Selection autoscroll now stops when the click anchor is invalidated by
a screen change (e.g. primary to alt)
- Deep press (macOS force touch) now selects the word at the original
press location and consumes the active drag gesture, preventing later
movement from dragging or autoscrolling that selection. This matches
built-in macOS apps.
- Mouse release records whether the gesture moved away from the pressed
cell, so link and prompt clicks are skipped after a drag while normal
clicks still activate them.

Example usage:

```zig
var gesture: terminal.SelectionGesture = .init;
defer gesture.deinit(t);

const press_selection = try gesture.press(t, .{
    .time = try std.time.Instant.now(),
    .pin = press_pin,
    .xpos = mouse_x,
    .ypos = mouse_y,
    .max_distance = cell_width,
    .repeat_interval = mouse_interval,
    .word_boundary_codepoints = selection_word_chars,
    .behaviors = &.{ .cell, .word, .output },
});
try t.screens.active.select(press_selection);

if (gesture.drag(t, drag_event)) |drag_selection| {
    try t.screens.active.select(drag_selection);
}

gesture.release(t, .{ .pin = release_pin });
```
2026-05-27 07:48:23 -07:00
Mitchell Hashimoto 68959c5c63
terminal: fix selection gesture edge cases
Selection gestures now treat releases with invalidated anchors as dragged,
so a press that crosses screen boundaries cannot also activate links or
prompt clicks on release. Cell drags that create a same-cell selection also
mark the gesture as dragged, which keeps click-only actions from firing
after a threshold-crossing drag.

Autoscroll now resolves the drag pin after moving the viewport instead of
reusing the pin from before the scroll. This keeps the selection aligned
with the row currently under the pointer. The inspector also validates the
tracked click pin before displaying it so stale pins from inactive screens
are ignored.
2026-05-27 06:58:44 -07:00
Mitchell Hashimoto 7d4d1e5819
terminal: add configurable behaviors based on click count 2026-05-27 06:32:51 -07:00
Mitchell Hashimoto 82a73f2bf1
terminal: SelectionGesture press returns standard behaviors 2026-05-27 06:26:07 -07:00
Mitchell Hashimoto 9b00bb436a
terminal: better SelectionGesture docs 2026-05-27 06:20:22 -07:00
ghostty-vouch[bot] ce4128afc4
Update VOUCHED list (#12829)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/12827#discussioncomment-17075382)
from @trag1c.

Denounce: @Cznorth

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-27 12:33:56 +00:00
Tunglies 5368adcd29
macos: avoid duplicate appearance sync on tab focus
Close #12825

Skip the initial emissions from the focused surface appearance publishers after a tab focus change. The focused surface is already synced immediately, so the initial Combine values only repeat the same titlebar and background updates. Subsequent derived config and OSC background changes still resync the window appearance.
2026-05-27 19:43:41 +08:00
Mitchell Hashimoto f5f9d32d0a
terminal: SelectionGesture deep press 2026-05-26 21:36:22 -07:00
Mitchell Hashimoto df98b6d983
terminal: SelectionGesture autoscrollTick 2026-05-26 21:26:49 -07:00
Mitchell Hashimoto 141c7d44d2
SelectionGesture: release event 2026-05-26 21:17:03 -07:00
Mitchell Hashimoto 229f4c1f4f
terminal: SelectionGesture handles word/line drag 2026-05-26 21:04:22 -07:00
Mitchell Hashimoto c00cdd886b
SelectionGesture: drag events 2026-05-26 20:56:49 -07:00
Mitchell Hashimoto 33f1558801
core: mouse left release renderer lock made more coarse
This will make our selection gesture extraction a bit easier.
2026-05-26 17:02:20 -07:00
Adam Bouker 57d202066d macOS: clear stale OSC 11 background cache on config change
SurfaceView caches the background color set by OSC 11 in
backgroundColor. TerminalWindow.preferredBackgroundColor consults
that cache before falling back to derivedConfig.backgroundColor,
so once OSC 11 has fired the cached value masks any later config
change. After a light/dark theme auto-switch this leaves the
window chrome on the previous theme's color until the application
next emits OSC 11.

In ghosttyConfigDidChange, after updating derivedConfig, drop the
cache when it no longer matches the new config-derived background.
A subsequent ghosttyColorDidChange repopulates it as before, so
within-config OSC 11 behavior is unchanged.
2026-05-26 14:11:45 -05:00
Jeffrey C. Ollie 2e5ad917eb
apprt/gtk: fix audio-bell GStreamer thread leak (reuse one MediaFile per surface) (#12815)
## Problem

Every audio bell calls `gtk.MediaFile.newForFilename`, which spins up a
full GStreamer pipeline. The GTK4 GStreamer backend's GL sink starts
`gstglcontext`/`gldisplay-event` threads that are **never joined on
teardown**, so allocating a fresh `MediaFile` per ring leaks a pipeline
and ~4 threads on every bell. The old `notify::ended -> unref` handler
discarded the pipeline but did not (and could not) join those threads.

A long-running instance accumulated **705 threads over ~4h** of normal
use.

## Fix

Cache one `MediaFile` per surface (`priv.bell_media`), rebuilt only when
`bell-audio-path` changes and unref'd on `dispose`. Each bell now
replays the same pipeline via `seek(0)` + `play()` instead of creating a
new one. `seek(0)` is required so an ended stream plays again (cf.
#8957).

## Verification

Confirmed on a real running instance with the fix: GStreamer's global
element counter only ever reached `oggdemux4` over an hour of use (one
pipeline per bell-ringing surface, reused for every subsequent bell) and
the process thread count stayed flat — versus the per-bell growth
before.

## Commits

1. **The fix** — reuse one MediaFile per surface.
2. **Unit regression test** — guards the `bellMediaFile` reuse contract
(same path → same object, changed path → rebuild). Runs in the existing
`test-gtk` CI job; needs no display.
3. **End-to-end CI job** *(kept separate so it can be dropped
independently)* — `test/bell-leak.sh` + a `test-gtk-bell-leak` workflow
job that runs ghostty headless (Xvfb + software GL), rings 120 bells,
and fails if the thread count grows per-bell. It's heavier and more
environment-sensitive (needs Xvfb/Mesa/GStreamer on the runner), so it's
isolated for easy review/removal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-05-26 11:20:02 -05:00
Mitchell Hashimoto 14df684a70
core: adapt Surface to use SelectionGesture with press only 2026-05-25 15:48:20 -07:00
Mitchell Hashimoto d86ff37a58
terminal: SelectionGesture, but only with mouse press 2026-05-25 15:48:16 -07:00
Nikolay Bryskin 9910a1a475
test: add audio-bell thread-leak NixOS check (GNOME/Wayland)
Adds a bell-leak-check-gnome NixOS test (nix/tests.nix) that launches
Ghostty under GNOME on Wayland, rings 100 bells in the window, and fails
if the GUI process thread count grows per-bell — the end-to-end
signature of the GStreamer pipeline leak fixed in this branch. Verified
locally: growth of ~1 thread over 100 bells, vs ~+400 pre-fix.

Replaces the earlier Xvfb shell script + workflow job: per review, X11
support in GNOME is going away, and this belongs as a Nix check
alongside the other *-gnome tests rather than a standalone script.

The VM has no GPU, so it renders via llvmpipe; the test gives the guest
enough cores/RAM for software GL and tolerates the +new-window D-Bus
activation exceeding its client-side timeout (the window still comes up)
by waiting for the window rather than hard-failing on the call.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-26 01:06:21 +03:00
Nikolay Bryskin 0708f932a5
apprt/gtk: add regression test for audio-bell MediaFile reuse
Guards the contract that prevents the bell thread leak: bellMediaFile
must return the same cached MediaFile for an unchanged path and only
rebuild when the path changes. A revert to per-bell allocation (the
leak) would fail this. Runs in the existing test-gtk CI job; needs no
display or playback since the path bookkeeping is all that's asserted.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 23:43:04 +03:00
Nikolay Bryskin 0b6d91e531
apprt/gtk: reuse one audio-bell MediaFile per surface to fix thread leak
Each audio bell called gtk.MediaFile.newForFilename, which spins up a
full GStreamer pipeline. The GTK4 GStreamer backend's GL sink starts
gstglcontext/gldisplay-event threads that are never joined on teardown,
so allocating a MediaFile per ring leaked a pipeline and ~4 threads on
every bell. A long-running instance accumulated 705 threads over ~4h of
normal use.

Cache one MediaFile per surface (priv.bell_media), rebuilt only when
bell-audio-path changes and unref'd on dispose. Each bell now replays
the same pipeline via seek(0)+play() instead of creating a new one. The
notify::ended -> unref handler is removed: it was what discarded (and
leaked) a pipeline per ring. seek(0) is required so an ended stream
plays again (#8957).

Verified on a real instance: GStreamer's global element counter reached
only oggdemux4 over an hour of use (one pipeline per bell-ringing
surface, reused) and thread count stayed flat, versus per-bell growth
before.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-25 23:43:01 +03:00
ghostty-vouch[bot] a746d0f728
Update VOUCHED list (#12816)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/12815#issuecomment-4537093020)
from @jcollie.

Vouch: @nikicat

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-25 20:38:13 +00:00
Jon Parise ac69942cdc cli: rework +ssh-cache internals and user interface
This change primarily focused on a revised +ssh-cache user interface,
but it also reworks a bunch of the internals.

The primary CLI improvement is support for positional arguments and a
consistent list output format that includes both the ISO-formatted
timestamp and relative age.

    ghostty +ssh-cache                           # List all cached destinations
    ghostty +ssh-cache user@example.com          # Show that destination
    ghostty +ssh-cache example.com               # Show all users on that host
    ghostty +ssh-cache --add=user@example.com    # Manually add a destination
    ghostty +ssh-cache --remove=user@example.com # Remove a destination
    ghostty +ssh-cache --prune=30d               # Remove entries older than 30 days
    ghostty +ssh-cache --clear                   # Clear entire cache

Notable, we now support a --prune operation that replaces the previous
--expire-days flag that was never actually hooked up to anything (!!).
--prune also supports a wider range of Duration-based values.

We're also much more consistent with error codes: 0=success, 1=failure,
2=usage.

While working on those changes, I also reworked the cache internals,
particularly the code around timestamp handling and errors. For example,
I dropped the explicit error sets because they were growing unwieldy,
and in practice we only matched on a subset of those errors.

Lastly, overall test coverage should be much improved, especially around
the time- and allocation-related operations.
2026-05-25 16:00:21 -04:00
ghostty-vouch[bot] 2d0fb81751
Update VOUCHED list (#12813)
Triggered by [discussion
comment](https://github.com/ghostty-org/ghostty/discussions/12793#discussioncomment-17052752)
from @bo2themax.

Vouch: @LePips

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-25 17:54:35 +00:00
Mitchell Hashimoto cb28160b5a
elvish: remove community maintenance note (#12812)
The Elvish integration is currently actively maintained by the Ghostty
maintainers. Contributions are of course still welcome.
2026-05-25 09:07:19 -07:00
Mitchell Hashimoto 53e400ad85
cli: fix readEntries leak and double-free (#12811)
readEntries had two memory bugs on the allocation failure path, both
only reachable under OOM:

- The map itself was never freed if we ran into an allocation failure
- The unconditional `errdefer`s for the dupe'd hostname and terminfo
values could double-free if there was a later allocation failure.

This change restructures this function so that these values are dupe'd
up-front, and then their ownership is tracked using optionals that can
be null'ed out once their ownership is transferred into the map.

Both of these cases are now covered by unit tests.
2026-05-25 09:07:09 -07:00
Jon Parise 16d7c8f2b4 elvish: remove community maintenance note
The Elvish integration is currently actively maintained by the Ghostty
maintainers. Contributions are of course still welcome.
2026-05-25 11:56:37 -04:00
Jon Parise a5550a2dcb cli: fix readEntries leak and double-free
readEntries had two memory bugs on the allocation failure path, both
only reachable under OOM:

- The map itself was never freed if we ran into an allocation failure
- The unconditional `errdefer`s for the dupe'd hostname and terminfo
  values could double-free if there was a later allocation failure.

This change restructures this function so that these values are dupe'd
up-front, and then their ownership is tracked using optionals that can
be null'ed out once their ownership is transferred into the map.

Both of these cases are now covered by unit tests.
2026-05-25 11:49:01 -04:00
ghostty-vouch[bot] ae52f97dca
Update VOUCHED list (#12809)
Triggered by
[comment](https://github.com/ghostty-org/ghostty/issues/12807#issuecomment-4534655288)
from @pluiedev.

Denounce: @eric-assetpass

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-05-25 13:31:49 +00:00