mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 00:03:45 +00:00
129 lines
3.0 KiB
TypeScript
129 lines
3.0 KiB
TypeScript
import { RuntimeClientError } from './runtime-client-error'
|
|
|
|
export type KeyChord = {
|
|
key: string
|
|
modifiers: string[]
|
|
}
|
|
|
|
const MODIFIER_NAMES: Record<string, string> = {
|
|
ctrl: 'Ctrl',
|
|
control: 'Ctrl',
|
|
shift: 'Shift',
|
|
alt: 'Alt',
|
|
meta: 'Meta',
|
|
super: 'Meta',
|
|
cmd: 'Meta',
|
|
command: 'Meta',
|
|
win: 'Meta'
|
|
}
|
|
|
|
const PLATFORM_MODIFIER_NAMES: Record<string, () => string> = {
|
|
cmdorctrl: () => (process.platform === 'darwin' ? 'Meta' : 'Ctrl'),
|
|
commandorcontrol: () => (process.platform === 'darwin' ? 'Meta' : 'Ctrl')
|
|
}
|
|
|
|
const KEY_NAMES: Record<string, string> = {
|
|
Return: 'Enter',
|
|
Enter: 'Enter',
|
|
Escape: 'Escape',
|
|
Esc: 'Escape',
|
|
BackSpace: 'Backspace',
|
|
Backspace: 'Backspace',
|
|
Tab: 'Tab',
|
|
space: 'Space',
|
|
Space: 'Space',
|
|
Page_Up: 'PageUp',
|
|
Page_Down: 'PageDown',
|
|
Home: 'Home',
|
|
End: 'End',
|
|
Up: 'ArrowUp',
|
|
Down: 'ArrowDown',
|
|
Left: 'ArrowLeft',
|
|
Right: 'ArrowRight',
|
|
Delete: 'Delete',
|
|
Insert: 'Insert',
|
|
KP_Add: 'NumpadAdd',
|
|
KP_Subtract: 'NumpadSubtract',
|
|
KP_Multiply: 'NumpadMultiply',
|
|
KP_Divide: 'NumpadDivide',
|
|
KP_Enter: 'NumpadEnter'
|
|
}
|
|
|
|
export function parseKey(input: string): KeyChord {
|
|
const parts = splitKeyChord(input)
|
|
if (parts.length === 0) {
|
|
throw new RuntimeClientError('invalid_argument', 'key must not be empty')
|
|
}
|
|
|
|
const keyPart = parts.at(-1)!
|
|
const modifiers = parts.slice(0, -1).map(parseModifier)
|
|
return {
|
|
key: parseBaseKey(keyPart),
|
|
modifiers: dedupeModifiers(modifiers)
|
|
}
|
|
}
|
|
|
|
function splitKeyChord(input: string): string[] {
|
|
const trimmed = input.trim()
|
|
// Why: `+` is both the chord separator and a printable key users can press.
|
|
if (trimmed === '+') {
|
|
return ['+']
|
|
}
|
|
if (trimmed.endsWith('+')) {
|
|
return [
|
|
...trimmed
|
|
.slice(0, -1)
|
|
.split('+')
|
|
.map((part) => part.trim())
|
|
.filter(Boolean),
|
|
'+'
|
|
]
|
|
}
|
|
return input
|
|
.split('+')
|
|
.map((part) => part.trim())
|
|
.filter(Boolean)
|
|
}
|
|
|
|
function parseModifier(input: string): string {
|
|
const platformModifier = PLATFORM_MODIFIER_NAMES[input.toLowerCase()]
|
|
if (platformModifier) {
|
|
return platformModifier()
|
|
}
|
|
const modifier = MODIFIER_NAMES[input.toLowerCase()]
|
|
if (!modifier) {
|
|
throw new RuntimeClientError('invalid_argument', `unknown modifier '${input}'`)
|
|
}
|
|
return modifier
|
|
}
|
|
|
|
function parseBaseKey(input: string): string {
|
|
const mapped = KEY_NAMES[input]
|
|
if (mapped) {
|
|
return mapped
|
|
}
|
|
if (/^F([1-9]|1[0-9]|2[0-4])$/.test(input)) {
|
|
return input
|
|
}
|
|
const keypadDigit = input.match(/^KP_([0-9])$/)
|
|
if (keypadDigit) {
|
|
return `Numpad${keypadDigit[1]}`
|
|
}
|
|
if (isPrintableAscii(input)) {
|
|
return input
|
|
}
|
|
throw new RuntimeClientError('invalid_argument', `unknown key '${input}'`)
|
|
}
|
|
|
|
function isPrintableAscii(input: string): boolean {
|
|
if (input.length !== 1) {
|
|
return false
|
|
}
|
|
const code = input.charCodeAt(0)
|
|
return code >= 0x20 && code <= 0x7e
|
|
}
|
|
|
|
function dedupeModifiers(modifiers: string[]): string[] {
|
|
return [...new Set(modifiers)]
|
|
}
|