mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 10:05:14 +00:00
feat(tui): add legacy terminal support for Ctrl+symbol keys
Ctrl+\ sends ASCII 28 (File Separator) in legacy terminals. This is commonly used as SIGQUIT in Unix. Ctrl+] sends ASCII 29 (Group Separator) in legacy terminals. This is commonly used as the telnet escape character. Ctrl+_ sends ASCII 31 (Unit Separator) in legacy terminals. On US keyboards, - and _ are on the same physical key, so this also functions as an alias for Ctrl+-.
This commit is contained in:
parent
6bde679a5f
commit
fb1242829d
2 changed files with 66 additions and 8 deletions
|
|
@ -642,9 +642,26 @@ function matchesModifyOtherKeys(data: string, expectedKeycode: number, expectedM
|
|||
// Generic Key Matching
|
||||
// =============================================================================
|
||||
|
||||
function rawCtrlChar(letter: string): string {
|
||||
const code = letter.toLowerCase().charCodeAt(0) - 96;
|
||||
return String.fromCharCode(code);
|
||||
/**
|
||||
* Get the control character for a key.
|
||||
* Uses the universal formula: code & 0x1f (mask to lower 5 bits)
|
||||
*
|
||||
* Works for:
|
||||
* - Letters a-z → 1-26
|
||||
* - Symbols [\]_ → 27, 28, 29, 31
|
||||
* - Also maps - to same as _ (same physical key on US keyboards)
|
||||
*/
|
||||
function rawCtrlChar(key: string): string | null {
|
||||
const char = key.toLowerCase();
|
||||
const code = char.charCodeAt(0);
|
||||
if ((code >= 97 && code <= 122) || char === "[" || char === "\\" || char === "]" || char === "_") {
|
||||
return String.fromCharCode(code & 0x1f);
|
||||
}
|
||||
// Handle - as _ (same physical key on US keyboards)
|
||||
if (char === "-") {
|
||||
return String.fromCharCode(31); // Same as Ctrl+_
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function parseKeyId(keyId: string): { key: string; ctrl: boolean; shift: boolean; alt: boolean } | null {
|
||||
|
|
@ -966,9 +983,11 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
// Handle single letter keys (a-z) and some symbols
|
||||
if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
|
||||
const codepoint = key.charCodeAt(0);
|
||||
const rawCtrl = rawCtrlChar(key);
|
||||
|
||||
if (ctrl && alt && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
|
||||
return data === `\x1b${rawCtrlChar(key)}`;
|
||||
if (ctrl && alt && !shift && !_kittyProtocolActive && rawCtrl) {
|
||||
// Legacy: ctrl+alt+key is ESC followed by the control character
|
||||
return data === `\x1b${rawCtrl}`;
|
||||
}
|
||||
|
||||
if (alt && !ctrl && !shift && !_kittyProtocolActive && key >= "a" && key <= "z") {
|
||||
|
|
@ -977,9 +996,8 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
|||
}
|
||||
|
||||
if (ctrl && !shift && !alt) {
|
||||
const raw = rawCtrlChar(key);
|
||||
if (data === raw) return true;
|
||||
if (data.length > 0 && data.charCodeAt(0) === raw.charCodeAt(0)) return true;
|
||||
// Legacy: ctrl+key sends the control character
|
||||
if (rawCtrl && data === rawCtrl) return true;
|
||||
return matchesKittySequence(data, codepoint, MODIFIERS.ctrl);
|
||||
}
|
||||
|
||||
|
|
@ -1062,6 +1080,13 @@ export function parseKey(data: string): string | undefined {
|
|||
|
||||
// Legacy sequences (used when Kitty protocol is not active, or for unambiguous sequences)
|
||||
if (data === "\x1b") return "escape";
|
||||
if (data === "\x1c") return "ctrl+\\";
|
||||
if (data === "\x1d") return "ctrl+]";
|
||||
if (data === "\x1f") return "ctrl+-";
|
||||
if (data === "\x1b\x1b") return "ctrl+alt+[";
|
||||
if (data === "\x1b\x1c") return "ctrl+alt+\\";
|
||||
if (data === "\x1b\x1d") return "ctrl+alt+]";
|
||||
if (data === "\x1b\x1f") return "ctrl+alt+-";
|
||||
if (data === "\t") return "tab";
|
||||
if (data === "\r" || (!_kittyProtocolActive && data === "\n") || data === "\x1bOM") return "enter";
|
||||
if (data === "\x00") return "ctrl+space";
|
||||
|
|
|
|||
|
|
@ -137,6 +137,39 @@ describe("matchesKey", () => {
|
|||
assert.strictEqual(parseKey("\x00"), "ctrl+space");
|
||||
});
|
||||
|
||||
it("should match legacy Ctrl+symbol", () => {
|
||||
setKittyProtocolActive(false);
|
||||
// Ctrl+\ sends ASCII 28 (File Separator) in legacy terminals
|
||||
assert.strictEqual(matchesKey("\x1c", "ctrl+\\"), true);
|
||||
assert.strictEqual(parseKey("\x1c"), "ctrl+\\");
|
||||
// Ctrl+] sends ASCII 29 (Group Separator) in legacy terminals
|
||||
assert.strictEqual(matchesKey("\x1d", "ctrl+]"), true);
|
||||
assert.strictEqual(parseKey("\x1d"), "ctrl+]");
|
||||
// Ctrl+_ sends ASCII 31 (Unit Separator) in legacy terminals
|
||||
// Ctrl+- is on the same physical key on US keyboards
|
||||
assert.strictEqual(matchesKey("\x1f", "ctrl+_"), true);
|
||||
assert.strictEqual(matchesKey("\x1f", "ctrl+-"), true);
|
||||
assert.strictEqual(parseKey("\x1f"), "ctrl+-");
|
||||
});
|
||||
|
||||
it("should match legacy Ctrl+Alt+symbol", () => {
|
||||
setKittyProtocolActive(false);
|
||||
// Ctrl+Alt+[ sends ESC followed by ESC (Ctrl+[ = ESC)
|
||||
assert.strictEqual(matchesKey("\x1b\x1b", "ctrl+alt+["), true);
|
||||
assert.strictEqual(parseKey("\x1b\x1b"), "ctrl+alt+[");
|
||||
// Ctrl+Alt+\ sends ESC followed by ASCII 28
|
||||
assert.strictEqual(matchesKey("\x1b\x1c", "ctrl+alt+\\"), true);
|
||||
assert.strictEqual(parseKey("\x1b\x1c"), "ctrl+alt+\\");
|
||||
// Ctrl+Alt+] sends ESC followed by ASCII 29
|
||||
assert.strictEqual(matchesKey("\x1b\x1d", "ctrl+alt+]"), true);
|
||||
assert.strictEqual(parseKey("\x1b\x1d"), "ctrl+alt+]");
|
||||
// Ctrl+_ sends ASCII 31 (Unit Separator) in legacy terminals
|
||||
// Ctrl+- is on the same physical key on US keyboards
|
||||
assert.strictEqual(matchesKey("\x1b\x1f", "ctrl+alt+_"), true);
|
||||
assert.strictEqual(matchesKey("\x1b\x1f", "ctrl+alt+-"), true);
|
||||
assert.strictEqual(parseKey("\x1b\x1f"), "ctrl+alt+-");
|
||||
});
|
||||
|
||||
it("should parse legacy alt-prefixed sequences when kitty inactive", () => {
|
||||
setKittyProtocolActive(false);
|
||||
assert.strictEqual(matchesKey("\x1b ", "alt+space"), true);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue