Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts
3296 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { Iterable } from '../../../../../base/common/iterator.js';
7
import { KeyCode, KeyMod } from '../../../../../base/common/keyCodes.js';
8
import { isEqual } from '../../../../../base/common/resources.js';
9
import { ThemeIcon } from '../../../../../base/common/themables.js';
10
import { URI, UriComponents } from '../../../../../base/common/uri.js';
11
import { ICodeEditor } from '../../../../../editor/browser/editorBrowser.js';
12
import { ILanguageService } from '../../../../../editor/common/languages/language.js';
13
import { localize, localize2 } from '../../../../../nls.js';
14
import { MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
15
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
16
import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contextkey.js';
17
import { ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
18
import { IDebugService } from '../../../debug/common/debug.js';
19
import { CTX_INLINE_CHAT_FOCUSED } from '../../../inlineChat/common/inlineChat.js';
20
import { insertCell } from './cellOperations.js';
21
import { NotebookChatController } from './chat/notebookChatController.js';
22
import { CELL_TITLE_CELL_GROUP_ID, CellToolbarOrder, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, NotebookAction, NotebookCellAction, NotebookMultiCellAction, cellExecutionArgs, getContextFromActiveEditor, getContextFromUri, parseMultiCellExecutionArgs } from './coreActions.js';
23
import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, IActiveNotebookEditor, ICellViewModel, IFocusNotebookCellOptions, ScrollToRevealBehavior } from '../notebookBrowser.js';
24
import * as icons from '../notebookIcons.js';
25
import { CellKind, CellUri, NotebookSetting } from '../../common/notebookCommon.js';
26
import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from '../../common/notebookContextKeys.js';
27
import { NotebookEditorInput } from '../../common/notebookEditorInput.js';
28
import { INotebookExecutionStateService } from '../../common/notebookExecutionStateService.js';
29
import { IEditorGroupsService } from '../../../../services/editor/common/editorGroupsService.js';
30
import { IEditorService } from '../../../../services/editor/common/editorService.js';
31
import { CodeCellViewModel } from '../viewModel/codeCellViewModel.js';
32
33
const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute';
34
const CANCEL_NOTEBOOK_COMMAND_ID = 'notebook.cancelExecution';
35
const INTERRUPT_NOTEBOOK_COMMAND_ID = 'notebook.interruptExecution';
36
const CANCEL_CELL_COMMAND_ID = 'notebook.cell.cancelExecution';
37
const EXECUTE_CELL_FOCUS_CONTAINER_COMMAND_ID = 'notebook.cell.executeAndFocusContainer';
38
const EXECUTE_CELL_SELECT_BELOW = 'notebook.cell.executeAndSelectBelow';
39
const EXECUTE_CELL_INSERT_BELOW = 'notebook.cell.executeAndInsertBelow';
40
const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow';
41
const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove';
42
const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells';
43
const REVEAL_RUNNING_CELL = 'notebook.revealRunningCell';
44
const REVEAL_LAST_FAILED_CELL = 'notebook.revealLastFailedCell';
45
46
// If this changes, update getCodeCellExecutionContextKeyService to match
47
export const executeCondition = ContextKeyExpr.and(
48
NOTEBOOK_CELL_TYPE.isEqualTo('code'),
49
ContextKeyExpr.or(
50
ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0),
51
ContextKeyExpr.greater(NOTEBOOK_KERNEL_SOURCE_COUNT.key, 0),
52
NOTEBOOK_MISSING_KERNEL_EXTENSION
53
));
54
55
export const executeThisCellCondition = ContextKeyExpr.and(
56
executeCondition,
57
NOTEBOOK_CELL_EXECUTING.toNegated());
58
59
export const executeSectionCondition = ContextKeyExpr.and(
60
NOTEBOOK_CELL_TYPE.isEqualTo('markup'),
61
);
62
63
function renderAllMarkdownCells(context: INotebookActionContext): void {
64
for (let i = 0; i < context.notebookEditor.getLength(); i++) {
65
const cell = context.notebookEditor.cellAt(i);
66
67
if (cell.cellKind === CellKind.Markup) {
68
cell.updateEditState(CellEditState.Preview, 'renderAllMarkdownCells');
69
}
70
}
71
}
72
73
async function runCell(editorGroupsService: IEditorGroupsService, context: INotebookActionContext, editorService?: IEditorService): Promise<void> {
74
const group = editorGroupsService.activeGroup;
75
76
if (group) {
77
if (group.activeEditor) {
78
group.pinEditor(group.activeEditor);
79
}
80
}
81
82
// If auto-reveal is enabled, ensure the notebook editor is visible before revealing cells
83
if (context.autoReveal && (context.cell || context.selectedCells?.length) && editorService) {
84
editorService.openEditor({ resource: context.notebookEditor.textModel.uri, options: { revealIfOpened: true } });
85
}
86
87
if (context.ui && context.cell) {
88
if (context.autoReveal) {
89
handleAutoReveal(context.cell, context.notebookEditor);
90
}
91
await context.notebookEditor.executeNotebookCells(Iterable.single(context.cell));
92
} else if (context.selectedCells?.length || context.cell) {
93
const selectedCells = context.selectedCells?.length ? context.selectedCells : [context.cell!];
94
const firstCell = selectedCells[0];
95
96
if (firstCell && context.autoReveal) {
97
handleAutoReveal(firstCell, context.notebookEditor);
98
}
99
await context.notebookEditor.executeNotebookCells(selectedCells);
100
}
101
102
let foundEditor: ICodeEditor | undefined = undefined;
103
for (const [, codeEditor] of context.notebookEditor.codeEditors) {
104
if (isEqual(codeEditor.getModel()?.uri, (context.cell ?? context.selectedCells?.[0])?.uri)) {
105
foundEditor = codeEditor;
106
break;
107
}
108
}
109
110
if (!foundEditor) {
111
return;
112
}
113
}
114
115
const SMART_VIEWPORT_TOP_REVEAL_PADDING = 20; // enough to not cut off top of cell toolbar
116
const SMART_VIEWPORT_BOTTOM_REVEAL_PADDING = 60; // enough to show full bottom of output element + tiny buffer below that vertical bar
117
function handleAutoReveal(cell: ICellViewModel, notebookEditor: IActiveNotebookEditor): void {
118
// always focus the container, blue bar is a good visual aid in tracking what's happening
119
notebookEditor.focusNotebookCell(cell, 'container', { skipReveal: true });
120
121
// Handle markup cells with simple reveal
122
if (cell.cellKind === CellKind.Markup) {
123
const cellIndex = notebookEditor.getCellIndex(cell);
124
notebookEditor.revealCellRangeInView({ start: cellIndex, end: cellIndex + 1 });
125
return;
126
}
127
128
// Ensure we're working with a code cell - we need the CodeCellViewModel type for accessing layout properties like outputTotalHeight
129
if (!(cell instanceof CodeCellViewModel)) {
130
return;
131
}
132
133
// Get all dimensions
134
const cellEditorScrollTop = notebookEditor.getAbsoluteTopOfElement(cell);
135
const cellEditorScrollBottom = cellEditorScrollTop + cell.layoutInfo.outputContainerOffset;
136
137
const cellOutputHeight = cell.layoutInfo.outputTotalHeight;
138
const cellOutputScrollBottom = notebookEditor.getAbsoluteBottomOfElement(cell);
139
140
const viewportHeight = notebookEditor.getLayoutInfo().height;
141
const viewportHeight34 = viewportHeight * 0.34;
142
const viewportHeight66 = viewportHeight * 0.66;
143
144
const totalHeight = cell.layoutInfo.totalHeight;
145
146
const isFullyVisible = cellEditorScrollTop >= notebookEditor.scrollTop && cellOutputScrollBottom <= notebookEditor.scrollBottom;
147
const isEditorBottomVisible = ((cellEditorScrollBottom - 25 /* padding for the cell status bar */) >= notebookEditor.scrollTop) &&
148
((cellEditorScrollBottom + 25 /* padding to see a sliver of the beginning of outputs */) <= notebookEditor.scrollBottom);
149
150
// Common scrolling functions
151
const revealWithTopPadding = (position: number) => { notebookEditor.setScrollTop(position - SMART_VIEWPORT_TOP_REVEAL_PADDING); };
152
const revealWithNoPadding = (position: number) => { notebookEditor.setScrollTop(position); };
153
const revealWithBottomPadding = (position: number) => { notebookEditor.setScrollTop(position + SMART_VIEWPORT_BOTTOM_REVEAL_PADDING); };
154
155
// CASE 0: Total is already visible
156
if (isFullyVisible) {
157
return;
158
}
159
160
// CASE 1: Total fits within viewport
161
if (totalHeight <= viewportHeight && !isEditorBottomVisible) {
162
revealWithTopPadding(cellEditorScrollTop);
163
return;
164
}
165
166
// CASE 2: Total doesn't fit in the viewport
167
if (totalHeight > viewportHeight && !isEditorBottomVisible) {
168
if (cellOutputHeight > 0 && cellOutputHeight >= viewportHeight66) {
169
// has large outputs -- Show 34% editor, 66% output
170
revealWithNoPadding(cellEditorScrollBottom - viewportHeight34);
171
} else if (cellOutputHeight > 0) {
172
// has small outputs -- Show output at viewport bottom
173
revealWithBottomPadding(cellOutputScrollBottom - viewportHeight);
174
} else {
175
// No outputs, just big cell -- put editor bottom @ 2/3 of viewport height
176
revealWithNoPadding(cellEditorScrollBottom - viewportHeight66);
177
}
178
}
179
}
180
181
registerAction2(class RenderAllMarkdownCellsAction extends NotebookAction {
182
constructor() {
183
super({
184
id: RENDER_ALL_MARKDOWN_CELLS,
185
title: localize('notebookActions.renderMarkdown', "Render All Markdown Cells"),
186
});
187
}
188
189
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
190
renderAllMarkdownCells(context);
191
}
192
});
193
194
registerAction2(class ExecuteNotebookAction extends NotebookAction {
195
constructor() {
196
super({
197
id: EXECUTE_NOTEBOOK_COMMAND_ID,
198
title: localize('notebookActions.executeNotebook', "Run All"),
199
icon: icons.executeAllIcon,
200
metadata: {
201
description: localize('notebookActions.executeNotebook', "Run All"),
202
args: [
203
{
204
name: 'uri',
205
description: 'The document uri'
206
}
207
]
208
},
209
menu: [
210
{
211
id: MenuId.EditorTitle,
212
order: -1,
213
group: 'navigation',
214
when: ContextKeyExpr.and(
215
NOTEBOOK_IS_ACTIVE_EDITOR,
216
ContextKeyExpr.or(NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(), NOTEBOOK_HAS_SOMETHING_RUNNING.toNegated()),
217
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
218
)
219
},
220
{
221
id: MenuId.NotebookToolbar,
222
order: -1,
223
group: 'navigation/execute',
224
when: ContextKeyExpr.and(
225
ContextKeyExpr.or(
226
NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(),
227
NOTEBOOK_HAS_SOMETHING_RUNNING.toNegated(),
228
),
229
ContextKeyExpr.and(NOTEBOOK_HAS_SOMETHING_RUNNING, NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated())?.negate(),
230
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
231
)
232
}
233
]
234
});
235
}
236
237
override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
238
return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService));
239
}
240
241
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
242
renderAllMarkdownCells(context);
243
244
const editorService = accessor.get(IEditorService);
245
const editor = editorService.findEditors({
246
resource: context.notebookEditor.textModel.uri,
247
typeId: NotebookEditorInput.ID,
248
editorId: context.notebookEditor.textModel.viewType
249
}).at(0);
250
const editorGroupService = accessor.get(IEditorGroupsService);
251
252
if (editor) {
253
const group = editorGroupService.getGroup(editor.groupId);
254
group?.pinEditor(editor.editor);
255
}
256
257
return context.notebookEditor.executeNotebookCells();
258
}
259
});
260
261
registerAction2(class ExecuteCell extends NotebookMultiCellAction {
262
constructor() {
263
super({
264
id: EXECUTE_CELL_COMMAND_ID,
265
precondition: executeThisCellCondition,
266
title: localize('notebookActions.execute', "Execute Cell"),
267
keybinding: {
268
when: NOTEBOOK_CELL_LIST_FOCUSED,
269
primary: KeyMod.WinCtrl | KeyCode.Enter,
270
win: {
271
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter
272
},
273
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
274
},
275
menu: {
276
id: MenuId.NotebookCellExecutePrimary,
277
when: executeThisCellCondition,
278
group: 'inline'
279
},
280
metadata: {
281
description: localize('notebookActions.execute', "Execute Cell"),
282
args: cellExecutionArgs
283
},
284
icon: icons.executeIcon
285
});
286
}
287
288
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
289
return parseMultiCellExecutionArgs(accessor, ...args);
290
}
291
292
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise<void> {
293
const editorGroupsService = accessor.get(IEditorGroupsService);
294
const editorService = accessor.get(IEditorService);
295
296
if (context.ui) {
297
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
298
}
299
300
const chatController = NotebookChatController.get(context.notebookEditor);
301
const editingCell = chatController?.getEditingCell();
302
if (chatController?.hasFocus() && editingCell) {
303
const group = editorGroupsService.activeGroup;
304
305
if (group) {
306
if (group.activeEditor) {
307
group.pinEditor(group.activeEditor);
308
}
309
}
310
311
await context.notebookEditor.executeNotebookCells([editingCell]);
312
return;
313
}
314
315
await runCell(editorGroupsService, context, editorService);
316
}
317
});
318
319
registerAction2(class ExecuteAboveCells extends NotebookMultiCellAction {
320
constructor() {
321
super({
322
id: EXECUTE_CELLS_ABOVE,
323
precondition: executeCondition,
324
title: localize('notebookActions.executeAbove', "Execute Above Cells"),
325
menu: [
326
{
327
id: MenuId.NotebookCellExecute,
328
when: ContextKeyExpr.and(
329
executeCondition,
330
ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, true))
331
},
332
{
333
id: MenuId.NotebookCellTitle,
334
order: CellToolbarOrder.ExecuteAboveCells,
335
group: CELL_TITLE_CELL_GROUP_ID,
336
when: ContextKeyExpr.and(
337
executeCondition,
338
ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, false))
339
}
340
],
341
icon: icons.executeAboveIcon
342
});
343
}
344
345
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
346
return parseMultiCellExecutionArgs(accessor, ...args);
347
}
348
349
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise<void> {
350
let endCellIdx: number | undefined = undefined;
351
if (context.ui) {
352
endCellIdx = context.notebookEditor.getCellIndex(context.cell);
353
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
354
} else {
355
endCellIdx = Math.min(...context.selectedCells.map(cell => context.notebookEditor.getCellIndex(cell)));
356
}
357
358
if (typeof endCellIdx === 'number') {
359
const range = { start: 0, end: endCellIdx };
360
const cells = context.notebookEditor.getCellsInRange(range);
361
context.notebookEditor.executeNotebookCells(cells);
362
}
363
}
364
});
365
366
registerAction2(class ExecuteCellAndBelow extends NotebookMultiCellAction {
367
constructor() {
368
super({
369
id: EXECUTE_CELL_AND_BELOW,
370
precondition: executeCondition,
371
title: localize('notebookActions.executeBelow', "Execute Cell and Below"),
372
menu: [
373
{
374
id: MenuId.NotebookCellExecute,
375
when: ContextKeyExpr.and(
376
executeCondition,
377
ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, true))
378
},
379
{
380
id: MenuId.NotebookCellTitle,
381
order: CellToolbarOrder.ExecuteCellAndBelow,
382
group: CELL_TITLE_CELL_GROUP_ID,
383
when: ContextKeyExpr.and(
384
executeCondition,
385
ContextKeyExpr.equals(`config.${NotebookSetting.consolidatedRunButton}`, false))
386
}
387
],
388
icon: icons.executeBelowIcon
389
});
390
}
391
392
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
393
return parseMultiCellExecutionArgs(accessor, ...args);
394
}
395
396
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise<void> {
397
let startCellIdx: number | undefined = undefined;
398
if (context.ui) {
399
startCellIdx = context.notebookEditor.getCellIndex(context.cell);
400
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
401
} else {
402
startCellIdx = Math.min(...context.selectedCells.map(cell => context.notebookEditor.getCellIndex(cell)));
403
}
404
405
if (typeof startCellIdx === 'number') {
406
const range = { start: startCellIdx, end: context.notebookEditor.getLength() };
407
const cells = context.notebookEditor.getCellsInRange(range);
408
context.notebookEditor.executeNotebookCells(cells);
409
}
410
}
411
});
412
413
registerAction2(class ExecuteCellFocusContainer extends NotebookMultiCellAction {
414
constructor() {
415
super({
416
id: EXECUTE_CELL_FOCUS_CONTAINER_COMMAND_ID,
417
precondition: executeThisCellCondition,
418
title: localize('notebookActions.executeAndFocusContainer', "Execute Cell and Focus Container"),
419
metadata: {
420
description: localize('notebookActions.executeAndFocusContainer', "Execute Cell and Focus Container"),
421
args: cellExecutionArgs
422
},
423
icon: icons.executeIcon
424
});
425
}
426
427
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
428
return parseMultiCellExecutionArgs(accessor, ...args);
429
}
430
431
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise<void> {
432
const editorGroupsService = accessor.get(IEditorGroupsService);
433
const editorService = accessor.get(IEditorService);
434
435
if (context.ui) {
436
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
437
} else {
438
const firstCell = context.selectedCells[0];
439
440
if (firstCell) {
441
await context.notebookEditor.focusNotebookCell(firstCell, 'container', { skipReveal: true });
442
}
443
}
444
445
await runCell(editorGroupsService, context, editorService);
446
}
447
});
448
449
const cellCancelCondition = ContextKeyExpr.or(
450
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'executing'),
451
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'pending'),
452
);
453
454
registerAction2(class CancelExecuteCell extends NotebookMultiCellAction {
455
constructor() {
456
super({
457
id: CANCEL_CELL_COMMAND_ID,
458
precondition: cellCancelCondition,
459
title: localize('notebookActions.cancel', "Stop Cell Execution"),
460
icon: icons.stopIcon,
461
menu: {
462
id: MenuId.NotebookCellExecutePrimary,
463
when: cellCancelCondition,
464
group: 'inline'
465
},
466
metadata: {
467
description: localize('notebookActions.cancel', "Stop Cell Execution"),
468
args: [
469
{
470
name: 'options',
471
description: 'The cell range options',
472
schema: {
473
'type': 'object',
474
'required': ['ranges'],
475
'properties': {
476
'ranges': {
477
'type': 'array',
478
items: [
479
{
480
'type': 'object',
481
'required': ['start', 'end'],
482
'properties': {
483
'start': {
484
'type': 'number'
485
},
486
'end': {
487
'type': 'number'
488
}
489
}
490
}
491
]
492
},
493
'document': {
494
'type': 'object',
495
'description': 'The document uri',
496
}
497
}
498
}
499
}
500
]
501
},
502
});
503
}
504
505
override parseArgs(accessor: ServicesAccessor, ...args: any[]): INotebookCommandContext | undefined {
506
return parseMultiCellExecutionArgs(accessor, ...args);
507
}
508
509
async runWithContext(accessor: ServicesAccessor, context: INotebookCommandContext | INotebookCellToolbarActionContext): Promise<void> {
510
if (context.ui) {
511
await context.notebookEditor.focusNotebookCell(context.cell, 'container', { skipReveal: true });
512
return context.notebookEditor.cancelNotebookCells(Iterable.single(context.cell));
513
} else {
514
return context.notebookEditor.cancelNotebookCells(context.selectedCells);
515
}
516
}
517
});
518
519
registerAction2(class ExecuteCellSelectBelow extends NotebookCellAction {
520
constructor() {
521
super({
522
id: EXECUTE_CELL_SELECT_BELOW,
523
precondition: ContextKeyExpr.or(executeThisCellCondition, NOTEBOOK_CELL_TYPE.isEqualTo('markup')),
524
title: localize('notebookActions.executeAndSelectBelow', "Execute Notebook Cell and Select Below"),
525
keybinding: {
526
when: ContextKeyExpr.and(
527
NOTEBOOK_CELL_LIST_FOCUSED,
528
CTX_INLINE_CHAT_FOCUSED.negate()
529
),
530
primary: KeyMod.Shift | KeyCode.Enter,
531
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
532
},
533
});
534
}
535
536
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
537
const editorGroupsService = accessor.get(IEditorGroupsService);
538
const editorService = accessor.get(IEditorService);
539
const idx = context.notebookEditor.getCellIndex(context.cell);
540
if (typeof idx !== 'number') {
541
return;
542
}
543
const languageService = accessor.get(ILanguageService);
544
545
const config = accessor.get(IConfigurationService);
546
const scrollBehavior = config.getValue(NotebookSetting.scrollToRevealCell);
547
let focusOptions: IFocusNotebookCellOptions;
548
if (scrollBehavior === 'none') {
549
focusOptions = { skipReveal: true };
550
} else {
551
focusOptions = {
552
revealBehavior: scrollBehavior === 'fullCell' ? ScrollToRevealBehavior.fullCell : ScrollToRevealBehavior.firstLine
553
};
554
}
555
556
if (context.cell.cellKind === CellKind.Markup) {
557
const nextCell = context.notebookEditor.cellAt(idx + 1);
558
context.cell.updateEditState(CellEditState.Preview, EXECUTE_CELL_SELECT_BELOW);
559
if (nextCell) {
560
await context.notebookEditor.focusNotebookCell(nextCell, 'container', focusOptions);
561
} else {
562
const newCell = insertCell(languageService, context.notebookEditor, idx, CellKind.Markup, 'below');
563
564
if (newCell) {
565
await context.notebookEditor.focusNotebookCell(newCell, 'editor', focusOptions);
566
}
567
}
568
return;
569
} else {
570
const nextCell = context.notebookEditor.cellAt(idx + 1);
571
if (nextCell) {
572
await context.notebookEditor.focusNotebookCell(nextCell, 'container', focusOptions);
573
} else {
574
const newCell = insertCell(languageService, context.notebookEditor, idx, CellKind.Code, 'below');
575
576
if (newCell) {
577
await context.notebookEditor.focusNotebookCell(newCell, 'editor', focusOptions);
578
}
579
}
580
581
return runCell(editorGroupsService, context, editorService);
582
}
583
}
584
});
585
586
registerAction2(class ExecuteCellInsertBelow extends NotebookCellAction {
587
constructor() {
588
super({
589
id: EXECUTE_CELL_INSERT_BELOW,
590
precondition: ContextKeyExpr.or(executeThisCellCondition, NOTEBOOK_CELL_TYPE.isEqualTo('markup')),
591
title: localize('notebookActions.executeAndInsertBelow', "Execute Notebook Cell and Insert Below"),
592
keybinding: {
593
when: NOTEBOOK_CELL_LIST_FOCUSED,
594
primary: KeyMod.Alt | KeyCode.Enter,
595
weight: NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT
596
},
597
});
598
}
599
600
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
601
const editorGroupsService = accessor.get(IEditorGroupsService);
602
const editorService = accessor.get(IEditorService);
603
const idx = context.notebookEditor.getCellIndex(context.cell);
604
const languageService = accessor.get(ILanguageService);
605
const newFocusMode = context.cell.focusMode === CellFocusMode.Editor ? 'editor' : 'container';
606
607
const newCell = insertCell(languageService, context.notebookEditor, idx, context.cell.cellKind, 'below');
608
if (newCell) {
609
await context.notebookEditor.focusNotebookCell(newCell, newFocusMode);
610
}
611
612
if (context.cell.cellKind === CellKind.Markup) {
613
context.cell.updateEditState(CellEditState.Preview, EXECUTE_CELL_INSERT_BELOW);
614
} else {
615
runCell(editorGroupsService, context, editorService);
616
}
617
}
618
});
619
620
class CancelNotebook extends NotebookAction {
621
override getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
622
return getContextFromUri(accessor, context) ?? getContextFromActiveEditor(accessor.get(IEditorService));
623
}
624
625
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
626
return context.notebookEditor.cancelNotebookCells();
627
}
628
}
629
630
registerAction2(class CancelAllNotebook extends CancelNotebook {
631
constructor() {
632
super({
633
id: CANCEL_NOTEBOOK_COMMAND_ID,
634
title: localize2('notebookActions.cancelNotebook', "Stop Execution"),
635
icon: icons.stopIcon,
636
menu: [
637
{
638
id: MenuId.EditorTitle,
639
order: -1,
640
group: 'navigation',
641
when: ContextKeyExpr.and(
642
NOTEBOOK_IS_ACTIVE_EDITOR,
643
NOTEBOOK_HAS_SOMETHING_RUNNING,
644
NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(),
645
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
646
)
647
},
648
{
649
id: MenuId.NotebookToolbar,
650
order: -1,
651
group: 'navigation/execute',
652
when: ContextKeyExpr.and(
653
NOTEBOOK_HAS_SOMETHING_RUNNING,
654
NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(),
655
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
656
)
657
}
658
]
659
});
660
}
661
});
662
663
registerAction2(class InterruptNotebook extends CancelNotebook {
664
constructor() {
665
super({
666
id: INTERRUPT_NOTEBOOK_COMMAND_ID,
667
title: localize2('notebookActions.interruptNotebook', "Interrupt"),
668
precondition: ContextKeyExpr.and(
669
NOTEBOOK_HAS_SOMETHING_RUNNING,
670
NOTEBOOK_INTERRUPTIBLE_KERNEL
671
),
672
icon: icons.stopIcon,
673
menu: [
674
{
675
id: MenuId.EditorTitle,
676
order: -1,
677
group: 'navigation',
678
when: ContextKeyExpr.and(
679
NOTEBOOK_IS_ACTIVE_EDITOR,
680
NOTEBOOK_HAS_SOMETHING_RUNNING,
681
NOTEBOOK_INTERRUPTIBLE_KERNEL,
682
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
683
)
684
},
685
{
686
id: MenuId.NotebookToolbar,
687
order: -1,
688
group: 'navigation/execute',
689
when: ContextKeyExpr.and(
690
NOTEBOOK_HAS_SOMETHING_RUNNING,
691
NOTEBOOK_INTERRUPTIBLE_KERNEL,
692
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
693
)
694
},
695
{
696
id: MenuId.InteractiveToolbar,
697
group: 'navigation/execute'
698
}
699
]
700
});
701
}
702
});
703
704
705
MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
706
title: localize('revealRunningCellShort', "Go To"),
707
submenu: MenuId.NotebookCellExecuteGoTo,
708
group: 'navigation/execute',
709
order: 20,
710
icon: ThemeIcon.modify(icons.executingStateIcon, 'spin')
711
});
712
713
registerAction2(class RevealRunningCellAction extends NotebookAction {
714
constructor() {
715
super({
716
id: REVEAL_RUNNING_CELL,
717
title: localize('revealRunningCell', "Go to Running Cell"),
718
tooltip: localize('revealRunningCell', "Go to Running Cell"),
719
shortTitle: localize('revealRunningCell', "Go to Running Cell"),
720
precondition: NOTEBOOK_HAS_RUNNING_CELL,
721
menu: [
722
{
723
id: MenuId.EditorTitle,
724
when: ContextKeyExpr.and(
725
NOTEBOOK_IS_ACTIVE_EDITOR,
726
NOTEBOOK_HAS_RUNNING_CELL,
727
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
728
),
729
group: 'navigation',
730
order: 0
731
},
732
{
733
id: MenuId.NotebookCellExecuteGoTo,
734
when: ContextKeyExpr.and(
735
NOTEBOOK_IS_ACTIVE_EDITOR,
736
NOTEBOOK_HAS_RUNNING_CELL,
737
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
738
),
739
group: 'navigation/execute',
740
order: 20
741
},
742
{
743
id: MenuId.InteractiveToolbar,
744
when: ContextKeyExpr.and(
745
NOTEBOOK_HAS_RUNNING_CELL,
746
ContextKeyExpr.equals('activeEditor', 'workbench.editor.interactive')
747
),
748
group: 'navigation',
749
order: 10
750
}
751
],
752
icon: ThemeIcon.modify(icons.executingStateIcon, 'spin')
753
});
754
}
755
756
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
757
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
758
const notebook = context.notebookEditor.textModel.uri;
759
const executingCells = notebookExecutionStateService.getCellExecutionsForNotebook(notebook);
760
if (executingCells[0]) {
761
const topStackFrameCell = this.findCellAtTopFrame(accessor, notebook);
762
const focusHandle = topStackFrameCell ?? executingCells[0].cellHandle;
763
const cell = context.notebookEditor.getCellByHandle(focusHandle);
764
if (cell) {
765
context.notebookEditor.focusNotebookCell(cell, 'container');
766
}
767
}
768
}
769
770
private findCellAtTopFrame(accessor: ServicesAccessor, notebook: URI): number | undefined {
771
const debugService = accessor.get(IDebugService);
772
for (const session of debugService.getModel().getSessions()) {
773
for (const thread of session.getAllThreads()) {
774
const sf = thread.getTopStackFrame();
775
if (sf) {
776
const parsed = CellUri.parse(sf.source.uri);
777
if (parsed && parsed.notebook.toString() === notebook.toString()) {
778
return parsed.handle;
779
}
780
}
781
}
782
}
783
784
return undefined;
785
}
786
});
787
788
registerAction2(class RevealLastFailedCellAction extends NotebookAction {
789
constructor() {
790
super({
791
id: REVEAL_LAST_FAILED_CELL,
792
title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
793
tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
794
shortTitle: localize('revealLastFailedCellShort', "Go to Most Recently Failed Cell"),
795
precondition: NOTEBOOK_LAST_CELL_FAILED,
796
menu: [
797
{
798
id: MenuId.EditorTitle,
799
when: ContextKeyExpr.and(
800
NOTEBOOK_IS_ACTIVE_EDITOR,
801
NOTEBOOK_LAST_CELL_FAILED,
802
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
803
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
804
),
805
group: 'navigation',
806
order: 0
807
},
808
{
809
id: MenuId.NotebookCellExecuteGoTo,
810
when: ContextKeyExpr.and(
811
NOTEBOOK_IS_ACTIVE_EDITOR,
812
NOTEBOOK_LAST_CELL_FAILED,
813
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
814
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
815
),
816
group: 'navigation/execute',
817
order: 20
818
},
819
],
820
icon: icons.errorStateIcon,
821
});
822
}
823
824
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
825
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
826
const notebook = context.notebookEditor.textModel.uri;
827
const lastFailedCellHandle = notebookExecutionStateService.getLastFailedCellForNotebook(notebook);
828
if (lastFailedCellHandle !== undefined) {
829
const lastFailedCell = context.notebookEditor.getCellByHandle(lastFailedCellHandle);
830
if (lastFailedCell) {
831
context.notebookEditor.focusNotebookCell(lastFailedCell, 'container');
832
}
833
}
834
}
835
});
836
837