{"version":3,"file":"chord.d.ts","sourceRoot":"","sources":["../src/chord.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGvE,MAAM,MAAM,YAAY,GAAG,MAAM,CAAC;AAElC,6DAA6D;AAC7D,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,YAAY,EAAE,CAKxD;AAED,MAAM,WAAW,WAAW;IAC3B,yEAAyE;IACzE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,UAAU,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IAChD,sGAAsG;IACtG,OAAO,EAAE,OAAO,CAAC;CACjB;AASD;;;;GAIG;AACH,qBAAa,YAAY;IACxB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,SAAS,CAAK;IAEtB,YAAY,IAAI,GAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAO,EAE5C;IAED,sCAAsC;IACtC,KAAK,IAAI,IAAI,CAEZ;IAED,qDAAqD;IACrD,UAAU,IAAI,OAAO,CAEpB;IAED;;;;OAIG;IACH,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,GAAG,WAAW,CAyC7F;CACD","sourcesContent":["/**\n * Chord-aware keybinding matcher.\n *\n * A chord is a sequence of single key presses separated by ` ` in the KeyId\n * (e.g. `\"ctrl+x ctrl+s\"` means: press Ctrl+X, then Ctrl+S). The plain\n * single-key matcher in `keybindings.ts` does not understand chords; this\n * module adds a stateful matcher that the consumer drives one input event at\n * a time.\n *\n * Reference: claude-code keybindings/parser.ts:1 + match.ts state machine.\n *\n * Usage:\n * ```\n * const session = new ChordSession();\n * for (const data of inputStream) {\n *   const result = session.feed(data, keys);\n *   if (result.match) handle(result.match);\n * }\n * ```\n */\n\nimport type { Keybinding, KeybindingsManager } from \"./keybindings.js\";\nimport { matchesKey } from \"./keys.js\";\n\nexport type ChordSegment = string; // a single key id like \"ctrl+x\"\n\n/** Split a chord notation into its single-press segments. */\nexport function parseChord(keyId: string): ChordSegment[] {\n\treturn keyId\n\t\t.trim()\n\t\t.split(/\\s+/)\n\t\t.filter((s) => s.length > 0);\n}\n\nexport interface ChordResult {\n\t/** True when the just-fed input completes one of the supplied chords. */\n\tmatch?: { keybinding: Keybinding; key: string };\n\t/** True when at least one chord is still mid-sequence (consumer should suppress default handling). */\n\tpartial: boolean;\n}\n\ninterface PendingChord {\n\tkeybinding: Keybinding;\n\tkey: string;\n\tsegments: ChordSegment[];\n\tcursor: number;\n}\n\n/**\n * Stateful chord matcher. One instance per input focus context. Call\n * `feed(data, keybindings)` once per input event; the session decides\n * whether the event advances any in-flight chord, completes one, or resets.\n */\nexport class ChordSession {\n\tprivate pending: PendingChord[] = [];\n\tprivate timeoutMs: number;\n\tprivate lastFedAt = 0;\n\n\tconstructor(opts: { timeoutMs?: number } = {}) {\n\t\tthis.timeoutMs = opts.timeoutMs ?? 1500;\n\t}\n\n\t/** Drop any in-flight chord state. */\n\treset(): void {\n\t\tthis.pending = [];\n\t}\n\n\t/** True while at least one chord is mid-sequence. */\n\thasPartial(): boolean {\n\t\treturn this.pending.length > 0;\n\t}\n\n\t/**\n\t * Advance the matcher with one terminal-input event. Returns\n\t * `{match, partial}`. `match` is set only when the input completes a\n\t * chord; partial=true while the consumer should hold its default action.\n\t */\n\tfeed(data: string, manager: KeybindingsManager, candidates: Iterable<Keybinding>): ChordResult {\n\t\tconst now = Date.now();\n\t\tif (this.pending.length > 0 && now - this.lastFedAt > this.timeoutMs) {\n\t\t\tthis.pending = [];\n\t\t}\n\t\tthis.lastFedAt = now;\n\n\t\t// First, advance any pending chords.\n\t\tconst advanced: PendingChord[] = [];\n\t\tlet match: ChordResult[\"match\"];\n\t\tfor (const p of this.pending) {\n\t\t\tconst segment = p.segments[p.cursor];\n\t\t\tif (segment && matchesKey(data, segment as Parameters<typeof matchesKey>[1])) {\n\t\t\t\tconst next = p.cursor + 1;\n\t\t\t\tif (next === p.segments.length) {\n\t\t\t\t\tmatch = { keybinding: p.keybinding, key: p.key };\n\t\t\t\t} else {\n\t\t\t\t\tadvanced.push({ ...p, cursor: next });\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Seed new chord candidates if no chord was already in flight.\n\t\tif (this.pending.length === 0) {\n\t\t\tfor (const kb of candidates) {\n\t\t\t\tfor (const key of manager.getKeys(kb)) {\n\t\t\t\t\tconst segments = parseChord(String(key));\n\t\t\t\t\tif (segments.length <= 1) continue; // single-key, handled elsewhere\n\t\t\t\t\tif (matchesKey(data, segments[0] as Parameters<typeof matchesKey>[1])) {\n\t\t\t\t\t\tif (segments.length === 1) {\n\t\t\t\t\t\t\tmatch = { keybinding: kb, key: String(key) };\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tadvanced.push({ keybinding: kb, key: String(key), segments, cursor: 1 });\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.pending = match ? [] : advanced;\n\t\treturn { match, partial: this.pending.length > 0 };\n\t}\n}\n"]}