@oh-my-pi/pi-tui 17.1.4 → 17.1.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/types/keys.d.ts +11 -9
- package/package.json +3 -3
- package/src/components/editor.ts +15 -22
- package/src/keys.ts +17 -12
- package/src/terminal.ts +17 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [17.1.6] - 2026-07-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed omp dying with an uncaught `setRawMode failed with errno: 2` instead of exiting 129 when the terminal disconnects. A recycled terminal pane revokes the pty, so the raw-mode restore in `stop()` hit an fd that is no longer a tty; the throw escaped `#markTerminalDisconnected()` and preempted its own SIGHUP. Terminal teardown on the disconnect path is now best-effort, matching `emergencyTerminalRestore()`, so the exit added in [#5837](https://github.com/can1357/oh-my-pi/pull/5837) always runs.
|
|
10
|
+
- Fixed the multi-line prompt editor bypassing the keybindings registry for word/line delete and yank: `ctrl+backspace` (a declared default of `tui.editor.deleteWordBackward`) never fired and `keybindings.yml` remaps of `deleteWordBackward`, `deleteWordForward`, `deleteToLineStart`, `deleteToLineEnd`, `yank`, and `yankPop` were ignored, because those actions were matched with hardcoded chords instead of `keybindings.matches(...)` like cursor motion and the single-line `Input` already do ([#6782](https://github.com/can1357/oh-my-pi/issues/6782)).
|
|
11
|
+
- Restored the Windows Terminal raw `0x08` → `ctrl+backspace` disambiguation (`WT_SESSION` set, `SSH_*` unset) by routing the exported `matchesRawBackspace` helper through the `matchesKey`/`parseKey` seam. Remote SSH/container sessions where terminal identity is unavailable can opt in with `PI_TUI_RAW_BACKSPACE_IS_CTRL=1` ([#6782](https://github.com/can1357/oh-my-pi/issues/6782)).
|
|
12
|
+
- Fixed plain Backspace deleting a whole word inside tmux/GNU screen/Zellij panes launched from Windows Terminal: multiplexers inherit `WT_SESSION` but emit raw `0x08` for plain Backspace, so the automatic raw-backspace → `ctrl+backspace` heuristic misfired. The heuristic now skips multiplexer sessions (`TMUX`/`STY`/`ZELLIJ` or `TERM` starting with `tmux`/`screen`); `PI_TUI_RAW_BACKSPACE_IS_CTRL=1` remains the explicit opt-in everywhere ([#6784](https://github.com/can1357/oh-my-pi/pull/6784)).
|
|
13
|
+
|
|
5
14
|
## [17.1.4] - 2026-07-26
|
|
6
15
|
|
|
7
16
|
### Fixed
|
package/dist/types/keys.d.ts
CHANGED
|
@@ -18,18 +18,19 @@
|
|
|
18
18
|
* - isKittyProtocolActive() - Query global Kitty protocol state
|
|
19
19
|
*/
|
|
20
20
|
import type { KeyEventType } from "@oh-my-pi/pi-natives";
|
|
21
|
-
|
|
21
|
+
/** Whether the local process is running directly under Windows Terminal. */
|
|
22
|
+
export declare function isWindowsTerminalSession(): boolean;
|
|
22
23
|
/**
|
|
23
|
-
*
|
|
24
|
+
* Match ambiguous legacy Backspace bytes against an expected modifier mask.
|
|
24
25
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
26
|
+
* Windows Terminal encodes Ctrl+Backspace as raw `0x08` (BS) and plain
|
|
27
|
+
* Backspace as `0x7f` (DEL). Remote/container sessions lose terminal identity,
|
|
28
|
+
* and multiplexers (tmux/screen/Zellij) inherit `WT_SESSION` while emitting
|
|
29
|
+
* raw `0x08` for plain Backspace themselves, so the automatic heuristic is
|
|
30
|
+
* limited to direct Windows Terminal sessions. `PI_TUI_RAW_BACKSPACE_IS_CTRL=1`
|
|
31
|
+
* explicitly opts into the mapping everywhere.
|
|
30
32
|
*/
|
|
31
|
-
declare function matchesRawBackspace(data: string, expectedModifier: number): boolean;
|
|
32
|
-
export { isWindowsTerminalSession, matchesRawBackspace };
|
|
33
|
+
export declare function matchesRawBackspace(data: string, expectedModifier: number): boolean;
|
|
33
34
|
/**
|
|
34
35
|
* Set the global Kitty keyboard protocol state.
|
|
35
36
|
* Called by ProcessTerminal after detecting protocol support.
|
|
@@ -206,3 +207,4 @@ export declare function matchesKey(data: string, keyId: KeyId): boolean;
|
|
|
206
207
|
* @param data - Raw input data from terminal
|
|
207
208
|
*/
|
|
208
209
|
export declare function parseKey(data: string): string | undefined;
|
|
210
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-tui",
|
|
4
|
-
"version": "17.1.
|
|
4
|
+
"version": "17.1.6",
|
|
5
5
|
"description": "Terminal User Interface library with differential rendering for efficient text-based applications",
|
|
6
6
|
"homepage": "https://omp.sh",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
"fmt": "biome format --write ."
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@oh-my-pi/pi-natives": "17.1.
|
|
41
|
-
"@oh-my-pi/pi-utils": "17.1.
|
|
40
|
+
"@oh-my-pi/pi-natives": "17.1.6",
|
|
41
|
+
"@oh-my-pi/pi-utils": "17.1.6",
|
|
42
42
|
"lru-cache": "11.5.2",
|
|
43
43
|
"marked": "^18.0.6"
|
|
44
44
|
},
|
package/src/components/editor.ts
CHANGED
|
@@ -1327,38 +1327,31 @@ export class Editor implements Component, Focusable {
|
|
|
1327
1327
|
}
|
|
1328
1328
|
|
|
1329
1329
|
// Continue with rest of input handling
|
|
1330
|
-
//
|
|
1331
|
-
if (
|
|
1330
|
+
// Delete to end of line
|
|
1331
|
+
if (kb.matches(data, "tui.editor.deleteToLineEnd")) {
|
|
1332
1332
|
this.#deleteToEndOfLine();
|
|
1333
1333
|
}
|
|
1334
|
-
//
|
|
1335
|
-
else if (
|
|
1334
|
+
// Delete to start of line
|
|
1335
|
+
else if (kb.matches(data, "tui.editor.deleteToLineStart")) {
|
|
1336
1336
|
this.#deleteToStartOfLine();
|
|
1337
1337
|
}
|
|
1338
|
-
//
|
|
1339
|
-
|
|
1338
|
+
// Delete word backward. Registry defaults cover ctrl+w, alt+backspace,
|
|
1339
|
+
// ctrl+backspace, and super+alt+backspace (Ghostty on macOS reports
|
|
1340
|
+
// Option+Backspace as super+alt — kitty mod 11, see #2064).
|
|
1341
|
+
else if (kb.matches(data, "tui.editor.deleteWordBackward")) {
|
|
1340
1342
|
this.#deleteWordBackwards();
|
|
1341
1343
|
}
|
|
1342
|
-
//
|
|
1343
|
-
//
|
|
1344
|
-
else if (
|
|
1345
|
-
this.#deleteWordBackwards();
|
|
1346
|
-
}
|
|
1347
|
-
// Option/Alt+D and Option+Delete - Delete word forwards. Same Ghostty quirk applies.
|
|
1348
|
-
else if (
|
|
1349
|
-
matchesKey(data, "alt+d") ||
|
|
1350
|
-
matchesKey(data, "alt+delete") ||
|
|
1351
|
-
matchesKey(data, "super+alt+d") ||
|
|
1352
|
-
matchesKey(data, "super+alt+delete")
|
|
1353
|
-
) {
|
|
1344
|
+
// Delete word forward. Registry defaults cover alt+d/alt+delete and their
|
|
1345
|
+
// super+alt variants for the same Ghostty quirk.
|
|
1346
|
+
else if (kb.matches(data, "tui.editor.deleteWordForward")) {
|
|
1354
1347
|
this.#deleteWordForwards();
|
|
1355
1348
|
}
|
|
1356
|
-
//
|
|
1357
|
-
else if (
|
|
1349
|
+
// Yank from kill ring
|
|
1350
|
+
else if (kb.matches(data, "tui.editor.yank")) {
|
|
1358
1351
|
this.#yankFromKillRing();
|
|
1359
1352
|
}
|
|
1360
|
-
//
|
|
1361
|
-
else if (
|
|
1353
|
+
// Yank-pop (cycle kill ring)
|
|
1354
|
+
else if (kb.matches(data, "tui.editor.yankPop")) {
|
|
1362
1355
|
this.#yankPop();
|
|
1363
1356
|
}
|
|
1364
1357
|
// Ctrl+A - Move to start of line
|
package/src/keys.ts
CHANGED
|
@@ -24,35 +24,38 @@ import {
|
|
|
24
24
|
parseKey as parseKeyNative,
|
|
25
25
|
parseKittySequence as parseKittySequenceNative,
|
|
26
26
|
} from "@oh-my-pi/pi-natives";
|
|
27
|
+
import { isInsideTerminalMultiplexer } from "./terminal-capabilities";
|
|
27
28
|
|
|
28
29
|
// =============================================================================
|
|
29
30
|
// Platform Detection
|
|
30
31
|
// =============================================================================
|
|
31
32
|
|
|
32
|
-
|
|
33
|
+
/** Whether the local process is running directly under Windows Terminal. */
|
|
34
|
+
export function isWindowsTerminalSession(): boolean {
|
|
33
35
|
return (
|
|
34
36
|
Boolean(process.env.WT_SESSION) && !process.env.SSH_CONNECTION && !process.env.SSH_CLIENT && !process.env.SSH_TTY
|
|
35
37
|
);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
/**
|
|
39
|
-
*
|
|
41
|
+
* Match ambiguous legacy Backspace bytes against an expected modifier mask.
|
|
40
42
|
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
43
|
+
* Windows Terminal encodes Ctrl+Backspace as raw `0x08` (BS) and plain
|
|
44
|
+
* Backspace as `0x7f` (DEL). Remote/container sessions lose terminal identity,
|
|
45
|
+
* and multiplexers (tmux/screen/Zellij) inherit `WT_SESSION` while emitting
|
|
46
|
+
* raw `0x08` for plain Backspace themselves, so the automatic heuristic is
|
|
47
|
+
* limited to direct Windows Terminal sessions. `PI_TUI_RAW_BACKSPACE_IS_CTRL=1`
|
|
48
|
+
* explicitly opts into the mapping everywhere.
|
|
46
49
|
*/
|
|
47
|
-
function matchesRawBackspace(data: string, expectedModifier: number): boolean {
|
|
50
|
+
export function matchesRawBackspace(data: string, expectedModifier: number): boolean {
|
|
48
51
|
if (data === "\x7f") return expectedModifier === 0;
|
|
49
52
|
if (data !== "\x08") return false;
|
|
50
|
-
|
|
51
|
-
|
|
53
|
+
const rawBackspaceIsCtrl =
|
|
54
|
+
process.env.PI_TUI_RAW_BACKSPACE_IS_CTRL === "1" ||
|
|
55
|
+
(isWindowsTerminalSession() && !isInsideTerminalMultiplexer(process.env));
|
|
56
|
+
return rawBackspaceIsCtrl ? expectedModifier === 4 : expectedModifier === 0;
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
export { isWindowsTerminalSession, matchesRawBackspace };
|
|
55
|
-
|
|
56
59
|
// =============================================================================
|
|
57
60
|
// Global Kitty Protocol State
|
|
58
61
|
// =============================================================================
|
|
@@ -545,6 +548,7 @@ function matchesKeypadKey(data: string, keyId: KeyId): boolean | undefined {
|
|
|
545
548
|
* @param keyId - Key identifier (e.g., "ctrl+c", "escape", Key.ctrl("c"))
|
|
546
549
|
*/
|
|
547
550
|
export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
551
|
+
if (matchesRawBackspace(data, 4)) return keyId === "ctrl+backspace";
|
|
548
552
|
return matchesKeypadKey(data, keyId) ?? matchesKeyNative(data, keyId, kittyProtocolActive);
|
|
549
553
|
}
|
|
550
554
|
|
|
@@ -557,5 +561,6 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|
|
557
561
|
* @param data - Raw input data from terminal
|
|
558
562
|
*/
|
|
559
563
|
export function parseKey(data: string): string | undefined {
|
|
564
|
+
if (matchesRawBackspace(data, 4)) return "ctrl+backspace";
|
|
560
565
|
return decodeKittyKeypadText(data) ?? parseKeyNative(data, kittyProtocolActive) ?? undefined;
|
|
561
566
|
}
|
package/src/terminal.ts
CHANGED
|
@@ -1462,9 +1462,15 @@ export class ProcessTerminal implements Terminal {
|
|
|
1462
1462
|
// where Ctrl+D could close the parent shell over SSH.
|
|
1463
1463
|
process.stdin.pause();
|
|
1464
1464
|
|
|
1465
|
-
// Restore raw mode state
|
|
1466
|
-
|
|
1467
|
-
|
|
1465
|
+
// Restore raw mode state. On a disconnected terminal (pane recycled, ssh
|
|
1466
|
+
// dropped) the fd is no longer a tty and Bun's node:tty shim throws; there
|
|
1467
|
+
// is nothing left to restore, and throwing would abort the caller. On a
|
|
1468
|
+
// live terminal the failure still surfaces - swallowing it would silently
|
|
1469
|
+
// leave stdin in raw mode.
|
|
1470
|
+
try {
|
|
1471
|
+
process.stdin.setRawMode?.(this.#wasRaw);
|
|
1472
|
+
} catch (err) {
|
|
1473
|
+
if (!this.#dead) throw err;
|
|
1468
1474
|
}
|
|
1469
1475
|
this.#stdoutErrorCleanup?.();
|
|
1470
1476
|
this.#stdoutErrorCleanup = undefined;
|
|
@@ -1482,7 +1488,14 @@ export class ProcessTerminal implements Terminal {
|
|
|
1482
1488
|
const disconnectHandler = this.#disconnectHandler;
|
|
1483
1489
|
this.#disconnectHandler = undefined;
|
|
1484
1490
|
if (!disconnectHandler) return;
|
|
1485
|
-
|
|
1491
|
+
// The handler tears the TUI down against a terminal that is already gone,
|
|
1492
|
+
// so any step in it can fail. Swallow that: the exit below is the whole
|
|
1493
|
+
// point of this method and must not be preempted by teardown noise.
|
|
1494
|
+
try {
|
|
1495
|
+
disconnectHandler();
|
|
1496
|
+
} catch (handlerErr) {
|
|
1497
|
+
logger.error("Terminal disconnect handler failed; exiting anyway", { err: handlerErr });
|
|
1498
|
+
}
|
|
1486
1499
|
|
|
1487
1500
|
if (process.platform === "win32") {
|
|
1488
1501
|
void postmortem.quit(129);
|