}>
  Lesson 3 of 8  -  Learn how duck-vim parses, normalizes, and validates key binding strings.

## What is a key binding string?

A key binding string is a human-readable representation of a key combination:

```txt
ctrl+shift+s
Mod+K
alt+enter
space
g
```

duck-vim parses these strings into structured objects that can be matched against keyboard events. Understanding the parsing rules helps you write correct bindings and debug issues.

***

## Parsing a binding

```ts
import { parseKeyBind } from '@gentleduck/vim/parser'

const result = parseKeyBind('ctrl+shift+s')
```

The result is a `ParsedKeyBind`:

```ts
{
  key: 's',         // The non-modifier key
  ctrl: true,
  shift: true,
  alt: false,
  meta: false,
  modifiers: ['ctrl', 'shift']  // Sorted alphabetically
}
```

Each binding has exactly one non-modifier key and zero or more modifiers.

***

## Normalization rules

duck-vim normalizes all bindings to a canonical form:

1. Everything is lowercased.
2. Modifiers are sorted alphabetically: <Kbd>Alt</Kbd>, <Kbd>Ctrl</Kbd>, <Kbd>Meta</Kbd>, <Kbd>Shift</Kbd>.
3. Aliases are resolved (see below).

This means these are all equivalent:

```ts
normalizeKeyBind('Ctrl+Shift+S')   // 'ctrl+shift+s'
normalizeKeyBind('shift+ctrl+s')   // 'ctrl+shift+s'
normalizeKeyBind('SHIFT+CTRL+S')   // 'ctrl+shift+s'
```

Use `normalizeKeyBind` when comparing bindings:

```ts
import { normalizeKeyBind } from '@gentleduck/vim/parser'

normalizeKeyBind(bindingA) === normalizeKeyBind(bindingB)
```

***

## Key aliases

Several keys have multiple common names. duck-vim accepts all of them:

| You can write | Resolves to |
| --- | --- |
| `command`, `cmd` | `meta` |
| `option`, `opt` | `alt` |
| `control` | `ctrl` |
| `escape` | `esc` |
| ` ` (space character) | `space` |

**Example:**

```ts
parseKeyBind('command+s')  // { key: 's', meta: true, ... }
parseKeyBind('cmd+s')      // { key: 's', meta: true, ... }
parseKeyBind('opt+k')      // { key: 'k', alt: true, ... }
```

***

## The Mod key

}>
  <Kbd>Mod</Kbd> is a virtual key that resolves based on the platform:

* **macOS:** <Kbd>Mod</Kbd> becomes <Kbd>Meta</Kbd> (Command key)
* **Windows/Linux:** <Kbd>Mod</Kbd> becomes <Kbd>Ctrl</Kbd>

```ts
// On macOS:
parseKeyBind('Mod+S') // { key: 's', meta: true, ctrl: false, ... }

// On Windows:
parseKeyBind('Mod+S') // { key: 's', meta: false, ctrl: true, ... }
```

You can force a specific platform:

```ts
parseKeyBind('Mod+S', 'mac')     // Always meta
parseKeyBind('Mod+S', 'windows') // Always ctrl
```

}>
  **Best practice:** Always use <Kbd>Mod</Kbd> instead of <Kbd>Ctrl</Kbd> or <Kbd>Meta</Kbd> for shortcuts that should work on all platforms. Reserve explicit <Kbd>Ctrl</Kbd> or <Kbd>Meta</Kbd> only when you specifically need that modifier.

***

## Validation

Use `validateKeyBind` to check a binding without throwing:

```ts
import { validateKeyBind } from '@gentleduck/vim/parser'

validateKeyBind('ctrl+k')
// { valid: true, warnings: [], errors: [] }

validateKeyBind('')
// { valid: false, warnings: [], errors: ['Key binding string cannot be empty'] }

validateKeyBind('ctrl+k+j')
// { valid: false, warnings: [], errors: ['Multiple non-modifier keys found'] }

validateKeyBind('ctrl+ctrl+k')
// { valid: false, warnings: [], errors: ["Duplicate modifier: 'ctrl'"] }

validateKeyBind('alt+n')
// { valid: true, warnings: ['Alt+letter may not work on macOS...'], errors: [] }
```

}>
  The last example is important: <Kbd>Alt</Kbd>+letter combinations produce special characters on macOS (e.g., <Kbd>Alt+N</Kbd> types a tilde), so the shortcut might not fire. The validator warns you about this.

}>
  **Use `validateKeyBind` before storing user-defined bindings.** It catches problems that would silently fail at runtime.

***

## Converting keyboard events to descriptors

When the KeyHandler receives a keyboard event, it converts it to a descriptor string:

```ts
import { keyboardEventToDescriptor } from '@gentleduck/vim/parser'

document.addEventListener('keydown', (e) => {
  const desc = keyboardEventToDescriptor(e)
  console.log(desc) // e.g. 'ctrl+k', 'shift+enter', 'a'
})
```

Returns `null` for pure modifier key presses (pressing just <Kbd>Shift</Kbd>, <Kbd>Ctrl</Kbd>, <Kbd>Alt</Kbd>, or <Kbd>Meta</Kbd> alone).

***

## Common mistakes

}>
  **Multiple non-modifier keys:** `'k+j'` is NOT a two-key sequence. It's parsed as a single binding with two non-modifier keys, which throws an error. For sequences, use the Registry's `'g+d'` format or the SequenceManager.

}>
  **Forgetting `preventDefault`:** Browser shortcuts like <Kbd>Ctrl+S</Kbd> (save), <Kbd>Ctrl+W</Kbd> (close tab), and <Kbd>Ctrl+N</Kbd> (new window) will still fire their default behavior unless you set `preventDefault: true`.

}>
  **Using `ctrl` on Mac:** macOS users expect <Kbd>Cmd+S</Kbd>, not <Kbd>Ctrl+S</Kbd>. Use <Kbd>Mod+S</Kbd> to get the right behavior on both platforms.

***

## Exercises

1. Parse `'Mod+Shift+Z'` for both Mac and Windows. What are the differences?
2. What does `validateKeyBind('shift+shift+a')` return?
3. Write code that listens for keydown events and logs the descriptor for each.

}>
  **Next:** [Lesson 4  -  React Integration](/duck-vim/course/04-react)