( input: string, key: Key, activeContexts: KeybindingContextName[], bindings: ParsedBinding[], pending: ParsedKeystroke[] | null, )
| 164 | * @returns Resolution result with chord state |
| 165 | */ |
| 166 | export function resolveKeyWithChordState( |
| 167 | input: string, |
| 168 | key: Key, |
| 169 | activeContexts: KeybindingContextName[], |
| 170 | bindings: ParsedBinding[], |
| 171 | pending: ParsedKeystroke[] | null, |
| 172 | ): ChordResolveResult { |
| 173 | // Cancel chord on escape |
| 174 | if (key.escape && pending !== null) { |
| 175 | return { type: 'chord_cancelled' } |
| 176 | } |
| 177 | |
| 178 | // Build current keystroke |
| 179 | const currentKeystroke = buildKeystroke(input, key) |
| 180 | if (!currentKeystroke) { |
| 181 | if (pending !== null) { |
| 182 | return { type: 'chord_cancelled' } |
| 183 | } |
| 184 | return { type: 'none' } |
| 185 | } |
| 186 | |
| 187 | // Build the full chord sequence to test |
| 188 | const testChord = pending |
| 189 | ? [...pending, currentKeystroke] |
| 190 | : [currentKeystroke] |
| 191 | |
| 192 | // Filter bindings by active contexts (Set lookup: O(n) instead of O(n·m)) |
| 193 | const ctxSet = new Set(activeContexts) |
| 194 | const contextBindings = bindings.filter(b => ctxSet.has(b.context)) |
| 195 | |
| 196 | // Check if this could be a prefix for longer chords. Group by chord |
| 197 | // string so a later null-override shadows the default it unbinds — |
| 198 | // otherwise null-unbinding `ctrl+x ctrl+k` still makes `ctrl+x` enter |
| 199 | // chord-wait and the single-key binding on the prefix never fires. |
| 200 | const chordWinners = new Map<string, string | null>() |
| 201 | for (const binding of contextBindings) { |
| 202 | if ( |
| 203 | binding.chord.length > testChord.length && |
| 204 | chordPrefixMatches(testChord, binding) |
| 205 | ) { |
| 206 | chordWinners.set(chordToString(binding.chord), binding.action) |
| 207 | } |
| 208 | } |
| 209 | let hasLongerChords = false |
| 210 | for (const action of chordWinners.values()) { |
| 211 | if (action !== null) { |
| 212 | hasLongerChords = true |
| 213 | break |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | // If this keystroke could start a longer chord, prefer that |
| 218 | // (even if there's an exact single-key match) |
| 219 | if (hasLongerChords) { |
| 220 | return { type: 'chord_started', pending: testChord } |
| 221 | } |
| 222 | |
| 223 | // Check for exact matches (last one wins) |
no test coverage detected