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

command-system.mddocs/

0

# Command System

1

2

The command system provides type-safe command registration and execution for editor actions. Commands are the primary way to interact with and modify the editor state in Milkdown.

3

4

## Capabilities

5

6

### Command Manager

7

8

The central command management system that handles registration, retrieval, and execution of commands.

9

10

```typescript { .api }

11

/**

12

* The command manager that handles all editor commands

13

*/

14

class CommandManager {

15

/** Register a command with the manager */

16

create<T>(meta: CmdKey<T>, value: Cmd<T>): SliceType<Cmd<T>>;

17

18

/** Get a registered command by its key */

19

get<T extends CmdKey<any>>(slice: string): Cmd<InferParams<T>>;

20

get<T>(slice: CmdKey<T>): Cmd<T>;

21

22

/** Remove a command from the manager */

23

remove<T extends CmdKey<any>>(slice: string): void;

24

remove<T>(slice: CmdKey<T>): void;

25

26

/** Execute a registered command with optional payload */

27

call<T extends CmdKey<any>>(slice: string, payload?: InferParams<T>): boolean;

28

call<T>(slice: CmdKey<T>, payload?: T): boolean;

29

30

/** Execute an inline ProseMirror command directly */

31

inline(command: Command): boolean;

32

33

/** Create a command chain for sequential execution */

34

chain(): CommandChain;

35

36

/** Get the current editor context */

37

readonly ctx: Ctx | null;

38

}

39

```

40

41

**Usage Examples:**

42

43

```typescript

44

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

45

46

// Create a command key

47

const toggleBoldKey = createCmdKey<void>('toggleBold');

48

49

// Register the command

50

editor.action((ctx) => {

51

const commands = ctx.get(commandsCtx);

52

53

commands.create(toggleBoldKey, () => (state, dispatch) => {

54

// ProseMirror command implementation

55

const { schema } = state;

56

const markType = schema.marks.strong;

57

return toggleMark(markType)(state, dispatch);

58

});

59

});

60

61

// Execute the command

62

editor.action((ctx) => {

63

const commands = ctx.get(commandsCtx);

64

const success = commands.call(toggleBoldKey);

65

console.log('Command executed:', success);

66

});

67

```

68

69

### Command Chain

70

71

Interface for chaining multiple commands together with sequential execution.

72

73

```typescript { .api }

74

/**

75

* A chainable command helper for executing multiple commands in sequence

76

*/

77

interface CommandChain {

78

/** Execute all commands in the chain */

79

run(): boolean;

80

81

/** Add a ProseMirror command directly to the chain */

82

inline(command: Command): CommandChain;

83

84

/** Add a registered command to the chain with optional payload */

85

pipe<T extends CmdKey<any>>(slice: string, payload?: InferParams<T>): CommandChain;

86

pipe<T>(slice: CmdKey<T>, payload?: T): CommandChain;

87

pipe(slice: string | CmdKey<any>, payload?: any): CommandChain;

88

}

89

```

90

91

**Usage Examples:**

92

93

```typescript

94

import { Editor, commandsCtx } from "@milkdown/core";

95

96

editor.action((ctx) => {

97

const commands = ctx.get(commandsCtx);

98

99

// Chain multiple commands

100

const success = commands.chain()

101

.pipe(toggleBoldKey)

102

.inline((state, dispatch) => {

103

// Inline command

104

return selectAll(state, dispatch);

105

})

106

.pipe(insertTextKey, 'Hello World')

107

.run();

108

109

console.log('Chain executed:', success);

110

});

111

```

112

113

### Command Key Creation

114

115

Function to create type-safe command keys for command registration.

116

117

```typescript { .api }

118

/**

119

* Create a command key for type-safe command registration

120

* @param key - Optional identifier for the command key

121

* @returns A typed command key slice

122

*/

123

function createCmdKey<T = undefined>(key?: string): CmdKey<T>;

124

```

125

126

**Usage Examples:**

127

128

```typescript

129

import { createCmdKey } from "@milkdown/core";

130

131

// Command with no payload

132

const saveDocumentKey = createCmdKey<void>('saveDocument');

133

134

// Command with string payload

135

const insertTextKey = createCmdKey<string>('insertText');

136

137

// Command with complex payload

138

interface FormatOptions {

139

bold?: boolean;

140

italic?: boolean;

141

color?: string;

142

}

143

const formatTextKey = createCmdKey<FormatOptions>('formatText');

144

145

// Usage

146

editor.action((ctx) => {

147

const commands = ctx.get(commandsCtx);

148

149

commands.call(saveDocumentKey);

150

commands.call(insertTextKey, 'Hello World');

151

commands.call(formatTextKey, { bold: true, color: 'red' });

152

});

153

```

154

155

### Command Context Slice

156

157

The context slice that provides access to the command manager.

158

159

```typescript { .api }

160

/**

161

* Context slice containing the command manager instance

162

*/

163

const commandsCtx: SliceType<CommandManager, 'commands'>;

164

```

165

166

## Command Types

167

168

### Core Command Types

169

170

```typescript { .api }

171

/**

172

* Command function type that returns a ProseMirror command

173

* @param payload - Optional payload data for the command

174

* @returns ProseMirror Command function

175

*/

176

type Cmd<T = undefined> = (payload?: T) => Command;

177

178

/**

179

* Command key type for type-safe command registration

180

*/

181

type CmdKey<T = undefined> = SliceType<Cmd<T>>;

182

183

/**

184

* Type helper to infer command payload types from CmdKey

185

*/

186

type InferParams<T> = T extends CmdKey<infer U> ? U : never;

187

```

188

189

## Advanced Usage

190

191

### Custom Command Implementation

192

193

```typescript

194

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

195

import { setBlockType } from "@milkdown/prose/commands";

196

197

// Create a custom heading command

198

const setHeadingKey = createCmdKey<{ level: number }>('setHeading');

199

200

editor.action((ctx) => {

201

const commands = ctx.get(commandsCtx);

202

203

commands.create(setHeadingKey, ({ level }) => (state, dispatch) => {

204

const { schema } = state;

205

const headingType = schema.nodes.heading;

206

207

if (!headingType) return false;

208

209

return setBlockType(headingType, { level })(state, dispatch);

210

});

211

});

212

213

// Use the command

214

editor.action((ctx) => {

215

const commands = ctx.get(commandsCtx);

216

commands.call(setHeadingKey, { level: 2 });

217

});

218

```

219

220

### Command Composition

221

222

```typescript

223

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

224

225

// Compose multiple operations into a single command

226

const formatParagraphKey = createCmdKey<{

227

bold?: boolean;

228

italic?: boolean;

229

align?: 'left' | 'center' | 'right';

230

}>('formatParagraph');

231

232

editor.action((ctx) => {

233

const commands = ctx.get(commandsCtx);

234

235

commands.create(formatParagraphKey, (options) => (state, dispatch, view) => {

236

// Chain multiple formatting commands

237

return commands.chain()

238

.pipe(toggleBoldKey)

239

.pipe(toggleItalicKey)

240

.pipe(setAlignmentKey, options.align || 'left')

241

.run();

242

});

243

});

244

```

245

246

### Conditional Command Execution

247

248

```typescript

249

import { Editor, commandsCtx } from "@milkdown/core";

250

251

editor.action((ctx) => {

252

const commands = ctx.get(commandsCtx);

253

254

// Execute command only if selection is not empty

255

const success = commands.inline((state, dispatch) => {

256

if (state.selection.empty) {

257

console.log('Cannot format empty selection');

258

return false;

259

}

260

261

return commands.call(formatSelectionKey, { bold: true });

262

});

263

});

264

```

265

266

## Error Handling

267

268

### Command Execution Errors

269

270

```typescript

271

import { Editor, commandsCtx } from "@milkdown/core";

272

import { callCommandBeforeEditorView } from "@milkdown/exception";

273

274

editor.action((ctx) => {

275

const commands = ctx.get(commandsCtx);

276

277

try {

278

const success = commands.call(someCommandKey, payload);

279

if (!success) {

280

console.warn('Command execution failed - command returned false');

281

}

282

} catch (error) {

283

if (error === callCommandBeforeEditorView()) {

284

console.error('Cannot execute command before editor view is ready');

285

} else {

286

console.error('Command execution error:', error);

287

}

288

}

289

});

290

```

291

292

### Command Registration Safety

293

294

```typescript

295

import { Editor, commandsCtx, createCmdKey } from "@milkdown/core";

296

297

const myCommandKey = createCmdKey<string>('myCommand');

298

299

editor.action((ctx) => {

300

const commands = ctx.get(commandsCtx);

301

302

// Check if command already exists before registering

303

try {

304

const existing = commands.get(myCommandKey);

305

console.log('Command already registered');

306

} catch {

307

// Command doesn't exist, safe to register

308

commands.create(myCommandKey, (text) => (state, dispatch) => {

309

// Command implementation

310

return true;

311

});

312

}

313

});

314

```

315

316

## Integration with ProseMirror

317

318

The command system seamlessly integrates with ProseMirror's command system:

319

320

```typescript

321

import { Editor, commandsCtx } from "@milkdown/core";

322

import {

323

toggleMark,

324

wrapIn,

325

setBlockType,

326

chainCommands,

327

exitCode

328

} from "@milkdown/prose/commands";

329

330

editor.action((ctx) => {

331

const commands = ctx.get(commandsCtx);

332

333

// Use ProseMirror commands directly

334

commands.inline(toggleMark(schema.marks.strong));

335

336

// Chain ProseMirror commands

337

commands.inline(chainCommands(

338

exitCode,

339

(state, dispatch) => {

340

// Custom logic

341

return true;

342

}

343

));

344

});

345

```