}>
  Lesson 5 of 8  -  Build Vim-style multi-key sequences with prefix matching and timeouts.

## Two ways to handle sequences

duck-vim offers two systems for multi-key sequences, each suited for different use cases.

Registry sequences
SequenceManager

Register a binding like `'g+d'` in the Registry. The KeyHandler detects <Kbd>G</Kbd> as a prefix, waits for the next key, and fires the command if <Kbd>D</Kbd> arrives in time.

```ts
registry.register('g+d', { name: 'Go Dashboard', execute: () => {} })
```

**Best for:** Simple character-based sequences (no modifiers per step).

For sequences where each step can be a full key combination, use `SequenceManager`:

```ts
const manager = new SequenceManager()
manager.register({
  steps: [{ binding: 'ctrl+k' }, { binding: 'ctrl+c' }],
  handler: () => commentSelection(),
})
```

**Best for:** VS Code-style chord sequences (<Kbd>Ctrl+K</Kbd>, <Kbd>Ctrl+C</Kbd>).

This lesson covers both.

***

## Registry sequences in detail

### How prefixes work

When you register `'g+d'`, the registry marks `'g'` as a prefix. Here's what happens when the user types:

User presses <Kbd>G</Kbd>. The KeyHandler builds descriptor `'g'`, checks the registry.
`'g'` is not a full command, but it IS a prefix. The handler starts a timer.
User presses <Kbd>D</Kbd> within the timeout. The handler builds `'g+d'`, finds the match, executes.
If the user presses something other than <Kbd>D</Kbd>, or the timeout expires, the buffer resets.

### Retry behavior

If the user presses <Kbd>G</Kbd>, then <Kbd>X</Kbd> (no match), the handler resets and retries <Kbd>X</Kbd> as a standalone binding. This means:

```ts
registry.register('g+d', { name: 'Go Dashboard', execute: dashFn })
registry.register('x', { name: 'Delete', execute: deleteFn })
```

Pressing <Kbd>G</Kbd>, <Kbd>X</Kbd> will: fail the `g+d` sequence, reset, then match `x` and fire `deleteFn`.

### Multiple sequences sharing a prefix

```ts
registry.register('g+d', goToDashboard)
registry.register('g+s', goToSettings)
registry.register('g+p', goToProfile)
```

All three share the prefix <Kbd>G</Kbd>. When the user presses <Kbd>G</Kbd>, the handler waits. The next key determines which command fires (or none, if it doesn't match).

***

## SequenceManager in detail

### Basic usage

```ts
import { SequenceManager } from '@gentleduck/vim/sequence'

const manager = new SequenceManager()

// Ctrl+K, then Ctrl+C
manager.register({
  steps: [
    { binding: 'ctrl+k' },
    { binding: 'ctrl+c' },
  ],
  handler: () => console.log('Comment!'),
  options: { timeout: 800 },
})

// You must wire up the event listener yourself
document.addEventListener('keydown', (e) => manager.handleKeyEvent(e))
```

### Tracking state

The `getState()` method tells you how far along the most-advanced sequence is:

```ts
const state = manager.getState()
// { completedSteps: 1, totalSteps: 2, isMatching: true }
```

Use this to show a visual indicator:

```tsx
function SequenceIndicator() {
  const ctx = useContext(KeyContext)
  const [state, setState] = useState({ isMatching: false, completedSteps: 0, totalSteps: 0 })

  useEffect(() => {
    const interval = setInterval(() => {
      if (ctx) setState(ctx.sequenceManager.getState())
    }, 50)
    return () => clearInterval(interval)
  }, [ctx])

  if (!state.isMatching) return null

  return (
    <div className="fixed bottom-4 right-4 bg-black text-white px-3 py-1 rounded text-sm">
      Step {state.completedSteps}/{state.totalSteps}
    </div>
  )
}
```

### The convenience function

For a single sequence without managing a `SequenceManager` instance:

```ts
import { createSequenceMatcher } from '@gentleduck/vim/sequence'

const matcher = createSequenceMatcher(
  ['g', 'd'],
  () => console.log('g+d!'),
  { timeout: 500 },
)

document.addEventListener('keydown', (e) => matcher.feed(e))
```

***

## React: `useKeySequence`

The React hook wraps `SequenceManager`:

```tsx
import { useKeySequence } from '@gentleduck/vim/react'

function Editor() {
  // Two-key character sequence
  useKeySequence(['g', 'd'], () => navigate('/dashboard'))

  // Chord sequence with modifiers
  useKeySequence(['ctrl+k', 'ctrl+c'], () => commentSelection())

  // With options
  useKeySequence(['z', 'z'], () => centerView(), {
    timeout: 400,
    enabled: isEditorFocused,
  })
}
```

***

## Timeout tuning

}>
  The timeout controls the maximum delay between steps. Finding the right value depends on your users.

| Value | Feel | Good for |
| --- | --- | --- |
| 300ms | Snappy, requires quick fingers | Gaming, power users |
| 600ms | Balanced (default) | Most applications |
| 1000ms | Forgiving | Accessibility, complex sequences |

You can set different timeouts per sequence in `SequenceManager`:

```ts
manager.register({
  steps: [...],
  handler: () => {},
  options: { timeout: 1000 }, // This sequence gets more time
})
```

***

## Common sequence patterns

### Navigation (<Kbd>G</Kbd> prefix)

```ts
registry.register('g+h', goHome)
registry.register('g+d', goDashboard)
registry.register('g+s', goSettings)
registry.register('g+p', goProfile)
```

### VS Code chords (<Kbd>Ctrl+K</Kbd> prefix)

```ts
useKeySequence(['ctrl+k', 'ctrl+c'], commentLine)
useKeySequence(['ctrl+k', 'ctrl+u'], uncommentLine)
useKeySequence(['ctrl+k', 'ctrl+f'], formatDocument)
```

### Vim-style double-tap

```ts
registry.register('d+d', deleteLine)
registry.register('y+y', yankLine)
```

***

## Exercises

1. Register three navigation shortcuts that share the <Kbd>G</Kbd> prefix.
2. Create a VS Code-style chord: <Kbd>Ctrl+K</Kbd>, <Kbd>Ctrl+S</Kbd> to save all files.
3. Build a sequence state indicator that shows when a sequence is in progress.

}>
  **Next:** [Lesson 6  -  Display Formatting](/duck-vim/course/06-formatting)