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:
@@ -12,6 +12,8 @@
|
||||
* the driver's size and told so (`{type:"size", cols, rows}`) so its xterm
|
||||
* renders the same grid, scaled to fit, instead of each browser resizing tmux
|
||||
* under the others. A driver that leaves sends `{type:"lead"}` to the rest.
|
||||
* A keystroke that follows a mouse report first takes the pane out of tmux's
|
||||
* copy-mode (the wheel's scrollback), so typing never lands in it.
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
@@ -73,7 +75,16 @@ function attachBinary(platform = process.platform) {
|
||||
// the paste in that issue's measurements.
|
||||
const PASTE_HOLD_MS = 700;
|
||||
|
||||
// runId -> { driver, viewers: Set<{ ws, pty, cols, rows }> }
|
||||
// Input that is not typing: a mouse report (SGR `ESC[<b;x;yM`, X10 `ESC[M` +
|
||||
// three bytes) or a focus in/out. A wheel report is what puts a pane into
|
||||
// tmux's copy-mode, where every typed key is taken as a scrollback command —
|
||||
// the pane looked frozen. So once a mouse report has passed, the next real
|
||||
// keystroke first leaves copy-mode (`tmux.leaveCopyMode`), then is written.
|
||||
const NOT_TYPING_RE = /\x1b\[<\d+;\d+;\d+[Mm]|\x1b\[M[\s\S]{3}|\x1b\[[IO]/g;
|
||||
|
||||
// runId -> { driver, viewers: Set<{ ws, pty, cols, rows }>, scrolled }
|
||||
// (`scrolled`: a mouse report reached the pane since the last keystroke — per
|
||||
// pane, since one browser may scroll and another type)
|
||||
const groups = new Map();
|
||||
|
||||
const tell = (v, msg) => {
|
||||
@@ -211,6 +222,16 @@ function attach(ws, runId, { cols, rows, platform = process.platform }) {
|
||||
/* the attach exited while its input was held — nothing to write to */
|
||||
}
|
||||
};
|
||||
// Keystrokes after a scroll leave copy-mode first; see NOT_TYPING_RE.
|
||||
function input(text) {
|
||||
if (text.replace(NOT_TYPING_RE, "") === "") {
|
||||
if (text.includes("\x1b[M") || text.includes("\x1b[<")) group.scrolled = true;
|
||||
} else if (group.scrolled) {
|
||||
group.scrolled = false;
|
||||
tmux.leaveCopyMode(runId);
|
||||
}
|
||||
write(text);
|
||||
}
|
||||
function write(text) {
|
||||
if (me.exited) return; // the attach is gone: nothing reads this pipe
|
||||
const wait = holdUntil - Date.now();
|
||||
@@ -248,7 +269,7 @@ function attach(ws, runId, { cols, rows, platform = process.platform }) {
|
||||
const binary = typeof isBinary === "boolean" ? isBinary : !!(isBinary && isBinary.binary);
|
||||
const text = data.toString("utf8");
|
||||
if (binary) {
|
||||
write(text);
|
||||
input(text);
|
||||
return;
|
||||
}
|
||||
// The browser's WebSocket API sends a JS string as a text frame, and
|
||||
@@ -262,7 +283,7 @@ function attach(ws, runId, { cols, rows, platform = process.platform }) {
|
||||
try {
|
||||
msg = JSON.parse(text);
|
||||
} catch {
|
||||
write(text);
|
||||
input(text);
|
||||
return;
|
||||
}
|
||||
if (msg && msg.type === "resize" && Number.isFinite(msg.cols) && Number.isFinite(msg.rows)) {
|
||||
@@ -274,7 +295,7 @@ function attach(ws, runId, { cols, rows, platform = process.platform }) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
write(text);
|
||||
input(text);
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
|
||||
Reference in New Issue
Block a user