mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-06 07:38:21 +00:00
macOS: fix focus update when using search or command palette (#11978)
This fixes two things: 1. Surface focus state is not consistent with first responder state when the search bar is open. > Reproduce: Open search, switch to another app and back, observe the cursor state of the surface. > And after switching back, `cmd+shift+f` will close the search bar, surface will become focused but not first responder, so it will not accept any input 2. Command palette is not focused when built with Xcode 26.4 (26.3 works fine). > This is weird to me, because the tip (and built with 26.3) works fine. I guess it's related to the SDK update? I couldn’t be sure what went wrong, but dispatching it to the next loop works as previously. > Also cleaned some previous checks when quickly open and reopen. > This fix works great both with 26.4 and 26.3 https://github.com/user-attachments/assets/c9cf4c1b-60d9-4c71-802c-55f82e40eec7
This commit is contained in:
@@ -64,7 +64,6 @@ struct CommandPaletteView: View {
|
||||
@State private var query = ""
|
||||
@State private var selectedIndex: UInt?
|
||||
@State private var hoveredOptionID: UUID?
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
|
||||
// The options that we should show, taking into account any filtering from
|
||||
// the query. Options with matching leadingColor are ranked higher.
|
||||
@@ -105,7 +104,7 @@ struct CommandPaletteView: View {
|
||||
}
|
||||
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
CommandPaletteQuery(query: $query, isTextFieldFocused: _isTextFieldFocused) { event in
|
||||
CommandPaletteQuery(query: $query) { event in
|
||||
switch event {
|
||||
case .exit:
|
||||
isPresented = false
|
||||
@@ -178,27 +177,13 @@ struct CommandPaletteView: View {
|
||||
.padding()
|
||||
.environment(\.colorScheme, scheme)
|
||||
.onChange(of: isPresented) { newValue in
|
||||
// Reset focus when quickly showing and hiding.
|
||||
// macOS will destroy this view after a while,
|
||||
// so task/onAppear will not be called again.
|
||||
// If you toggle it rather quickly, we reset
|
||||
// it here when dismissing.
|
||||
isTextFieldFocused = newValue
|
||||
if !isPresented {
|
||||
if !newValue {
|
||||
// This is optional, since most of the time
|
||||
// there will be a delay before the next use.
|
||||
// To keep behavior the same as before, we reset it.
|
||||
query = ""
|
||||
}
|
||||
}
|
||||
.task {
|
||||
// Grab focus on the first appearance.
|
||||
// This happens right after onAppear,
|
||||
// so we don’t need to dispatch it again.
|
||||
// Fixes: https://github.com/ghostty-org/ghostty/issues/8497
|
||||
// Also fixes initial focus while animating.
|
||||
isTextFieldFocused = isPresented
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a score (0.0 to 1.0) indicating how well a color matches a search query color name.
|
||||
@@ -234,10 +219,9 @@ private struct CommandPaletteQuery: View {
|
||||
var onEvent: ((KeyboardEvent) -> Void)?
|
||||
@FocusState private var isTextFieldFocused: Bool
|
||||
|
||||
init(query: Binding<String>, isTextFieldFocused: FocusState<Bool>, onEvent: ((KeyboardEvent) -> Void)? = nil) {
|
||||
init(query: Binding<String>, onEvent: ((KeyboardEvent) -> Void)? = nil) {
|
||||
_query = query
|
||||
self.onEvent = onEvent
|
||||
_isTextFieldFocused = isTextFieldFocused
|
||||
}
|
||||
|
||||
enum KeyboardEvent {
|
||||
@@ -280,6 +264,17 @@ private struct CommandPaletteQuery: View {
|
||||
.onExitCommand { onEvent?(.exit) }
|
||||
.onMoveCommand { onEvent?(.move($0)) }
|
||||
.onSubmit { onEvent?(.submit) }
|
||||
.onAppear {
|
||||
// Grab focus on the first appearance.
|
||||
// Debug and Release build using Xcode 26.4,
|
||||
// has same issue again
|
||||
// Fixes: https://github.com/ghostty-org/ghostty/issues/8497
|
||||
// SearchOverlay works magically as expected, I don't know
|
||||
// why it's different here, but dispatching to next loop fixes it
|
||||
DispatchQueue.main.async {
|
||||
isTextFieldFocused = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,9 +301,8 @@ class BaseTerminalController: NSWindowController,
|
||||
// Our focus state requires that this window is key and our currently
|
||||
// focused surface is the surface in this view.
|
||||
let focused: Bool = (window?.isKeyWindow ?? false) &&
|
||||
!commandPaletteIsShowing &&
|
||||
focusedSurface != nil &&
|
||||
surfaceView == focusedSurface!
|
||||
surfaceView == focusedSurface &&
|
||||
surfaceView.isFirstResponder
|
||||
surfaceView.focusDidChange(focused)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ extension NSView {
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
/// Returns true if this view is currently the first responder
|
||||
var isFirstResponder: Bool {
|
||||
window?.firstResponder === self
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Screenshot
|
||||
|
||||
Reference in New Issue
Block a user