0
# Keymap Management
1
2
The keymap system provides keyboard shortcut handling with priority-based execution and seamless integration with ProseMirror's keymap system. It allows for flexible keyboard interaction management across the editor.
3
4
## Capabilities
5
6
### Keymap Manager
7
8
The central keymap management system that handles keyboard shortcut registration and execution.
9
10
```typescript { .api }
11
/**
12
* The keymap manager that handles keyboard shortcuts and key bindings
13
*/
14
class KeymapManager {
15
/** Add a single keymap item with optional priority */
16
add(keymap: KeymapItem): () => void;
17
18
/** Add multiple keymap items as an object mapping keys to commands */
19
addObjectKeymap(keymaps: Record<string, Command | KeymapItem>): () => void;
20
21
/** Add the standard ProseMirror base keymap with Milkdown customizations */
22
addBaseKeymap(): () => void;
23
24
/** Get the current editor context */
25
readonly ctx: Ctx | null;
26
}
27
```
28
29
**Usage Examples:**
30
31
```typescript
32
import { Editor, keymapCtx } from "@milkdown/core";
33
34
editor.action((ctx) => {
35
const keymap = ctx.get(keymapCtx);
36
37
// Add a single keymap item
38
const removeHandler = keymap.add({
39
key: 'Mod-b',
40
onRun: (ctx) => toggleBoldCommand(),
41
priority: 100
42
});
43
44
// Add multiple keymaps at once
45
const removeMultiple = keymap.addObjectKeymap({
46
'Mod-i': toggleItalicCommand(),
47
'Mod-k': {
48
key: 'Mod-k',
49
onRun: (ctx) => insertLinkCommand(),
50
priority: 50
51
}
52
});
53
54
// Add base keymap (backspace, enter, etc.)
55
const removeBase = keymap.addBaseKeymap();
56
57
// Remove keymaps when needed
58
removeHandler();
59
removeMultiple();
60
removeBase();
61
});
62
```
63
64
### Keymap Item
65
66
Interface defining a keyboard shortcut with its associated command and priority.
67
68
```typescript { .api }
69
/**
70
* Configuration item for a keyboard shortcut
71
*/
72
interface KeymapItem {
73
/** The keyboard shortcut (e.g., 'Mod-b' for Ctrl/Cmd+B) */
74
key: string;
75
76
/** Function that returns the command to execute when key is pressed */
77
onRun: (ctx: Ctx) => Command;
78
79
/** Optional priority for command execution order (higher = first, default: 50) */
80
priority?: number;
81
}
82
```
83
84
**Usage Examples:**
85
86
```typescript
87
import { Editor, keymapCtx, commandsCtx } from "@milkdown/core";
88
89
// High priority shortcut (executes first)
90
const highPriorityKeymap: KeymapItem = {
91
key: 'Mod-s',
92
onRun: (ctx) => {
93
const commands = ctx.get(commandsCtx);
94
return (state, dispatch) => {
95
// Custom save logic with high priority
96
return commands.call(saveDocumentKey);
97
};
98
},
99
priority: 100
100
};
101
102
// Default priority shortcut
103
const defaultKeymap: KeymapItem = {
104
key: 'Mod-Enter',
105
onRun: (ctx) => (state, dispatch) => {
106
// Insert hard break
107
return insertHardBreakCommand()(state, dispatch);
108
}
109
// priority defaults to 50
110
};
111
112
// Low priority shortcut (executes last)
113
const lowPriorityKeymap: KeymapItem = {
114
key: 'Tab',
115
onRun: (ctx) => (state, dispatch) => {
116
// Fallback tab behavior
117
return insertTabCommand()(state, dispatch);
118
},
119
priority: 10
120
};
121
```
122
123
### Keymap Context Slice
124
125
The context slice that provides access to the keymap manager.
126
127
```typescript { .api }
128
/**
129
* Context slice containing the keymap manager instance
130
*/
131
const keymapCtx: SliceType<KeymapManager, 'keymap'>;
132
```
133
134
### Keymap Key Type
135
136
Type alias for keymap item slice types.
137
138
```typescript { .api }
139
/**
140
* Type for keymap key slice references
141
*/
142
type KeymapKey = SliceType<KeymapItem>;
143
```
144
145
## Key Syntax
146
147
Keymap keys use ProseMirror's key syntax:
148
149
- **Mod**: Maps to Cmd on Mac, Ctrl on other platforms
150
- **Shift**: Shift modifier
151
- **Alt**: Alt modifier (Option on Mac)
152
- **Ctrl**: Always Ctrl (even on Mac)
153
- **Cmd**: Always Cmd (Mac only)
154
155
**Common Key Examples:**
156
157
```typescript
158
const keymaps = {
159
'Mod-b': toggleBoldCommand(), // Ctrl/Cmd + B
160
'Mod-Shift-k': insertLinkCommand(), // Ctrl/Cmd + Shift + K
161
'Alt-ArrowUp': moveLineUpCommand(), // Alt + Up Arrow
162
'Ctrl-Space': showAutoCompleteCommand(), // Ctrl + Space
163
'Enter': insertNewlineCommand(), // Enter key
164
'Backspace': deleteCharCommand(), // Backspace
165
'Mod-z': undoCommand(), // Ctrl/Cmd + Z
166
'Mod-Shift-z': redoCommand(), // Ctrl/Cmd + Shift + Z
167
};
168
```
169
170
## Priority System
171
172
The keymap system supports priority-based execution for handling conflicting key bindings:
173
174
```typescript
175
import { Editor, keymapCtx } from "@milkdown/core";
176
177
editor.action((ctx) => {
178
const keymap = ctx.get(keymapCtx);
179
180
// High priority - executes first
181
keymap.add({
182
key: 'Mod-Enter',
183
onRun: (ctx) => customSubmitCommand(),
184
priority: 100
185
});
186
187
// Medium priority - executes if high priority returns false
188
keymap.add({
189
key: 'Mod-Enter',
190
onRun: (ctx) => insertParagraphCommand(),
191
priority: 50
192
});
193
194
// Low priority - fallback behavior
195
keymap.add({
196
key: 'Mod-Enter',
197
onRun: (ctx) => defaultEnterCommand(),
198
priority: 10
199
});
200
});
201
```
202
203
## Base Keymap Integration
204
205
Milkdown provides customized base keymap with enhanced backspace behavior:
206
207
```typescript
208
import { Editor, keymapCtx } from "@milkdown/core";
209
210
editor.action((ctx) => {
211
const keymap = ctx.get(keymapCtx);
212
213
// Add base keymap with Milkdown enhancements
214
const removeBase = keymap.addBaseKeymap();
215
216
// The base keymap includes:
217
// - Enhanced Backspace (undo input rules, delete selection, join blocks, select backward)
218
// - Standard navigation (arrows, home, end, page up/down)
219
// - Text selection (Shift + navigation)
220
// - Standard editing (Enter, Delete, etc.)
221
});
222
```
223
224
## Advanced Usage
225
226
### Conditional Keymaps
227
228
```typescript
229
import { Editor, keymapCtx } from "@milkdown/core";
230
231
editor.action((ctx) => {
232
const keymap = ctx.get(keymapCtx);
233
234
keymap.add({
235
key: 'Tab',
236
onRun: (ctx) => (state, dispatch, view) => {
237
// Different behavior based on selection
238
if (state.selection.empty) {
239
return insertTabCommand()(state, dispatch, view);
240
} else {
241
return indentSelectionCommand()(state, dispatch, view);
242
}
243
},
244
priority: 60
245
});
246
});
247
```
248
249
### Dynamic Keymap Management
250
251
```typescript
252
import { Editor, keymapCtx } from "@milkdown/core";
253
254
let vimModeEnabled = false;
255
let vimKeymapRemover: (() => void) | null = null;
256
257
function toggleVimMode() {
258
editor.action((ctx) => {
259
const keymap = ctx.get(keymapCtx);
260
261
if (vimModeEnabled) {
262
// Disable vim mode
263
if (vimKeymapRemover) {
264
vimKeymapRemover();
265
vimKeymapRemover = null;
266
}
267
vimModeEnabled = false;
268
} else {
269
// Enable vim mode
270
vimKeymapRemover = keymap.addObjectKeymap({
271
'h': moveLeftCommand(),
272
'j': moveDownCommand(),
273
'k': moveUpCommand(),
274
'l': moveRightCommand(),
275
'i': enterInsertModeCommand(),
276
'Escape': exitInsertModeCommand()
277
});
278
vimModeEnabled = true;
279
}
280
});
281
}
282
```
283
284
### Context-Aware Commands
285
286
```typescript
287
import { Editor, keymapCtx, commandsCtx } from "@milkdown/core";
288
289
editor.action((ctx) => {
290
const keymap = ctx.get(keymapCtx);
291
292
keymap.add({
293
key: 'Mod-/',
294
onRun: (ctx) => (state, dispatch, view) => {
295
const commands = ctx.get(commandsCtx);
296
297
// Context-aware comment toggling
298
const { $from } = state.selection;
299
const node = $from.parent;
300
301
if (node.type.name === 'code_block') {
302
return commands.call(toggleCodeCommentKey);
303
} else if (node.type.name === 'paragraph') {
304
return commands.call(toggleLineCommentKey);
305
} else {
306
return false;
307
}
308
}
309
});
310
});
311
```
312
313
## Error Handling
314
315
### Safe Keymap Registration
316
317
```typescript
318
import { Editor, keymapCtx } from "@milkdown/core";
319
import { ctxCallOutOfScope } from "@milkdown/exception";
320
321
editor.action((ctx) => {
322
const keymap = ctx.get(keymapCtx);
323
324
const safeKeymap: KeymapItem = {
325
key: 'Mod-x',
326
onRun: (ctx) => (state, dispatch, view) => {
327
try {
328
// Attempt command execution
329
return dangerousCommand()(state, dispatch, view);
330
} catch (error) {
331
console.error('Keymap command failed:', error);
332
return false; // Prevent command from appearing to succeed
333
}
334
}
335
};
336
337
keymap.add(safeKeymap);
338
});
339
```
340
341
### Context Validation
342
343
```typescript
344
import { Editor, keymapCtx } from "@milkdown/core";
345
346
editor.action((ctx) => {
347
const keymap = ctx.get(keymapCtx);
348
349
keymap.add({
350
key: 'Mod-p',
351
onRun: (ctx) => (state, dispatch, view) => {
352
// Validate context before execution
353
if (!ctx || !dispatch || !view) {
354
console.warn('Invalid context for print command');
355
return false;
356
}
357
358
return printDocumentCommand()(state, dispatch, view);
359
}
360
});
361
});
362
```
363
364
## Integration Patterns
365
366
### Plugin Keymap Integration
367
368
```typescript
369
import { MilkdownPlugin } from "@milkdown/ctx";
370
import { keymapCtx, KeymapReady } from "@milkdown/core";
371
372
const myPlugin: MilkdownPlugin = (ctx) => {
373
ctx.inject(/* plugin context */);
374
375
return async () => {
376
await ctx.wait(KeymapReady);
377
378
const keymap = ctx.get(keymapCtx);
379
380
// Add plugin-specific keymaps
381
const removeKeymaps = keymap.addObjectKeymap({
382
'Mod-Shift-p': pluginSpecificCommand(),
383
'Alt-p': anotherPluginCommand()
384
});
385
386
// Return cleanup function
387
return () => {
388
removeKeymaps();
389
};
390
};
391
};
392
```
393
394
### Theme-Based Keymaps
395
396
```typescript
397
import { Editor, keymapCtx } from "@milkdown/core";
398
399
function applyKeymapTheme(theme: 'standard' | 'vim' | 'emacs') {
400
editor.action((ctx) => {
401
const keymap = ctx.get(keymapCtx);
402
403
switch (theme) {
404
case 'vim':
405
return keymap.addObjectKeymap({
406
'h': moveLeftCommand(),
407
'j': moveDownCommand(),
408
'k': moveUpCommand(),
409
'l': moveRightCommand()
410
});
411
412
case 'emacs':
413
return keymap.addObjectKeymap({
414
'Ctrl-f': moveRightCommand(),
415
'Ctrl-b': moveLeftCommand(),
416
'Ctrl-n': moveDownCommand(),
417
'Ctrl-p': moveUpCommand()
418
});
419
420
default:
421
return keymap.addBaseKeymap();
422
}
423
});
424
}
425
```