diff --git a/macos/Sources/Features/Terminal/TerminalRestorable.swift b/macos/Sources/Features/Terminal/TerminalRestorable.swift index 68695f5ee..642b1679f 100644 --- a/macos/Sources/Features/Terminal/TerminalRestorable.swift +++ b/macos/Sources/Features/Terminal/TerminalRestorable.swift @@ -6,9 +6,11 @@ class TerminalRestorableState: Codable { static let versionKey = "version" static let version: Int = 1 + let focusedSurface: String? let surfaceTree: Ghostty.SplitNode? init(from controller: TerminalController) { + self.focusedSurface = controller.focusedSurface?.uuid.uuidString self.surfaceTree = controller.surfaceTree } @@ -25,6 +27,7 @@ class TerminalRestorableState: Codable { } self.surfaceTree = v.value.surfaceTree + self.focusedSurface = v.value.focusedSurface } func encode(with coder: NSCoder) { @@ -86,7 +89,31 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration { // Setup our restored state on the controller c.surfaceTree = state.surfaceTree + if let focusedStr = state.focusedSurface, + let focusedUUID = UUID(uuidString: focusedStr), + let view = c.surfaceTree?.findUUID(uuid: focusedUUID) { + c.focusedSurface = view + restoreFocus(to: view, inWindow: window) + } completionHandler(window, nil) } + + /// This restores the focus state of the surfaceview within the given window. When restoring, + /// the view isn't immediately attached to the window since we have to wait for SwiftUI to + /// catch up. Therefore, we sit in an async loop waiting for the attachment to happen. + private static func restoreFocus(to: Ghostty.SurfaceView, inWindow: NSWindow) { + DispatchQueue.main.async { + // If the view is not attached to a window yet then we repeat. + guard let viewWindow = to.window else { + restoreFocus(to: to, inWindow: inWindow) + return + } + + // If the view is attached to some other window, we give up + guard viewWindow == inWindow else { return } + + inWindow.makeFirstResponder(to) + } + } } diff --git a/macos/Sources/Ghostty/Ghostty.SplitNode.swift b/macos/Sources/Ghostty/Ghostty.SplitNode.swift index 4e87feda0..d9403b62f 100644 --- a/macos/Sources/Ghostty/Ghostty.SplitNode.swift +++ b/macos/Sources/Ghostty/Ghostty.SplitNode.swift @@ -101,6 +101,22 @@ extension Ghostty { } } + /// Find a surface view by UUID. + func findUUID(uuid: UUID) -> SurfaceView? { + switch (self) { + case .leaf(let leaf): + if (leaf.surface.uuid == uuid) { + return leaf.surface + } + + return nil + + case .split(let container): + return container.topLeft.findUUID(uuid: uuid) ?? + container.bottomRight.findUUID(uuid: uuid) + } + } + // MARK: - Equatable static func == (lhs: SplitNode, rhs: SplitNode) -> Bool { @@ -121,7 +137,7 @@ extension Ghostty { weak var parent: SplitNode.Container? /// Initialize a new leaf which creates a new terminal surface. - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: NSUUID? = nil) { + init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { self.app = app self.surface = SurfaceView(app, baseConfig: baseConfig, uuid: uuid) } @@ -155,7 +171,7 @@ extension Ghostty { } let container = try decoder.container(keyedBy: CodingKeys.self) - let uuid = NSUUID(uuidString: try container.decode(String.self, forKey: .uuid)) + let uuid = UUID(uuidString: try container.decode(String.self, forKey: .uuid)) var config = SurfaceConfiguration() config.workingDirectory = try container.decode(String?.self, forKey: .pwd) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index b53c42c94..0ebcf5a15 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -264,7 +264,7 @@ extension Ghostty { /// The NSView implementation for a terminal surface. class SurfaceView: NSView, NSTextInputClient, ObservableObject { /// Unique ID per surface - let uuid: NSUUID + let uuid: UUID // The current title of the surface as defined by the pty. This can be // changed with escape codes. This is public because the callbacks go @@ -347,7 +347,7 @@ extension Ghostty { case pendingHidden } - init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: NSUUID? = nil) { + init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) { self.markedText = NSMutableAttributedString() self.uuid = uuid ?? .init()