macOS: SurfaceView should implement Identifiable

This has no meaningful functionality yet, it was one of the paths I was
looking at for #8505 but didn't pursue further. But I still think that
this makes more sense in general for the macOS app and will likely be
more useful later.
pull/8506/head
Mitchell Hashimoto 2025-09-03 07:27:12 -07:00
parent e6d60dee07
commit fe3dab9467
No known key found for this signature in database
GPG Key ID: 523D5DC389D273BC
6 changed files with 40 additions and 14 deletions

View File

@ -937,7 +937,7 @@ class AppDelegate: NSObject,
func findSurface(forUUID uuid: UUID) -> Ghostty.SurfaceView? {
for c in TerminalController.all {
for view in c.surfaceTree {
if view.uuid == uuid {
if view.id == uuid {
return view
}
}

View File

@ -34,7 +34,7 @@ struct TerminalEntity: AppEntity {
/// Returns the view associated with this entity. This may no longer exist.
@MainActor
var surfaceView: Ghostty.SurfaceView? {
Self.defaultQuery.all.first { $0.uuid == self.id }
Self.defaultQuery.all.first { $0.id == self.id }
}
@MainActor
@ -46,7 +46,7 @@ struct TerminalEntity: AppEntity {
@MainActor
init(_ view: Ghostty.SurfaceView) {
self.id = view.uuid
self.id = view.id
self.title = view.title
self.workingDirectory = view.pwd
if let nsImage = ImageRenderer(content: view.screenshot()).nsImage {
@ -80,7 +80,7 @@ struct TerminalQuery: EntityStringQuery, EnumerableEntityQuery {
@MainActor
func entities(for identifiers: [TerminalEntity.ID]) async throws -> [TerminalEntity] {
return all.filter {
identifiers.contains($0.uuid)
identifiers.contains($0.id)
}.map {
TerminalEntity($0)
}

View File

@ -1,7 +1,7 @@
import AppKit
/// SplitTree represents a tree of views that can be divided.
struct SplitTree<ViewType: NSView & Codable> {
struct SplitTree<ViewType: NSView & Codable & Identifiable> {
/// The root of the tree. This can be nil to indicate the tree is empty.
let root: Node?
@ -127,6 +127,13 @@ extension SplitTree {
root: try root.insert(view: view, at: at, direction: direction),
zoomed: nil)
}
/// Find a node containing a view with the specified ID.
/// - Parameter id: The ID of the view to find
/// - Returns: The node containing the view if found, nil otherwise
func find(id: ViewType.ID) -> Node? {
guard let root else { return nil }
return root.find(id: id)
}
/// Remove a node from the tree. If the node being removed is part of a split,
/// the sibling node takes the place of the parent split.
@ -396,6 +403,23 @@ extension SplitTree.Node {
typealias SplitError = SplitTree.SplitError
typealias Path = SplitTree.Path
/// Find a node containing a view with the specified ID.
/// - Parameter id: The ID of the view to find
/// - Returns: The node containing the view if found, nil otherwise
func find(id: ViewType.ID) -> Node? {
switch self {
case .leaf(let view):
return view.id == id ? self : nil
case .split(let split):
if let found = split.left.find(id: id) {
return found
}
return split.right.find(id: id)
}
}
/// Returns the node in the tree that contains the given view.
func node(view: ViewType) -> Node? {
switch (self) {

View File

@ -860,7 +860,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
// Restore focus to the previously focused surface
if let focusedUUID = undoState.focusedSurface,
let focusTarget = surfaceTree.first(where: { $0.uuid == focusedUUID }) {
let focusTarget = surfaceTree.first(where: { $0.id == focusedUUID }) {
DispatchQueue.main.async {
Ghostty.moveFocus(to: focusTarget, from: nil)
}
@ -875,7 +875,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
return .init(
frame: window.frame,
surfaceTree: surfaceTree,
focusedSurface: focusedSurface?.uuid,
focusedSurface: focusedSurface?.id,
tabIndex: window.tabGroup?.windows.firstIndex(of: window),
tabGroup: window.tabGroup)
}

View File

@ -10,7 +10,7 @@ class TerminalRestorableState: Codable {
let surfaceTree: SplitTree<Ghostty.SurfaceView>
init(from controller: TerminalController) {
self.focusedSurface = controller.focusedSurface?.uuid.uuidString
self.focusedSurface = controller.focusedSurface?.id.uuidString
self.surfaceTree = controller.surfaceTree
}
@ -96,7 +96,7 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration {
if let focusedStr = state.focusedSurface {
var foundView: Ghostty.SurfaceView?
for view in c.surfaceTree {
if view.uuid.uuidString == focusedStr {
if view.id.uuidString == focusedStr {
foundView = view
break
}

View File

@ -6,9 +6,11 @@ import GhosttyKit
extension Ghostty {
/// The NSView implementation for a terminal surface.
class SurfaceView: OSView, ObservableObject, Codable {
class SurfaceView: OSView, ObservableObject, Codable, Identifiable {
typealias ID = UUID
/// Unique ID per surface
let uuid: UUID
let id: 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
@ -180,7 +182,7 @@ extension Ghostty {
init(_ app: ghostty_app_t, baseConfig: SurfaceConfiguration? = nil, uuid: UUID? = nil) {
self.markedText = NSMutableAttributedString()
self.uuid = uuid ?? .init()
self.id = uuid ?? .init()
// Our initial config always is our application wide config.
if let appDelegate = NSApplication.shared.delegate as? AppDelegate {
@ -1468,7 +1470,7 @@ extension Ghostty {
content.body = body
content.sound = UNNotificationSound.default
content.categoryIdentifier = Ghostty.userNotificationCategory
content.userInfo = ["surface": self.uuid.uuidString]
content.userInfo = ["surface": self.id.uuidString]
let uuid = UUID().uuidString
let request = UNNotificationRequest(
@ -1576,7 +1578,7 @@ extension Ghostty {
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)
try container.encode(id.uuidString, forKey: .uuid)
try container.encode(title, forKey: .title)
try container.encode(titleFromTerminal != nil, forKey: .isUserSetTitle)
}