| 206 | * ``` |
| 207 | */ |
| 208 | export function parseKeySequence(input: string): ParseKeyResult { |
| 209 | const trimmed = input.trim(); |
| 210 | |
| 211 | if (trimmed.length === 0) { |
| 212 | return { |
| 213 | ok: false, |
| 214 | error: { code: "EMPTY_SEQUENCE", detail: "keybinding string is empty" }, |
| 215 | }; |
| 216 | } |
| 217 | |
| 218 | // Split on whitespace for chord sequences |
| 219 | const parts = trimmed.split(/\s+/); |
| 220 | const keys: ParsedKey[] = []; |
| 221 | |
| 222 | for (const part of parts) { |
| 223 | const result = parseKeyPart(part); |
| 224 | if (!result.ok) { |
| 225 | return result; |
| 226 | } |
| 227 | keys.push(result.value); |
| 228 | } |
| 229 | |
| 230 | if (keys.length === 0) { |
| 231 | return { |
| 232 | ok: false, |
| 233 | error: { code: "EMPTY_SEQUENCE", detail: "no keys in sequence" }, |
| 234 | }; |
| 235 | } |
| 236 | |
| 237 | if (keys.length > MAX_CHORD_LENGTH) { |
| 238 | return { |
| 239 | ok: false, |
| 240 | error: { |
| 241 | code: "INVALID_KEY", |
| 242 | detail: `chord sequence length ${String(keys.length)} exceeds maximum of ${String(MAX_CHORD_LENGTH)}`, |
| 243 | }, |
| 244 | }; |
| 245 | } |
| 246 | |
| 247 | return { |
| 248 | ok: true, |
| 249 | value: Object.freeze({ keys: Object.freeze(keys) }), |
| 250 | }; |
| 251 | } |
| 252 | |
| 253 | /** |
| 254 | * Check if two ParsedKey objects are equal. |