or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

command-system.mdcontext-system.mdeditor-management.mdindex.mdinternal-plugins.mdkeymap-management.md

keymap-management.mddocs/

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

```