Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Sources/App/App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,27 @@ enum ApplicationShortcutMenuModel {
.toggleSidebar,
]

/// Find targets the Application Log sheet's own terminal, so that sheet
/// must not take the Find bindings away from the menu.
static let logViewerActions: Set<ApplicationShortcutAction> = [
.find,
.findNext,
.findPrevious,
.hideFindBar,
]

static func sheetSuppressesBinding(
for action: ApplicationShortcutAction,
settingsPresented: Bool,
commandPalettePresented: Bool,
logViewerPresented: Bool
) -> Bool {
if settingsPresented || commandPalettePresented {
return true
}
return logViewerPresented && !logViewerActions.contains(action)
}

static func items(
_ actions: [ApplicationShortcutAction],
shortcuts: ResolvedApplicationShortcuts
Expand Down Expand Up @@ -214,6 +235,7 @@ struct GhosthubApp: App {
AppMenuCommands(updateController: updateController)
CommandGroup(replacing: .toolbar) {}
editMenuCommands
FindMenuCommands()
FileMenuCommands(applicationDelegate: appDelegate)
SessionMenuCommands()
ViewMenuCommands()
Expand Down
5 changes: 3 additions & 2 deletions Sources/App/BorrowedHerdrSessionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,8 @@ private struct NativeHerdrTerminalView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(TerminalSurfaceBackdrop.color(for: backgroundAppearance))
.overlay(alignment: .top) {
if let message = surfaceView.paneSplitErrorMessage {
NativePaneSplitErrorOverlay(message: message)
if let message = surfaceView.terminalOperationErrorMessage {
NativeTerminalOperationErrorOverlay(message: message)
}
}
.onAppear {
Expand All @@ -207,5 +207,6 @@ private struct NativeHerdrTerminalView: View {
\.terminalHasEffectiveKeyboardFocus,
surfaceView.hasEffectiveKeyboardFocus
)
.focusedSceneObject(surfaceView.terminalFindController)
}
}
14 changes: 12 additions & 2 deletions Sources/App/BorrowedTmuxSessionView.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import GhosthubTransport
import GhosthubTerminal
import GhosthubTerminalSupport
import GhosthubTmux
import GhosthubUI
import SwiftUI
Expand Down Expand Up @@ -253,10 +254,18 @@ private struct NativeTmuxTerminalView: View {
.frame(maxWidth: .infinity, maxHeight: .infinity)
.background(TerminalSurfaceBackdrop.color(for: backgroundAppearance))
.overlay(alignment: .top) {
if let message = surfaceView.paneSplitErrorMessage {
NativePaneSplitErrorOverlay(message: message)
if let message = surfaceView.terminalOperationErrorMessage {
NativeTerminalOperationErrorOverlay(message: message)
}
}
.overlay(alignment: .topTrailing) {
TerminalFindOverlay(
controller: surfaceView.terminalFindController,
restoreTerminalFocus: { [weak surfaceView] in
surfaceView?.requestKeyboardFocus()
}
)
}
.onAppear {
surfaceView.registerPaneCloseRequestObserver(
id: observerID,
Expand All @@ -275,5 +284,6 @@ private struct NativeTmuxTerminalView: View {
\.terminalHasEffectiveKeyboardFocus,
surfaceView.hasEffectiveKeyboardFocus
)
.focusedSceneObject(surfaceView.terminalFindController)
}
}
1 change: 1 addition & 0 deletions Sources/App/BorrowedZellijSessionView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -197,5 +197,6 @@ private struct NativeZellijTerminalView: View {
\.terminalHasEffectiveKeyboardFocus,
surfaceView.hasEffectiveKeyboardFocus
)
.focusedSceneObject(surfaceView.terminalFindController)
}
}
74 changes: 68 additions & 6 deletions Sources/App/MenuCommands.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct MenuActionContext {
for: action,
sceneIsFocused:
sceneModel?.acceptsApplicationShortcutKeyEvents == true,
hasAttachedSheet: sceneHasAttachedSheet,
hasAttachedSheet: sheetSuppressesBinding(for: action),
actionIsAvailable: actionIsAvailable
)?.swiftUI
}
Expand All @@ -46,16 +46,21 @@ struct MenuActionContext {
for: action,
sceneIsFocused:
sceneModel?.acceptsApplicationShortcutKeyEvents == true,
hasAttachedSheet: sceneHasAttachedSheet,
hasAttachedSheet: sheetSuppressesBinding(for: action),
actionIsAvailable: sceneModel?.canSplitActivePane == true
)?.swiftUI
}

var sceneHasAttachedSheet: Bool {
private func sheetSuppressesBinding(
for action: ApplicationShortcutAction
) -> Bool {
guard let sceneModel else { return false }
return sceneModel.isSettingsPresented
|| sceneModel.isCommandPalettePresented
|| sceneModel.isLogViewerPresented
return ApplicationShortcutMenuModel.sheetSuppressesBinding(
for: action,
settingsPresented: sceneModel.isSettingsPresented,
commandPalettePresented: sceneModel.isCommandPalettePresented,
logViewerPresented: sceneModel.isLogViewerPresented
)
}

func invoke(_ action: ApplicationShortcutAction) {
Expand Down Expand Up @@ -108,6 +113,63 @@ struct AppMenuCommands: Commands {
}
}

struct FindMenuCommands: Commands {
@FocusedValue(\.sceneModel) private var focusedSceneModel
@FocusedObject private var findController: TerminalFindController?
@ObservedObject private var settingsStore = SettingsStore.shared

private var context: MenuActionContext {
MenuActionContext(
sceneModel: focusedSceneModel,
terminalHasEffectiveKeyboardFocus: nil,
settingsStore: settingsStore
)
}

var body: some Commands {
CommandGroup(after: .pasteboard) {
if let findController {
Divider()
Button("Find…") {
context.invoke(.find)
}
.keyboardShortcut(context.shortcut(
.find,
actionIsAvailable: findController.isAvailable
))
.disabled(!findController.isAvailable)

Button("Find Next") {
context.invoke(.findNext)
}
.keyboardShortcut(context.shortcut(
.findNext,
actionIsAvailable: findController.canNavigate
))
.disabled(!findController.canNavigate)

Button("Find Previous") {
context.invoke(.findPrevious)
}
.keyboardShortcut(context.shortcut(
.findPrevious,
actionIsAvailable: findController.canNavigate
))
.disabled(!findController.canNavigate)

Button("Hide Find Bar") {
context.invoke(.hideFindBar)
}
.keyboardShortcut(context.shortcut(
.hideFindBar,
actionIsAvailable: findController.isOpen
))
.disabled(!findController.isOpen)
}
}
}
}

struct FileMenuCommands: Commands {
let applicationDelegate: ApplicationDelegate
@FocusedValue(\.sceneModel) private var focusedSceneModel
Expand Down
5 changes: 3 additions & 2 deletions Sources/App/NativeHerdrSessionCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ final class NativeHerdrSessionCoordinator {
)
return nil
}
surface.terminalFindController = .unavailable
if let error = surface.launchError {
failSurfaceLaunch(
handle,
Expand Down Expand Up @@ -653,7 +654,7 @@ final class NativeHerdrSessionCoordinator {
guard attachments[handle.id]?.id == request.attachmentID,
launchedHandles.contains(handle.id)
else { continue }
request.surface.paneSplitErrorMessage = nil
request.surface.terminalOperationErrorMessage = nil
let failure = await paneSplitter.split(
request.shortcut,
target: request.target
Expand All @@ -662,7 +663,7 @@ final class NativeHerdrSessionCoordinator {
paneSplitWorkers[handle.id]?.id == workerID,
attachments[handle.id]?.id == request.attachmentID
else { return }
request.surface.paneSplitErrorMessage = failure?.localizedDescription
request.surface.terminalOperationErrorMessage = failure?.localizedDescription
if let failure {
invalidateUnusableConnection(
status: failure.status,
Expand Down
13 changes: 11 additions & 2 deletions Sources/App/NativeSessionAttachmentSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ protocol NativeSessionPaneSurfacing: AnyObject {
var paneSplitShortcutHandler: ((TerminalPaneSplitShortcut) -> Void)? {
get set
}
var paneSplitErrorMessage: String? { get set }
var terminalOperationErrorMessage: String? { get set }
var terminalFindController: TerminalFindController { get set }
var hasEffectiveKeyboardFocus: Bool { get }
var launchError: Error? { get }
/// True when `launchError` describes a transient condition that a later
/// attach can recover from, rather than a rejected launch.
var launchFailureIsRetryable: Bool { get }
var childExitCode: UInt32? { get }
func requestKeyboardFocus()
@discardableResult
func sizeForPreviewGrid(columns: Int, rows: Int) -> Bool
func clearPreviewGridSize()
Expand All @@ -31,15 +33,22 @@ extension NativeSessionPaneSurfacing {
set {}
}

var paneSplitErrorMessage: String? {
var terminalOperationErrorMessage: String? {
get { nil }
set {}
}

var terminalFindController: TerminalFindController {
get { .unavailable }
set {}
}

var hasEffectiveKeyboardFocus: Bool { false }

var launchFailureIsRetryable: Bool { false }

func requestKeyboardFocus() {}

@discardableResult
func sizeForPreviewGrid(columns _: Int, rows _: Int) -> Bool {
false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import SwiftUI

struct NativePaneSplitErrorOverlay: View {
struct NativeTerminalOperationErrorOverlay: View {
let message: String

var body: some View {
Expand Down
Loading
Loading