Handle child-exit close for last-terminal workspaces (#254)
This commit is contained in:
parent
ff11ba0309
commit
63d3fc5949
2 changed files with 92 additions and 0 deletions
|
|
@ -896,6 +896,26 @@ class TabManager: ObservableObject {
|
|||
/// This should never prompt: the process is already gone, and Ghostty emits the
|
||||
/// `SHOW_CHILD_EXITED` action specifically so the host app can decide what to do.
|
||||
func closePanelAfterChildExited(tabId: UUID, surfaceId: UUID) {
|
||||
guard let tab = tabs.first(where: { $0.id == tabId }) else { return }
|
||||
guard tab.panels[surfaceId] != nil else { return }
|
||||
|
||||
// Child-exit on the last panel should collapse the workspace, matching explicit close
|
||||
// semantics (and close the window when it was the last workspace).
|
||||
if tab.panels.count <= 1 {
|
||||
if tabs.count <= 1 {
|
||||
if let app = AppDelegate.shared {
|
||||
app.notificationStore?.clearNotifications(forTabId: tabId)
|
||||
app.closeMainWindowContainingTabId(tabId)
|
||||
} else {
|
||||
// Headless/test fallback when no AppDelegate window context exists.
|
||||
closeRuntimeSurface(tabId: tabId, surfaceId: surfaceId)
|
||||
}
|
||||
} else {
|
||||
closeWorkspace(tab)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
closeRuntimeSurface(tabId: tabId, surfaceId: surfaceId)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -877,6 +877,78 @@ final class WorkspaceReorderTests: XCTestCase {
|
|||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TabManagerChildExitCloseTests: XCTestCase {
|
||||
func testChildExitOnLastPanelClosesSelectedWorkspaceAndKeepsIndexStable() {
|
||||
let manager = TabManager()
|
||||
let first = manager.tabs[0]
|
||||
let second = manager.addWorkspace()
|
||||
let third = manager.addWorkspace()
|
||||
|
||||
manager.selectWorkspace(second)
|
||||
XCTAssertEqual(manager.selectedTabId, second.id)
|
||||
|
||||
guard let secondPanelId = second.focusedPanelId else {
|
||||
XCTFail("Expected focused panel in selected workspace")
|
||||
return
|
||||
}
|
||||
|
||||
manager.closePanelAfterChildExited(tabId: second.id, surfaceId: secondPanelId)
|
||||
|
||||
XCTAssertEqual(manager.tabs.map(\.id), [first.id, third.id])
|
||||
XCTAssertEqual(
|
||||
manager.selectedTabId,
|
||||
third.id,
|
||||
"Expected selection to stay at the same index after deleting the selected workspace"
|
||||
)
|
||||
}
|
||||
|
||||
func testChildExitOnLastPanelInLastWorkspaceSelectsPreviousWorkspace() {
|
||||
let manager = TabManager()
|
||||
let first = manager.tabs[0]
|
||||
let second = manager.addWorkspace()
|
||||
|
||||
manager.selectWorkspace(second)
|
||||
XCTAssertEqual(manager.selectedTabId, second.id)
|
||||
|
||||
guard let secondPanelId = second.focusedPanelId else {
|
||||
XCTFail("Expected focused panel in selected workspace")
|
||||
return
|
||||
}
|
||||
|
||||
manager.closePanelAfterChildExited(tabId: second.id, surfaceId: secondPanelId)
|
||||
|
||||
XCTAssertEqual(manager.tabs.map(\.id), [first.id])
|
||||
XCTAssertEqual(
|
||||
manager.selectedTabId,
|
||||
first.id,
|
||||
"Expected previous workspace to be selected after closing the last-index workspace"
|
||||
)
|
||||
}
|
||||
|
||||
func testChildExitOnNonLastPanelClosesOnlyPanel() {
|
||||
let manager = TabManager()
|
||||
guard let workspace = manager.selectedWorkspace,
|
||||
let initialPanelId = workspace.focusedPanelId else {
|
||||
XCTFail("Expected selected workspace with focused panel")
|
||||
return
|
||||
}
|
||||
|
||||
guard let splitPanel = workspace.newTerminalSplit(from: initialPanelId, orientation: .horizontal) else {
|
||||
XCTFail("Expected split terminal panel to be created")
|
||||
return
|
||||
}
|
||||
|
||||
let panelCountBefore = workspace.panels.count
|
||||
manager.closePanelAfterChildExited(tabId: workspace.id, surfaceId: splitPanel.id)
|
||||
|
||||
XCTAssertEqual(manager.tabs.count, 1)
|
||||
XCTAssertEqual(manager.tabs.first?.id, workspace.id)
|
||||
XCTAssertEqual(workspace.panels.count, panelCountBefore - 1)
|
||||
XCTAssertNotNil(workspace.panels[initialPanelId], "Expected sibling panel to remain")
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
final class TabManagerPendingUnfocusPolicyTests: XCTestCase {
|
||||
func testDoesNotUnfocusWhenPendingTabIsCurrentlySelected() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue