diff --git a/CLI/cmux.swift b/CLI/cmux.swift index ce976ec38849..898b2d22134e 100644 --- a/CLI/cmux.swift +++ b/CLI/cmux.swift @@ -11550,13 +11550,66 @@ struct CMUXCLI { return nil } + private let tmuxShortFormatAliases: [Character: String] = [ + "D": "pane_id", + "F": "window_flags", + "I": "window_index", + "P": "pane_index", + "S": "session_name", + "W": "window_name" + ] + + private func tmuxExpandShortFormatAliases( + _ format: String, + context: [String: String] + ) -> String { + guard format.contains("#") else { return format } + + var rendered = "" + rendered.reserveCapacity(format.count) + var index = format.startIndex + while index < format.endIndex { + let character = format[index] + guard character == "#" else { + rendered.append(character) + index = format.index(after: index) + continue + } + + let nextIndex = format.index(after: index) + guard nextIndex < format.endIndex else { + rendered.append(character) + break + } + + let nextCharacter = format[nextIndex] + if nextCharacter == "#" { + rendered.append("##") + index = format.index(after: nextIndex) + continue + } + + if let key = tmuxShortFormatAliases[nextCharacter], + let value = context[key] { + rendered.append(value) + index = format.index(after: nextIndex) + continue + } + + rendered.append(character) + index = nextIndex + } + + return rendered + } + private func tmuxRenderFormat( _ format: String?, context: [String: String], fallback: String ) -> String { guard let format, !format.isEmpty else { return fallback } - var rendered = format + var rendered = tmuxExpandShortFormatAliases(format, context: context) for (key, value) in context { rendered = rendered.replacingOccurrences(of: "#{\(key)}", with: value) } @@ -11578,6 +11631,7 @@ struct CMUXCLI { let canonicalWorkspaceId = try resolveWorkspaceId(workspaceId, client: client) var context: [String: String] = [ "session_name": "cmux", + "window_flags": "*", "window_id": "@\(canonicalWorkspaceId)", "window_uuid": canonicalWorkspaceId ] diff --git a/daemon/remote/cmd/cmuxd-remote/tmux_compat.go b/daemon/remote/cmd/cmuxd-remote/tmux_compat.go index 0f06008642ec..1575b6957a05 100644 --- a/daemon/remote/cmd/cmuxd-remote/tmux_compat.go +++ b/daemon/remote/cmd/cmuxd-remote/tmux_compat.go @@ -168,11 +168,54 @@ func parseTmuxArgs(args []string, valueFlags, boolFlags []string) *tmuxParsed { var tmuxFormatVarRe = regexp.MustCompile(`#\{[^}]+\}`) +var tmuxShortFormatAliases = map[byte]string{ + 'D': "pane_id", + 'F': "window_flags", + 'I': "window_index", + 'P': "pane_index", + 'S': "session_name", + 'W': "window_name", +} + +func tmuxExpandShortFormatAliases(format string, context map[string]string) string { + if format == "" { + return format + } + + var builder strings.Builder + builder.Grow(len(format)) + for i := 0; i < len(format); i++ { + ch := format[i] + if ch != '#' || i+1 >= len(format) { + builder.WriteByte(ch) + continue + } + + next := format[i+1] + if next == '#' { + builder.WriteString("##") + i++ + continue + } + if key, ok := tmuxShortFormatAliases[next]; ok { + if value, exists := context[key]; exists { + builder.WriteString(value) + i++ + continue + } + } + + builder.WriteByte(ch) + } + + return builder.String() +} + func tmuxRenderFormat(format string, context map[string]string, fallback string) string { if format == "" { return fallback } - rendered := format + rendered := tmuxExpandShortFormatAliases(format, context) for key, value := range context { rendered = strings.ReplaceAll(rendered, "#{"+key+"}", value) } diff --git a/daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go b/daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go index 850f0f7fdecc..ed69c38d087f 100644 --- a/daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go +++ b/daemon/remote/cmd/cmuxd-remote/tmux_compat_test.go @@ -68,9 +68,14 @@ func TestParseTmuxArgsClusteredValueFlag(t *testing.T) { func TestTmuxRenderFormat(t *testing.T) { ctx := map[string]string{ - "pane_id": "%abc123", - "pane_width": "80", - "window_id": "@ws1", + "session_name": "cmux", + "pane_id": "%abc123", + "pane_index": "2", + "pane_width": "80", + "window_flags": "*", + "window_id": "@ws1", + "window_index": "4", + "window_name": "editor", } tests := []struct { @@ -80,6 +85,9 @@ func TestTmuxRenderFormat(t *testing.T) { }{ {"#{pane_id}", "fallback", "%abc123"}, {"#{pane_id}:#{pane_width}", "", "%abc123:80"}, + {"#S:#I.#P #W", "", "cmux:4.2 editor"}, + {"#F #D", "", "* %abc123"}, + {"##S #S", "", "##S cmux"}, {"#{unknown_var}", "fallback", "fallback"}, {"", "fallback", "fallback"}, {"#{pane_id} #{pane_width} #{window_id}", "", "%abc123 80 @ws1"}, diff --git a/tests_v2/test_tmux_compat_matrix.py b/tests_v2/test_tmux_compat_matrix.py index 59ee3d3ac791..6a75d13babc8 100644 --- a/tests_v2/test_tmux_compat_matrix.py +++ b/tests_v2/test_tmux_compat_matrix.py @@ -269,6 +269,10 @@ def main() -> int: shown = _run_cli(cli, ["display-message", "-p", msg]) _must(msg in shown.stdout, f"display-message -p should print message: {shown.stdout!r}") + short_aliases = _run_cli(cli, ["display-message", "-p", "#S:#I.#P #W"]) + _must(short_aliases.stdout.strip() == f"cmux:0.0 {ws}", + f"display-message short aliases should expand: {short_aliases.stdout!r}") + print("PASS: tmux compatibility matrix commands are wired and tested") return 0