add a toolbar item to open cell diagnostic actions (#208358)

* add a toolbar item to open cell diagnostic actions

* fix updated diagnostic handling

* prioritize to be next to cell result

* dispose status bar items, better handling of non-code cell
This commit is contained in:
Aaron Munger 2024-03-22 12:26:41 -07:00 committed by GitHub
parent 9d6c275fb7
commit a832f9955c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 86 additions and 20 deletions

View file

@ -357,7 +357,7 @@ const COLLAPSE_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.collapseAllCellOutpu
const EXPAND_ALL_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.expandAllCellOutputs';
const TOGGLE_CELL_OUTPUTS_COMMAND_ID = 'notebook.cell.toggleOutputs';
const TOGGLE_CELL_OUTPUT_SCROLLING = 'notebook.cell.toggleOutputScrolling';
const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions';
export const OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID = 'notebook.cell.openFailureActions';
registerAction2(class CollapseCellInputAction extends NotebookMultiCellAction {
constructor() {
@ -601,7 +601,7 @@ registerAction2(class ExpandAllCellOutputsAction extends NotebookCellAction {
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
if (context.cell instanceof CodeCellViewModel) {
const error = context.cell.cellErrorDetails;
const error = context.cell.cellDiagnostics.ErrorDetails;
if (error?.location) {
const location = Range.lift({
startLineNumber: error.location.startLineNumber + 1,

View file

@ -12,10 +12,14 @@ import { Iterable } from 'vs/base/common/iterator';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { NotebookSetting } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { Emitter, Event } from 'vs/base/common/event';
export class CellDiagnostics extends Disposable {
private readonly _onDidDiagnosticsChange = new Emitter<void>();
readonly onDidDiagnosticsChange: Event<void> = this._onDidDiagnosticsChange.event;
static ID: string = 'workbench.notebook.cellDiagnostics';
private enabled = false;
@ -48,7 +52,7 @@ export class CellDiagnostics extends Disposable {
const settingEnabled = this.configurationService.getValue(NotebookSetting.cellFailureDiagnostics);
if (this.enabled && (!settingEnabled || Iterable.isEmpty(this.inlineChatService.getAllProvider()))) {
this.enabled = false;
this.clearDiagnostics();
this.clear();
} else if (!this.enabled && settingEnabled && !Iterable.isEmpty(this.inlineChatService.getAllProvider())) {
this.enabled = true;
if (!this.listening) {
@ -62,7 +66,7 @@ export class CellDiagnostics extends Disposable {
if (this.enabled && e.type === NotebookExecutionType.cell && e.affectsCell(this.cell.uri)) {
if (!!e.changed) {
// cell is running
this.clearDiagnostics();
this.clear();
} else {
this.setDiagnostics();
}
@ -71,21 +75,19 @@ export class CellDiagnostics extends Disposable {
public clear() {
if (this.ErrorDetails) {
this.clearDiagnostics();
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
this.errorDetails = undefined;
this._onDidDiagnosticsChange.fire();
}
}
private clearDiagnostics() {
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, []);
this.errorDetails = undefined;
}
private setDiagnostics() {
const metadata = this.cell.model.internalMetadata;
if (!metadata.lastRunSuccess && metadata?.error?.location) {
const marker = this.createMarkerData(metadata.error.message, metadata.error.location);
this.markerService.changeOne(CellDiagnostics.ID, this.cell.uri, [marker]);
this.errorDetails = metadata.error;
this._onDidDiagnosticsChange.fire();
}
}

View file

@ -19,6 +19,9 @@ import { CellStatusbarAlignment, INotebookCellStatusBarItem, NotebookCellExecuti
import { INotebookCellExecution, INotebookExecutionStateService, NotebookExecutionType } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IMarkdownString } from 'vs/base/common/htmlContent';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/contrib/cellCommands/cellCommands';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export function formatCellDuration(duration: number, showMilliseconds: boolean = true): string {
if (showMilliseconds && duration < 1000) {
@ -333,3 +336,60 @@ class TimerCellStatusBarItem extends Disposable {
this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this._cell.handle, items: [] }]);
}
}
export class DiagnosticCellStatusBarContrib extends Disposable implements INotebookEditorContribution {
static id: string = 'workbench.notebook.statusBar.diagtnostic';
constructor(
notebookEditor: INotebookEditor,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
this._register(new NotebookStatusBarController(notebookEditor, (vm, cell) =>
cell instanceof CodeCellViewModel ?
instantiationService.createInstance(DiagnosticCellStatusBarItem, vm, cell) :
Disposable.None
));
}
}
registerNotebookContribution(DiagnosticCellStatusBarContrib.id, DiagnosticCellStatusBarContrib);
class DiagnosticCellStatusBarItem extends Disposable {
private _currentItemIds: string[] = [];
constructor(
private readonly _notebookViewModel: INotebookViewModel,
private readonly cell: CodeCellViewModel,
@IKeybindingService private readonly keybindingService: IKeybindingService
) {
super();
this._update();
this._register(this.cell.cellDiagnostics.onDidDiagnosticsChange(() => this._update()));
}
private async _update() {
let item: INotebookCellStatusBarItem | undefined;
if (!!this.cell.cellDiagnostics.ErrorDetails) {
const keybinding = this.keybindingService.lookupKeybinding(OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID)?.getLabel();
const tooltip = localize('notebook.cell.status.diagnostic', "Quick Actions {0}", `(${keybinding})`);
item = {
text: `$(sparkle)`,
tooltip,
alignment: CellStatusbarAlignment.Left,
command: OPEN_CELL_FAILURE_ACTIONS_COMMAND_ID,
priority: Number.MAX_SAFE_INTEGER - 1
};
}
const items = item ? [item] : [];
this._currentItemIds = this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items }]);
}
override dispose() {
super.dispose();
this._notebookViewModel.deltaCellStatusBarItems(this._currentItemIds, [{ handle: this.cell.handle, items: [] }]);
}
}

View file

@ -1101,9 +1101,9 @@ configurationRegistry.registerConfiguration({
default: false
},
[NotebookSetting.cellFailureDiagnostics]: {
markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Enable experimental diagnostics for cell failures."),
markdownDescription: nls.localize('notebook.cellFailureDiagnostics', "Show available diagnostics for cell failures."),
type: 'boolean',
default: false
default: true
},
}
});

View file

@ -101,6 +101,7 @@ export class CellContextKeyManager extends Disposable {
if (element instanceof CodeCellViewModel) {
this.elementDisposables.add(element.onDidChangeOutputs(() => this.updateForOutputs()));
this.elementDisposables.add(element.cellDiagnostics.onDidDiagnosticsChange(() => this.updateForDiagnostics()));
}
this.elementDisposables.add(this.notebookEditor.onDidChangeActiveCell(() => this.updateForFocusState()));
@ -118,6 +119,7 @@ export class CellContextKeyManager extends Disposable {
this.updateForCollapseState();
this.updateForOutputs();
this.updateForChat();
this.updateForDiagnostics();
this.cellLineNumbers.set(this.element!.lineNumbers);
this.cellResource.set(this.element!.uri.toString());
@ -202,10 +204,6 @@ export class CellContextKeyManager extends Disposable {
this.cellRunState.set('idle');
this.cellExecuting.set(false);
}
if (this.element instanceof CodeCellViewModel) {
this.cellHasErrorDiagnostics.set(!!this.element.cellErrorDetails);
}
}
private updateForEditState() {
@ -247,4 +245,10 @@ export class CellContextKeyManager extends Disposable {
this.cellGeneratedByChat.set(chatController.isCellGeneratedByChat(this.element));
}
private updateForDiagnostics() {
if (this.element instanceof CodeCellViewModel) {
this.cellHasErrorDiagnostics.set(!!this.element.cellDiagnostics.ErrorDetails);
}
}
}

View file

@ -47,8 +47,8 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
private _outputCollection: number[] = [];
private readonly _cellDiagnostics: CellDiagnostics;
get cellErrorDetails() {
return this._cellDiagnostics.ErrorDetails;
get cellDiagnostics() {
return this._cellDiagnostics;
}
private _outputsTop: PrefixSumComputer | null = null;
@ -436,10 +436,10 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
protected onDidChangeTextModelContent(): void {
if (this.getEditState() !== CellEditState.Editing) {
this._cellDiagnostics.clear();
this.updateEditState(CellEditState.Editing, 'onDidChangeTextModelContent');
this._onDidChangeState.fire({ contentChanged: true });
}
this._cellDiagnostics.clear();
}
onDeselect() {

View file

@ -956,7 +956,7 @@ export const NotebookSetting = {
cellChat: 'notebook.experimental.cellChat',
notebookVariablesView: 'notebook.experimental.variablesView',
InteractiveWindowPromptToSave: 'interactiveWindow.promptToSaveOnClose',
cellFailureDiagnostics: 'notebook.experimental.cellFailureDiagnostics',
cellFailureDiagnostics: 'notebook.cellFailureDiagnostics',
} as const;
export const enum CellStatusbarAlignment {