This was an unused codepath and it complicates some things I'd like to
do, such as resetting our pools during resize. It complicates those
paths because if a user provides a pool we can't reset it (because other
things might be in it). It's best to own the pools. And since we didn't
reuse pools anyway, let's remove that.
Note this was previously used by our old render state mechanism (before
`terminal.RenderState`) as a way for the renderer to speed up clones by
having a preheated pool that was likely the right size before every
frame. Since we changed methods, we don't need it.
Fixes: #10196
If user invokes `search_selection` the contents of that string are no
longer ignored.
I'm not very familar with how apprt actions are sent to macos to ensure
the defer free will work there, but it is required for GTK otherwise we
are leaking the memory
I'm also open to making the string optional instead of checking against
`""` thats just how the current `start_search` action was sending its
needle (which I assume is better for macos?)
The text is also highlighted currently due to `grabFocus` always
selecting the search contents, not sure how the macos part works but
that could easily be changed to change the contents after focus was
grabbed
https://github.com/ghostty-org/ghostty/blob/main/src/apprt/gtk/class/search_overlay.zig#L233-L241
This _finally_ resolves#9962 (and the myriad of dupes).
The core issue was that when a non-standard size page is reused as part
of our scrollback pruning, it was resized to be standard size, which
caused our future frees to believe it was pooled and not call `munmap`
properly.
The solution I chose was to never reuse non-standard sized pages. If
during scrollback pruning we detect a non-standard page, we destroy it
and re-alloc. This frees the old memory and reuses pool memory for the
new page.
As part of this, I also introduced a custom page allocator that uses
macOS's mach kernel virtual memory tagging feature to specifically tag
our PageList memory. I was able to use this in conjunction with
Instruments and `footprint` to verify that our PageList memory was
previously not freed and is now successfully freed.
**No AI was used in my work here.** AI was used by others in their
analysis of this issue that I used the results of to help guide me and
give me other things to consider, but the ultimate understanding and fix
was all done via my own meat sticks.
## Detailed Explainer
Ghostty uses a memory pool of fixed-size `mmap`-ed pages to serve as the
backing memory for our terminal. If the terminal requires a non-standard
(larger) amount of memory due to an abundance of emoji, styles,
hyperlinks, etc. then we allocate non-pooled pages directly with `mmap`.
When freeing our pages, if it is `<= standard size` we just put it back
into the memory pool. If it is larger, we `munmap` it.
This explains and defines both a _standard_ and therefore _non-standard_
page.
Ghostty also has the concept of a "scrollback limit" (exposed to the
user via the `scrollback-limit` config). This caps the size of our
scrollback or history. When we reach this limit, we have a trick that we
do: to avoid allocation, we reuse the oldest page in the history.
Unfortunately, as part of this process, we were resizing the underlying
memory to be standard size again. This was causing our future frees to
believe this was pooled memory, and never unmap it. This was the main
source of the leak.
## Thanks
Big shout out to @grishy for being the person that finally got me a
reproduction so I could analyze the issue for myself. His own analysis
got to the same conclusion as me but thanks to the reproduction I was
able to verify both our understandings independently.
A handful of improvements. See individual commits.
1. **Actually compare values for the binding set.** This sounds crazy,
but up until now (for _years_) we've only compared _the hash value_ of a
trigger or action for our binding set. It's actually astounding this
hasn't bit us or at least not that we know of. This could result in
different triggers overwriting each other. Anyways, we actually compare
them now.
2. **Use an `ArrayHashMap` for sets.** This has been on the back burner
for awhile. Using an array hash map is a good idea in general (see:
https://github.com/ziglang/zig/issues/17851) but it also is a nicer API
for our use case and cleaned things up.
All unit tests pass, many new unit tests added particularly for equality
comparison. Hopeful this doesn't regress any bindings but this is the
right path forward so we should fix those if they come up.
**AI disclosure:** AI helped write the deepEqual unit tests, otherwise
everything else is certified meat.
This is recommended for ongoing performance:
https://github.com/ziglang/zig/issues/17851
Likely not an issue for this particular use case which is why it never
bit us; we don't actively modify this map much once it is created. But,
its still good hygiene and ArrayHashMap made some of the API usage
nicer.
We previously only compared the hashes for triggers and actions for hash
map equality. I'm genuinely surprised this never bit us before because
it can result in false positives when two different values have the same
hash. Fix that up!
Fixes#10239
The main menu uses first responder which will hit a surface. If a
binding would target `all:` we need to avoid it. To achieve this, our
`is_key_binding` API now returns information about the binding (if any).
I've cleaned up the Swift to implement this.
In doing this I realized we have to do the same for `performable` since
main menus will effectively always consume.
Fixes#10227
The big comment in `search/screen.zig` describes the solution well. The
problem is that our search is discrete by page and a page can contain
some amount of history as well.
For zero-scrollback screens, we need to fully prune any history lines.
For everyone else, everything in the PageList is scrollable and visible
so we should search it.
The big comment in `search/screen.zig` describes the solution well. The
problem is that our search is discrete by page and a page can contain
some amount of history as well.
For zero-scrollback screens, we need to fully prune any history lines.
For everyone else, everything in the PageList is scrollable and visible
so we should search it.
Partial #10227
This fixes the scrollbar part of #10227, but not the search part.
The way PageList works is that max_size is advisory: we always allocate
on page boundaries so we always have _some_ extra space (usually, unless
you ask for a byte-perfect max size). Normally this is fine, it doesn't
cause any real issues. And this has been true since Ghostty 1.0.
But with the introduction of scrollbars (and search), we were exposing
this hidden space to the user. To fix this, the easiest approach is to
special-case the zero-scrollback scenario, since it is already
documented that scrollback limit is not _exact_ and is subject to some
minimum allocations. But with zero-scrollback we really expect NOTHING.
Partial #10227
This fixes the scrollbar part of #10227, but not the search part.
The way PageList works is that max_size is advisory: we always allocate
on page boundaries so we always have _some_ extra space (usually, unless
you ask for a byte-perfect max size). Normally this is fine, it doesn't
cause any real issues.
But with the introduction of scrollbars (and search), we were exposing
this hidden space to the user. To fix this, the easiest approach is to
special-case the zero-scrollback scenario, since it is already
documented that scrollback limit is not _exact_ and is subject to some
minimum allocations. But with zero-scrollback we really expect NOTHING.
This replaces the OSC parser with one that only uses a state machine to
determine which OSC is being handled, rather than parsing the whole OSC.
Once the OSC command is determined the remainder of the data is stored
in a buffer until the terminator is found. The data is then parsed to
determine the final OSC command.
This PR introduces a new `key-remap` configuration option that allows
users to remap modifier keys at the application level without affecting
system-wide settings.
## Issue
Closes#5160
## Usage
```ini
# Make Ctrl act as Cmd within Ghostty
key-remap = ctrl=super
# Swap Ctrl and Cmd
key-remap = ctrl=super
key-remap = super=ctrl
# Remap only left Alt to Ctrl
key-remap = left_alt=ctrl
```
### Supported Modifiers
| Generic | Left-sided | Right-sided |
|---------|------------|-------------|
| `ctrl` / `control` | `left_ctrl` | `right_ctrl` |
| `alt` / `opt` / `option` | `left_alt` | `right_alt` |
| `shift` | `left_shift` | `right_shift` |
| `super` / `cmd` / `command` | `left_super` | `right_super` |
## Behavior
Per the issue specification:
- **One-way remapping**: `ctrl=super` means Ctrl becomes Super, but
Super remains Super
- **Non-transitive**: `ctrl=super` + `alt=ctrl` → Alt becomes Ctrl (NOT
Super)
- **Sided support**: Generic modifiers match both sides; use `left_*` or
`right_*` for specific sides
- **Immediate effect**: Changes apply on config reload
## Limitations
- Implemented in Zig core, works on both macOS and Linux
- Only modifier keys can be remapped (not regular keys)