0
# Editor Commands
1
2
Custom command functions for editor interactions, keyboard shortcuts, and special behaviors in JupyterLab context.
3
4
## Capabilities
5
6
### StateCommands Namespace
7
8
Collection of editor state commands designed for JupyterLab integration and context-aware behavior.
9
10
```typescript { .api }
11
/**
12
* CodeMirror commands namespace
13
* Provides context-aware editor commands for JupyterLab integration
14
*/
15
namespace StateCommands {
16
/**
17
* Indent or insert a tab as appropriate, with completer awareness
18
*/
19
function indentMoreOrInsertTab(target: CommandTarget): boolean;
20
21
/**
22
* Insert new line if completer is not active
23
*/
24
function completerOrInsertNewLine(target: CommandTarget): boolean;
25
26
/**
27
* Prevent insertion of new line when running cell with Ctrl/Command + Enter (deprecated)
28
*/
29
function preventNewLineOnRun(target: { dom: HTMLElement }): boolean;
30
31
/**
32
* Insert a new line or run a cell with Ctrl/Command + Enter
33
*/
34
function insertBlankLineOnRun(target: CommandTarget): boolean;
35
36
/**
37
* Simplify selection but do not prevent default to allow switching to command mode
38
*/
39
function simplifySelectionAndMaybeSwitchToCommandMode(target: CommandTarget): boolean;
40
41
/**
42
* Prevent dedenting when launching inspection request (tooltip)
43
*/
44
function dedentIfNotLaunchingTooltip(target: CommandTarget): boolean;
45
}
46
47
/**
48
* Command target interface for state commands
49
*/
50
interface CommandTarget {
51
dom: HTMLElement;
52
state: EditorState;
53
dispatch: (transaction: Transaction) => void;
54
}
55
```
56
57
**Usage Examples:**
58
59
```typescript
60
import { StateCommands } from "@jupyterlab/codemirror";
61
import { keymap } from "@codemirror/view";
62
63
// Create keymap with JupyterLab-aware commands
64
const jupyterLabKeymap = keymap.of([
65
{
66
key: "Tab",
67
run: StateCommands.indentMoreOrInsertTab
68
},
69
{
70
key: "Enter",
71
run: StateCommands.completerOrInsertNewLine
72
},
73
{
74
key: "Ctrl-Enter",
75
run: StateCommands.insertBlankLineOnRun
76
},
77
{
78
key: "Escape",
79
run: StateCommands.simplifySelectionAndMaybeSwitchToCommandMode
80
},
81
{
82
key: "Shift-Tab",
83
run: StateCommands.dedentIfNotLaunchingTooltip
84
}
85
]);
86
87
// Use in editor configuration
88
const editor = new EditorView({
89
extensions: [
90
jupyterLabKeymap,
91
// ... other extensions
92
]
93
});
94
```
95
96
### Indent and Tab Commands
97
98
Smart indentation and tab handling with context awareness.
99
100
```typescript { .api }
101
/**
102
* Indent or insert a tab as appropriate
103
* Handles completer state and selection context
104
*/
105
function indentMoreOrInsertTab(target: CommandTarget): boolean;
106
```
107
108
**Behavior:**
109
- When completer is enabled but not at line beginning: returns false (allows completer)
110
- When text is selected: performs indentation
111
- When cursor is at beginning of line or in whitespace: performs indentation
112
- Otherwise: inserts tab character
113
114
**Usage Example:**
115
116
```typescript
117
// Manual command execution
118
const result = StateCommands.indentMoreOrInsertTab({
119
dom: editor.dom,
120
state: editor.state,
121
dispatch: editor.dispatch
122
});
123
124
if (result) {
125
console.log("Command handled indentation/tab");
126
} else {
127
console.log("Command deferred to other handlers");
128
}
129
```
130
131
### Newline and Enter Commands
132
133
Context-aware newline insertion with completer and cell runner integration.
134
135
```typescript { .api }
136
/**
137
* Insert new line if completer is not active
138
* Integrates with JupyterLab completer and cell execution
139
*/
140
function completerOrInsertNewLine(target: CommandTarget): boolean;
141
142
/**
143
* Insert a new line or run a cell with Ctrl/Command + Enter
144
*/
145
function insertBlankLineOnRun(target: CommandTarget): boolean;
146
```
147
148
**Behavior:**
149
- `completerOrInsertNewLine`: Defers to completer when active, otherwise inserts newline
150
- `insertBlankLineOnRun`: Defers to cell runner when in code runner context, otherwise inserts blank line
151
152
**Usage Examples:**
153
154
```typescript
155
// Configure Enter key behavior
156
const enterKeyBinding = {
157
key: "Enter",
158
run: StateCommands.completerOrInsertNewLine
159
};
160
161
// Configure Ctrl+Enter for cell execution
162
const ctrlEnterBinding = {
163
key: "Ctrl-Enter",
164
run: StateCommands.insertBlankLineOnRun
165
};
166
167
// Use in keymap
168
const keymap = keymap.of([enterKeyBinding, ctrlEnterBinding]);
169
```
170
171
### Selection and Navigation Commands
172
173
Commands for managing selections and editor navigation in JupyterLab context.
174
175
```typescript { .api }
176
/**
177
* Simplify selection but do not prevent default to allow switching to command mode
178
* Enables JupyterLab notebook cell mode switching
179
*/
180
function simplifySelectionAndMaybeSwitchToCommandMode(target: CommandTarget): boolean;
181
182
/**
183
* Prevent dedenting when launching inspection request (tooltip)
184
* Avoids interference with Shift+Tab tooltip functionality
185
*/
186
function dedentIfNotLaunchingTooltip(target: CommandTarget): boolean;
187
```
188
189
**Behavior:**
190
- `simplifySelectionAndMaybeSwitchToCommandMode`: Simplifies selection, allows notebook command mode switching
191
- `dedentIfNotLaunchingTooltip`: Only dedents if not in tooltip-capable context
192
193
### Context Detection
194
195
The commands use CSS selectors to detect JupyterLab context and adjust behavior accordingly.
196
197
```typescript
198
// Selectors used by commands for context detection
199
const CODE_RUNNER_SELECTOR = '[data-jp-code-runner]';
200
const TERMINAL_CODE_RUNNER_SELECTOR = '[data-jp-interaction-mode="terminal"]';
201
const TOOLTIP_OPENER_SELECTOR = '.jp-CodeMirrorEditor:not(.jp-mod-has-primary-selection):not(.jp-mod-in-leading-whitespace):not(.jp-mod-completer-active)';
202
const ACTIVE_CELL_IN_EDIT_MODE_SELECTOR = '.jp-mod-editMode .jp-Cell.jp-mod-active';
203
204
// Commands check these contexts to determine appropriate behavior
205
function contextAwareCommand(target: CommandTarget): boolean {
206
const element = target.dom;
207
208
// Check if in code runner context
209
if (element.closest(CODE_RUNNER_SELECTOR)) {
210
// Defer to code runner
211
return false;
212
}
213
214
// Check if in terminal mode
215
if (element.closest(TERMINAL_CODE_RUNNER_SELECTOR)) {
216
// Handle terminal-specific behavior
217
return handleTerminalMode(target);
218
}
219
220
// Default editor behavior
221
return handleDefaultMode(target);
222
}
223
```
224
225
### Custom Command Creation
226
227
Creating custom commands that integrate with JupyterLab's command system.
228
229
```typescript
230
import { StateCommands } from "@jupyterlab/codemirror";
231
import { EditorState, Transaction } from "@codemirror/state";
232
233
// Create custom command with JupyterLab awareness
234
function customJupyterLabCommand(target: CommandTarget): boolean {
235
const { dom, state, dispatch } = target;
236
237
// Check JupyterLab context
238
const isInNotebook = dom.closest('.jp-Notebook');
239
const isInConsole = dom.closest('.jp-CodeConsole');
240
const isReadOnly = state.readOnly;
241
242
if (isReadOnly) {
243
return false; // Don't handle read-only editors
244
}
245
246
if (isInNotebook) {
247
// Notebook-specific behavior
248
return handleNotebookCommand(target);
249
} else if (isInConsole) {
250
// Console-specific behavior
251
return handleConsoleCommand(target);
252
} else {
253
// File editor behavior
254
return handleFileEditorCommand(target);
255
}
256
}
257
258
function handleNotebookCommand(target: CommandTarget): boolean {
259
// Custom notebook cell command logic
260
const { state, dispatch } = target;
261
262
// Example: Auto-complete imports in Python cells
263
const selection = state.selection.main;
264
const line = state.doc.lineAt(selection.head);
265
const lineText = line.text;
266
267
if (lineText.startsWith('import ') && selection.head === line.to) {
268
// Auto-complete common imports
269
const completion = '\nfrom typing import List, Dict, Optional';
270
dispatch(state.update({
271
changes: { from: selection.head, insert: completion },
272
selection: { anchor: selection.head + completion.length }
273
}));
274
return true;
275
}
276
277
return false;
278
}
279
280
// Register custom command in keymap
281
const customKeymap = keymap.of([
282
{
283
key: "Ctrl-Alt-i",
284
run: customJupyterLabCommand
285
}
286
]);
287
```
288
289
### Advanced Command Patterns
290
291
Complex command scenarios and command composition.
292
293
```typescript
294
// Compose multiple commands
295
function compositeCommand(...commands: Array<(target: CommandTarget) => boolean>) {
296
return (target: CommandTarget): boolean => {
297
for (const command of commands) {
298
if (command(target)) {
299
return true; // First successful command wins
300
}
301
}
302
return false; // No command handled the input
303
};
304
}
305
306
// Create fallback command chain
307
const smartIndentCommand = compositeCommand(
308
StateCommands.indentMoreOrInsertTab,
309
(target) => {
310
// Fallback: always insert 2 spaces
311
target.dispatch(target.state.update({
312
changes: { from: target.state.selection.main.head, insert: " " },
313
selection: { anchor: target.state.selection.main.head + 2 }
314
}));
315
return true;
316
}
317
);
318
319
// Conditional command execution
320
function conditionalCommand(
321
condition: (target: CommandTarget) => boolean,
322
trueCommand: (target: CommandTarget) => boolean,
323
falseCommand?: (target: CommandTarget) => boolean
324
) {
325
return (target: CommandTarget): boolean => {
326
if (condition(target)) {
327
return trueCommand(target);
328
} else if (falseCommand) {
329
return falseCommand(target);
330
}
331
return false;
332
};
333
}
334
335
// Use conditional command
336
const contextSensitiveEnter = conditionalCommand(
337
(target) => target.dom.closest('.jp-CodeConsole') !== null,
338
StateCommands.insertBlankLineOnRun, // Console: run command
339
StateCommands.completerOrInsertNewLine // Notebook: handle completer
340
);
341
```
342
343
### Integration with Editor
344
345
Using commands within the editor configuration and extension system.
346
347
```typescript
348
import { StateCommands } from "@jupyterlab/codemirror";
349
import { CodeMirrorEditor } from "@jupyterlab/codemirror";
350
import { keymap } from "@codemirror/view";
351
352
// Create editor with JupyterLab commands
353
const editor = new CodeMirrorEditor({
354
model,
355
host,
356
extensions: [
357
keymap.of([
358
{ key: "Tab", run: StateCommands.indentMoreOrInsertTab },
359
{ key: "Enter", run: StateCommands.completerOrInsertNewLine },
360
{ key: "Ctrl-Enter", run: StateCommands.insertBlankLineOnRun },
361
{ key: "Escape", run: StateCommands.simplifySelectionAndMaybeSwitchToCommandMode },
362
{ key: "Shift-Tab", run: StateCommands.dedentIfNotLaunchingTooltip }
363
])
364
]
365
});
366
367
// Execute commands programmatically
368
editor.execCommand(StateCommands.indentMoreOrInsertTab);
369
370
// Create command-based extension
371
function jupyterLabCommandsExtension() {
372
return keymap.of([
373
{ key: "Tab", run: StateCommands.indentMoreOrInsertTab },
374
{ key: "Enter", run: StateCommands.completerOrInsertNewLine },
375
{ key: "Ctrl-Enter", run: StateCommands.insertBlankLineOnRun },
376
{ key: "Escape", run: StateCommands.simplifySelectionAndMaybeSwitchToCommandMode },
377
{ key: "Shift-Tab", run: StateCommands.dedentIfNotLaunchingTooltip }
378
]);
379
}
380
```
381
382
## Types
383
384
```typescript { .api }
385
interface CommandTarget {
386
dom: HTMLElement;
387
state: EditorState;
388
dispatch: (transaction: Transaction) => void;
389
}
390
391
type StateCommand = (target: CommandTarget) => boolean;
392
393
namespace StateCommands {
394
function indentMoreOrInsertTab(target: CommandTarget): boolean;
395
function completerOrInsertNewLine(target: CommandTarget): boolean;
396
function preventNewLineOnRun(target: { dom: HTMLElement }): boolean;
397
function insertBlankLineOnRun(target: CommandTarget): boolean;
398
function simplifySelectionAndMaybeSwitchToCommandMode(target: CommandTarget): boolean;
399
function dedentIfNotLaunchingTooltip(target: CommandTarget): boolean;
400
}
401
```