diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 6a0f369c9..6d0cc21be 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -405,21 +405,24 @@ extension Ghostty { get: { searchState?.needle ?? "" }, set: { searchState?.needle = $0 } )) - .textFieldStyle(.plain) - .frame(width: 180) - .padding(.horizontal, 8) - .padding(.vertical, 6) - .background(Color.primary.opacity(0.1)) - .cornerRadius(6) - .focused($isSearchFieldFocused) - .onSubmit { - guard let surface = surfaceView.surface else { return } - let action = "navigate_search:next" - ghostty_surface_binding_action(surface, action, UInt(action.count)) - } - .onExitCommand { - Ghostty.moveFocus(to: surfaceView) - } + .textFieldStyle(.plain) + .frame(width: 180) + .padding(.horizontal, 8) + .padding(.vertical, 6) + .background(Color.primary.opacity(0.1)) + .cornerRadius(6) + .focused($isSearchFieldFocused) + .onExitCommand { + Ghostty.moveFocus(to: surfaceView) + } + .backport.onKeyPress(.return) { modifiers in + guard let surface = surfaceView.surface else { return .ignored } + let action = modifiers.contains(.shift) + ? "navigate_search:previous" + : "navigate_search:next" + ghostty_surface_binding_action(surface, action, UInt(action.count)) + return .handled + } Button(action: { guard let surface = surfaceView.surface else { return } @@ -526,7 +529,7 @@ extension Ghostty { } } } - + /// A surface is terminology in Ghostty for a terminal surface, or a place where a terminal is actually drawn /// and interacted with. The word "surface" is used because a surface may represent a window, a tab, /// a split, a small preview pane, etc. It is ANYTHING that has a terminal drawn to it. diff --git a/macos/Sources/Helpers/Backport.swift b/macos/Sources/Helpers/Backport.swift index a28be15ae..8c43652e4 100644 --- a/macos/Sources/Helpers/Backport.swift +++ b/macos/Sources/Helpers/Backport.swift @@ -18,6 +18,12 @@ extension Backport where Content: Scene { // None currently } +/// Result type for backported onKeyPress handler +enum BackportKeyPressResult { + case handled + case ignored +} + extension Backport where Content: View { func pointerVisibility(_ v: BackportVisibility) -> some View { #if canImport(AppKit) @@ -42,6 +48,24 @@ extension Backport where Content: View { return content #endif } + + /// Backported onKeyPress that works on macOS 14+ and is a no-op on macOS 13. + func onKeyPress(_ key: KeyEquivalent, action: @escaping (EventModifiers) -> BackportKeyPressResult) -> some View { + #if canImport(AppKit) + if #available(macOS 14, *) { + return content.onKeyPress(key, phases: .down, action: { keyPress in + switch action(keyPress.modifiers) { + case .handled: return .handled + case .ignored: return .ignored + } + }) + } else { + return content + } + #else + return content + #endif + } } enum BackportVisibility {