Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Sources/RemoteTmuxController+Attach.swift
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,10 @@ extension RemoteTmuxController {
let workspaces = sourceManager.tabs.filter { hostWorkspaceIds.contains($0.id) }
// A window holding nothing but this host's mirrors has no reason to stay once they
// leave. Emptied, it recovers by opening a fresh local shell, which leaves a blank
// window on screen that nobody asked for.
// window on screen that nobody asked for. Its Dock panels belong to no workspace
// and close with the window, so a window with any of those stays.
let holdsOnlyTheseMirrors = workspaces.count == sourceManager.tabs.count
&& (AppDelegate.shared?.existingWindowDock(for: sourceManager)?.panels.isEmpty ?? true)
for workspace in workspaces {
guard let detached = sourceManager.detachWorkspace(tabId: workspace.id) else { continue }
targetManager.attachWorkspace(detached, select: false)
Expand Down
39 changes: 39 additions & 0 deletions cmuxTests/RemoteTmuxMirrorCloseDetachTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,45 @@ import Testing
)
}

/// The window's Dock holds terminals and browsers that belong to no workspace. Closing the
/// window closes them, so a window whose workspaces were all mirrors stays open while its
/// Dock has panels.
@Test func consolidatingMirrorsKeepsAWindowWhoseDockHasPanels() throws {
// The cleanup detach runs ssh to close its master; `true` stands in for it.
let previousSSH = environmentValue(for: sshOverrideKey)
setenv(sshOverrideKey, "/usr/bin/true", 1)
defer { restoreEnvironment(sshOverrideKey, previousValue: previousSSH) }
let source = try Harness()
defer { source.tearDown() }
let target = try Harness()
defer { target.tearDown() }
let host = RemoteTmuxHost(destination: "consolidate-\(UUID().uuidString)@example.test")
let controller = source.controller
controller.cacheConnection(RemoteTmuxControlConnection(host: host, sessionName: "dev"))
defer {
if controller.sessionMirror(host: host, sessionName: "dev") != nil {
controller.detach(host: host, sessionName: "dev")
}
}
#expect(try controller.mirrorSession(host: host, sessionName: "dev", into: source.manager))
let mirrorWorkspace = try #require(source.manager.tabs.first(where: { $0.isRemoteTmuxMirror }))
source.manager.closeWorkspace(source.workspace, recordHistory: false)
#expect(source.manager.tabs.map(\.id) == [mirrorWorkspace.id])
let dock = source.appDelegate.windowDock(forWindowId: source.windowId)
let dockPanel = TerminalPanel(workspaceId: dock.workspaceId, runtimeSpawnPolicy: .pacedSessionRestore)
dock.panels[dockPanel.id] = dockPanel

controller.moveExistingMirrors(for: host, into: target.manager)

#expect(target.manager.tabs.contains { $0.id == mirrorWorkspace.id })
#expect(
source.appDelegate.listMainWindowSummaries().contains { $0.windowId == source.windowId },
"the window closed while its Dock still held a terminal"
)
#expect(!dock.isRetired)
#expect(dock.panels[dockPanel.id] != nil)
}

/// A window with workspaces of its own keeps them, and gains nothing, when the host's
/// mirrors move out.
@Test func consolidatingMirrorsLeavesAWindowWithOtherWorkspacesAlone() throws {
Expand Down
Loading