Skip to content

Commit 6779fd9

Browse files
authored
Add "Go to Last Failed Cell" Button (#154443)
* Add go to last failed cell function
1 parent 179883d commit 6779fd9

File tree

7 files changed

+111
-9
lines changed

7 files changed

+111
-9
lines changed

src/vs/workbench/contrib/notebook/browser/controller/executeActions.ts

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
1515
import { EditorsOrder } from 'vs/workbench/common/editor';
1616
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
1717
import { cellExecutionArgs, CellToolbarOrder, CELL_TITLE_CELL_GROUP_ID, executeNotebookCondition, getContextFromActiveEditor, getContextFromUri, INotebookActionContext, INotebookCellActionContext, INotebookCellToolbarActionContext, INotebookCommandContext, NotebookAction, NotebookCellAction, NotebookMultiCellAction, NOTEBOOK_EDITOR_WIDGET_ACTION_WEIGHT, parseMultiCellExecutionArgs } from 'vs/workbench/contrib/notebook/browser/controller/coreActions';
18-
import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
18+
import { NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
1919
import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
2020
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
2121
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -35,6 +35,7 @@ const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow';
3535
const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove';
3636
const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells';
3737
const REVEAL_RUNNING_CELL = 'notebook.revealRunningCell';
38+
const REVEAL_LAST_FAILED_CELL = 'notebook.revealLastFailedCell';
3839

3940
// If this changes, update getCodeCellExecutionContextKeyService to match
4041
export const executeCondition = ContextKeyExpr.and(
@@ -594,3 +595,52 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
594595
}
595596
}
596597
});
598+
599+
registerAction2(class RevealLastFailedCellAction extends NotebookAction {
600+
constructor() {
601+
super({
602+
id: REVEAL_LAST_FAILED_CELL,
603+
title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
604+
tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
605+
shortTitle: localize('revealLastFailedCellShort', "Go To"),
606+
precondition: NOTEBOOK_LAST_CELL_FAILED,
607+
menu: [
608+
{
609+
id: MenuId.EditorTitle,
610+
when: ContextKeyExpr.and(
611+
NOTEBOOK_IS_ACTIVE_EDITOR,
612+
NOTEBOOK_LAST_CELL_FAILED,
613+
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
614+
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
615+
),
616+
group: 'navigation',
617+
order: 0
618+
},
619+
{
620+
id: MenuId.NotebookToolbar,
621+
when: ContextKeyExpr.and(
622+
NOTEBOOK_IS_ACTIVE_EDITOR,
623+
NOTEBOOK_LAST_CELL_FAILED,
624+
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
625+
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
626+
),
627+
group: 'navigation/execute',
628+
order: 0
629+
},
630+
],
631+
icon: icons.errorStateIcon,
632+
});
633+
}
634+
635+
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
636+
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
637+
const notebook = context.notebookEditor.textModel.uri;
638+
const lastFailedCellHandle = notebookExecutionStateService.getLastFailedCellForNotebook(notebook);
639+
if (lastFailedCellHandle !== undefined) {
640+
const lastFailedCell = context.notebookEditor.getCellByHandle(lastFailedCellHandle);
641+
if (lastFailedCell) {
642+
context.notebookEditor.focusNotebookCell(lastFailedCell, 'container');
643+
}
644+
}
645+
}
646+
});

src/vs/workbench/contrib/notebook/browser/media/notebookToolbar.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,3 +80,7 @@
8080
.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar:not(.vertical) .action-item.active {
8181
background-color: unset;
8282
}
83+
84+
.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar .action-item .codicon-notebook-state-error {
85+
color: var(--notebook-cell-status-icon-error);
86+
}

src/vs/workbench/contrib/notebook/browser/notebookExecutionStateServiceImpl.ts

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log';
1313
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
1414
import { CellEditType, CellUri, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1515
import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
16-
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
16+
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
1717
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
1818

1919
export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService {
@@ -22,10 +22,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
2222
private readonly _executions = new ResourceMap<Map<number, CellExecution>>();
2323
private readonly _notebookListeners = new ResourceMap<NotebookExecutionListeners>();
2424
private readonly _cellListeners = new ResourceMap<IDisposable>();
25+
private readonly _lastFailedCells = new ResourceMap<number>();
2526

2627
private readonly _onDidChangeCellExecution = this._register(new Emitter<ICellExecutionStateChangedEvent>());
2728
onDidChangeCellExecution = this._onDidChangeCellExecution.event;
2829

30+
private readonly _onDidChangeLastRunFailState = this._register(new Emitter<INotebookFailStateChangedEvent>());
31+
onDidChangeLastRunFailState = this._onDidChangeLastRunFailState.event;
32+
2933
constructor(
3034
@IInstantiationService private readonly _instantiationService: IInstantiationService,
3135
@ILogService private readonly _logService: ILogService,
@@ -34,6 +38,10 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
3438
super();
3539
}
3640

41+
getLastFailedCellForNotebook(notebook: URI): number | undefined {
42+
return this._lastFailedCells.get(notebook);
43+
}
44+
3745
forceCancelNotebookExecutions(notebookUri: URI): void {
3846
const notebookExecutions = this._executions.get(notebookUri);
3947
if (!notebookExecutions) {
@@ -68,7 +76,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
6876
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe));
6977
}
7078

71-
private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution): void {
79+
private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution, lastRunSuccess?: boolean): void {
7280
const notebookExecutions = this._executions.get(notebookUri);
7381
if (!notebookExecutions) {
7482
this._logService.debug(`NotebookExecutionStateService#_onCellExecutionDidComplete - unknown notebook ${notebookUri.toString()}`);
@@ -86,6 +94,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
8694
this._notebookListeners.delete(notebookUri);
8795
}
8896

97+
if (lastRunSuccess !== undefined) {
98+
if (lastRunSuccess) {
99+
this._clearLastFailedCell(notebookUri);
100+
} else {
101+
this._setLastFailedCell(notebookUri, cellHandle);
102+
}
103+
}
104+
89105
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle));
90106
}
91107

@@ -119,12 +135,22 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
119135
const exe: CellExecution = this._instantiationService.createInstance(CellExecution, cellHandle, notebook);
120136
const disposable = combinedDisposable(
121137
exe.onDidUpdate(() => this._onCellExecutionDidChange(notebookUri, cellHandle, exe)),
122-
exe.onDidComplete(() => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe)));
138+
exe.onDidComplete(lastRunSuccess => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe, lastRunSuccess)));
123139
this._cellListeners.set(CellUri.generate(notebookUri, cellHandle), disposable);
124140

125141
return exe;
126142
}
127143

144+
private _setLastFailedCell(notebook: URI, cellHandle: number) {
145+
this._lastFailedCells.set(notebook, cellHandle);
146+
this._onDidChangeLastRunFailState.fire({ failed: true, notebook });
147+
}
148+
149+
private _clearLastFailedCell(notebook: URI) {
150+
this._lastFailedCells.delete(notebook);
151+
this._onDidChangeLastRunFailState.fire({ failed: false, notebook: notebook });
152+
}
153+
128154
override dispose(): void {
129155
super.dispose();
130156
this._executions.forEach(executionMap => {
@@ -250,7 +276,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
250276
private readonly _onDidUpdate = this._register(new Emitter<void>());
251277
readonly onDidUpdate = this._onDidUpdate.event;
252278

253-
private readonly _onDidComplete = this._register(new Emitter<void>());
279+
private readonly _onDidComplete = this._register(new Emitter<boolean | undefined>());
254280
readonly onDidComplete = this._onDidComplete.event;
255281

256282
private _state: NotebookCellExecutionState = NotebookCellExecutionState.Unconfirmed;
@@ -350,7 +376,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
350376
this._applyExecutionEdits([edit]);
351377
}
352378

353-
this._onDidComplete.fire();
379+
this._onDidComplete.fire(completionData.lastRunSuccess);
354380
}
355381

356382
private _applyExecutionEdits(edits: ICellEditOperation[]): void {

src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
77
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
88
import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
9-
import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
10-
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
9+
import { NOTEBOOK_CELL_TOOLBAR_LOCATION, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL_SOURCE_COUNT, NOTEBOOK_LAST_CELL_FAILED, NOTEBOOK_MISSING_KERNEL_EXTENSION, NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
10+
import { INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
1111
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
1212
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
1313

@@ -24,6 +24,7 @@ export class NotebookEditorContextKeys {
2424
private readonly _viewType!: IContextKey<string>;
2525
private readonly _missingKernelExtension: IContextKey<boolean>;
2626
private readonly _cellToolbarLocation: IContextKey<'left' | 'right' | 'hidden'>;
27+
private readonly _lastCellFailed: IContextKey<boolean>;
2728

2829
private readonly _disposables = new DisposableStore();
2930
private readonly _viewModelDisposables = new DisposableStore();
@@ -47,6 +48,7 @@ export class NotebookEditorContextKeys {
4748
this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService);
4849
this._notebookKernelSourceCount = NOTEBOOK_KERNEL_SOURCE_COUNT.bindTo(contextKeyService);
4950
this._cellToolbarLocation = NOTEBOOK_CELL_TOOLBAR_LOCATION.bindTo(contextKeyService);
51+
this._lastCellFailed = NOTEBOOK_LAST_CELL_FAILED.bindTo(contextKeyService);
5052

5153
this._handleDidChangeModel();
5254
this._updateForNotebookOptions();
@@ -58,6 +60,7 @@ export class NotebookEditorContextKeys {
5860
this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this));
5961
this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this));
6062
this._disposables.add(_notebookExecutionStateService.onDidChangeCellExecution(this._updateForCellExecution, this));
63+
this._disposables.add(_notebookExecutionStateService.onDidChangeLastRunFailState(this._updateForLastRunFailState, this));
6164
}
6265

6366
dispose(): void {
@@ -132,6 +135,12 @@ export class NotebookEditorContextKeys {
132135
}
133136
}
134137

138+
private _updateForLastRunFailState(e: INotebookFailStateChangedEvent): void {
139+
if (e.notebook === this._editor.textModel?.uri) {
140+
this._lastCellFailed.set(e.failed);
141+
}
142+
}
143+
135144
private async _updateForInstalledExtension(): Promise<void> {
136145
if (!this._editor.hasModel()) {
137146
return;

src/vs/workbench/contrib/notebook/common/notebookContextKeys.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export const NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON = new RawContextKey<boolean
2424
export const NOTEBOOK_BREAKPOINT_MARGIN_ACTIVE = new RawContextKey<boolean>('notebookBreakpointMargin', false);
2525
export const NOTEBOOK_CELL_TOOLBAR_LOCATION = new RawContextKey<'left' | 'right' | 'hidden'>('notebookCellToolbarLocation', 'left');
2626
export const NOTEBOOK_CURSOR_NAVIGATION_MODE = new RawContextKey<boolean>('notebookCursorNavigationMode', false);
27+
export const NOTEBOOK_LAST_CELL_FAILED = new RawContextKey<boolean>('notebookLastCellFailed', false);
2728

2829
// Cell keys
2930
export const NOTEBOOK_VIEW_TYPE = new RawContextKey<string>('notebookType', undefined);

src/vs/workbench/contrib/notebook/common/notebookExecutionStateService.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,18 +39,24 @@ export interface ICellExecutionStateChangedEvent {
3939
affectsCell(cell: URI): boolean;
4040
affectsNotebook(notebook: URI): boolean;
4141
}
42+
export interface INotebookFailStateChangedEvent {
43+
failed: boolean;
44+
notebook: URI;
45+
}
4246

4347
export const INotebookExecutionStateService = createDecorator<INotebookExecutionStateService>('INotebookExecutionStateService');
4448

4549
export interface INotebookExecutionStateService {
4650
_serviceBrand: undefined;
4751

4852
onDidChangeCellExecution: Event<ICellExecutionStateChangedEvent>;
53+
onDidChangeLastRunFailState: Event<INotebookFailStateChangedEvent>;
4954

5055
forceCancelNotebookExecutions(notebookUri: URI): void;
5156
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[];
5257
getCellExecution(cellUri: URI): INotebookCellExecution | undefined;
5358
createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution;
59+
getLastFailedCellForNotebook(notebook: URI): number | undefined;
5460
}
5561

5662
export interface INotebookCellExecution {

src/vs/workbench/contrib/notebook/test/browser/testNotebookEditor.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie
4444
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
4545
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
4646
import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
47-
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
47+
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
4848
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
4949
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
5050
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
@@ -405,11 +405,17 @@ class TestCellExecution implements INotebookCellExecution {
405405
}
406406

407407
class TestNotebookExecutionStateService implements INotebookExecutionStateService {
408+
409+
getLastFailedCellForNotebook(notebook: URI): number | undefined {
410+
return;
411+
}
412+
408413
_serviceBrand: undefined;
409414

410415
private _executions = new ResourceMap<INotebookCellExecution>();
411416

412417
onDidChangeCellExecution = new Emitter<ICellExecutionStateChangedEvent>().event;
418+
onDidChangeLastRunFailState = new Emitter<INotebookFailStateChangedEvent>().event;
413419

414420
forceCancelNotebookExecutions(notebookUri: URI): void {
415421
}

0 commit comments

Comments
 (0)