diff --git a/CLI/cmux.swift b/CLI/cmux.swift index 7b47b783c17a..0e0219cb9766 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -2922,7 +2922,9 @@ struct CMUXCLI { let (windowOpt, argsAfterWindow) = parseOption(argsAfterWorkspace, name: "--window") let (surfaceOpt, argsAfterSurface) = parseOption(argsAfterWindow, name: "--surface") let (directionOpt, argsAfterDirection) = parseOption(argsAfterSurface, name: "--direction") - args = argsAfterDirection + let (paneOpt, argsAfterPane) = parseOption(argsAfterDirection, name: "--pane") + let noFocus = argsAfterPane.contains("--no-focus") + args = argsAfterPane.filter { $0 != "--no-focus" } // Determine subcommand. Explicit "open" is supported, otherwise treat // a single positional argument as shorthand path. @@ -2937,7 +2939,7 @@ struct CMUXCLI { if let first = args.first, first.hasPrefix("-") { throw CLIError( message: - "markdown open: unknown flag '\(first)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up]" + "markdown open: unknown flag '\(first)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up] [--pane ] [--no-focus]" ) } else if let first = args.first, looksLikePath(first) || first.contains(".") { subArgs = args @@ -2955,13 +2957,13 @@ struct CMUXCLI { if let unknownFlag = trailingArgs.first(where: { $0.hasPrefix("-") }) { throw CLIError( message: - "markdown open: unknown flag '\(unknownFlag)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up]" + "markdown open: unknown flag '\(unknownFlag)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up] [--pane ] [--no-focus]" ) } if let extraArg = trailingArgs.first { throw CLIError( message: - "markdown open: unexpected argument '\(extraArg)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up]" + "markdown open: unexpected argument '\(extraArg)'. Usage: cmux markdown open [--workspace ] [--surface ] [--window ] [--direction right|down|left|up] [--pane ] [--no-focus]" ) } @@ -2970,6 +2972,9 @@ struct CMUXCLI { // Build params let direction = directionOpt ?? "right" var params: [String: Any] = ["path": absolutePath, "direction": direction] + if noFocus { + params["focus"] = false + } if let surfaceRaw = surfaceOpt { if let surface = try normalizeSurfaceHandle(surfaceRaw, client: client) { params["surface_id"] = surface @@ -2986,6 +2991,11 @@ struct CMUXCLI { params["window_id"] = window } } + if let paneRaw = paneOpt { + if let pane = try normalizePaneHandle(paneRaw, client: client, workspaceHandle: workspaceRaw) { + params["pane_id"] = pane + } + } let payload = try client.sendV2(method: "markdown.open", params: params) @@ -8094,12 +8104,16 @@ struct CMUXCLI { --surface Source surface to split from (default: focused surface) --window Target window --direction Split direction (default: right) + --pane Add as tab in existing pane instead of splitting + --no-focus Open without focusing the new panel Examples: cmux markdown open plan.md cmux markdown ~/project/CHANGELOG.md cmux markdown open ./docs/design.md --workspace 0 cmux markdown open plan.md --direction down + cmux markdown open plan.md --pane pane:5 + cmux markdown open plan.md --no-focus """ default: return nil diff --git a/Sources/TerminalController.swift b/Sources/TerminalController.swift index 035e36b1dce9..459c2e45d60a 100644 --- a/Sources/TerminalController.swift +++ b/Sources/TerminalController.swift @@ -7615,35 +7615,58 @@ class TerminalController { v2MaybeFocusWindow(for: tabManager) v2MaybeSelectWorkspace(tabManager, workspace: ws) - let sourceSurfaceId = v2UUID(params, "surface_id") ?? ws.focusedPanelId - guard let sourceSurfaceId else { - result = .err(code: "not_found", message: "No focused surface to split", data: nil) - return - } - guard ws.panels[sourceSurfaceId] != nil else { - result = .err(code: "not_found", message: "Source surface not found", data: ["surface_id": sourceSurfaceId.uuidString]) - return - } + let focus = v2FocusAllowed(requested: v2Bool(params, "focus") ?? true) + let requestedPaneUUID = v2UUID(params, "pane_id") - let sourcePaneUUID = ws.paneId(forPanelId: sourceSurfaceId)?.id + let markdownPanelId: UUID? + let sourceSurfaceId: UUID? + let sourcePaneUUID: UUID? - let directionStr = v2String(params, "direction") ?? "right" - guard let direction = parseSplitDirection(directionStr) else { - result = .err(code: "invalid_params", message: "Invalid direction '\(directionStr)' (left|right|up|down)", data: nil) - return - } - let orientation: SplitOrientation = direction.isHorizontal ? .horizontal : .vertical - let insertFirst = (direction == .left || direction == .up) + if let requestedPaneUUID { + // --pane mode: add as tab in existing pane + guard let paneId = ws.bonsplitController.allPaneIds.first(where: { $0.id == requestedPaneUUID }) else { + result = .err(code: "not_found", message: "Pane not found", data: ["pane_id": requestedPaneUUID.uuidString]) + return + } + sourceSurfaceId = nil + sourcePaneUUID = paneId.id + markdownPanelId = ws.newMarkdownSurface( + inPane: paneId, + filePath: filePath, + focus: focus + )?.id + } else { + // Default: split from source surface + let resolvedSourceId = v2UUID(params, "surface_id") ?? ws.focusedPanelId + guard let resolvedSourceId else { + result = .err(code: "not_found", message: "No focused surface to split", data: nil) + return + } + guard ws.panels[resolvedSourceId] != nil else { + result = .err(code: "not_found", message: "Source surface not found", data: ["surface_id": resolvedSourceId.uuidString]) + return + } + sourceSurfaceId = resolvedSourceId + sourcePaneUUID = ws.paneId(forPanelId: resolvedSourceId)?.id - let createdPanel = ws.newMarkdownSplit( - from: sourceSurfaceId, - orientation: orientation, - insertFirst: insertFirst, - filePath: filePath, - focus: v2FocusAllowed() - ) + let directionStr = v2String(params, "direction") ?? "right" + guard let direction = parseSplitDirection(directionStr) else { + result = .err(code: "invalid_params", message: "Invalid direction '\(directionStr)' (left|right|up|down)", data: nil) + return + } + let orientation: SplitOrientation = direction.isHorizontal ? .horizontal : .vertical + let insertFirst = (direction == .left || direction == .up) + + markdownPanelId = ws.newMarkdownSplit( + from: resolvedSourceId, + orientation: orientation, + insertFirst: insertFirst, + filePath: filePath, + focus: focus + )?.id + } - guard let markdownPanelId = createdPanel?.id else { + guard let markdownPanelId else { result = .err(code: "internal_error", message: "Failed to create markdown panel", data: nil) return } @@ -7659,7 +7682,7 @@ class TerminalController { "pane_ref": v2Ref(kind: .pane, uuid: targetPaneUUID), "surface_id": markdownPanelId.uuidString, "surface_ref": v2Ref(kind: .surface, uuid: markdownPanelId), - "source_surface_id": sourceSurfaceId.uuidString, + "source_surface_id": v2OrNull(sourceSurfaceId?.uuidString), "source_surface_ref": v2Ref(kind: .surface, uuid: sourceSurfaceId), "source_pane_id": v2OrNull(sourcePaneUUID?.uuidString), "source_pane_ref": v2Ref(kind: .pane, uuid: sourcePaneUUID),