macos: make sure we're not registering unnecessary undos
parent
b234cb2014
commit
973a2afdde
|
|
@ -64,6 +64,7 @@
|
||||||
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586366A2DF0A98900E04A10 /* Array+Extension.swift */; };
|
A586366B2DF0A98C00E04A10 /* Array+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586366A2DF0A98900E04A10 /* Array+Extension.swift */; };
|
||||||
A586366F2DF25D8600E04A10 /* Duration+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586366E2DF25D8300E04A10 /* Duration+Extension.swift */; };
|
A586366F2DF25D8600E04A10 /* Duration+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A586366E2DF25D8300E04A10 /* Duration+Extension.swift */; };
|
||||||
A58636712DF298FB00E04A10 /* ExpiringUndoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58636702DF298F700E04A10 /* ExpiringUndoManager.swift */; };
|
A58636712DF298FB00E04A10 /* ExpiringUndoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58636702DF298F700E04A10 /* ExpiringUndoManager.swift */; };
|
||||||
|
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A58636722DF4813000E04A10 /* UndoManager+Extension.swift */; };
|
||||||
A5874D992DAD751B00E83852 /* CGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5874D982DAD751A00E83852 /* CGS.swift */; };
|
A5874D992DAD751B00E83852 /* CGS.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5874D982DAD751A00E83852 /* CGS.swift */; };
|
||||||
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */; };
|
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */; };
|
||||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A59444F629A2ED5200725BBA /* SettingsView.swift */; };
|
||||||
|
|
@ -171,6 +172,7 @@
|
||||||
A586366A2DF0A98900E04A10 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = "<group>"; };
|
A586366A2DF0A98900E04A10 /* Array+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extension.swift"; sourceTree = "<group>"; };
|
A586366E2DF25D8300E04A10 /* Duration+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Duration+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A58636702DF298F700E04A10 /* ExpiringUndoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringUndoManager.swift; sourceTree = "<group>"; };
|
A58636702DF298F700E04A10 /* ExpiringUndoManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExpiringUndoManager.swift; sourceTree = "<group>"; };
|
||||||
|
A58636722DF4813000E04A10 /* UndoManager+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UndoManager+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A5874D982DAD751A00E83852 /* CGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGS.swift; sourceTree = "<group>"; };
|
A5874D982DAD751A00E83852 /* CGS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CGS.swift; sourceTree = "<group>"; };
|
||||||
A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWindow+Extension.swift"; sourceTree = "<group>"; };
|
A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSWindow+Extension.swift"; sourceTree = "<group>"; };
|
||||||
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
A59444F629A2ED5200725BBA /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||||
|
|
@ -447,6 +449,7 @@
|
||||||
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
C1F26EA62B738B9900404083 /* NSView+Extension.swift */,
|
||||||
A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */,
|
A5874D9C2DAD785F00E83852 /* NSWindow+Extension.swift */,
|
||||||
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
A5985CD62C320C4500C57AD3 /* String+Extension.swift */,
|
||||||
|
A58636722DF4813000E04A10 /* UndoManager+Extension.swift */,
|
||||||
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
A5CC36142C9CDA03004D6760 /* View+Extension.swift */,
|
||||||
);
|
);
|
||||||
path = Extensions;
|
path = Extensions;
|
||||||
|
|
@ -683,6 +686,7 @@
|
||||||
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
|
A54B0CEB2D0CFB4C00CBEFF8 /* NSImage+Extension.swift in Sources */,
|
||||||
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
A5874D9D2DAD786100E83852 /* NSWindow+Extension.swift in Sources */,
|
||||||
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
A54D786C2CA7978E001B19B1 /* BaseTerminalController.swift in Sources */,
|
||||||
|
A58636732DF4813400E04A10 /* UndoManager+Extension.swift in Sources */,
|
||||||
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
A59FB5CF2AE0DB50009128F3 /* InspectorView.swift in Sources */,
|
||||||
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
CFBB5FEA2D231E5000FD62EE /* QuickTerminalSpaceBehavior.swift in Sources */,
|
||||||
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
A54B0CE92D0CECD100CBEFF8 /* ColorizedGhosttyIconView.swift in Sources */,
|
||||||
|
|
|
||||||
|
|
@ -260,8 +260,8 @@ class BaseTerminalController: NSWindowController,
|
||||||
self.alert = alert
|
self.alert = alert
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Focus Management
|
// MARK: Split Tree Management
|
||||||
|
|
||||||
/// Find the next surface to focus when a node is being closed.
|
/// Find the next surface to focus when a node is being closed.
|
||||||
/// Goes to previous split unless we're the leftmost leaf, then goes to next.
|
/// Goes to previous split unless we're the leftmost leaf, then goes to next.
|
||||||
private func findNextFocusTargetAfterClosing(node: SplitTree<Ghostty.SurfaceView>.Node) -> Ghostty.SurfaceView? {
|
private func findNextFocusTargetAfterClosing(node: SplitTree<Ghostty.SurfaceView>.Node) -> Ghostty.SurfaceView? {
|
||||||
|
|
@ -282,45 +282,63 @@ class BaseTerminalController: NSWindowController,
|
||||||
///
|
///
|
||||||
/// This does no confirmation and assumes confirmation is already done.
|
/// This does no confirmation and assumes confirmation is already done.
|
||||||
private func removeSurfaceNode(_ node: SplitTree<Ghostty.SurfaceView>.Node) {
|
private func removeSurfaceNode(_ node: SplitTree<Ghostty.SurfaceView>.Node) {
|
||||||
let nextTarget = findNextFocusTargetAfterClosing(node: node)
|
|
||||||
let oldFocused = focusedSurface
|
|
||||||
let focused = node.contains { $0 == focusedSurface }
|
|
||||||
|
|
||||||
// Keep track of the old tree for undo management.
|
|
||||||
let oldTree = surfaceTree
|
|
||||||
|
|
||||||
// Remove the node from the tree
|
|
||||||
surfaceTree = surfaceTree.remove(node)
|
|
||||||
|
|
||||||
// Move focus if the closed surface was focused and we have a next target
|
// Move focus if the closed surface was focused and we have a next target
|
||||||
if let nextTarget, focused {
|
let nextFocus: Ghostty.SurfaceView? = if node.contains(
|
||||||
|
where: { $0 == focusedSurface }
|
||||||
|
) {
|
||||||
|
findNextFocusTargetAfterClosing(node: node)
|
||||||
|
} else {
|
||||||
|
nil
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceSurfaceTree(
|
||||||
|
surfaceTree.remove(node),
|
||||||
|
moveFocusTo: nextFocus,
|
||||||
|
moveFocusFrom: focusedSurface,
|
||||||
|
undoAction: "Close Terminal"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func replaceSurfaceTree(
|
||||||
|
_ newTree: SplitTree<Ghostty.SurfaceView>,
|
||||||
|
moveFocusTo newView: Ghostty.SurfaceView? = nil,
|
||||||
|
moveFocusFrom oldView: Ghostty.SurfaceView? = nil,
|
||||||
|
undoAction: String? = nil
|
||||||
|
) {
|
||||||
|
// Setup our new split tree
|
||||||
|
let oldTree = surfaceTree
|
||||||
|
surfaceTree = newTree
|
||||||
|
if let newView {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Ghostty.moveFocus(to: nextTarget, from: oldFocused)
|
Ghostty.moveFocus(to: newView, from: oldView)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our undo
|
// Setup our undo
|
||||||
if let undoManager {
|
if let undoManager {
|
||||||
undoManager.setActionName("Close Terminal")
|
if let undoAction {
|
||||||
|
undoManager.setActionName(undoAction)
|
||||||
|
}
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: self,
|
withTarget: self,
|
||||||
expiresAfter: undoExpiration) { target in
|
expiresAfter: undoExpiration
|
||||||
|
) { target in
|
||||||
target.surfaceTree = oldTree
|
target.surfaceTree = oldTree
|
||||||
if let oldFocused {
|
if let oldView {
|
||||||
DispatchQueue.main.async {
|
DispatchQueue.main.async {
|
||||||
Ghostty.moveFocus(to: oldFocused, from: target.focusedSurface)
|
Ghostty.moveFocus(to: oldView, from: target.focusedSurface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: target,
|
withTarget: target,
|
||||||
expiresAfter: target.undoExpiration) { target in
|
expiresAfter: target.undoExpiration
|
||||||
target.closeSurfaceNode(
|
) { target in
|
||||||
node,
|
target.replaceSurfaceTree(
|
||||||
withConfirmation: node.contains {
|
newTree,
|
||||||
$0.needsConfirmQuit
|
moveFocusTo: newView,
|
||||||
}
|
moveFocusFrom: target.focusedSurface,
|
||||||
)
|
undoAction: undoAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -478,36 +496,11 @@ class BaseTerminalController: NSWindowController,
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Keep track of the old tree for undo
|
replaceSurfaceTree(
|
||||||
let oldTree = surfaceTree
|
newTree,
|
||||||
|
moveFocusTo: newView,
|
||||||
// Setup our new split tree
|
moveFocusFrom: oldView,
|
||||||
surfaceTree = newTree
|
undoAction: "New Split")
|
||||||
DispatchQueue.main.async {
|
|
||||||
Ghostty.moveFocus(to: newView, from: oldView)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setup our undo
|
|
||||||
if let undoManager {
|
|
||||||
undoManager.setActionName("New Split")
|
|
||||||
undoManager.registerUndo(
|
|
||||||
withTarget: self,
|
|
||||||
expiresAfter: undoExpiration) { target in
|
|
||||||
target.surfaceTree = oldTree
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
Ghostty.moveFocus(to: oldView, from: target.focusedSurface)
|
|
||||||
}
|
|
||||||
|
|
||||||
undoManager.registerUndo(
|
|
||||||
withTarget: target,
|
|
||||||
expiresAfter: target.undoExpiration) { target in
|
|
||||||
target.surfaceTree = newTree
|
|
||||||
DispatchQueue.main.async {
|
|
||||||
Ghostty.moveFocus(to: newView, from: target.focusedSurface)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func ghosttyDidEqualizeSplits(_ notification: Notification) {
|
@objc private func ghosttyDidEqualizeSplits(_ notification: Notification) {
|
||||||
|
|
|
||||||
|
|
@ -210,14 +210,18 @@ class TerminalController: BaseTerminalController {
|
||||||
undoManager.setActionName("New Window")
|
undoManager.setActionName("New Window")
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: c,
|
withTarget: c,
|
||||||
expiresAfter: c.undoExpiration) { target in
|
expiresAfter: c.undoExpiration
|
||||||
|
) { target in
|
||||||
// Close the window when undoing
|
// Close the window when undoing
|
||||||
target.closeWindow(nil)
|
undoManager.disableUndoRegistration {
|
||||||
|
target.closeWindow(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Register redo action
|
// Register redo action
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: ghostty,
|
withTarget: ghostty,
|
||||||
expiresAfter: target.undoExpiration) { ghostty in
|
expiresAfter: target.undoExpiration
|
||||||
|
) { ghostty in
|
||||||
_ = TerminalController.newWindow(
|
_ = TerminalController.newWindow(
|
||||||
ghostty,
|
ghostty,
|
||||||
withBaseConfig: baseConfig,
|
withBaseConfig: baseConfig,
|
||||||
|
|
@ -314,14 +318,18 @@ class TerminalController: BaseTerminalController {
|
||||||
undoManager.setActionName("New Tab")
|
undoManager.setActionName("New Tab")
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: controller,
|
withTarget: controller,
|
||||||
expiresAfter: controller.undoExpiration) { target in
|
expiresAfter: controller.undoExpiration
|
||||||
|
) { target in
|
||||||
// Close the tab when undoing
|
// Close the tab when undoing
|
||||||
target.closeTab(nil)
|
undoManager.disableUndoRegistration {
|
||||||
|
target.closeTab(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// Register redo action
|
// Register redo action
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: ghostty,
|
withTarget: ghostty,
|
||||||
expiresAfter: target.undoExpiration) { ghostty in
|
expiresAfter: target.undoExpiration
|
||||||
|
) { ghostty in
|
||||||
_ = TerminalController.newTab(
|
_ = TerminalController.newTab(
|
||||||
ghostty,
|
ghostty,
|
||||||
from: parent,
|
from: parent,
|
||||||
|
|
@ -617,14 +625,16 @@ class TerminalController: BaseTerminalController {
|
||||||
undoManager.setActionName("Close Tab")
|
undoManager.setActionName("Close Tab")
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: ghostty,
|
withTarget: ghostty,
|
||||||
expiresAfter: undoExpiration) { ghostty in
|
expiresAfter: undoExpiration
|
||||||
|
) { ghostty in
|
||||||
let newController = TerminalController(ghostty, with: undoState)
|
let newController = TerminalController(ghostty, with: undoState)
|
||||||
|
|
||||||
// Register redo action
|
// Register redo action
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: newController,
|
withTarget: newController,
|
||||||
expiresAfter: newController.undoExpiration) { target in
|
expiresAfter: newController.undoExpiration
|
||||||
target.closeTab(nil)
|
) { target in
|
||||||
|
target.closeTabImmediately()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -654,7 +664,7 @@ class TerminalController: BaseTerminalController {
|
||||||
undoManager.registerUndo(
|
undoManager.registerUndo(
|
||||||
withTarget: newController,
|
withTarget: newController,
|
||||||
expiresAfter: newController.undoExpiration) { target in
|
expiresAfter: newController.undoExpiration) { target in
|
||||||
target.closeWindow(nil)
|
target.closeWindowImmediately()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,11 @@ class ExpiringUndoManager: UndoManager {
|
||||||
// Ignore instantly expiring undos
|
// Ignore instantly expiring undos
|
||||||
guard duration.timeInterval > 0 else { return }
|
guard duration.timeInterval > 0 else { return }
|
||||||
|
|
||||||
|
// Ignore when undo registration is disabled. UndoManager still lets
|
||||||
|
// registration happen then cancels later but I was seeing some
|
||||||
|
// weird behavior with this so let's just guard on it.
|
||||||
|
guard self.isUndoRegistrationEnabled else { return }
|
||||||
|
|
||||||
let expiringTarget = ExpiringTarget(
|
let expiringTarget = ExpiringTarget(
|
||||||
target,
|
target,
|
||||||
expiresAfter: duration,
|
expiresAfter: duration,
|
||||||
|
|
@ -64,7 +69,10 @@ class ExpiringUndoManager: UndoManager {
|
||||||
// Call super to handle standard removal
|
// Call super to handle standard removal
|
||||||
super.removeAllActions(withTarget: target)
|
super.removeAllActions(withTarget: target)
|
||||||
|
|
||||||
if !(target is ExpiringTarget) {
|
// If the target is an expiring target, remove it.
|
||||||
|
if let expiring = target as? ExpiringTarget {
|
||||||
|
expiringTargets.remove(expiring)
|
||||||
|
} else {
|
||||||
// Find and remove any ExpiringTarget instances that wrap this target.
|
// Find and remove any ExpiringTarget instances that wrap this target.
|
||||||
expiringTargets
|
expiringTargets
|
||||||
.filter { $0.target == nil || $0.target === (target as AnyObject) }
|
.filter { $0.target == nil || $0.target === (target as AnyObject) }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
extension UndoManager {
|
||||||
|
/// A Boolean value that indicates whether the undo manager is currently performing
|
||||||
|
/// either an undo or redo operation.
|
||||||
|
var isUndoingOrRedoing: Bool {
|
||||||
|
isUndoing || isRedoing
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Temporarily disables undo registration while executing the provided handler.
|
||||||
|
///
|
||||||
|
/// This method provides a convenient way to perform operations without recording them
|
||||||
|
/// in the undo stack. It ensures that undo registration is properly re-enabled even
|
||||||
|
/// if the handler throws an error.
|
||||||
|
func disableUndoRegistration(handler: () -> Void) {
|
||||||
|
disableUndoRegistration()
|
||||||
|
handler()
|
||||||
|
enableUndoRegistration()
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue