fix(tmux): scrolling a pane no longer freezes typing
The wheel puts a pane into tmux's copy-mode, which took every typed key as a scrollback command and painted a yellow [0/0] box in the corner. In AgentHub's own panes (every other session on the server keeps tmux's defaults, restored verbatim): - the copy-mode position box is off (copy-mode-position-format ""); - the first keystroke after a mouse report leaves copy-mode (`copy-mode -q`, pty-attach) before it is written, once per scroll; - copy-mode keys that open a tmux prompt (g, :, /, f, digits…) leave copy-mode and reach the program; - the wheel scrolls 10 lines a notch instead of 5, and the first notch already scrolls instead of only entering copy-mode.
This commit is contained in:
@@ -608,6 +608,119 @@ function enableMouse(name) {
|
||||
} catch {
|
||||
/* tmux < 3.7 */
|
||||
}
|
||||
// The wheel's copy-mode paints a yellow `[0/0]` position box in the pane's
|
||||
// corner; an empty format draws none. A window option of a recent tmux
|
||||
// only — an older one refuses it and keeps the box.
|
||||
try {
|
||||
run(["set-option", "-w", "-t", pane(name), "copy-mode-position-format", ""]);
|
||||
} catch {
|
||||
/* tmux without copy-mode-position-format */
|
||||
}
|
||||
try {
|
||||
if (exact()) onServerOf(name, copyModePromptBindings());
|
||||
} catch {
|
||||
/* an old tmux without `if-shell -F`/`#{m:}`: the prompts stay */
|
||||
}
|
||||
try {
|
||||
if (exact()) onServerOf(name, wheelBindings());
|
||||
} catch {
|
||||
/* an old tmux: the wheel keeps tmux's default speed */
|
||||
}
|
||||
}
|
||||
|
||||
// tmux's wheel scrolls 5 lines a notch, and its first notch only ENTERS
|
||||
// copy-mode without scrolling. In OUR panes a notch is WHEEL_LINES lines and
|
||||
// the first one already scrolls; every other session keeps tmux's default
|
||||
// binding, restored verbatim (same server-wide re-bind as the prompts below).
|
||||
const WHEEL_LINES = 10;
|
||||
const WHEEL_ROOT_GUARD = "#{||:#{alternate_on},#{pane_in_mode},#{mouse_any_flag}}";
|
||||
|
||||
/** One `;`-chained argv re-binding the wheel in `root` and both copy-mode tables. */
|
||||
function wheelBindings() {
|
||||
const scroll = (dir, n) => `select-pane ; send-keys -X -N ${n} scroll-${dir}`;
|
||||
const cmds = [
|
||||
[
|
||||
...["bind-key", "-T", "root", "WheelUpPane", "if-shell", "-F", WHEEL_ROOT_GUARD],
|
||||
"send-keys -M",
|
||||
`if-shell -F "${OURS}" { copy-mode -e ; send-keys -X -N ${WHEEL_LINES} scroll-up } { copy-mode -e }`,
|
||||
],
|
||||
];
|
||||
for (const table of ["copy-mode", "copy-mode-vi"])
|
||||
for (const [key, dir] of [
|
||||
["WheelUpPane", "up"],
|
||||
["WheelDownPane", "down"],
|
||||
])
|
||||
cmds.push([
|
||||
...["bind-key", "-T", table, key, "if-shell", "-F", OURS],
|
||||
scroll(dir, WHEEL_LINES),
|
||||
scroll(dir, 5),
|
||||
]);
|
||||
return cmds.flatMap((c, i) => (i ? [";", ...c] : c));
|
||||
}
|
||||
|
||||
// The wheel puts a pane into copy-mode, and there a typed `g` (`:` with vi
|
||||
// keys), `f`, `/`, a digit… opens a tmux prompt — "(goto line)", "(jump
|
||||
// forward)", "(repeat)" — instead of reaching the CLI. In OUR panes those keys
|
||||
// leave copy-mode and are typed into the program; every other session on the
|
||||
// server (the user's own tmux shares the default socket) keeps tmux's default
|
||||
// binding, restored verbatim below. Key tables are server-wide, so this is
|
||||
// re-bound on every attach like the options above (idempotent).
|
||||
const OURS = "#{m:agenthub-lane-*,#{session_name}}";
|
||||
const COPY_MODE_PROMPTS = {
|
||||
"copy-mode": {
|
||||
g: 'command-prompt -P -p "(goto line)" { send-keys -X goto-line -- "%%" }',
|
||||
f: 'command-prompt -1P -p "(jump forward)" { send-keys -X jump-forward -- "%%" }',
|
||||
F: 'command-prompt -1P -p "(jump backward)" { send-keys -X jump-backward -- "%%" }',
|
||||
t: 'command-prompt -1P -p "(jump to forward)" { send-keys -X jump-to-forward -- "%%" }',
|
||||
T: 'command-prompt -1P -p "(jump to backward)" { send-keys -X jump-to-backward -- "%%" }',
|
||||
},
|
||||
"copy-mode-vi": {
|
||||
":": 'command-prompt -P -p "(goto line)" { send-keys -X goto-line -- "%%" }',
|
||||
"/": 'command-prompt -P -T search -p "(search down)" { send-keys -X search-forward -- "%%" }',
|
||||
"?": 'command-prompt -P -T search -p "(search up)" { send-keys -X search-backward -- "%%" }',
|
||||
f: 'command-prompt -1P -p "(jump forward)" { send-keys -X jump-forward -- "%%" }',
|
||||
F: 'command-prompt -1P -p "(jump backward)" { send-keys -X jump-backward -- "%%" }',
|
||||
t: 'command-prompt -1P -p "(jump to forward)" { send-keys -X jump-to-forward -- "%%" }',
|
||||
T: 'command-prompt -1P -p "(jump to backward)" { send-keys -X jump-to-backward -- "%%" }',
|
||||
...Object.fromEntries(
|
||||
[1, 2, 3, 4, 5, 6, 7, 8, 9].map((n) => [
|
||||
String(n),
|
||||
`command-prompt -NP -I ${n} -p (repeat) { send-keys -N "%%" }`,
|
||||
])
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
/** One `;`-chained argv re-binding every key in `COPY_MODE_PROMPTS`. */
|
||||
function copyModePromptBindings() {
|
||||
const cmds = [];
|
||||
for (const [table, keys] of Object.entries(COPY_MODE_PROMPTS))
|
||||
for (const [key, original] of Object.entries(keys))
|
||||
cmds.push([
|
||||
"bind-key",
|
||||
"-T",
|
||||
table,
|
||||
key,
|
||||
"if-shell",
|
||||
"-F",
|
||||
OURS,
|
||||
`send-keys -X cancel ; send-keys -l -- '${key}'`,
|
||||
original,
|
||||
]);
|
||||
return cmds.flatMap((c, i) => (i ? [";", ...c] : c));
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the pane out of copy-mode (or any mode) so what is typed next reaches
|
||||
* the program, not tmux's scrollback keys. A no-op for a pane in no mode;
|
||||
* best-effort — a tmux without `copy-mode -q` (< 3.2) leaves it as it is.
|
||||
*/
|
||||
function leaveCopyMode(name) {
|
||||
try {
|
||||
onServerOf(name, ["copy-mode", "-q", "-t", pane(name)]);
|
||||
} catch {
|
||||
/* session gone or an old tmux */
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -898,6 +1011,7 @@ module.exports = {
|
||||
hasSession,
|
||||
newSession,
|
||||
enableMouse,
|
||||
leaveCopyMode,
|
||||
paneCommand,
|
||||
sendCommand,
|
||||
sendText,
|
||||
|
||||
Reference in New Issue
Block a user