macos: hook up start_search apprt action to open search

pull/9709/head
Mitchell Hashimoto 2025-11-25 12:02:27 -08:00
parent aeaa8d4ead
commit bc44b187d6
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
4 changed files with 70 additions and 4 deletions

View File

@ -115,6 +115,18 @@ extension Ghostty.Action {
len = c.len
}
}
struct StartSearch {
let needle: String?
init(c: ghostty_action_start_search_s) {
if let needleCString = c.needle {
self.needle = String(cString: needleCString)
} else {
self.needle = nil
}
}
}
}
// Putting the initializer in an extension preserves the automatic one.

View File

@ -606,6 +606,9 @@ extension Ghostty {
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
closeAllWindows(app, target: target)
case GHOSTTY_ACTION_START_SEARCH:
startSearch(app, target: target, v: action.action.start_search)
case GHOSTTY_ACTION_TOGGLE_TAB_OVERVIEW:
fallthrough
case GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS:
@ -1641,6 +1644,29 @@ extension Ghostty {
}
}
private static func startSearch(
_ app: ghostty_app_t,
target: ghostty_target_s,
v: ghostty_action_start_search_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("start_search does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
let startSearch = Ghostty.Action.StartSearch(c: v)
DispatchQueue.main.async {
surfaceView.searchState = Ghostty.SurfaceView.SearchState(from: startSearch)
}
default:
assertionFailure()
}
}
private static func configReload(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@ -199,7 +199,9 @@ extension Ghostty {
#endif
// Search overlay
SurfaceSearchOverlay()
if surfaceView.searchState != nil {
SurfaceSearchOverlay(searchState: $surfaceView.searchState)
}
// Show bell border if enabled
if (ghostty.config.bellFeatures.contains(.border)) {
@ -387,10 +389,12 @@ extension Ghostty {
/// Search overlay view that displays a search bar with input field and navigation buttons.
struct SurfaceSearchOverlay: View {
@Binding var searchState: SurfaceView.SearchState?
@State private var searchText: String = ""
@State private var corner: Corner = .topRight
@State private var dragOffset: CGSize = .zero
@State private var barSize: CGSize = .zero
@FocusState private var isSearchFieldFocused: Bool
private let padding: CGFloat = 8
@ -404,6 +408,7 @@ extension Ghostty {
.padding(.vertical, 6)
.background(Color.primary.opacity(0.1))
.cornerRadius(6)
.focused($isSearchFieldFocused)
Button(action: {}) {
Image(systemName: "chevron.up")
@ -415,7 +420,9 @@ extension Ghostty {
}
.buttonStyle(.borderless)
Button(action: {}) {
Button(action: {
searchState = nil
}) {
Image(systemName: "xmark")
}
.buttonStyle(.borderless)
@ -424,6 +431,12 @@ extension Ghostty {
.background(.background)
.cornerRadius(8)
.shadow(radius: 4)
.onAppear {
if let needle = searchState?.needle {
searchText = needle
}
isSearchFieldFocused = true
}
.background(
GeometryReader { barGeo in
Color.clear.onAppear {
@ -770,3 +783,15 @@ extension FocusedValues {
typealias Value = OSSize
}
}
// MARK: Search State
extension Ghostty.SurfaceView {
class SearchState: ObservableObject {
@Published var needle: String = ""
init(from startSearch: Ghostty.Action.StartSearch) {
self.needle = startSearch.needle ?? ""
}
}
}

View File

@ -64,6 +64,9 @@ extension Ghostty {
// The currently active key sequence. The sequence is not active if this is empty.
@Published var keySequence: [KeyboardShortcut] = []
// The current search state. When non-nil, the search overlay should be shown.
@Published var searchState: SearchState? = nil
// The time this surface last became focused. This is a ContinuousClock.Instant
// on supported platforms.
@Published var focusInstant: ContinuousClock.Instant? = nil