Add set-color/clear-color workspace actions for tab color via CLI (#1873)
* Add set-color/clear-color workspace actions for tab color via CLI Expose the existing tab color functionality through the workspace-action CLI command, enabling programmatic tab color setting without the GUI context menu. Supports both named colors (Red, Blue, Amber, etc.) and hex values (#RRGGBB). Named colors resolve against the built-in palette via case-insensitive matching. Usage: cmux workspace-action --action set-color --color blue cmux workspace-action --action set-color --color "#C0392B" cmux workspace-action set-color Amber cmux workspace-action clear-color * Return explicit null color in clear_color JSON response Restore "color": null in the clear_color response payload so JSON consumers can distinguish "color was cleared" from "no color field". --------- Co-authored-by: Ariel Tobiana <arieltobiana@gmail.com>
This commit is contained in:
parent
533699f98c
commit
7cbd07e8cb
2 changed files with 41 additions and 27 deletions
|
|
@ -3379,14 +3379,18 @@ struct CMUXCLI {
|
|||
let workspaceArg = workspaceOpt ?? (windowOverride == nil ? ProcessInfo.processInfo.environment["CMUX_WORKSPACE_ID"] : nil)
|
||||
let workspaceId = try normalizeWorkspaceHandle(workspaceArg, client: client, allowCurrent: true)
|
||||
|
||||
let inferredTitle = positional.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let title = (titleOpt ?? (inferredTitle.isEmpty ? nil : inferredTitle))?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let inferredPositional = positional.joined(separator: " ").trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
let title = (titleOpt ?? (action == "rename" && !inferredPositional.isEmpty ? inferredPositional : nil))?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
|
||||
if action == "rename", (title?.isEmpty ?? true) {
|
||||
throw CLIError(message: "workspace-action rename requires --title <text> (or a trailing title)")
|
||||
}
|
||||
if action == "set_color", (colorOpt?.isEmpty ?? true) {
|
||||
throw CLIError(message: "workspace-action set-color requires --color <#hex|name>")
|
||||
|
||||
let color = (
|
||||
colorOpt ?? (action == "set_color" ? (inferredPositional.isEmpty ? nil : inferredPositional) : nil)
|
||||
)?.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if action == "set_color", (color?.isEmpty ?? true) {
|
||||
throw CLIError(message: "workspace-action set-color requires --color <name|#hex> (or a trailing color)")
|
||||
}
|
||||
|
||||
var params: [String: Any] = ["action": action]
|
||||
|
|
@ -3396,7 +3400,7 @@ struct CMUXCLI {
|
|||
if let title, !title.isEmpty {
|
||||
params["title"] = title
|
||||
}
|
||||
if let color = colorOpt, !color.isEmpty {
|
||||
if let color, !color.isEmpty {
|
||||
params["color"] = color
|
||||
}
|
||||
|
||||
|
|
@ -3414,6 +3418,9 @@ struct CMUXCLI {
|
|||
if let index = payload["index"] {
|
||||
summaryParts.append("index=\(index)")
|
||||
}
|
||||
if let color = payload["color"] as? String {
|
||||
summaryParts.append("color=\(color)")
|
||||
}
|
||||
printV2Payload(payload, jsonOutput: jsonOutput, idFormat: idFormat, fallbackText: summaryParts.joined(separator: " "))
|
||||
}
|
||||
|
||||
|
|
@ -6260,15 +6267,21 @@ struct CMUXCLI {
|
|||
Flags:
|
||||
--action <name> Action name (required if not positional)
|
||||
--workspace <id|ref|index> Target workspace (default: current/$CMUX_WORKSPACE_ID)
|
||||
--title <text> Title for rename (or pass trailing title text)
|
||||
--color <#hex|name> Color for set-color (e.g. '#C0392B' or 'Red')
|
||||
--title <text> Title for rename
|
||||
--color <name|#hex> Color for set-color (name or #RRGGBB hex)
|
||||
|
||||
Named colors:
|
||||
Red, Crimson, Orange, Amber, Olive, Green, Teal, Aqua,
|
||||
Blue, Navy, Indigo, Purple, Magenta, Rose, Brown, Charcoal
|
||||
|
||||
Example:
|
||||
cmux workspace-action --workspace workspace:2 --action pin
|
||||
cmux workspace-action --action rename --title "infra"
|
||||
cmux workspace-action close-others
|
||||
cmux workspace-action --action set-color --workspace workspace:1 --color '#C0392B'
|
||||
cmux workspace-action --action clear-color --workspace workspace:1
|
||||
cmux workspace-action --action set-color --color blue
|
||||
cmux workspace-action --action set-color --color "#C0392B"
|
||||
cmux workspace-action set-color Amber
|
||||
cmux workspace-action clear-color
|
||||
"""
|
||||
case "tab-action":
|
||||
return """
|
||||
|
|
@ -11981,7 +11994,7 @@ struct CMUXCLI {
|
|||
close-window --window <id>
|
||||
move-workspace-to-window --workspace <id|ref> --window <id|ref>
|
||||
reorder-workspace --workspace <id|ref|index> (--index <n> | --before <id|ref|index> | --after <id|ref|index>) [--window <id|ref|index>]
|
||||
workspace-action --action <name> [--workspace <id|ref|index>] [--title <text>] [--color <#hex|name>]
|
||||
workspace-action --action <name> [--workspace <id|ref|index>] [--title <text>] [--color <name|#hex>]
|
||||
list-workspaces
|
||||
new-workspace [--cwd <path>] [--command <text>]
|
||||
ssh <destination> [--name <title>] [--port <n>] [--identity <path>] [--ssh-option <opt>] [-- <remote-command-args>]
|
||||
|
|
|
|||
|
|
@ -4085,29 +4085,30 @@ class TerminalController {
|
|||
finish()
|
||||
|
||||
case "set_color":
|
||||
guard let colorRaw = v2String(params, "color"), !colorRaw.isEmpty else {
|
||||
result = .err(code: "invalid_params", message: "set-color requires --color", data: nil)
|
||||
guard let colorRaw = v2String(params, "color"),
|
||||
!colorRaw.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
|
||||
result = .err(code: "invalid_params", message: "Missing or invalid color", data: nil)
|
||||
return
|
||||
}
|
||||
// Resolve named color to hex via palette lookup
|
||||
let resolved: String
|
||||
if colorRaw.hasPrefix("#") {
|
||||
guard let normalized = WorkspaceTabColorSettings.normalizedHex(colorRaw) else {
|
||||
result = .err(code: "invalid_params", message: "Invalid hex color '\(colorRaw)'. Expected #RRGGBB", data: nil)
|
||||
return
|
||||
}
|
||||
resolved = normalized
|
||||
} else if let entry = WorkspaceTabColorSettings.defaultPalette.first(where: {
|
||||
$0.name.lowercased() == colorRaw.lowercased()
|
||||
let colorInput = colorRaw.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
// Resolve named colors from effective palette (includes user overrides, excludes custom entries)
|
||||
let effectivePalette = WorkspaceTabColorSettings.defaultPaletteWithOverrides()
|
||||
let hex: String
|
||||
if let entry = effectivePalette.first(where: {
|
||||
$0.name.caseInsensitiveCompare(colorInput) == .orderedSame
|
||||
}) {
|
||||
resolved = entry.hex
|
||||
hex = entry.hex
|
||||
} else if let normalized = WorkspaceTabColorSettings.normalizedHex(colorInput) {
|
||||
hex = normalized
|
||||
} else {
|
||||
let names = WorkspaceTabColorSettings.defaultPalette.map(\.name).joined(separator: ", ")
|
||||
result = .err(code: "invalid_params", message: "Unknown color '\(colorRaw)'. Use #RRGGBB or: \(names)", data: nil)
|
||||
let colorNames = effectivePalette.map(\.name)
|
||||
result = .err(code: "invalid_params", message: "Invalid color. Use a hex value (#RRGGBB) or a named color.", data: [
|
||||
"named_colors": colorNames
|
||||
])
|
||||
return
|
||||
}
|
||||
tabManager.setTabColor(tabId: workspace.id, color: resolved)
|
||||
finish(["color": resolved])
|
||||
tabManager.setTabColor(tabId: workspace.id, color: hex)
|
||||
finish(["color": hex])
|
||||
|
||||
case "clear_color":
|
||||
tabManager.setTabColor(tabId: workspace.id, color: nil)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue