tui: fix shift+space/backspace/delete on kitty-protocol terminals

Kitty and other smart terminals send a specific CSI u control code for
these shifted special keys, which are interpreted as a no-op by the
editor component. These should send the underlying key instead, matching
the behaviour of other TUI programs (e.g. readline, vim insert mode).

On less smart terminals, these key combinations are the same as the
non-shifted versions, and so already work with the existing code.
This commit is contained in:
nathyong 2026-01-03 06:53:14 +11:00
parent fd35d9188c
commit c9b08d7643
2 changed files with 39 additions and 4 deletions

View file

@ -42,6 +42,7 @@ const CODEPOINTS = {
escape: 27,
tab: 9,
enter: 13,
space: 32,
backspace: 127,
} as const;
@ -464,6 +465,15 @@ export function isBackspace(data: string): boolean {
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
}
/**
* Check if input matches Shift+Backspace (Kitty protocol).
* Returns true so caller can treat it as regular backspace.
* Ignores lock key bits.
*/
export function isShiftBackspace(data: string): boolean {
return matchesKittySequence(data, CODEPOINTS.backspace, MODIFIERS.shift);
}
/**
* Check if input matches Shift+Enter.
* Ignores lock key bits.
@ -480,6 +490,15 @@ export function isAltEnter(data: string): boolean {
return data === Keys.ALT_ENTER || data === "\x1b\r" || matchesKittySequence(data, CODEPOINTS.enter, MODIFIERS.alt);
}
/**
* Check if input matches Shift+Space (Kitty protocol).
* Returns true so caller can insert a regular space.
* Ignores lock key bits.
*/
export function isShiftSpace(data: string): boolean {
return matchesKittySequence(data, CODEPOINTS.space, MODIFIERS.shift);
}
/**
* Check if input matches Option/Alt+Left (word navigation).
* Handles multiple formats including Kitty protocol.
@ -545,3 +564,12 @@ export function isEnd(data: string): boolean {
export function isDelete(data: string): boolean {
return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
}
/**
* Check if input matches Shift+Delete (Kitty protocol).
* Returns true so caller can treat it as regular delete.
* Ignores lock key bits.
*/
export function isShiftDelete(data: string): boolean {
return matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, MODIFIERS.shift);
}