mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-18 15:03:02 +00:00
feat(tui): add symbol key support to keybinding system (#450)
- Added SymbolKey type with 32 symbol keys - Added symbol key constants to Key helper (Key.backtick, Key.comma, Key.period, etc.) - Updated matchesKey() and parseKey() to handle symbol key input - Added documentation in coding-agent README with examples
This commit is contained in:
parent
61cadc226c
commit
b836a9d2ee
2 changed files with 131 additions and 4 deletions
|
|
@ -272,7 +272,12 @@ Both modes are configurable via `/settings`: "one-at-a-time" delivers messages o
|
||||||
|
|
||||||
All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to one or more keys.
|
All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Each action can be bound to one or more keys.
|
||||||
|
|
||||||
**Key format:** `modifier+key` where modifiers are `ctrl`, `shift`, `alt` and keys are `a-z`, `0-9`, `escape`, `tab`, `enter`, `space`, `backspace`, `delete`, `home`, `end`, `up`, `down`, `left`, `right`.
|
**Key format:** `modifier+key` where modifiers are `ctrl`, `shift`, `alt` and keys are:
|
||||||
|
|
||||||
|
- Letters: `a-z`
|
||||||
|
- Numbers: `0-9`
|
||||||
|
- Special keys: `escape`, `tab`, `enter`, `space`, `backspace`, `delete`, `home`, `end`, `up`, `down`, `left`, `right`
|
||||||
|
- Symbol keys: `` ` ``, `-`, `=`, `[`, `]`, `\`, `;`, `'`, `,`, `.`, `/`, `!`, `@`, `#`, `$`, `%`, `^`, `&`, `*`, `(`, `)`, `_`, `+`, `|`, `~`, `{`, `}`, `:`, `<`, `>`, `?`
|
||||||
|
|
||||||
**Configurable actions:**
|
**Configurable actions:**
|
||||||
|
|
||||||
|
|
@ -338,6 +343,21 @@ All keyboard shortcuts can be customized via `~/.pi/agent/keybindings.json`. Eac
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Example (symbol keys):**
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"submit": ["enter", "ctrl+j"],
|
||||||
|
"newLine": ["shift+enter", "ctrl+;"],
|
||||||
|
"toggleThinking": "ctrl+/",
|
||||||
|
"cycleModelForward": "ctrl+.",
|
||||||
|
"cycleModelBackward": "ctrl+,",
|
||||||
|
"interrupt": ["escape", "ctrl+`"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Note:** Some `ctrl+symbol` combinations overlap with ASCII control characters due to terminal legacy behavior (e.g., `ctrl+[` is the same as Escape, `ctrl+M` is the same as Enter). These can still be used with `ctrl+shift+key` (e.g., `ctrl+shift+]`). See [Kitty keyboard protocol: legacy ctrl mapping of ASCII keys](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys) for all unsupported keys.
|
||||||
|
|
||||||
### Bash Mode
|
### Bash Mode
|
||||||
|
|
||||||
Prefix commands with `!` to execute them and add output to context:
|
Prefix commands with `!` to execute them and add output to context:
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,11 @@
|
||||||
* Supports both legacy terminal sequences and Kitty keyboard protocol.
|
* Supports both legacy terminal sequences and Kitty keyboard protocol.
|
||||||
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
|
||||||
*
|
*
|
||||||
|
* Symbol keys are also supported, however some ctrl+symbol combos
|
||||||
|
* overlap with ASCII codes, e.g. ctrl+[ = ESC.
|
||||||
|
* See: https://sw.kovidgoyal.net/kitty/keyboard-protocol/#legacy-ctrl-mapping-of-ascii-keys
|
||||||
|
* Those can still be * used for ctrl+shift combos
|
||||||
|
*
|
||||||
* API:
|
* API:
|
||||||
* - matchesKey(data, keyId) - Check if input matches a key identifier
|
* - matchesKey(data, keyId) - Check if input matches a key identifier
|
||||||
* - parseKey(data) - Parse input and return the key identifier
|
* - parseKey(data) - Parse input and return the key identifier
|
||||||
|
|
@ -42,6 +47,39 @@ type Letter =
|
||||||
| "y"
|
| "y"
|
||||||
| "z";
|
| "z";
|
||||||
|
|
||||||
|
type SymbolKey =
|
||||||
|
| "`"
|
||||||
|
| "-"
|
||||||
|
| "="
|
||||||
|
| "["
|
||||||
|
| "]"
|
||||||
|
| "\\"
|
||||||
|
| ";"
|
||||||
|
| "'"
|
||||||
|
| ","
|
||||||
|
| "."
|
||||||
|
| "/"
|
||||||
|
| "!"
|
||||||
|
| "@"
|
||||||
|
| "#"
|
||||||
|
| "$"
|
||||||
|
| "%"
|
||||||
|
| "^"
|
||||||
|
| "&"
|
||||||
|
| "*"
|
||||||
|
| "("
|
||||||
|
| ")"
|
||||||
|
| "_"
|
||||||
|
| "+"
|
||||||
|
| "|"
|
||||||
|
| "~"
|
||||||
|
| "{"
|
||||||
|
| "}"
|
||||||
|
| ":"
|
||||||
|
| "<"
|
||||||
|
| ">"
|
||||||
|
| "?";
|
||||||
|
|
||||||
type SpecialKey =
|
type SpecialKey =
|
||||||
| "escape"
|
| "escape"
|
||||||
| "esc"
|
| "esc"
|
||||||
|
|
@ -58,7 +96,7 @@ type SpecialKey =
|
||||||
| "left"
|
| "left"
|
||||||
| "right";
|
| "right";
|
||||||
|
|
||||||
type BaseKey = Letter | SpecialKey;
|
type BaseKey = Letter | SymbolKey | SpecialKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Union type of all valid key identifiers.
|
* Union type of all valid key identifiers.
|
||||||
|
|
@ -87,6 +125,7 @@ export type KeyId =
|
||||||
*
|
*
|
||||||
* Usage:
|
* Usage:
|
||||||
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
* - Key.escape, Key.enter, Key.tab, etc. for special keys
|
||||||
|
* - Key.backtick, Key.comma, Key.period, etc. for symbol keys
|
||||||
* - Key.ctrl("c"), Key.alt("x") for single modifier
|
* - Key.ctrl("c"), Key.alt("x") for single modifier
|
||||||
* - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
|
* - Key.ctrlShift("p"), Key.ctrlAlt("x") for combined modifiers
|
||||||
*/
|
*/
|
||||||
|
|
@ -107,6 +146,39 @@ export const Key = {
|
||||||
left: "left" as const,
|
left: "left" as const,
|
||||||
right: "right" as const,
|
right: "right" as const,
|
||||||
|
|
||||||
|
// Symbol keys
|
||||||
|
backtick: "`" as const,
|
||||||
|
hyphen: "-" as const,
|
||||||
|
equals: "=" as const,
|
||||||
|
leftbracket: "[" as const,
|
||||||
|
rightbracket: "]" as const,
|
||||||
|
backslash: "\\" as const,
|
||||||
|
semicolon: ";" as const,
|
||||||
|
quote: "'" as const,
|
||||||
|
comma: "," as const,
|
||||||
|
period: "." as const,
|
||||||
|
slash: "/" as const,
|
||||||
|
exclamation: "!" as const,
|
||||||
|
at: "@" as const,
|
||||||
|
hash: "#" as const,
|
||||||
|
dollar: "$" as const,
|
||||||
|
percent: "%" as const,
|
||||||
|
caret: "^" as const,
|
||||||
|
ampersand: "&" as const,
|
||||||
|
asterisk: "*" as const,
|
||||||
|
leftparen: "(" as const,
|
||||||
|
rightparen: ")" as const,
|
||||||
|
underscore: "_" as const,
|
||||||
|
plus: "+" as const,
|
||||||
|
pipe: "|" as const,
|
||||||
|
tilde: "~" as const,
|
||||||
|
leftbrace: "{" as const,
|
||||||
|
rightbrace: "}" as const,
|
||||||
|
colon: ":" as const,
|
||||||
|
lessthan: "<" as const,
|
||||||
|
greaterthan: ">" as const,
|
||||||
|
question: "?" as const,
|
||||||
|
|
||||||
// Single modifiers
|
// Single modifiers
|
||||||
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
ctrl: <K extends BaseKey>(key: K): `ctrl+${K}` => `ctrl+${key}`,
|
||||||
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
shift: <K extends BaseKey>(key: K): `shift+${K}` => `shift+${key}`,
|
||||||
|
|
@ -128,6 +200,40 @@ export const Key = {
|
||||||
// Constants
|
// Constants
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
|
const SYMBOL_KEYS = new Set([
|
||||||
|
"`",
|
||||||
|
"-",
|
||||||
|
"=",
|
||||||
|
"[",
|
||||||
|
"]",
|
||||||
|
"\\",
|
||||||
|
";",
|
||||||
|
"'",
|
||||||
|
",",
|
||||||
|
".",
|
||||||
|
"/",
|
||||||
|
"!",
|
||||||
|
"@",
|
||||||
|
"#",
|
||||||
|
"$",
|
||||||
|
"%",
|
||||||
|
"^",
|
||||||
|
"&",
|
||||||
|
"*",
|
||||||
|
"(",
|
||||||
|
")",
|
||||||
|
"_",
|
||||||
|
"+",
|
||||||
|
"|",
|
||||||
|
"~",
|
||||||
|
"{",
|
||||||
|
"}",
|
||||||
|
":",
|
||||||
|
"<",
|
||||||
|
">",
|
||||||
|
"?",
|
||||||
|
]);
|
||||||
|
|
||||||
const MODIFIERS = {
|
const MODIFIERS = {
|
||||||
shift: 1,
|
shift: 1,
|
||||||
alt: 2,
|
alt: 2,
|
||||||
|
|
@ -403,8 +509,8 @@ export function matchesKey(data: string, keyId: KeyId): boolean {
|
||||||
return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
|
return matchesKittySequence(data, ARROW_CODEPOINTS.right, modifier);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle single letter keys (a-z)
|
// Handle single letter keys (a-z) and some symbols
|
||||||
if (key.length === 1 && key >= "a" && key <= "z") {
|
if (key.length === 1 && ((key >= "a" && key <= "z") || SYMBOL_KEYS.has(key))) {
|
||||||
const codepoint = key.charCodeAt(0);
|
const codepoint = key.charCodeAt(0);
|
||||||
|
|
||||||
if (ctrl && !shift && !alt) {
|
if (ctrl && !shift && !alt) {
|
||||||
|
|
@ -464,6 +570,7 @@ export function parseKey(data: string): string | undefined {
|
||||||
else if (codepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
else if (codepoint === ARROW_CODEPOINTS.left) keyName = "left";
|
||||||
else if (codepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
else if (codepoint === ARROW_CODEPOINTS.right) keyName = "right";
|
||||||
else if (codepoint >= 97 && codepoint <= 122) keyName = String.fromCharCode(codepoint);
|
else if (codepoint >= 97 && codepoint <= 122) keyName = String.fromCharCode(codepoint);
|
||||||
|
else if (SYMBOL_KEYS.has(String.fromCharCode(codepoint))) keyName = String.fromCharCode(codepoint);
|
||||||
|
|
||||||
if (keyName) {
|
if (keyName) {
|
||||||
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
return mods.length > 0 ? `${mods.join("+")}+${keyName}` : keyName;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue