diff --git a/macos/Sources/Ghostty/Ghostty.SplitView.swift b/macos/Sources/Ghostty/Ghostty.SplitView.swift index 4cff3217c..52f8fdde6 100644 --- a/macos/Sources/Ghostty/Ghostty.SplitView.swift +++ b/macos/Sources/Ghostty/Ghostty.SplitView.swift @@ -45,6 +45,23 @@ extension Ghostty { } } + /// Close the surface associated with this node. This will likely deinitialize the + /// surface. At this point, the surface view in this node tree can never be used again. + func close() { + switch (self) { + case .noSplit(let leaf): + leaf.surface.close() + + case .horizontal(let container): + container.topLeft.close() + container.bottomRight.close() + + case .vertical(let container): + container.topLeft.close() + container.bottomRight.close() + } + } + class Leaf: ObservableObject { let app: ghostty_app_t @Published var surface: SurfaceView @@ -144,6 +161,11 @@ extension Ghostty { ) .onChange(of: requestClose) { value in guard value else { return } + + // Free any resources associated with this root, we're closing. + node.close() + + // Call our callback guard let onClose = self.onClose else { return } onClose() } @@ -289,6 +311,9 @@ extension Ghostty { .onChange(of: closeTopLeft) { value in guard value else { return } + // Close the top left and release all resources + container.topLeft.close() + // When closing the topLeft, our parent becomes the bottomRight. node = container.bottomRight TerminalSplitLeaf.moveFocus(node, previous: container.topLeft) @@ -307,6 +332,9 @@ extension Ghostty { .onChange(of: closeBottomRight) { value in guard value else { return } + // Close the node and release all resources + container.bottomRight.close() + // When closing the bottomRight, our parent becomes the topLeft. node = container.topLeft TerminalSplitLeaf.moveFocus(node, previous: container.bottomRight) diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 659de9da7..5e0c2c67b 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -153,6 +153,16 @@ extension Ghostty { ghostty_surface_free(surface) } + /// Close the surface early. This will free the associated Ghostty surface and the view will + /// no longer render. The view can never be used again. This is a way for us to free the + /// Ghostty resources while references may still be held to this view. I've found that SwiftUI + /// tends to hold this view longer than it should so we free the expensive stuff explicitly. + func close() { + guard let surface = self.surface else { return } + ghostty_surface_free(surface) + self.surface = nil + } + func focusDidChange(_ focused: Bool) { guard let surface = self.surface else { return } ghostty_surface_set_focus(surface, focused)