Repository navigation
Handle OMX extended-keys tmux probes in cmux compat layer #2675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3096a65
758c722
7637ca6
e0a1a27
15cd50e
912beac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2694,7 +2694,13 @@ struct CMUXCLI { | |
| "bind-key", | ||
| "unbind-key", | ||
| "copy-mode", | ||
| "show-option", | ||
| "show-options", | ||
| "set", | ||
| "set-option", | ||
| "set-window-option", | ||
| "set-buffer", | ||
| "setw", | ||
| "paste-buffer", | ||
| "list-buffers", | ||
| "respawn-pane", | ||
|
|
@@ -6871,6 +6877,37 @@ struct CMUXCLI { | |
| cmux omc team 3:claude "implement feature" | ||
| cmux omc --watch | ||
| """) | ||
| case "show-option", "show-options": | ||
| return """ | ||
| Usage: cmux show-options [-svq] [name] | ||
|
|
||
| Show tmux-compat option values. Without a name, list all known values. | ||
|
|
||
| Flags: | ||
| -s Server option scope compatibility flag | ||
| -v Print only the option value | ||
| -q Suppress errors for missing or unsupported options | ||
|
|
||
| Examples: | ||
| cmux show-options | ||
| cmux show-options -sv extended-keys | ||
| """ | ||
| case "set", "set-option", "set-window-option", "setw": | ||
| return """ | ||
| Usage: cmux set-option [-aouq] <name> <value> | ||
|
|
||
| Persist a tmux-compat option value. | ||
|
|
||
| Flags: | ||
| -a Append to the existing value | ||
| -o Only set if the option is not already set | ||
| -u Unset the option | ||
| -q Suppress errors for unknown options | ||
|
|
||
| Examples: | ||
| cmux set-option extended-keys always | ||
| cmux set-option -u extended-keys | ||
| """ | ||
| case "identify": | ||
| return """ | ||
| Usage: cmux identify [--workspace <id|ref|index>] [--surface <id|ref|index>] [--no-caller] | ||
|
|
@@ -11626,7 +11663,7 @@ struct CMUXCLI { | |
| print(buffer) | ||
| } | ||
|
|
||
| case "last-window", "next-window", "previous-window", "set-hook", "set-buffer", "list-buffers": | ||
| case "last-window", "next-window", "previous-window", "set-hook", "show-option", "show-options", "set", "set-option", "set-window-option", "set-buffer", "list-buffers", "setw": | ||
| try runTmuxCompatCommand( | ||
| command: command, | ||
| commandArgs: rawArgs, | ||
|
|
@@ -11689,7 +11726,7 @@ struct CMUXCLI { | |
| try tmuxPruneCompatWorkspaceState(workspaceId: workspaceId) | ||
| } | ||
|
|
||
| case "set-option", "set", "set-window-option", "setw", "source-file", "refresh-client", "attach-session", "detach-client": | ||
| case "source-file", "refresh-client", "attach-session", "detach-client": | ||
| return | ||
|
|
||
| case "-V", "-v": | ||
|
|
@@ -11712,6 +11749,7 @@ struct CMUXCLI { | |
| private struct TmuxCompatStore: Codable { | ||
| var buffers: [String: String] = [:] | ||
| var hooks: [String: String] = [:] | ||
| var options: [String: String] = [:] | ||
| /// Tracks main-vertical layout state per workspace, keyed by workspace ID. | ||
| var mainVerticalLayouts: [String: MainVerticalState] = [:] | ||
| /// Tracks the last surface created by split-window per workspace. | ||
|
|
@@ -11720,19 +11758,41 @@ struct CMUXCLI { | |
| var lastSplitSurface: [String: String] = [:] | ||
|
|
||
| /// Custom decoder so older store files missing newer keys | ||
| /// (mainVerticalLayouts, lastSplitSurface) decode gracefully | ||
| /// (options, mainVerticalLayouts, lastSplitSurface) decode gracefully | ||
| /// instead of throwing and resetting the entire store. | ||
| init(from decoder: Decoder) throws { | ||
| let container = try decoder.container(keyedBy: CodingKeys.self) | ||
| buffers = try container.decodeIfPresent([String: String].self, forKey: .buffers) ?? [:] | ||
| hooks = try container.decodeIfPresent([String: String].self, forKey: .hooks) ?? [:] | ||
| options = try container.decodeIfPresent([String: String].self, forKey: .options) ?? [:] | ||
| mainVerticalLayouts = try container.decodeIfPresent([String: MainVerticalState].self, forKey: .mainVerticalLayouts) ?? [:] | ||
| lastSplitSurface = try container.decodeIfPresent([String: String].self, forKey: .lastSplitSurface) ?? [:] | ||
| } | ||
|
|
||
| init() {} | ||
| } | ||
|
|
||
| private func tmuxCompatDefaultOptions() -> [String: String] { | ||
| [ | ||
| // cmux is not a tmux server, but OMX expects the tmux compatibility | ||
| // shim to answer lease-management probes for extended-keys. | ||
| "extended-keys": "off" | ||
| ] | ||
| } | ||
|
|
||
| private func tmuxCompatDefaultOptionValue(_ name: String) -> String? { | ||
| tmuxCompatDefaultOptions()[name.lowercased()] | ||
| } | ||
|
|
||
| private func tmuxCompatMergedOptions(store: TmuxCompatStore) -> [String: String] { | ||
| tmuxCompatDefaultOptions().merging(store.options) { _, stored in stored } | ||
| } | ||
|
|
||
| private func tmuxCompatOptionValue(_ name: String, store: TmuxCompatStore) -> String? { | ||
| let normalizedName = name.lowercased() | ||
| return tmuxCompatMergedOptions(store: store)[normalizedName] | ||
| } | ||
|
|
||
| private func tmuxCompatStoreURL() -> URL { | ||
| let homePath = ProcessInfo.processInfo.environment["HOME"] | ||
| ?? NSString(string: "~").expandingTildeInPath | ||
|
|
@@ -12204,6 +12264,85 @@ struct CMUXCLI { | |
| try saveTmuxCompatStore(store) | ||
| print("OK") | ||
|
|
||
| case "show-option", "show-options": | ||
| let parsed = try parseTmuxArguments( | ||
| commandArgs, | ||
| valueFlags: [], | ||
| boolFlags: ["-g", "-p", "-q", "-s", "-v", "-w"] | ||
| ) | ||
| let store = loadTmuxCompatStore() | ||
| let optionName = parsed.positional.first? | ||
| .trimmingCharacters(in: .whitespacesAndNewlines) | ||
|
|
||
| if let optionName, !optionName.isEmpty { | ||
| guard let value = tmuxCompatOptionValue(optionName, store: store) else { | ||
| if parsed.hasFlag("-q") { | ||
| return | ||
| } | ||
| throw CLIError(message: "Unsupported tmux compatibility option: \(optionName)") | ||
| } | ||
|
|
||
| if parsed.hasFlag("-v") { | ||
| print(value) | ||
| } else { | ||
| print("\(optionName) \(value)") | ||
| } | ||
| return | ||
| } | ||
|
|
||
| let allOptions = tmuxCompatMergedOptions(store: store) | ||
| if allOptions.isEmpty { | ||
| if parsed.hasFlag("-q") { | ||
| return | ||
| } | ||
| throw CLIError(message: "No tmux compatibility options are set") | ||
| } | ||
| for (name, value) in allOptions.sorted(by: { $0.key < $1.key }) { | ||
| if parsed.hasFlag("-v") { | ||
| print(value) | ||
| } else { | ||
| print("\(name) \(value)") | ||
| } | ||
| } | ||
|
|
||
| case "set-option", "set", "set-window-option", "setw": | ||
| let parsed = try parseTmuxArguments( | ||
| commandArgs, | ||
| valueFlags: [], | ||
| boolFlags: ["-F", "-a", "-g", "-o", "-p", "-q", "-s", "-u", "-w"] | ||
| ) | ||
| guard let optionName = parsed.positional.first? | ||
| .trimmingCharacters(in: .whitespacesAndNewlines), | ||
| !optionName.isEmpty else { | ||
| throw CLIError(message: "\(command) requires an option name") | ||
| } | ||
|
|
||
| var store = loadTmuxCompatStore() | ||
| let normalizedName = optionName.lowercased() | ||
| if parsed.hasFlag("-u") { | ||
| store.options.removeValue(forKey: normalizedName) | ||
| try saveTmuxCompatStore(store) | ||
| return | ||
| } | ||
|
|
||
| let valueParts = Array(parsed.positional.dropFirst()) | ||
| guard !valueParts.isEmpty else { | ||
| throw CLIError(message: "\(command) requires an option value") | ||
| } | ||
| let value = valueParts.joined(separator: " ") | ||
|
|
||
| if parsed.hasFlag("-o"), tmuxCompatOptionValue(normalizedName, store: store) != nil { | ||
| return | ||
| } | ||
|
|
||
| if parsed.hasFlag("-a") { | ||
| let existing = tmuxCompatOptionValue(normalizedName, store: store) ?? "" | ||
| store.options[normalizedName] = existing + value | ||
| } else { | ||
| store.options[normalizedName] = value | ||
| } | ||
| try saveTmuxCompatStore(store) | ||
|
|
||
|
Comment on lines
+12308
to
+12345
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Implement parsed Line 12261 parses 🔧 Suggested patch case "set-option", "set", "set-window-option", "setw":
let parsed = try parseTmuxArguments(
commandArgs,
valueFlags: [],
boolFlags: ["-F", "-a", "-g", "-o", "-p", "-q", "-s", "-u", "-w"]
)
@@
if parsed.hasFlag("-u") {
store.options.removeValue(forKey: normalizedName)
try saveTmuxCompatStore(store)
return
}
@@
- store.options[normalizedName] = value
+ if parsed.hasFlag("-o"), store.options[normalizedName] != nil {
+ return
+ }
+
+ if parsed.hasFlag("-a"), let existing = store.options[normalizedName] {
+ store.options[normalizedName] = existing + value
+ } else {
+ store.options[normalizedName] = value
+ }
try saveTmuxCompatStore(store)🤖 Prompt for AI Agents |
||
| case "popup": | ||
| throw CLIError(message: "popup is not supported yet in cmux CLI parity mode") | ||
|
|
||
|
|
@@ -14393,6 +14532,8 @@ struct CMUXCLI { | |
| last-pane [--workspace <id|ref>] | ||
| find-window [--content] [--select] <query> | ||
| clear-history [--workspace <id|ref>] [--surface <id|ref>] | ||
| show-options [-sv] [name] | ||
| set-option [-asq] <name> <value> | ||
| set-hook [--list] [--unset <event>] | <event> <command> | ||
| popup | ||
| bind-key | unbind-key | copy-mode | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -157,6 +157,45 @@ def main() -> int: | |||||
| c.send_surface(s1, f"echo {capture_token}\n") | ||||||
| _wait_for(lambda: _surface_has(c, ws, s1, capture_token)) | ||||||
|
|
||||||
| show_extended_keys = _run_cli(cli, ["show-options", "-sv", "extended-keys"]) | ||||||
| _must(show_extended_keys.stdout.strip() == "off", f"show-options should default extended-keys to off, got {show_extended_keys.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-sq", "extended-keys", "always"]) | ||||||
| show_extended_keys = _run_cli(cli, ["show-options", "-sv", "extended-keys"]) | ||||||
| _must(show_extended_keys.stdout.strip() == "always", f"set-option should persist extended-keys, got {show_extended_keys.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-sq", "extended-keys", "off"]) | ||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The teardown sets
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. current OMX restores extended-keys with set-option -sq ..., not -u |
||||||
| show_all_options = _run_cli(cli, ["show-options"]) | ||||||
| _must("extended-keys off" in show_all_options.stdout, f"show-options should list default options, got {show_all_options.stdout!r}") | ||||||
|
|
||||||
| custom_option = f"@compat_probe_{stamp}" | ||||||
| _run_cli(cli, ["set-option", custom_option, "alpha"]) | ||||||
| _run_cli(cli, ["set-option", "-o", custom_option, "beta"]) | ||||||
| custom_value = _run_cli(cli, ["show-options", "-v", custom_option]) | ||||||
| _must(custom_value.stdout.strip() == "alpha", f"set-option -o should keep the existing value, got {custom_value.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-a", custom_option, "gamma"]) | ||||||
| custom_value = _run_cli(cli, ["show-options", "-v", custom_option]) | ||||||
| _must(custom_value.stdout.strip() == "alphagamma", f"set-option -a should append to the existing value, got {custom_value.stdout!r}") | ||||||
| show_all_options = _run_cli(cli, ["show-options"]) | ||||||
| _must(f"{custom_option} alphagamma" in show_all_options.stdout, f"show-options should list custom options, got {show_all_options.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-u", custom_option]) | ||||||
|
|
||||||
| show_help = _run_cli(cli, ["show-options", "--help"]) | ||||||
| _must("Usage: cmux show-options" in show_help.stdout, f"show-options --help should print usage, got {show_help.stdout!r}") | ||||||
| set_help = _run_cli(cli, ["set-option", "--help"]) | ||||||
| _must("Usage: cmux set-option" in set_help.stdout, f"set-option --help should print usage, got {set_help.stdout!r}") | ||||||
|
|
||||||
| empty_option = f"@empty_probe_{stamp}" | ||||||
| _run_cli(cli, ["set-option", empty_option, ""]) | ||||||
| empty_value = _run_cli(cli, ["show-options", "-v", empty_option]) | ||||||
| _must(empty_value.stdout == "\n", f"set-option should preserve explicit empty values, got {empty_value.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-u", empty_option]) | ||||||
|
|
||||||
| spaced_option = f"@space_probe_{stamp}" | ||||||
| spaced_value = " padded value " | ||||||
| _run_cli(cli, ["set-option", spaced_option, spaced_value]) | ||||||
| shown_spaced_value = _run_cli(cli, ["show-options", "-v", spaced_option]) | ||||||
| _must(shown_spaced_value.stdout == spaced_value + "\n", f"set-option should preserve leading/trailing whitespace, got {shown_spaced_value.stdout!r}") | ||||||
| _run_cli(cli, ["set-option", "-u", spaced_option]) | ||||||
|
|
||||||
| cap = _run_cli(cli, ["capture-pane", "--workspace", ws, "--surface", s1, "--scrollback"]) | ||||||
| _must(capture_token in cap.stdout, f"capture-pane missing token: {cap.stdout!r}") | ||||||
|
|
||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.