mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-22 02:03:42 +00:00
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:
parent
fd35d9188c
commit
c9b08d7643
2 changed files with 39 additions and 4 deletions
|
|
@ -22,7 +22,10 @@ import {
|
||||||
isEnter,
|
isEnter,
|
||||||
isEscape,
|
isEscape,
|
||||||
isHome,
|
isHome,
|
||||||
|
isShiftBackspace,
|
||||||
|
isShiftDelete,
|
||||||
isShiftEnter,
|
isShiftEnter,
|
||||||
|
isShiftSpace,
|
||||||
isTab,
|
isTab,
|
||||||
} from "../keys.js";
|
} from "../keys.js";
|
||||||
import type { Component } from "../tui.js";
|
import type { Component } from "../tui.js";
|
||||||
|
|
@ -457,8 +460,8 @@ export class Editor implements Component {
|
||||||
this.onSubmit(result);
|
this.onSubmit(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Backspace
|
// Backspace (including Shift+Backspace)
|
||||||
else if (isBackspace(data)) {
|
else if (isBackspace(data) || isShiftBackspace(data)) {
|
||||||
this.handleBackspace();
|
this.handleBackspace();
|
||||||
}
|
}
|
||||||
// Line navigation shortcuts (Home/End keys)
|
// Line navigation shortcuts (Home/End keys)
|
||||||
|
|
@ -467,8 +470,8 @@ export class Editor implements Component {
|
||||||
} else if (isEnd(data)) {
|
} else if (isEnd(data)) {
|
||||||
this.moveToLineEnd();
|
this.moveToLineEnd();
|
||||||
}
|
}
|
||||||
// Forward delete (Fn+Backspace or Delete key)
|
// Forward delete (Fn+Backspace or Delete key, including Shift+Delete)
|
||||||
else if (isDelete(data)) {
|
else if (isDelete(data) || isShiftDelete(data)) {
|
||||||
this.handleForwardDelete();
|
this.handleForwardDelete();
|
||||||
}
|
}
|
||||||
// Word navigation (Option/Alt + Arrow or Ctrl + Arrow)
|
// Word navigation (Option/Alt + Arrow or Ctrl + Arrow)
|
||||||
|
|
@ -503,6 +506,10 @@ export class Editor implements Component {
|
||||||
// Left
|
// Left
|
||||||
this.moveCursor(0, -1);
|
this.moveCursor(0, -1);
|
||||||
}
|
}
|
||||||
|
// Shift+Space - insert regular space (Kitty protocol sends escape sequence)
|
||||||
|
else if (isShiftSpace(data)) {
|
||||||
|
this.insertCharacter(" ");
|
||||||
|
}
|
||||||
// Regular characters (printable characters and unicode, but not control characters)
|
// Regular characters (printable characters and unicode, but not control characters)
|
||||||
else if (data.charCodeAt(0) >= 32) {
|
else if (data.charCodeAt(0) >= 32) {
|
||||||
this.insertCharacter(data);
|
this.insertCharacter(data);
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,7 @@ const CODEPOINTS = {
|
||||||
escape: 27,
|
escape: 27,
|
||||||
tab: 9,
|
tab: 9,
|
||||||
enter: 13,
|
enter: 13,
|
||||||
|
space: 32,
|
||||||
backspace: 127,
|
backspace: 127,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
@ -464,6 +465,15 @@ export function isBackspace(data: string): boolean {
|
||||||
return data === "\x7f" || data === "\x08" || matchesKittySequence(data, CODEPOINTS.backspace, 0);
|
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.
|
* Check if input matches Shift+Enter.
|
||||||
* Ignores lock key bits.
|
* 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);
|
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).
|
* Check if input matches Option/Alt+Left (word navigation).
|
||||||
* Handles multiple formats including Kitty protocol.
|
* Handles multiple formats including Kitty protocol.
|
||||||
|
|
@ -545,3 +564,12 @@ export function isEnd(data: string): boolean {
|
||||||
export function isDelete(data: string): boolean {
|
export function isDelete(data: string): boolean {
|
||||||
return data === "\x1b[3~" || matchesKittySequence(data, FUNCTIONAL_CODEPOINTS.delete, 0);
|
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);
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue