diff --git a/macos/Sources/App/macOS/AppDelegate.swift b/macos/Sources/App/macOS/AppDelegate.swift
index 9700a31ae..05d516811 100644
--- a/macos/Sources/App/macOS/AppDelegate.swift
+++ b/macos/Sources/App/macOS/AppDelegate.swift
@@ -1,4 +1,5 @@
import AppKit
+import Combine
import SwiftUI
import UserNotifications
import OSLog
@@ -39,6 +40,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuUndo: NSMenuItem?
@IBOutlet private var menuRedo: NSMenuItem?
+ @IBOutlet private var menuCut: NSMenuItem?
@IBOutlet private var menuCopy: NSMenuItem?
@IBOutlet private var menuPaste: NSMenuItem?
@IBOutlet private var menuPasteSelection: NSMenuItem?
@@ -154,6 +156,8 @@ class AppDelegate: NSObject,
private let appIconUpdater = AppIconUpdater()
@MainActor private lazy var menuShortcutManager = Ghostty.MenuShortcutManager()
+ /// A observer for resetting menu shortcuts based on firstResponder
+ private var resetMenuObserver: Any?
override init() {
#if DEBUG
@@ -209,6 +213,8 @@ class AppDelegate: NSObject,
toggleSecureInput(self)
}
+ setupRestorableMenuItems()
+
// Initial config loading
ghosttyConfigDidChange(config: ghostty.config)
@@ -1128,6 +1134,50 @@ extension AppDelegate {
self.menuFindParent?.setImageIfDesired(systemSymbolName: "text.page.badge.magnifyingglass")
}
+ /// Save restorable menu items and restore them whenever the first responder is not a terminal surface
+ ///
+ /// If you plan to add more items here, make sure you add the default shortcut in MainMenu.xib
+ @MainActor private func setupRestorableMenuItems() {
+ [
+ (menuUndo, "undo"),
+ (menuRedo, "redo"),
+ (menuCut, nil),
+ (menuCopy, "copy_to_clipboard"),
+ (menuPaste, "paste_from_clipboard"),
+ (menuSelectAll, "select_all"),
+ ].forEach(menuShortcutManager.saveRestorableMenuItem(_:action:))
+
+ // The reason we're using publisher here instead of `windowDidBecomeKey`
+ // is that we want reset the observer whenever the key window is changed.
+ //
+ // I had a case where help menu's window becomes key with terminal window opened then moving away from help menu doesn't trigger another notification.
+ resetMenuObserver = NSApp.publisher(for: \.keyWindow)
+ .flatMap {
+ $0?.publisher(for: \.firstResponder, options: [.new])
+ .map { $0 is Ghostty.SurfaceView }
+ .eraseToAnyPublisher() ??
+ Just(true).eraseToAnyPublisher()
+ // When the keyWindow is nil, we want to re-sync them
+ }
+ .removeDuplicates()
+ .sink { [weak self] isSurfaceFocused in
+ guard let self else { return }
+ if isSurfaceFocused {
+ menuShortcutManager.reSyncRestoredMenuShortcuts(config: ghostty.config)
+ } else {
+ // Restore for non terminal responders, like:
+ // 1. About Window
+ // 2. Alert modal
+ // 3. ConfigurationErrors
+ // 4. InlineTitleEditor
+ // 5. SearchOverlay
+ // 6. CommandPalette
+ // 7. Help search
+ menuShortcutManager.restoreMenuShortcuts()
+ }
+ }
+ }
+
/// Sync all of our menu item keyboard shortcuts with the Ghostty configuration.
@MainActor private func syncMenuShortcuts(_ config: Ghostty.Config) {
guard ghostty.readiness == .ready else { return }
@@ -1152,6 +1202,7 @@ extension AppDelegate {
syncMenuShortcut(config, action: "undo", menuItem: self.menuUndo)
syncMenuShortcut(config, action: "redo", menuItem: self.menuRedo)
+ syncMenuShortcut(config, action: nil, menuItem: self.menuCut)
syncMenuShortcut(config, action: "copy_to_clipboard", menuItem: self.menuCopy)
syncMenuShortcut(config, action: "paste_from_clipboard", menuItem: self.menuPaste)
syncMenuShortcut(config, action: "paste_from_selection", menuItem: self.menuPasteSelection)
@@ -1200,7 +1251,7 @@ extension AppDelegate {
reloadDockMenu()
}
- @MainActor private func syncMenuShortcut(_ config: Ghostty.Config, action: String, menuItem: NSMenuItem?) {
+ @MainActor private func syncMenuShortcut(_ config: Ghostty.Config, action: String?, menuItem: NSMenuItem?) {
menuShortcutManager.syncMenuShortcut(config, action: action, menuItem: menuItem)
}
@@ -1285,7 +1336,8 @@ extension AppDelegate: NSMenuItemValidation {
item.title = "Redo"
}
return undoManager.canRedo
-
+ case menuCut?.action:
+ return false
default:
return true
}
diff --git a/macos/Sources/App/macOS/MainMenu.xib b/macos/Sources/App/macOS/MainMenu.xib
index 30cd985db..2ec221ba7 100644
--- a/macos/Sources/App/macOS/MainMenu.xib
+++ b/macos/Sources/App/macOS/MainMenu.xib
@@ -25,6 +25,7 @@