mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-09 08:59:40 +02:00
fix(tui): advertise the key that actually detaches, and stop tmux painting it green
Two things the attach status bar got wrong, both found in a beta test. The bar read `Ctrl+B D`. tmux key tables are case-sensitive: lowercase `d` is `detach-client`, capital `D` is `choose-client`. Pressing what the bar said opened a client chooser and left the tester attached, with the way out on screen and inert. The key is now READ from `list-keys -T prefix` the same way the prefix already was, rather than hardcoded, so a rebound tmux is followed too and the label cannot drift from the binding again. It never goes through formatPrefixKey(), which uppercases. The bar also rendered as a full-width bright green slab. Only `status-format[0]` was styled, so tmux's stock `status-style` (`bg=green,fg=black`) stayed underneath it and won; `#[reverse]` on top could not undo it. `status-style` is now set explicitly to `bg=default,fg=default` and snapshotted/restored with the rest, so the bar sits on the terminal's own background and reads as a hint line. Tests pin both: that the chord ends in lowercase `d` and never ` D`, that a rebound key prints verbatim, that `status-style` is part of the banner, and that parseDetachKey() picks `d` out of verbatim tmux 3.4 `list-keys` output while ignoring `detach-client -a`/`-P`, which act on other clients.
This commit is contained in:
@@ -467,6 +467,28 @@ export function parseSessionOptions(stdout: string, keys: readonly string[]): Tu
|
||||
return options;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key bound to a bare `detach-client` in `list-keys -T prefix` output, or
|
||||
* null when nothing there detaches.
|
||||
*
|
||||
* ⚠️ Read rather than assumed because the two candidates differ only by case:
|
||||
* tmux ships `d` as `detach-client` and `D` as `choose-client`, and advertising
|
||||
* the wrong one leaves a tester attached with the way out on screen. Bindings
|
||||
* that pass ARGUMENTS to `detach-client` (`-a`, `-P`) are skipped: those act on
|
||||
* other clients or kill the pane's process, which is not what the bar promises.
|
||||
* A single-character binding wins over a named key, since that is what a status
|
||||
* line can print literally.
|
||||
*/
|
||||
export function parseDetachKey(stdout: string): string | null {
|
||||
const candidates: string[] = [];
|
||||
for (const line of stdout.split('\n')) {
|
||||
const match = /^bind-key\s+(?:-\S+\s+)*?-T\s+prefix\s+(\S+)\s+detach-client\s*$/.exec(line.trim());
|
||||
const key = match?.[1];
|
||||
if (key) candidates.push(unquoteTmuxValue(key));
|
||||
}
|
||||
return candidates.find((key) => key.length === 1) ?? candidates[0] ?? null;
|
||||
}
|
||||
|
||||
/** `status-format[0]` → `status-format`; a plain option name → null. */
|
||||
export function arrayOptionBase(key: string): string | null {
|
||||
const match = /^([^[\]]+)\[\d+\]$/.exec(key);
|
||||
@@ -853,6 +875,20 @@ export class TuiClient {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* The key that detaches, straight from tmux's own key table. Key tables are
|
||||
* server-global, so unlike the prefix this takes no session. A failure is
|
||||
* null and the caller falls back to tmux's stock `d`.
|
||||
*/
|
||||
async readDetachKey(): Promise<string | null> {
|
||||
try {
|
||||
const { stdout } = await this.exec('tmux', ['-L', this.socket, 'list-keys', '-T', 'prefix']);
|
||||
return parseDetachKey(stdout);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Snapshot the session-level options an attach is about to overwrite. */
|
||||
async readSessionOptions(muxName: string, keys: readonly string[]): Promise<TuiSessionOptions | null> {
|
||||
if (!MUX_NAME_PATTERN.test(muxName)) return null;
|
||||
|
||||
Reference in New Issue
Block a user