tmux compat: implement issue-153 command set with matrix tests (#221)
* Add tmux rename-window workspace compatibility Implement workspace.rename in the v2 API and wire CLI commands rename-workspace/rename-window with help text. Add a regression test that validates API and CLI rename parity plus error handling. Refs: https://github.com/manaflow-ai/cmux/issues/153 * Add full tmux compatibility command matrix and regression coverage
This commit is contained in:
parent
6f1e100db6
commit
6cb282bf09
5 changed files with 1440 additions and 0 deletions
|
|
@ -667,6 +667,14 @@ class TerminalController {
|
|||
return v2Result(id: id, self.v2WorkspaceMoveToWindow(params: params))
|
||||
case "workspace.reorder":
|
||||
return v2Result(id: id, self.v2WorkspaceReorder(params: params))
|
||||
case "workspace.rename":
|
||||
return v2Result(id: id, self.v2WorkspaceRename(params: params))
|
||||
case "workspace.next":
|
||||
return v2Result(id: id, self.v2WorkspaceNext(params: params))
|
||||
case "workspace.previous":
|
||||
return v2Result(id: id, self.v2WorkspacePrevious(params: params))
|
||||
case "workspace.last":
|
||||
return v2Result(id: id, self.v2WorkspaceLast(params: params))
|
||||
|
||||
|
||||
// Surfaces / input
|
||||
|
|
@ -696,6 +704,8 @@ class TerminalController {
|
|||
return v2Result(id: id, self.v2SurfaceSendText(params: params))
|
||||
case "surface.send_key":
|
||||
return v2Result(id: id, self.v2SurfaceSendKey(params: params))
|
||||
case "surface.clear_history":
|
||||
return v2Result(id: id, self.v2SurfaceClearHistory(params: params))
|
||||
case "surface.trigger_flash":
|
||||
return v2Result(id: id, self.v2SurfaceTriggerFlash(params: params))
|
||||
|
||||
|
|
@ -708,6 +718,16 @@ class TerminalController {
|
|||
return v2Result(id: id, self.v2PaneSurfaces(params: params))
|
||||
case "pane.create":
|
||||
return v2Result(id: id, self.v2PaneCreate(params: params))
|
||||
case "pane.resize":
|
||||
return v2Result(id: id, self.v2PaneResize(params: params))
|
||||
case "pane.swap":
|
||||
return v2Result(id: id, self.v2PaneSwap(params: params))
|
||||
case "pane.break":
|
||||
return v2Result(id: id, self.v2PaneBreak(params: params))
|
||||
case "pane.join":
|
||||
return v2Result(id: id, self.v2PaneJoin(params: params))
|
||||
case "pane.last":
|
||||
return v2Result(id: id, self.v2PaneLast(params: params))
|
||||
|
||||
// Notifications
|
||||
case "notification.create":
|
||||
|
|
@ -962,6 +982,10 @@ class TerminalController {
|
|||
"workspace.close",
|
||||
"workspace.move_to_window",
|
||||
"workspace.reorder",
|
||||
"workspace.rename",
|
||||
"workspace.next",
|
||||
"workspace.previous",
|
||||
"workspace.last",
|
||||
"surface.list",
|
||||
"surface.current",
|
||||
"surface.focus",
|
||||
|
|
@ -976,11 +1000,17 @@ class TerminalController {
|
|||
"surface.send_text",
|
||||
"surface.send_key",
|
||||
"surface.read_text",
|
||||
"surface.clear_history",
|
||||
"surface.trigger_flash",
|
||||
"pane.list",
|
||||
"pane.focus",
|
||||
"pane.surfaces",
|
||||
"pane.create",
|
||||
"pane.resize",
|
||||
"pane.swap",
|
||||
"pane.break",
|
||||
"pane.join",
|
||||
"pane.last",
|
||||
"notification.create",
|
||||
"notification.create_for_surface",
|
||||
"notification.create_for_target",
|
||||
|
|
@ -1686,6 +1716,116 @@ class TerminalController {
|
|||
"index": v2OrNull(newIndex)
|
||||
])
|
||||
}
|
||||
private func v2WorkspaceRename(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
guard let workspaceId = v2UUID(params, "workspace_id") else {
|
||||
return .err(code: "invalid_params", message: "Missing or invalid workspace_id", data: nil)
|
||||
}
|
||||
guard let titleRaw = v2String(params, "title"),
|
||||
!titleRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
return .err(code: "invalid_params", message: "Missing or invalid title", data: nil)
|
||||
}
|
||||
|
||||
let title = titleRaw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
var renamed = false
|
||||
v2MainSync {
|
||||
guard tabManager.tabs.contains(where: { $0.id == workspaceId }) else { return }
|
||||
tabManager.setCustomTitle(tabId: workspaceId, title: title)
|
||||
renamed = true
|
||||
}
|
||||
|
||||
guard renamed else {
|
||||
return .err(code: "not_found", message: "Workspace not found", data: [
|
||||
"workspace_id": workspaceId.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspaceId)
|
||||
])
|
||||
}
|
||||
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
return .ok([
|
||||
"workspace_id": workspaceId.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspaceId),
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId),
|
||||
"title": title
|
||||
])
|
||||
}
|
||||
private func v2WorkspaceNext(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
|
||||
var result: V2CallResult = .err(code: "not_found", message: "No workspace selected", data: nil)
|
||||
v2MainSync {
|
||||
guard tabManager.selectedTabId != nil else { return }
|
||||
if let windowId = v2ResolveWindowId(tabManager: tabManager) {
|
||||
_ = AppDelegate.shared?.focusMainWindow(windowId: windowId)
|
||||
setActiveTabManager(tabManager)
|
||||
}
|
||||
tabManager.selectNextTab()
|
||||
guard let workspaceId = tabManager.selectedTabId else { return }
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"workspace_id": workspaceId.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspaceId),
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func v2WorkspacePrevious(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
|
||||
var result: V2CallResult = .err(code: "not_found", message: "No workspace selected", data: nil)
|
||||
v2MainSync {
|
||||
guard tabManager.selectedTabId != nil else { return }
|
||||
if let windowId = v2ResolveWindowId(tabManager: tabManager) {
|
||||
_ = AppDelegate.shared?.focusMainWindow(windowId: windowId)
|
||||
setActiveTabManager(tabManager)
|
||||
}
|
||||
tabManager.selectPreviousTab()
|
||||
guard let workspaceId = tabManager.selectedTabId else { return }
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"workspace_id": workspaceId.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspaceId),
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func v2WorkspaceLast(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
|
||||
var result: V2CallResult = .err(code: "not_found", message: "No previous workspace in history", data: nil)
|
||||
v2MainSync {
|
||||
guard let before = tabManager.selectedTabId else { return }
|
||||
if let windowId = v2ResolveWindowId(tabManager: tabManager) {
|
||||
_ = AppDelegate.shared?.focusMainWindow(windowId: windowId)
|
||||
setActiveTabManager(tabManager)
|
||||
}
|
||||
tabManager.navigateBack()
|
||||
guard let after = tabManager.selectedTabId, after != before else { return }
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"workspace_id": after.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: after),
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - V2 Surface Methods
|
||||
|
||||
|
|
@ -2390,6 +2530,47 @@ class TerminalController {
|
|||
return result
|
||||
}
|
||||
|
||||
private func v2SurfaceClearHistory(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
|
||||
var result: V2CallResult = .err(code: "internal_error", message: "Failed to clear history", data: nil)
|
||||
v2MainSync {
|
||||
guard let ws = v2ResolveWorkspace(params: params, tabManager: tabManager) else {
|
||||
result = .err(code: "not_found", message: "Workspace not found", data: nil)
|
||||
return
|
||||
}
|
||||
let surfaceId = v2UUID(params, "surface_id") ?? ws.focusedPanelId
|
||||
guard let surfaceId else {
|
||||
result = .err(code: "not_found", message: "No focused surface", data: nil)
|
||||
return
|
||||
}
|
||||
guard let terminalPanel = ws.terminalPanel(for: surfaceId) else {
|
||||
result = .err(code: "invalid_params", message: "Surface is not a terminal", data: ["surface_id": surfaceId.uuidString])
|
||||
return
|
||||
}
|
||||
|
||||
guard terminalPanel.performBindingAction("clear_screen") else {
|
||||
result = .err(code: "not_supported", message: "clear_screen binding action is unavailable", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
terminalPanel.surface.forceRefresh()
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"workspace_id": ws.id.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: ws.id),
|
||||
"surface_id": surfaceId.uuidString,
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId),
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId)
|
||||
])
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private func v2SurfaceReadText(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
|
|
@ -2725,6 +2906,269 @@ class TerminalController {
|
|||
return result
|
||||
}
|
||||
|
||||
private func v2PaneResize(params: [String: Any]) -> V2CallResult {
|
||||
let direction = (v2String(params, "direction") ?? "").lowercased()
|
||||
let amount = v2Int(params, "amount") ?? 1
|
||||
guard ["left", "right", "up", "down"].contains(direction), amount > 0 else {
|
||||
return .err(code: "invalid_params", message: "direction must be one of left|right|up|down and amount must be > 0", data: nil)
|
||||
}
|
||||
return .err(
|
||||
code: "not_supported",
|
||||
message: "pane.resize is not supported yet; Bonsplit does not currently expose a stable programmable divider API",
|
||||
data: [
|
||||
"direction": direction,
|
||||
"amount": amount
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
private func v2PaneSwap(params: [String: Any]) -> V2CallResult {
|
||||
guard let sourcePaneUUID = v2UUID(params, "pane_id") else {
|
||||
return .err(code: "invalid_params", message: "Missing or invalid pane_id", data: nil)
|
||||
}
|
||||
guard let targetPaneUUID = v2UUID(params, "target_pane_id") else {
|
||||
return .err(code: "invalid_params", message: "Missing or invalid target_pane_id", data: nil)
|
||||
}
|
||||
if sourcePaneUUID == targetPaneUUID {
|
||||
return .err(code: "invalid_params", message: "pane_id and target_pane_id must be different", data: nil)
|
||||
}
|
||||
let focus = v2Bool(params, "focus") ?? true
|
||||
|
||||
var result: V2CallResult = .err(code: "internal_error", message: "Failed to swap panes", data: nil)
|
||||
v2MainSync {
|
||||
guard let located = v2LocatePane(sourcePaneUUID) else {
|
||||
result = .err(code: "not_found", message: "Source pane not found", data: ["pane_id": sourcePaneUUID.uuidString])
|
||||
return
|
||||
}
|
||||
guard let targetPane = located.workspace.bonsplitController.allPaneIds.first(where: { $0.id == targetPaneUUID }) else {
|
||||
result = .err(code: "not_found", message: "Target pane not found in source workspace", data: ["target_pane_id": targetPaneUUID.uuidString])
|
||||
return
|
||||
}
|
||||
let workspace = located.workspace
|
||||
let sourcePane = located.paneId
|
||||
|
||||
guard let selectedSourceTab = workspace.bonsplitController.selectedTab(inPane: sourcePane),
|
||||
let selectedTargetTab = workspace.bonsplitController.selectedTab(inPane: targetPane),
|
||||
let sourceSurfaceId = workspace.panelIdFromSurfaceId(selectedSourceTab.id),
|
||||
let targetSurfaceId = workspace.panelIdFromSurfaceId(selectedTargetTab.id) else {
|
||||
result = .err(code: "invalid_state", message: "Both panes must have a selected surface", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Keep pane identities stable during swap when one side has a single surface.
|
||||
var sourcePlaceholder: UUID?
|
||||
var targetPlaceholder: UUID?
|
||||
if workspace.bonsplitController.tabs(inPane: sourcePane).count <= 1 {
|
||||
sourcePlaceholder = workspace.newTerminalSurface(inPane: sourcePane, focus: false)?.id
|
||||
if sourcePlaceholder == nil {
|
||||
result = .err(code: "internal_error", message: "Failed to create source placeholder surface", data: nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
if workspace.bonsplitController.tabs(inPane: targetPane).count <= 1 {
|
||||
targetPlaceholder = workspace.newTerminalSurface(inPane: targetPane, focus: false)?.id
|
||||
if targetPlaceholder == nil {
|
||||
result = .err(code: "internal_error", message: "Failed to create target placeholder surface", data: nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
guard workspace.moveSurface(panelId: sourceSurfaceId, toPane: targetPane, focus: false) else {
|
||||
result = .err(code: "internal_error", message: "Failed moving source surface into target pane", data: nil)
|
||||
return
|
||||
}
|
||||
guard workspace.moveSurface(panelId: targetSurfaceId, toPane: sourcePane, focus: false) else {
|
||||
result = .err(code: "internal_error", message: "Failed moving target surface into source pane", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if let sourcePlaceholder {
|
||||
_ = workspace.closePanel(sourcePlaceholder, force: true)
|
||||
}
|
||||
if let targetPlaceholder {
|
||||
_ = workspace.closePanel(targetPlaceholder, force: true)
|
||||
}
|
||||
|
||||
if focus {
|
||||
workspace.bonsplitController.focusPane(targetPane)
|
||||
}
|
||||
let windowId = located.windowId
|
||||
result = .ok([
|
||||
"window_id": windowId.uuidString,
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId),
|
||||
"workspace_id": workspace.id.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: workspace.id),
|
||||
"pane_id": sourcePane.id.uuidString,
|
||||
"pane_ref": v2Ref(kind: .pane, uuid: sourcePane.id),
|
||||
"target_pane_id": targetPane.id.uuidString,
|
||||
"target_pane_ref": v2Ref(kind: .pane, uuid: targetPane.id),
|
||||
"source_surface_id": sourceSurfaceId.uuidString,
|
||||
"source_surface_ref": v2Ref(kind: .surface, uuid: sourceSurfaceId),
|
||||
"target_surface_id": targetSurfaceId.uuidString,
|
||||
"target_surface_ref": v2Ref(kind: .surface, uuid: targetSurfaceId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func v2PaneBreak(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
let focus = v2Bool(params, "focus") ?? true
|
||||
|
||||
var result: V2CallResult = .err(code: "internal_error", message: "Failed to break pane", data: nil)
|
||||
v2MainSync {
|
||||
guard let sourceWorkspace = v2ResolveWorkspace(params: params, tabManager: tabManager) else {
|
||||
result = .err(code: "not_found", message: "Workspace not found", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let sourcePaneUUID = v2UUID(params, "pane_id")
|
||||
let sourcePane: PaneID? = {
|
||||
if let sourcePaneUUID {
|
||||
return sourceWorkspace.bonsplitController.allPaneIds.first(where: { $0.id == sourcePaneUUID })
|
||||
}
|
||||
return sourceWorkspace.bonsplitController.focusedPaneId
|
||||
}()
|
||||
|
||||
let surfaceId: UUID? = {
|
||||
if let explicitSurface = v2UUID(params, "surface_id") { return explicitSurface }
|
||||
if let sourcePane,
|
||||
let selected = sourceWorkspace.bonsplitController.selectedTab(inPane: sourcePane) {
|
||||
return sourceWorkspace.panelIdFromSurfaceId(selected.id)
|
||||
}
|
||||
return sourceWorkspace.focusedPanelId
|
||||
}()
|
||||
guard let surfaceId else {
|
||||
result = .err(code: "not_found", message: "No source surface to break", data: nil)
|
||||
return
|
||||
}
|
||||
guard sourceWorkspace.panels[surfaceId] != nil else {
|
||||
result = .err(code: "not_found", message: "Surface not found", data: ["surface_id": surfaceId.uuidString])
|
||||
return
|
||||
}
|
||||
let sourceIndex = sourceWorkspace.indexInPane(forPanelId: surfaceId)
|
||||
let sourcePaneForRollback = sourceWorkspace.paneId(forPanelId: surfaceId)
|
||||
|
||||
guard let detached = sourceWorkspace.detachSurface(panelId: surfaceId) else {
|
||||
result = .err(code: "internal_error", message: "Failed to detach source surface", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
let destinationWorkspace = tabManager.addWorkspace()
|
||||
guard let destinationPane = destinationWorkspace.bonsplitController.focusedPaneId
|
||||
?? destinationWorkspace.bonsplitController.allPaneIds.first else {
|
||||
if let sourcePaneForRollback {
|
||||
_ = sourceWorkspace.attachDetachedSurface(
|
||||
detached,
|
||||
inPane: sourcePaneForRollback,
|
||||
atIndex: sourceIndex,
|
||||
focus: true
|
||||
)
|
||||
}
|
||||
result = .err(code: "internal_error", message: "Destination workspace has no pane", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
guard destinationWorkspace.attachDetachedSurface(detached, inPane: destinationPane, focus: focus) != nil else {
|
||||
if let sourcePaneForRollback {
|
||||
_ = sourceWorkspace.attachDetachedSurface(
|
||||
detached,
|
||||
inPane: sourcePaneForRollback,
|
||||
atIndex: sourceIndex,
|
||||
focus: true
|
||||
)
|
||||
}
|
||||
result = .err(code: "internal_error", message: "Failed to attach surface to new workspace", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
if !focus {
|
||||
tabManager.selectWorkspace(sourceWorkspace)
|
||||
}
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId),
|
||||
"workspace_id": destinationWorkspace.id.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: destinationWorkspace.id),
|
||||
"pane_id": destinationPane.id.uuidString,
|
||||
"pane_ref": v2Ref(kind: .pane, uuid: destinationPane.id),
|
||||
"surface_id": surfaceId.uuidString,
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: surfaceId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func v2PaneJoin(params: [String: Any]) -> V2CallResult {
|
||||
guard let targetPaneUUID = v2UUID(params, "target_pane_id") else {
|
||||
return .err(code: "invalid_params", message: "Missing or invalid target_pane_id", data: nil)
|
||||
}
|
||||
|
||||
var surfaceId = v2UUID(params, "surface_id")
|
||||
if surfaceId == nil, let sourcePaneUUID = v2UUID(params, "pane_id") {
|
||||
guard let sourceLocated = v2LocatePane(sourcePaneUUID),
|
||||
let selected = sourceLocated.workspace.bonsplitController.selectedTab(inPane: sourceLocated.paneId),
|
||||
let selectedSurface = sourceLocated.workspace.panelIdFromSurfaceId(selected.id) else {
|
||||
return .err(code: "not_found", message: "Unable to resolve selected surface in source pane", data: [
|
||||
"pane_id": sourcePaneUUID.uuidString
|
||||
])
|
||||
}
|
||||
surfaceId = selectedSurface
|
||||
}
|
||||
guard let surfaceId else {
|
||||
return .err(code: "invalid_params", message: "Missing surface_id (or pane_id with selected surface)", data: nil)
|
||||
}
|
||||
|
||||
var moveParams: [String: Any] = [
|
||||
"surface_id": surfaceId.uuidString,
|
||||
"pane_id": targetPaneUUID.uuidString
|
||||
]
|
||||
if let focus = v2Bool(params, "focus") {
|
||||
moveParams["focus"] = focus
|
||||
}
|
||||
return v2SurfaceMove(params: moveParams)
|
||||
}
|
||||
|
||||
private func v2PaneLast(params: [String: Any]) -> V2CallResult {
|
||||
guard let tabManager = v2ResolveTabManager(params: params) else {
|
||||
return .err(code: "unavailable", message: "TabManager not available", data: nil)
|
||||
}
|
||||
|
||||
var result: V2CallResult = .err(code: "not_found", message: "No alternate pane available", data: nil)
|
||||
v2MainSync {
|
||||
guard let ws = v2ResolveWorkspace(params: params, tabManager: tabManager) else {
|
||||
result = .err(code: "not_found", message: "Workspace not found", data: nil)
|
||||
return
|
||||
}
|
||||
guard let focused = ws.bonsplitController.focusedPaneId else {
|
||||
result = .err(code: "not_found", message: "No focused pane", data: nil)
|
||||
return
|
||||
}
|
||||
guard let target = ws.bonsplitController.allPaneIds.first(where: { $0.id != focused.id }) else {
|
||||
result = .err(code: "not_found", message: "No alternate pane available", data: nil)
|
||||
return
|
||||
}
|
||||
|
||||
ws.bonsplitController.focusPane(target)
|
||||
let selectedSurfaceId = ws.bonsplitController.selectedTab(inPane: target).flatMap { ws.panelIdFromSurfaceId($0.id) }
|
||||
let windowId = v2ResolveWindowId(tabManager: tabManager)
|
||||
result = .ok([
|
||||
"window_id": v2OrNull(windowId?.uuidString),
|
||||
"window_ref": v2Ref(kind: .window, uuid: windowId),
|
||||
"workspace_id": ws.id.uuidString,
|
||||
"workspace_ref": v2Ref(kind: .workspace, uuid: ws.id),
|
||||
"pane_id": target.id.uuidString,
|
||||
"pane_ref": v2Ref(kind: .pane, uuid: target.id),
|
||||
"surface_id": v2OrNull(selectedSurfaceId?.uuidString),
|
||||
"surface_ref": v2Ref(kind: .surface, uuid: selectedSurfaceId)
|
||||
])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// MARK: - V2 Notification Methods
|
||||
|
||||
private func v2NotificationCreate(params: [String: Any]) -> V2CallResult {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue