macos: restoration for new split tree
parent
33d94521ea
commit
d1dce1e372
|
|
@ -1,7 +1,7 @@
|
||||||
import AppKit
|
import AppKit
|
||||||
|
|
||||||
/// SplitTree represents a tree of views that can be divided.
|
/// SplitTree represents a tree of views that can be divided.
|
||||||
struct SplitTree {
|
struct SplitTree<ViewType: NSView & Codable>: Codable {
|
||||||
/// The root of the tree. This can be nil to indicate the tree is empty.
|
/// The root of the tree. This can be nil to indicate the tree is empty.
|
||||||
let root: Node?
|
let root: Node?
|
||||||
|
|
||||||
|
|
@ -11,11 +11,11 @@ struct SplitTree {
|
||||||
|
|
||||||
/// A single node in the tree is either a leaf node (a view) or a split (has a
|
/// A single node in the tree is either a leaf node (a view) or a split (has a
|
||||||
/// left/right or top/bottom).
|
/// left/right or top/bottom).
|
||||||
indirect enum Node {
|
indirect enum Node: Codable {
|
||||||
case leaf(view: NSView)
|
case leaf(view: ViewType)
|
||||||
case split(Split)
|
case split(Split)
|
||||||
|
|
||||||
struct Split: Equatable {
|
struct Split: Equatable, Codable {
|
||||||
let direction: Direction
|
let direction: Direction
|
||||||
let ratio: Double
|
let ratio: Double
|
||||||
let left: Node
|
let left: Node
|
||||||
|
|
@ -23,7 +23,7 @@ struct SplitTree {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Direction {
|
enum Direction: Codable {
|
||||||
case horizontal // Splits are laid out left and right
|
case horizontal // Splits are laid out left and right
|
||||||
case vertical // Splits are laid out top and bottom
|
case vertical // Splits are laid out top and bottom
|
||||||
}
|
}
|
||||||
|
|
@ -63,12 +63,12 @@ extension SplitTree {
|
||||||
self.init(root: nil, zoomed: nil)
|
self.init(root: nil, zoomed: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
init(view: NSView) {
|
init(view: ViewType) {
|
||||||
self.init(root: .leaf(view: view), zoomed: nil)
|
self.init(root: .leaf(view: view), zoomed: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Insert a new view at the given view point by creating a split in the given direction.
|
/// Insert a new view at the given view point by creating a split in the given direction.
|
||||||
func insert(view: NSView, at: NSView, direction: NewDirection) throws -> Self {
|
func insert(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
|
||||||
guard let root else { throw SplitError.viewNotFound }
|
guard let root else { throw SplitError.viewNotFound }
|
||||||
return .init(
|
return .init(
|
||||||
root: try root.insert(view: view, at: at, direction: direction),
|
root: try root.insert(view: view, at: at, direction: direction),
|
||||||
|
|
@ -122,7 +122,7 @@ extension SplitTree.Node {
|
||||||
typealias Path = SplitTree.Path
|
typealias Path = SplitTree.Path
|
||||||
|
|
||||||
/// Returns the node in the tree that contains the given view.
|
/// Returns the node in the tree that contains the given view.
|
||||||
func node(view: NSView) -> Node? {
|
func node(view: ViewType) -> Node? {
|
||||||
switch (self) {
|
switch (self) {
|
||||||
case .leaf(view):
|
case .leaf(view):
|
||||||
return self
|
return self
|
||||||
|
|
@ -188,7 +188,7 @@ extension SplitTree.Node {
|
||||||
///
|
///
|
||||||
/// - Note: If the existing view (`at`) is not found in the tree, this method does nothing. We should
|
/// - Note: If the existing view (`at`) is not found in the tree, this method does nothing. We should
|
||||||
/// maybe throw instead but at the moment we just do nothing.
|
/// maybe throw instead but at the moment we just do nothing.
|
||||||
func insert(view: NSView, at: NSView, direction: NewDirection) throws -> Self {
|
func insert(view: ViewType, at: ViewType, direction: NewDirection) throws -> Self {
|
||||||
// Get the path to our insertion point. If it doesn't exist we do
|
// Get the path to our insertion point. If it doesn't exist we do
|
||||||
// nothing.
|
// nothing.
|
||||||
guard let path = path(to: .leaf(view: at)) else {
|
guard let path = path(to: .leaf(view: at)) else {
|
||||||
|
|
@ -351,11 +351,51 @@ extension SplitTree.Node: Equatable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: SplitTree Codable
|
||||||
|
|
||||||
|
extension SplitTree.Node {
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case view
|
||||||
|
case split
|
||||||
|
}
|
||||||
|
|
||||||
|
init(from decoder: Decoder) throws {
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
if container.contains(.view) {
|
||||||
|
let view = try container.decode(ViewType.self, forKey: .view)
|
||||||
|
self = .leaf(view: view)
|
||||||
|
} else if container.contains(.split) {
|
||||||
|
let split = try container.decode(Split.self, forKey: .split)
|
||||||
|
self = .split(split)
|
||||||
|
} else {
|
||||||
|
throw DecodingError.dataCorrupted(
|
||||||
|
DecodingError.Context(
|
||||||
|
codingPath: decoder.codingPath,
|
||||||
|
debugDescription: "No valid node type found"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
|
||||||
|
switch self {
|
||||||
|
case .leaf(let view):
|
||||||
|
try container.encode(view, forKey: .view)
|
||||||
|
|
||||||
|
case .split(let split):
|
||||||
|
try container.encode(split, forKey: .split)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MARK: SplitTree Sequences
|
// MARK: SplitTree Sequences
|
||||||
|
|
||||||
extension SplitTree.Node {
|
extension SplitTree.Node {
|
||||||
/// Returns all leaf views in this subtree
|
/// Returns all leaf views in this subtree
|
||||||
func leaves() -> [NSView] {
|
func leaves() -> [ViewType] {
|
||||||
switch self {
|
switch self {
|
||||||
case .leaf(let view):
|
case .leaf(let view):
|
||||||
return [view]
|
return [view]
|
||||||
|
|
@ -367,13 +407,13 @@ extension SplitTree.Node {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SplitTree: Sequence {
|
extension SplitTree: Sequence {
|
||||||
func makeIterator() -> [NSView].Iterator {
|
func makeIterator() -> [ViewType].Iterator {
|
||||||
return root?.leaves().makeIterator() ?? [].makeIterator()
|
return root?.leaves().makeIterator() ?? [].makeIterator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SplitTree.Node: Sequence {
|
extension SplitTree.Node: Sequence {
|
||||||
func makeIterator() -> [NSView].Iterator {
|
func makeIterator() -> [ViewType].Iterator {
|
||||||
return leaves().makeIterator()
|
return leaves().makeIterator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TerminalSplitTreeView: View {
|
struct TerminalSplitTreeView: View {
|
||||||
let tree: SplitTree
|
let tree: SplitTree<Ghostty.SurfaceView>
|
||||||
let onResize: (SplitTree.Node, Double) -> Void
|
let onResize: (SplitTree<Ghostty.SurfaceView>.Node, Double) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if let node = tree.root {
|
if let node = tree.root {
|
||||||
|
|
@ -14,16 +14,15 @@ struct TerminalSplitTreeView: View {
|
||||||
struct TerminalSplitSubtreeView: View {
|
struct TerminalSplitSubtreeView: View {
|
||||||
@EnvironmentObject var ghostty: Ghostty.App
|
@EnvironmentObject var ghostty: Ghostty.App
|
||||||
|
|
||||||
let node: SplitTree.Node
|
let node: SplitTree<Ghostty.SurfaceView>.Node
|
||||||
var isRoot: Bool = false
|
var isRoot: Bool = false
|
||||||
let onResize: (SplitTree.Node, Double) -> Void
|
let onResize: (SplitTree<Ghostty.SurfaceView>.Node, Double) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
switch (node) {
|
switch (node) {
|
||||||
case .leaf(let leafView):
|
case .leaf(let leafView):
|
||||||
// TODO: Fix the as!
|
|
||||||
Ghostty.InspectableSurface(
|
Ghostty.InspectableSurface(
|
||||||
surfaceView: leafView as! Ghostty.SurfaceView,
|
surfaceView: leafView,
|
||||||
isSplit: !isRoot)
|
isSplit: !isRoot)
|
||||||
|
|
||||||
case .split(let split):
|
case .split(let split):
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ class BaseTerminalController: NSWindowController,
|
||||||
didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) }
|
didSet { surfaceTreeDidChange(from: oldValue, to: surfaceTree) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var surfaceTree2: SplitTree = .init()
|
@Published var surfaceTree2: SplitTree<Ghostty.SurfaceView> = .init()
|
||||||
|
|
||||||
/// This can be set to show/hide the command palette.
|
/// This can be set to show/hide the command palette.
|
||||||
@Published var commandPaletteIsShowing: Bool = false
|
@Published var commandPaletteIsShowing: Bool = false
|
||||||
|
|
@ -88,7 +88,8 @@ class BaseTerminalController: NSWindowController,
|
||||||
|
|
||||||
init(_ ghostty: Ghostty.App,
|
init(_ ghostty: Ghostty.App,
|
||||||
baseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
baseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||||
surfaceTree tree: Ghostty.SplitNode? = nil
|
surfaceTree tree: Ghostty.SplitNode? = nil,
|
||||||
|
surfaceTree2 tree2: SplitTree<Ghostty.SurfaceView>? = nil
|
||||||
) {
|
) {
|
||||||
self.ghostty = ghostty
|
self.ghostty = ghostty
|
||||||
self.derivedConfig = DerivedConfig(ghostty.config)
|
self.derivedConfig = DerivedConfig(ghostty.config)
|
||||||
|
|
@ -98,9 +99,7 @@ class BaseTerminalController: NSWindowController,
|
||||||
// Initialize our initial surface.
|
// Initialize our initial surface.
|
||||||
guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") }
|
guard let ghostty_app = ghostty.app else { preconditionFailure("app must be loaded") }
|
||||||
self.surfaceTree = tree ?? .leaf(.init(ghostty_app, baseConfig: base))
|
self.surfaceTree = tree ?? .leaf(.init(ghostty_app, baseConfig: base))
|
||||||
|
self.surfaceTree2 = tree2 ?? .init(view: Ghostty.SurfaceView(ghostty_app, baseConfig: base))
|
||||||
let firstView = Ghostty.SurfaceView(ghostty_app, baseConfig: base)
|
|
||||||
self.surfaceTree2 = .init(view: firstView)
|
|
||||||
|
|
||||||
// Setup our notifications for behaviors
|
// Setup our notifications for behaviors
|
||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
|
|
@ -175,16 +174,14 @@ class BaseTerminalController: NSWindowController,
|
||||||
/// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about
|
/// Update all surfaces with the focus state. This ensures that libghostty has an accurate view about
|
||||||
/// what surface is focused. This must be called whenever a surface OR window changes focus.
|
/// what surface is focused. This must be called whenever a surface OR window changes focus.
|
||||||
func syncFocusToSurfaceTree() {
|
func syncFocusToSurfaceTree() {
|
||||||
for view in surfaceTree2 {
|
for surfaceView in surfaceTree2 {
|
||||||
if let surfaceView = view as? Ghostty.SurfaceView {
|
// Our focus state requires that this window is key and our currently
|
||||||
// Our focus state requires that this window is key and our currently
|
// focused surface is the surface in this view.
|
||||||
// focused surface is the surface in this view.
|
let focused: Bool = (window?.isKeyWindow ?? false) &&
|
||||||
let focused: Bool = (window?.isKeyWindow ?? false) &&
|
!commandPaletteIsShowing &&
|
||||||
!commandPaletteIsShowing &&
|
focusedSurface != nil &&
|
||||||
focusedSurface != nil &&
|
surfaceView == focusedSurface!
|
||||||
surfaceView == focusedSurface!
|
surfaceView.focusDidChange(focused)
|
||||||
surfaceView.focusDidChange(focused)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -335,7 +332,7 @@ class BaseTerminalController: NSWindowController,
|
||||||
// Determine our desired direction
|
// Determine our desired direction
|
||||||
guard let directionAny = notification.userInfo?["direction"] else { return }
|
guard let directionAny = notification.userInfo?["direction"] else { return }
|
||||||
guard let direction = directionAny as? ghostty_action_split_direction_e else { return }
|
guard let direction = directionAny as? ghostty_action_split_direction_e else { return }
|
||||||
let splitDirection: SplitTree.NewDirection
|
let splitDirection: SplitTree<Ghostty.SurfaceView>.NewDirection
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case GHOSTTY_SPLIT_DIRECTION_RIGHT: splitDirection = .right
|
case GHOSTTY_SPLIT_DIRECTION_RIGHT: splitDirection = .right
|
||||||
case GHOSTTY_SPLIT_DIRECTION_LEFT: splitDirection = .left
|
case GHOSTTY_SPLIT_DIRECTION_LEFT: splitDirection = .left
|
||||||
|
|
@ -388,16 +385,16 @@ class BaseTerminalController: NSWindowController,
|
||||||
|
|
||||||
private func localEventFlagsChanged(_ event: NSEvent) -> NSEvent? {
|
private func localEventFlagsChanged(_ event: NSEvent) -> NSEvent? {
|
||||||
// Also update surfaceTree2
|
// Also update surfaceTree2
|
||||||
var surfaces2: [Ghostty.SurfaceView] = surfaceTree2.compactMap { $0 as? Ghostty.SurfaceView }
|
var surfaces: [Ghostty.SurfaceView] = surfaceTree2.map { $0 }
|
||||||
|
|
||||||
// If we're the main window receiving key input, then we want to avoid
|
// If we're the main window receiving key input, then we want to avoid
|
||||||
// calling this on our focused surface because that'll trigger a double
|
// calling this on our focused surface because that'll trigger a double
|
||||||
// flagsChanged call.
|
// flagsChanged call.
|
||||||
if NSApp.mainWindow == window {
|
if NSApp.mainWindow == window {
|
||||||
surfaces2 = surfaces2.filter { $0 != focusedSurface }
|
surfaces = surfaces.filter { $0 != focusedSurface }
|
||||||
}
|
}
|
||||||
|
|
||||||
for surface in surfaces2 {
|
for surface in surfaces {
|
||||||
surface.flagsChanged(with: event)
|
surface.flagsChanged(with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -455,7 +452,7 @@ class BaseTerminalController: NSWindowController,
|
||||||
|
|
||||||
func zoomStateDidChange(to: Bool) {}
|
func zoomStateDidChange(to: Bool) {}
|
||||||
|
|
||||||
func splitDidResize(node: SplitTree.Node, to newRatio: Double) {
|
func splitDidResize(node: SplitTree<Ghostty.SurfaceView>.Node, to newRatio: Double) {
|
||||||
let resizedNode = node.resize(to: newRatio)
|
let resizedNode = node.resize(to: newRatio)
|
||||||
do {
|
do {
|
||||||
surfaceTree2 = try surfaceTree2.replace(node: node, with: resizedNode)
|
surfaceTree2 = try surfaceTree2.replace(node: node, with: resizedNode)
|
||||||
|
|
@ -675,8 +672,7 @@ class BaseTerminalController: NSWindowController,
|
||||||
func windowDidChangeOcclusionState(_ notification: Notification) {
|
func windowDidChangeOcclusionState(_ notification: Notification) {
|
||||||
let visible = self.window?.occlusionState.contains(.visible) ?? false
|
let visible = self.window?.occlusionState.contains(.visible) ?? false
|
||||||
for view in surfaceTree2 {
|
for view in surfaceTree2 {
|
||||||
if let surfaceView = view as? Ghostty.SurfaceView,
|
if let surface = view.surface {
|
||||||
let surface = surfaceView.surface {
|
|
||||||
ghostty_surface_set_occlusion(surface, visible)
|
ghostty_surface_set_occlusion(surface, visible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,8 @@ class TerminalController: BaseTerminalController {
|
||||||
|
|
||||||
init(_ ghostty: Ghostty.App,
|
init(_ ghostty: Ghostty.App,
|
||||||
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||||
withSurfaceTree tree: Ghostty.SplitNode? = nil
|
withSurfaceTree tree: Ghostty.SplitNode? = nil,
|
||||||
|
withSurfaceTree2 tree2: SplitTree<Ghostty.SurfaceView>? = nil
|
||||||
) {
|
) {
|
||||||
// The window we manage is not restorable if we've specified a command
|
// The window we manage is not restorable if we've specified a command
|
||||||
// to execute. We do this because the restored window is meaningless at the
|
// to execute. We do this because the restored window is meaningless at the
|
||||||
|
|
@ -44,7 +45,7 @@ class TerminalController: BaseTerminalController {
|
||||||
// Setup our initial derived config based on the current app config
|
// Setup our initial derived config based on the current app config
|
||||||
self.derivedConfig = DerivedConfig(ghostty.config)
|
self.derivedConfig = DerivedConfig(ghostty.config)
|
||||||
|
|
||||||
super.init(ghostty, baseConfig: base, surfaceTree: tree)
|
super.init(ghostty, baseConfig: base, surfaceTree: tree, surfaceTree2: tree2)
|
||||||
|
|
||||||
// Setup our notifications for behaviors
|
// Setup our notifications for behaviors
|
||||||
let center = NotificationCenter.default
|
let center = NotificationCenter.default
|
||||||
|
|
|
||||||
|
|
@ -197,9 +197,10 @@ class TerminalManager {
|
||||||
|
|
||||||
/// Creates a window controller, adds it to our managed list, and returns it.
|
/// Creates a window controller, adds it to our managed list, and returns it.
|
||||||
func createWindow(withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
func createWindow(withBaseConfig base: Ghostty.SurfaceConfiguration? = nil,
|
||||||
withSurfaceTree tree: Ghostty.SplitNode? = nil) -> TerminalController {
|
withSurfaceTree tree: Ghostty.SplitNode? = nil,
|
||||||
|
withSurfaceTree2 tree2: SplitTree<Ghostty.SurfaceView>? = nil) -> TerminalController {
|
||||||
// Initialize our controller to load the window
|
// Initialize our controller to load the window
|
||||||
let c = TerminalController(ghostty, withBaseConfig: base, withSurfaceTree: tree)
|
let c = TerminalController(ghostty, withBaseConfig: base, withSurfaceTree: tree, withSurfaceTree2: tree2)
|
||||||
|
|
||||||
// Create a listener for when the window is closed so we can remove it.
|
// Create a listener for when the window is closed so we can remove it.
|
||||||
let pubClose = NotificationCenter.default.publisher(
|
let pubClose = NotificationCenter.default.publisher(
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,16 @@ import Cocoa
|
||||||
class TerminalRestorableState: Codable {
|
class TerminalRestorableState: Codable {
|
||||||
static let selfKey = "state"
|
static let selfKey = "state"
|
||||||
static let versionKey = "version"
|
static let versionKey = "version"
|
||||||
static let version: Int = 2
|
static let version: Int = 3
|
||||||
|
|
||||||
let focusedSurface: String?
|
let focusedSurface: String?
|
||||||
let surfaceTree: Ghostty.SplitNode?
|
let surfaceTree: Ghostty.SplitNode?
|
||||||
|
let surfaceTree2: SplitTree<Ghostty.SurfaceView>?
|
||||||
|
|
||||||
init(from controller: TerminalController) {
|
init(from controller: TerminalController) {
|
||||||
self.focusedSurface = controller.focusedSurface?.uuid.uuidString
|
self.focusedSurface = controller.focusedSurface?.uuid.uuidString
|
||||||
self.surfaceTree = controller.surfaceTree
|
self.surfaceTree = controller.surfaceTree
|
||||||
|
self.surfaceTree2 = controller.surfaceTree2
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(coder aDecoder: NSCoder) {
|
init?(coder aDecoder: NSCoder) {
|
||||||
|
|
@ -27,6 +29,7 @@ class TerminalRestorableState: Codable {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.surfaceTree = v.value.surfaceTree
|
self.surfaceTree = v.value.surfaceTree
|
||||||
|
self.surfaceTree2 = v.value.surfaceTree2
|
||||||
self.focusedSurface = v.value.focusedSurface
|
self.focusedSurface = v.value.focusedSurface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -83,18 +86,37 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration {
|
||||||
// can be found for events from libghostty. This uses the low-level
|
// can be found for events from libghostty. This uses the low-level
|
||||||
// createWindow so that AppKit can place the window wherever it should
|
// createWindow so that AppKit can place the window wherever it should
|
||||||
// be.
|
// be.
|
||||||
let c = appDelegate.terminalManager.createWindow(withSurfaceTree: state.surfaceTree)
|
let c = appDelegate.terminalManager.createWindow(
|
||||||
|
withSurfaceTree: state.surfaceTree,
|
||||||
|
withSurfaceTree2: state.surfaceTree2
|
||||||
|
)
|
||||||
guard let window = c.window else {
|
guard let window = c.window else {
|
||||||
completionHandler(nil, TerminalRestoreError.windowDidNotLoad)
|
completionHandler(nil, TerminalRestoreError.windowDidNotLoad)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup our restored state on the controller
|
// Setup our restored state on the controller
|
||||||
|
// First try to find the focused surface in surfaceTree2
|
||||||
if let focusedStr = state.focusedSurface,
|
if let focusedStr = state.focusedSurface,
|
||||||
let focusedUUID = UUID(uuidString: focusedStr),
|
let focusedUUID = UUID(uuidString: focusedStr) {
|
||||||
let view = c.surfaceTree?.findUUID(uuid: focusedUUID) {
|
// Try surfaceTree2 first
|
||||||
c.focusedSurface = view
|
var foundView: Ghostty.SurfaceView?
|
||||||
restoreFocus(to: view, inWindow: window)
|
for view in c.surfaceTree2 {
|
||||||
|
if view.uuid.uuidString == focusedStr {
|
||||||
|
foundView = view
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to surfaceTree if not found
|
||||||
|
if foundView == nil {
|
||||||
|
foundView = c.surfaceTree?.findUUID(uuid: focusedUUID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if let view = foundView {
|
||||||
|
c.focusedSurface = view
|
||||||
|
restoreFocus(to: view, inWindow: window)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
completionHandler(window, nil)
|
completionHandler(window, nil)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ protocol TerminalViewDelegate: AnyObject {
|
||||||
func performAction(_ action: String, on: Ghostty.SurfaceView)
|
func performAction(_ action: String, on: Ghostty.SurfaceView)
|
||||||
|
|
||||||
/// A split is resizing to a given value.
|
/// A split is resizing to a given value.
|
||||||
func splitDidResize(node: SplitTree.Node, to newRatio: Double)
|
func splitDidResize(node: SplitTree<Ghostty.SurfaceView>.Node, to newRatio: Double)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The view model is a required implementation for TerminalView callers. This contains
|
/// The view model is a required implementation for TerminalView callers. This contains
|
||||||
|
|
@ -30,7 +30,7 @@ protocol TerminalViewDelegate: AnyObject {
|
||||||
protocol TerminalViewModel: ObservableObject {
|
protocol TerminalViewModel: ObservableObject {
|
||||||
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
/// The tree of terminal surfaces (splits) within the view. This is mutated by TerminalView
|
||||||
/// and children. This should be @Published.
|
/// and children. This should be @Published.
|
||||||
var surfaceTree2: SplitTree { get set }
|
var surfaceTree2: SplitTree<Ghostty.SurfaceView> { get set }
|
||||||
|
|
||||||
/// The command palette state.
|
/// The command palette state.
|
||||||
var commandPaletteIsShowing: Bool { get set }
|
var commandPaletteIsShowing: Bool { get set }
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import GhosttyKit
|
||||||
|
|
||||||
extension Ghostty {
|
extension Ghostty {
|
||||||
/// The NSView implementation for a terminal surface.
|
/// The NSView implementation for a terminal surface.
|
||||||
class SurfaceView: OSView, ObservableObject {
|
class SurfaceView: OSView, ObservableObject, Codable {
|
||||||
/// Unique ID per surface
|
/// Unique ID per surface
|
||||||
let uuid: UUID
|
let uuid: UUID
|
||||||
|
|
||||||
|
|
@ -1431,6 +1431,35 @@ extension Ghostty {
|
||||||
self.windowAppearance = .init(ghosttyConfig: config)
|
self.windowAppearance = .init(ghosttyConfig: config)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: - Codable
|
||||||
|
|
||||||
|
enum CodingKeys: String, CodingKey {
|
||||||
|
case pwd
|
||||||
|
case uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
required convenience init(from decoder: Decoder) throws {
|
||||||
|
// Decoding uses the global Ghostty app
|
||||||
|
guard let del = NSApplication.shared.delegate,
|
||||||
|
let appDel = del as? AppDelegate,
|
||||||
|
let app = appDel.ghostty.app else {
|
||||||
|
throw TerminalRestoreError.delegateInvalid
|
||||||
|
}
|
||||||
|
|
||||||
|
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||||
|
let uuid = UUID(uuidString: try container.decode(String.self, forKey: .uuid))
|
||||||
|
var config = Ghostty.SurfaceConfiguration()
|
||||||
|
config.workingDirectory = try container.decode(String?.self, forKey: .pwd)
|
||||||
|
|
||||||
|
self.init(app, baseConfig: config, uuid: uuid)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encode(to encoder: Encoder) throws {
|
||||||
|
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||||
|
try container.encode(pwd, forKey: .pwd)
|
||||||
|
try container.encode(uuid.uuidString, forKey: .uuid)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue