fix(tui): skip Kitty query inside Zellij\n\nfixes #3163
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Alt keybindings inside Zellij by skipping the Kitty keyboard protocol query there and enabling xterm `modifyOtherKeys` mode 2 directly ([#3163](https://github.com/badlogic/pi-mono/issues/3163))
|
||||
- Fixed `/scoped-models` reordering to propagate into the `/model` scoped tab, preserving the user-defined scoped model order instead of re-sorting it ([#3217](https://github.com/badlogic/pi-mono/issues/3217))
|
||||
- Fixed `session_shutdown` to fire on `SIGHUP` and `SIGTERM` in interactive, print, and RPC modes so extensions can run shutdown cleanup on those signal-driven exits ([#3212](https://github.com/badlogic/pi-mono/issues/3212))
|
||||
- Fixed screenshot path parsing to handle lower case am/pm in macOS screenshot filenames ([#3194](https://github.com/badlogic/pi-mono/pull/3194) by [@jay-aye-see-kay](https://github.com/jay-aye-see-kay))
|
||||
|
||||
@@ -32,6 +32,12 @@ If you want `Shift+Enter` to keep working in tmux via that remap, add `ctrl+j` t
|
||||
}
|
||||
```
|
||||
|
||||
## Zellij
|
||||
|
||||
Pi detects Zellij automatically and skips the Kitty keyboard protocol query there.
|
||||
Zellij currently forwards that query to the outer terminal but still sends Alt as legacy ESC-prefixed sequences, which can break Alt keybindings if applications enable Kitty mode.
|
||||
Pi uses xterm `modifyOtherKeys` mode 2 inside Zellij instead, so no extra Zellij config is required.
|
||||
|
||||
## WezTerm
|
||||
|
||||
Create `~/.wezterm.lua`:
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed Alt keybindings inside Zellij by skipping the Kitty keyboard protocol query there and enabling xterm `modifyOtherKeys` mode 2 directly ([#3163](https://github.com/badlogic/pi-mono/issues/3163))
|
||||
|
||||
## [0.67.2] - 2026-04-14
|
||||
|
||||
### Added
|
||||
|
||||
@@ -184,6 +184,17 @@ export class ProcessTerminal implements Terminal {
|
||||
private queryAndEnableKittyProtocol(): void {
|
||||
this.setupStdinBuffer();
|
||||
process.stdin.on("data", this.stdinDataHandler!);
|
||||
|
||||
// Zellij forwards the Kitty query to the outer terminal, which can make
|
||||
// Pi enable its Kitty parser even though Zellij still sends Alt as
|
||||
// legacy ESC-prefixed sequences. Skip the Kitty query there and use
|
||||
// modifyOtherKeys directly instead.
|
||||
if (process.env.ZELLIJ) {
|
||||
process.stdout.write("\x1b[>4;2m");
|
||||
this._modifyOtherKeysActive = true;
|
||||
return;
|
||||
}
|
||||
|
||||
process.stdout.write("\x1b[?u");
|
||||
setTimeout(() => {
|
||||
if (!this._kittyProtocolActive && !this._modifyOtherKeysActive) {
|
||||
|
||||
76
packages/tui/test/terminal.test.ts
Normal file
76
packages/tui/test/terminal.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import assert from "node:assert";
|
||||
import { describe, it } from "node:test";
|
||||
import { ProcessTerminal } from "../src/terminal.js";
|
||||
|
||||
function withEnv(name: string, value: string | undefined, fn: () => void): void {
|
||||
const previous = process.env[name];
|
||||
if (value === undefined) delete process.env[name];
|
||||
else process.env[name] = value;
|
||||
try {
|
||||
fn();
|
||||
} finally {
|
||||
if (previous === undefined) delete process.env[name];
|
||||
else process.env[name] = previous;
|
||||
}
|
||||
}
|
||||
|
||||
describe("ProcessTerminal", () => {
|
||||
it("should skip the Kitty query inside Zellij and enable modifyOtherKeys immediately", () => {
|
||||
const terminal = new ProcessTerminal();
|
||||
const writes: string[] = [];
|
||||
const stdinOnCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = [];
|
||||
const stdinRemoveCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = [];
|
||||
const stdoutRemoveCalls: Array<{ event: string | symbol; listener: (...args: unknown[]) => void }> = [];
|
||||
|
||||
const originalStdoutWrite = process.stdout.write;
|
||||
const originalStdinOn = process.stdin.on;
|
||||
const originalStdinRemoveListener = process.stdin.removeListener;
|
||||
const originalStdinPause = process.stdin.pause;
|
||||
const originalStdoutRemoveListener = process.stdout.removeListener;
|
||||
|
||||
process.stdout.write = ((chunk: string | Uint8Array) => {
|
||||
writes.push(typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8"));
|
||||
return true;
|
||||
}) as typeof process.stdout.write;
|
||||
process.stdin.on = ((event: string | symbol, listener: (...args: unknown[]) => void) => {
|
||||
stdinOnCalls.push({ event, listener });
|
||||
return process.stdin;
|
||||
}) as typeof process.stdin.on;
|
||||
process.stdin.removeListener = ((event: string | symbol, listener: (...args: unknown[]) => void) => {
|
||||
stdinRemoveCalls.push({ event, listener });
|
||||
return process.stdin;
|
||||
}) as typeof process.stdin.removeListener;
|
||||
process.stdin.pause = (() => process.stdin) as typeof process.stdin.pause;
|
||||
process.stdout.removeListener = ((event: string | symbol, listener: (...args: unknown[]) => void) => {
|
||||
stdoutRemoveCalls.push({ event, listener });
|
||||
return process.stdout;
|
||||
}) as typeof process.stdout.removeListener;
|
||||
|
||||
try {
|
||||
withEnv("ZELLIJ", "1", () => {
|
||||
(
|
||||
terminal as unknown as {
|
||||
queryAndEnableKittyProtocol(): void;
|
||||
}
|
||||
).queryAndEnableKittyProtocol();
|
||||
});
|
||||
|
||||
assert.deepStrictEqual(writes, ["\x1b[>4;2m"]);
|
||||
assert.strictEqual(stdinOnCalls.length, 1);
|
||||
assert.strictEqual(stdinOnCalls[0]?.event, "data");
|
||||
|
||||
terminal.stop();
|
||||
|
||||
assert.deepStrictEqual(writes, ["\x1b[>4;2m", "\x1b[?2004l", "\x1b[>4;0m"]);
|
||||
assert.strictEqual(stdinRemoveCalls.length, 1);
|
||||
assert.strictEqual(stdinRemoveCalls[0]?.event, "data");
|
||||
assert.strictEqual(stdoutRemoveCalls.length, 0);
|
||||
} finally {
|
||||
process.stdout.write = originalStdoutWrite;
|
||||
process.stdin.on = originalStdinOn;
|
||||
process.stdin.removeListener = originalStdinRemoveListener;
|
||||
process.stdin.pause = originalStdinPause;
|
||||
process.stdout.removeListener = originalStdoutRemoveListener;
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user