Fixes#189
This adds the UI for search to GTK. There is still polish to be done in
follow-ups but this makes search work well with GTK to start!
**AI disclosure:** Believe it or not, almost this entire PR was
AI-written. Amp did an excellent job looking at our existing codebase,
comparing it to the macOS codebase, writing blueprint files, etc. I
reviewed everything written, modified some basics, and verified it
manually and under Valgrind.
## Demo
<img width="1654" height="1234" alt="CleanShot 2025-11-29 at 20 52
11@2x"
src="https://github.com/user-attachments/assets/0eb38367-398f-4165-9838-7a35465857bc"
/>
## Future Improvements
- When dragging the overlay, we should change the cursor and show drop
targets
- There's probably some small stylistic tweaks we can make to this
- I'm not sure the CSS is right for both light and dark modes so we may
need to tweak that
This fixes memory corruption where future matches on a fully dirty row
would write highlights out of bounds. It was easy to reproduce in debug
by searching for `$` in `ghostty +boo`
When columns shrink during resize-without-reflow, page.size.cols is
updated but page.capacity.cols retains the old larger value. When
adjustCapacity later runs (e.g., to expand style/grapheme storage),
it was creating a new page using page.capacity which has the stale
column count, causing size.cols to revert to the old value.
This caused a crash in render.zig where an assertion checks that
page.size.cols matches PageList.cols.
Fix by explicitly copying page.size.cols to the new page after
creation, matching how size.rows is already handled.
Amp-Thread-ID: https://ampcode.com/threads/T-976bc49a-7bfd-40bd-bbbb-38f66fc925ff
Co-authored-by: Amp <amp@ampcode.com>
This fixes memory corruption where future matches on a fully dirty row
would write highlights out of bounds. It was easy to reproduce in debug
by searching for `$` in `ghostty +boo`
not an accepted issue
https://github.com/ghostty-org/ghostty/discussions/9745 but it seemed
simple enough to add, pretty much copies how the previewer sorts based
on color
This allows --color argument to work when using --plain to sort the list
returned
Fixes#2660
Rather than calculate our window frame size based on various chrome
calculations, we now utilize SwiftUI layouts and view intrinsic content
sizes with `setContentSize` to setup our content size ignoring all our
other widgets.
I'm sure there's some edge cases I'm missing here but this should be a
whole lot more reliable on the whole.
Fixes#2660
Rather than calculate our window frame size based on various chrome
calculations, we now utilize SwiftUI layouts and view intrinsic content
sizes with `setContentSize` to setup our content size ignoring all our
other widgets.
I'm sure there's some edge cases I'm missing here but this should be a
whole lot more reliable on the whole.
This fixes regression of #5690, which kind of comes from #9576. 05b42919d5 (before #9576) has weird behaviours too, restored windows are not properly focused. With this pr, we only order `selectedWindow` front so we won't mess up with its selection state and the order of the tab group.
This means that the pin we're using to track our position in the
PageList was part of a node that got reused/recycled at some point. We
can't make any meaningful guarantees about the state of the PageList.
This only happens with scrollback pruning so we can treat it as a
complete search.
This means that the pin we're using to track our position in the
PageList was part of a node that got reused/recycled at some point. We
can't make any meaningful guarantees about the state of the PageList.
This only happens with scrollback pruning so we can treat it as a
complete search.
Swift conveniently converts strings to UTF-8 encoded cstrings when
passing them to external functions, however our libghostty functions
also take a length and we were using String.count for that, which
returns the number of _characters_ not the byte length, which caused
searches with multi-byte characters to get truncated.
I went ahead and changed _all_ invocations that pass a string length to
use the utf-8 byte length even if the string is comptime-known and all
ASCII, just so that it's proper and if someone copies one of the calls
in the future for user-inputted data they don't reproduce this bug.
ref:
https://developer.apple.com/documentation/swift/string/counthttps://developer.apple.com/documentation/swift/stringprotocol/lengthofbytes(using:)
Swift conveniently converts strings to UTF-8 encoded cstrings when
passing them to external functions, however our libghostty functions
also take a length and we were using String.count for that, which
returns the number of _characters_ not the byte length, which caused
searches with multi-byte characters to get truncated.
I went ahead and changed _all_ invocations that pass a string length to
use the utf-8 byte length even if the string is comptime-known and all
ASCII, just so that it's proper and if someone copies one of the calls
in the future for user-inputted data they don't reproduce this bug.
ref:
https://developer.apple.com/documentation/swift/string/counthttps://developer.apple.com/documentation/swift/stringprotocol/lengthofbytes(using:)
Move the search result counter inside the search text field using an
overlay, preventing layout shift when results appear.
**Before:** The counter appeared as a separate element in the HStack,
causing the text field to shift when results loaded.
**After:** The counter is overlaid inside the text field on the right
side with reserved padding, eliminating layout shift.
---
**AI Disclosure: The was entirely authored with Claude Code,
specifically with Claude Opus 4.5.**
Move the search result counter (e.g. "1/30") inside the search text
field using an overlay, preventing layout shift when results appear.
This PR was authored with Claude Code.
#189 for macOS
This adds a search GUI for macOS, including macOS-standard menu bar
items, and keybindings that match other native macOS applications such
as Terminal app, Safari, and others. This introduces a new keybinding
action `start_search` to start a blank search for GUIs.
This PR also resolves a number of minor issues found in the search
subsystem and renderer related to search from prior PRs. This should
result in overall improved search stability. **Please note there are
still known issues (bottom of this PR).**
> [!WARNING]
>
> **A note on stability:** I know a lot of people are eager to have this
feature, and I'm excited
> for this feature to soon be available in tip releases. But note that
new features like this are
> always filled with performance issues, bugs, crashes, etc. That's the
point of tip releases:
> to find and address these before wider availability. We will do our
best to respond rapidly to
> major issues found in tip, but don't expect perfect functionality
immediately!
## Demo
https://github.com/user-attachments/assets/3b81752e-d7e5-4875-9864-92497333b23e
> [!NOTE]
>
> You can drag the search window to any of the four corners if its
getting in the way of reading the terminal.
## Known Issues
TODO prior to this PR merging:
- [x] Single-byte search terms cause a crash since our sliding window
can't handle it. This PR temporarily requires search terms with length
2+ before starting a search to avoid it. 😄
- [x] The way `ScreenSearch` prunes history is fundamental unsafe. With
a rapidly growing screen that could reach history limits and an active
search at the same time, Ghostty is almost guaranteed to crash
currently. The workaround is to not search actively scrolling screens
(new data) for now.