Add --panel flag to new-split command (#10)

* Add --panel flag to new-split command

Allows splitting a specific panel without changing focus first.

Usage: cmuxterm new-split <direction> [--panel <id|index>]

Example: cmuxterm new-split down --panel 1

* Return new panel ID from new-split command

new-split now returns the UUID of the newly created panel, enabling
reliable chaining of split operations without index drift issues.

Before: OK
After:  OK F2675177-3838-49AF-A1A0-1744C0048E99

Example workflow to create left + 2x2 grid on right:
  RIGHT=$(cmuxterm new-split right | awk '{print $2}')
  BOTTOM=$(cmuxterm new-split down --panel $RIGHT | awk '{print $2}')
  cmuxterm new-split right --panel $RIGHT
  cmuxterm new-split right --panel $BOTTOM
This commit is contained in:
Lawrence Chen 2026-02-03 21:37:49 -08:00 committed by GitHub
parent 7bae1216ff
commit 600683cd7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 45 additions and 16 deletions

View file

@ -192,10 +192,12 @@ struct CMUXCLI {
print(response)
case "new-split":
guard let direction = commandArgs.first else {
let (panelArg, remaining) = parseOption(commandArgs, name: "--panel")
guard let direction = remaining.first else {
throw CLIError(message: "new-split requires a direction")
}
let response = try client.send(command: "new_split \(direction)")
let cmd = panelArg != nil ? "new_split \(direction) \(panelArg!)" : "new_split \(direction)"
let response = try client.send(command: cmd)
print(response)
case "list-panels":
@ -509,7 +511,7 @@ struct CMUXCLI {
ping
list-tabs
new-tab
new-split <left|right|up|down>
new-split <left|right|up|down> [--panel <id|index>]
list-panels [--tab <id|index>]
focus-panel --panel <id|index>
close-tab --tab <id|index>

View file

@ -429,7 +429,7 @@ class GhosttyApp {
return false
}
return performOnMain {
tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction)
tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction) != nil
}
case GHOSTTY_ACTION_GOTO_SPLIT:
guard let tabId = surfaceView.tabId,

View file

@ -764,9 +764,9 @@ class TabManager: ObservableObject {
}
}
func newSplit(tabId: UUID, surfaceId: UUID, direction: SplitTree<TerminalSurface>.NewDirection) -> Bool {
guard let tab = tabs.first(where: { $0.id == tabId }) else { return false }
return tab.newSplit(from: surfaceId, direction: direction) != nil
func newSplit(tabId: UUID, surfaceId: UUID, direction: SplitTree<TerminalSurface>.NewDirection) -> UUID? {
guard let tab = tabs.first(where: { $0.id == tabId }) else { return nil }
return tab.newSplit(from: surfaceId, direction: direction)?.id
}
func moveSplitFocus(tabId: UUID, surfaceId: UUID, direction: SplitTree<TerminalSurface>.FocusDirection) -> Bool {

View file

@ -236,7 +236,7 @@ class TerminalController {
ping - Check if server is running
list_tabs - List all tabs with IDs
new_tab - Create a new tab
new_split <direction> - Split focused surface (left/right/up/down)
new_split <direction> [panel] - Split surface (left/right/up/down), optionally specify panel
list_surfaces [tab] - List surfaces for tab (current tab if omitted)
focus_surface <id|idx> - Focus surface by ID or index (current tab)
close_tab <id> - Close tab by ID
@ -311,24 +311,51 @@ class TerminalController {
return "OK \(newTabId?.uuidString ?? "unknown")"
}
private func newSplit(_ directionArg: String) -> String {
private func newSplit(_ args: String) -> String {
guard let tabManager = tabManager else { return "ERROR: TabManager not available" }
let trimmed = directionArg.trimmingCharacters(in: .whitespacesAndNewlines)
guard let direction = parseSplitDirection(trimmed) else {
let trimmed = args.trimmingCharacters(in: .whitespacesAndNewlines)
let parts = trimmed.split(separator: " ", maxSplits: 1).map(String.init)
guard !parts.isEmpty else {
return "ERROR: Invalid direction. Use left, right, up, or down."
}
var success = false
let directionArg = parts[0]
let panelArg = parts.count > 1 ? parts[1] : ""
guard let direction = parseSplitDirection(directionArg) else {
return "ERROR: Invalid direction. Use left, right, up, or down."
}
var result = "ERROR: Failed to create split"
DispatchQueue.main.sync {
guard let tabId = tabManager.selectedTabId,
let tab = tabManager.tabs.first(where: { $0.id == tabId }),
let surfaceId = tab.focusedSurfaceId else {
let tab = tabManager.tabs.first(where: { $0.id == tabId }) else {
return
}
success = tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction)
// If panel arg provided, resolve it; otherwise use focused surface
let surfaceId: UUID?
if !panelArg.isEmpty {
surfaceId = resolveSurfaceId(from: panelArg, tab: tab)
if surfaceId == nil {
result = "ERROR: Panel not found"
return
}
} else {
surfaceId = tab.focusedSurfaceId
}
guard let targetSurface = surfaceId else {
result = "ERROR: No surface to split"
return
}
if let newPanelId = tabManager.newSplit(tabId: tabId, surfaceId: targetSurface, direction: direction) {
result = "OK \(newPanelId.uuidString)"
}
}
return success ? "OK" : "ERROR: Failed to create split"
return result
}
private func listSurfaces(_ tabArg: String) -> String {