Add "Go to Last Failed Cell" Button (#154443)

* Add go to last failed cell function
This commit is contained in:
Andrea Mah 2022-07-15 09:52:17 -07:00 committed by GitHub
parent 179883d98e
commit 6779fd96bf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 111 additions and 9 deletions

View file

@ -15,7 +15,7 @@ import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { EditorsOrder } from 'vs/workbench/common/editor';
import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations';
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';
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';
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';
import { CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CellKind, NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@ -35,6 +35,7 @@ const EXECUTE_CELL_AND_BELOW = 'notebook.cell.executeCellAndBelow';
const EXECUTE_CELLS_ABOVE = 'notebook.cell.executeCellsAbove';
const RENDER_ALL_MARKDOWN_CELLS = 'notebook.renderAllMarkdownCells';
const REVEAL_RUNNING_CELL = 'notebook.revealRunningCell';
const REVEAL_LAST_FAILED_CELL = 'notebook.revealLastFailedCell';
// If this changes, update getCodeCellExecutionContextKeyService to match
export const executeCondition = ContextKeyExpr.and(
@ -594,3 +595,52 @@ registerAction2(class RevealRunningCellAction extends NotebookAction {
}
}
});
registerAction2(class RevealLastFailedCellAction extends NotebookAction {
constructor() {
super({
id: REVEAL_LAST_FAILED_CELL,
title: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
tooltip: localize('revealLastFailedCell', "Go to Most Recently Failed Cell"),
shortTitle: localize('revealLastFailedCellShort', "Go To"),
precondition: NOTEBOOK_LAST_CELL_FAILED,
menu: [
{
id: MenuId.EditorTitle,
when: ContextKeyExpr.and(
NOTEBOOK_IS_ACTIVE_EDITOR,
NOTEBOOK_LAST_CELL_FAILED,
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
ContextKeyExpr.notEquals('config.notebook.globalToolbar', true)
),
group: 'navigation',
order: 0
},
{
id: MenuId.NotebookToolbar,
when: ContextKeyExpr.and(
NOTEBOOK_IS_ACTIVE_EDITOR,
NOTEBOOK_LAST_CELL_FAILED,
NOTEBOOK_HAS_RUNNING_CELL.toNegated(),
ContextKeyExpr.equals('config.notebook.globalToolbar', true)
),
group: 'navigation/execute',
order: 0
},
],
icon: icons.errorStateIcon,
});
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
const notebookExecutionStateService = accessor.get(INotebookExecutionStateService);
const notebook = context.notebookEditor.textModel.uri;
const lastFailedCellHandle = notebookExecutionStateService.getLastFailedCellForNotebook(notebook);
if (lastFailedCellHandle !== undefined) {
const lastFailedCell = context.notebookEditor.getCellByHandle(lastFailedCellHandle);
if (lastFailedCell) {
context.notebookEditor.focusNotebookCell(lastFailedCell, 'container');
}
}
}
});

View file

@ -80,3 +80,7 @@
.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar:not(.vertical) .action-item.active {
background-color: unset;
}
.monaco-workbench .notebookOverlay .notebook-toolbar-container .monaco-action-bar .action-item .codicon-notebook-state-error {
color: var(--notebook-cell-status-icon-error);
}

View file

@ -13,7 +13,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellEditType, CellUri, ICellEditOperation, NotebookCellExecutionState, NotebookCellInternalMetadata, NotebookTextModelWillAddRemoveEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType, INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService {
@ -22,10 +22,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
private readonly _executions = new ResourceMap<Map<number, CellExecution>>();
private readonly _notebookListeners = new ResourceMap<NotebookExecutionListeners>();
private readonly _cellListeners = new ResourceMap<IDisposable>();
private readonly _lastFailedCells = new ResourceMap<number>();
private readonly _onDidChangeCellExecution = this._register(new Emitter<ICellExecutionStateChangedEvent>());
onDidChangeCellExecution = this._onDidChangeCellExecution.event;
private readonly _onDidChangeLastRunFailState = this._register(new Emitter<INotebookFailStateChangedEvent>());
onDidChangeLastRunFailState = this._onDidChangeLastRunFailState.event;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
@ -34,6 +38,10 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
super();
}
getLastFailedCellForNotebook(notebook: URI): number | undefined {
return this._lastFailedCells.get(notebook);
}
forceCancelNotebookExecutions(notebookUri: URI): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
@ -68,7 +76,7 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe));
}
private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution): void {
private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution, lastRunSuccess?: boolean): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
this._logService.debug(`NotebookExecutionStateService#_onCellExecutionDidComplete - unknown notebook ${notebookUri.toString()}`);
@ -86,6 +94,14 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
this._notebookListeners.delete(notebookUri);
}
if (lastRunSuccess !== undefined) {
if (lastRunSuccess) {
this._clearLastFailedCell(notebookUri);
} else {
this._setLastFailedCell(notebookUri, cellHandle);
}
}
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle));
}
@ -119,12 +135,22 @@ export class NotebookExecutionStateService extends Disposable implements INotebo
const exe: CellExecution = this._instantiationService.createInstance(CellExecution, cellHandle, notebook);
const disposable = combinedDisposable(
exe.onDidUpdate(() => this._onCellExecutionDidChange(notebookUri, cellHandle, exe)),
exe.onDidComplete(() => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe)));
exe.onDidComplete(lastRunSuccess => this._onCellExecutionDidComplete(notebookUri, cellHandle, exe, lastRunSuccess)));
this._cellListeners.set(CellUri.generate(notebookUri, cellHandle), disposable);
return exe;
}
private _setLastFailedCell(notebook: URI, cellHandle: number) {
this._lastFailedCells.set(notebook, cellHandle);
this._onDidChangeLastRunFailState.fire({ failed: true, notebook });
}
private _clearLastFailedCell(notebook: URI) {
this._lastFailedCells.delete(notebook);
this._onDidChangeLastRunFailState.fire({ failed: false, notebook: notebook });
}
override dispose(): void {
super.dispose();
this._executions.forEach(executionMap => {
@ -250,7 +276,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
private readonly _onDidUpdate = this._register(new Emitter<void>());
readonly onDidUpdate = this._onDidUpdate.event;
private readonly _onDidComplete = this._register(new Emitter<void>());
private readonly _onDidComplete = this._register(new Emitter<boolean | undefined>());
readonly onDidComplete = this._onDidComplete.event;
private _state: NotebookCellExecutionState = NotebookCellExecutionState.Unconfirmed;
@ -350,7 +376,7 @@ class CellExecution extends Disposable implements INotebookCellExecution {
this._applyExecutionEdits([edit]);
}
this._onDidComplete.fire();
this._onDidComplete.fire(completionData.lastRunSuccess);
}
private _applyExecutionEdits(edits: ICellEditOperation[]): void {

View file

@ -6,8 +6,8 @@
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICellViewModel, INotebookEditorDelegate, KERNEL_EXTENSIONS } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
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';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
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';
import { INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
@ -24,6 +24,7 @@ export class NotebookEditorContextKeys {
private readonly _viewType!: IContextKey<string>;
private readonly _missingKernelExtension: IContextKey<boolean>;
private readonly _cellToolbarLocation: IContextKey<'left' | 'right' | 'hidden'>;
private readonly _lastCellFailed: IContextKey<boolean>;
private readonly _disposables = new DisposableStore();
private readonly _viewModelDisposables = new DisposableStore();
@ -47,6 +48,7 @@ export class NotebookEditorContextKeys {
this._missingKernelExtension = NOTEBOOK_MISSING_KERNEL_EXTENSION.bindTo(contextKeyService);
this._notebookKernelSourceCount = NOTEBOOK_KERNEL_SOURCE_COUNT.bindTo(contextKeyService);
this._cellToolbarLocation = NOTEBOOK_CELL_TOOLBAR_LOCATION.bindTo(contextKeyService);
this._lastCellFailed = NOTEBOOK_LAST_CELL_FAILED.bindTo(contextKeyService);
this._handleDidChangeModel();
this._updateForNotebookOptions();
@ -58,6 +60,7 @@ export class NotebookEditorContextKeys {
this._disposables.add(_editor.notebookOptions.onDidChangeOptions(this._updateForNotebookOptions, this));
this._disposables.add(_extensionService.onDidChangeExtensions(this._updateForInstalledExtension, this));
this._disposables.add(_notebookExecutionStateService.onDidChangeCellExecution(this._updateForCellExecution, this));
this._disposables.add(_notebookExecutionStateService.onDidChangeLastRunFailState(this._updateForLastRunFailState, this));
}
dispose(): void {
@ -132,6 +135,12 @@ export class NotebookEditorContextKeys {
}
}
private _updateForLastRunFailState(e: INotebookFailStateChangedEvent): void {
if (e.notebook === this._editor.textModel?.uri) {
this._lastCellFailed.set(e.failed);
}
}
private async _updateForInstalledExtension(): Promise<void> {
if (!this._editor.hasModel()) {
return;

View file

@ -24,6 +24,7 @@ export const NOTEBOOK_USE_CONSOLIDATED_OUTPUT_BUTTON = new RawContextKey<boolean
export const NOTEBOOK_BREAKPOINT_MARGIN_ACTIVE = new RawContextKey<boolean>('notebookBreakpointMargin', false);
export const NOTEBOOK_CELL_TOOLBAR_LOCATION = new RawContextKey<'left' | 'right' | 'hidden'>('notebookCellToolbarLocation', 'left');
export const NOTEBOOK_CURSOR_NAVIGATION_MODE = new RawContextKey<boolean>('notebookCursorNavigationMode', false);
export const NOTEBOOK_LAST_CELL_FAILED = new RawContextKey<boolean>('notebookLastCellFailed', false);
// Cell keys
export const NOTEBOOK_VIEW_TYPE = new RawContextKey<string>('notebookType', undefined);

View file

@ -39,6 +39,10 @@ export interface ICellExecutionStateChangedEvent {
affectsCell(cell: URI): boolean;
affectsNotebook(notebook: URI): boolean;
}
export interface INotebookFailStateChangedEvent {
failed: boolean;
notebook: URI;
}
export const INotebookExecutionStateService = createDecorator<INotebookExecutionStateService>('INotebookExecutionStateService');
@ -46,11 +50,13 @@ export interface INotebookExecutionStateService {
_serviceBrand: undefined;
onDidChangeCellExecution: Event<ICellExecutionStateChangedEvent>;
onDidChangeLastRunFailState: Event<INotebookFailStateChangedEvent>;
forceCancelNotebookExecutions(notebookUri: URI): void;
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[];
getCellExecution(cellUri: URI): INotebookCellExecution | undefined;
createCellExecution(notebook: URI, cellHandle: number): INotebookCellExecution;
getLastFailedCellForNotebook(notebook: URI): number | undefined;
}
export interface INotebookCellExecution {

View file

@ -44,7 +44,7 @@ import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/vie
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, CellUri, INotebookDiffEditorModel, INotebookEditorModel, INotebookSearchOptions, IOutputDto, IResolvedNotebookEditorModel, NotebookCellExecutionState, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService, INotebookFailStateChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { NotebookOptions } from 'vs/workbench/contrib/notebook/common/notebookOptions';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange';
import { TextModelResolverService } from 'vs/workbench/services/textmodelResolver/common/textModelResolverService';
@ -405,11 +405,17 @@ class TestCellExecution implements INotebookCellExecution {
}
class TestNotebookExecutionStateService implements INotebookExecutionStateService {
getLastFailedCellForNotebook(notebook: URI): number | undefined {
return;
}
_serviceBrand: undefined;
private _executions = new ResourceMap<INotebookCellExecution>();
onDidChangeCellExecution = new Emitter<ICellExecutionStateChangedEvent>().event;
onDidChangeLastRunFailState = new Emitter<INotebookFailStateChangedEvent>().event;
forceCancelNotebookExecutions(notebookUri: URI): void {
}