diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 0269522e..323cd891 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -9345,7 +9345,7 @@ class TerminalController { sidebar_overlay_gate [active|inactive] - Return true/false if sidebar outside-drop overlay would capture (test-only) terminal_drop_overlay_probe [deferred|direct] - Trigger focused terminal drop-overlay show path and report animation counts (test-only) activate_app - Bring app + main window to front (test-only) - send_workspace - Send text to a workspace's focused terminal (test-only) + send_workspace - Send text to a workspace's selected terminal (test-only) is_terminal_focused - Return true/false if terminal surface is first responder (test-only) read_terminal_text [id|idx] - Read visible terminal text (base64, test-only) render_stats [id|idx] - Read terminal render stats (draw counters, test-only) @@ -11617,9 +11617,13 @@ class TerminalController { error = "ERROR: Workspace not found" return } - guard let tab = targetManager.tabs.first(where: { $0.id == workspaceId }), - let terminalPanel = tab.focusedTerminalPanel else { - error = "ERROR: No focused terminal in workspace" + guard let tab = targetManager.tabs.first(where: { $0.id == workspaceId }) else { + error = "ERROR: Workspace not found" + return + } + + guard let terminalPanel = sendableWorkspaceTerminalPanel(in: tab) else { + error = "ERROR: No selected terminal in workspace" return } @@ -11641,6 +11645,44 @@ class TerminalController { return success ? "OK" : "ERROR: Failed to send input" } + private func sendableWorkspaceTerminalPanel(in workspace: Workspace) -> TerminalPanel? { + func selectedTerminalPanel(in paneId: PaneID) -> TerminalPanel? { + guard let selectedTab = workspace.bonsplitController.selectedTab(inPane: paneId), + let panelId = workspace.panelIdFromSurfaceId(selectedTab.id), + let terminalPanel = workspace.panels[panelId] as? TerminalPanel else { + return nil + } + return terminalPanel + } + + func isSelectedTerminalPanel(_ terminalPanel: TerminalPanel) -> Bool { + guard let surfaceId = workspace.surfaceIdFromPanelId(terminalPanel.id) else { + return false + } + return workspace.bonsplitController.allPaneIds.contains { paneId in + workspace.bonsplitController.selectedTab(inPane: paneId)?.id == surfaceId + } + } + + if let focusedPane = workspace.bonsplitController.focusedPaneId, + let terminalPanel = selectedTerminalPanel(in: focusedPane) { + return terminalPanel + } + + if let rememberedTerminal = workspace.lastRememberedTerminalPanelForConfigInheritance(), + isSelectedTerminalPanel(rememberedTerminal) { + return rememberedTerminal + } + + for paneId in workspace.bonsplitController.allPaneIds { + if let terminalPanel = selectedTerminalPanel(in: paneId) { + return terminalPanel + } + } + + return nil + } + private func sendInputToSurface(_ args: String) -> String { guard let tabManager = tabManager else { return "ERROR: TabManager not available" } let parts = args.split(separator: " ", maxSplits: 1).map(String.init) diff --git a/cmuxUITests/MultiWindowNotificationsUITests.swift b/cmuxUITests/MultiWindowNotificationsUITests.swift index 5753173f..2ad740ce 100644 --- a/cmuxUITests/MultiWindowNotificationsUITests.swift +++ b/cmuxUITests/MultiWindowNotificationsUITests.swift @@ -289,8 +289,12 @@ final class MultiWindowNotificationsUITests: XCTestCase { "2>\(shellSingleQuote(commandStderrPath));", "printf '%s' $? >\(shellSingleQuote(commandStatusPath))) >/dev/null 2>&1 &" ].joined(separator: " ") - guard socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") == "OK" else { - XCTFail("Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1)") + let sendWorkspaceResponse = socketCommand("send_workspace \(tabId1) \(notifyCommand)\\n") + guard sendWorkspaceResponse == "OK" else { + XCTFail( + "Failed to inject delayed bundled `cmux notify` command into source workspace \(tabId1). " + + "response=\(sendWorkspaceResponse ?? "")" + ) return }