NotebookExecutionStateService - createCellExecution returns an execution object. For #125668

This commit is contained in:
Rob Lourens 2022-01-18 10:10:06 -08:00
parent 62b0d5cb81
commit 07b7831746
22 changed files with 336 additions and 258 deletions

View file

@ -97,7 +97,6 @@ export namespace NotebookDto {
if (data.editType === CellExecutionUpdateType.Output) {
return {
editType: data.editType,
cellHandle: data.cellHandle,
append: data.append,
outputs: data.outputs.map(fromNotebookOutputDto)
};

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { flatten, groupBy, isNonEmptyArray } from 'vs/base/common/arrays';
import { flatten, isNonEmptyArray } from 'vs/base/common/arrays';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@ -14,8 +14,7 @@ import { NotebookDto } from 'vs/workbench/api/browser/mainThreadNotebookDto';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernel, INotebookKernelChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { SerializableObjectWithBuffers } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtHostContext, ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, ICellExecutionCompleteDto, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
@ -107,7 +106,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
private readonly _kernels = new Map<number, [kernel: MainThreadKernel, registraion: IDisposable]>();
private readonly _proxy: ExtHostNotebookKernelsShape;
private readonly _executions = new Set<string>();
private readonly _executions = new Map<number, INotebookCellExecution>();
constructor(
extHostContext: IExtHostContext,
@ -125,10 +124,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
this._disposables.add(toDisposable(() => {
// EH shut down, complete all executions started by this EH
this._executions.forEach(e => {
const uri = CellUri.parse(URI.parse(e));
if (uri) {
this._notebookExecutionStateService.completeNotebookCellExecution(uri.notebook, uri.handle, { });
}
e.complete({ });
});
}));
@ -249,34 +245,34 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
// --- execution
$addExecution(rawUri: UriComponents, cellHandle: number): void {
$createExecution(handle: number, controllerId: string, rawUri: UriComponents, cellHandle: number): void {
const uri = URI.revive(rawUri);
this._notebookExecutionStateService.createNotebookCellExecution(uri, cellHandle);
const cellUri = CellUri.generateCellUri(uri, cellHandle, uri.scheme);
this._executions.add(cellUri.toString());
const execution = this._notebookExecutionStateService.createCellExecution(controllerId, uri, cellHandle);
this._executions.set(handle, execution);
}
$updateExecutions(data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {
$updateExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {
const updates = data.value;
const groupedUpdates = groupBy(updates, (a, b) => a.cellHandle - b.cellHandle);
groupedUpdates.forEach(datas => {
const first = datas[0];
try {
const uri = URI.revive(first.uri);
this._notebookExecutionStateService.updateNotebookCellExecution(uri, first.cellHandle, datas.map(NotebookDto.fromCellExecuteUpdateDto));
} catch (e) {
onUnexpectedError(e);
try {
const execution = this._executions.get(handle);
if (execution) {
execution.update(updates.map(NotebookDto.fromCellExecuteUpdateDto));
}
});
} catch (e) {
onUnexpectedError(e);
}
}
$completeExecution(rawUri: UriComponents, cellHandle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void {
const uri = URI.revive(rawUri);
this._notebookExecutionStateService.completeNotebookCellExecution(uri, cellHandle, NotebookDto.fromCellExecuteCompleteDto(data.value));
const cellUri = CellUri.generateCellUri(uri, cellHandle, uri.scheme);
this._executions.delete(cellUri.toString());
$completeExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void {
try {
const execution = this._executions.get(handle);
if (execution) {
execution.complete(NotebookDto.fromCellExecuteCompleteDto(data.value));
}
} catch (e) {
onUnexpectedError(e);
} finally {
this._executions.delete(handle);
}
}
}

View file

@ -948,29 +948,21 @@ export interface INotebookKernelDto2 {
export interface ICellExecuteOutputEditDto {
editType: CellExecutionUpdateType.Output;
uri: UriComponents;
cellHandle: number;
append?: boolean;
outputs: NotebookOutputDto[];
}
export interface ICellExecuteOutputItemEditDto {
editType: CellExecutionUpdateType.OutputItems;
uri: UriComponents;
cellHandle: number;
append?: boolean;
outputId: string;
items: NotebookOutputItemDto[];
}
export interface ICellExecutionStateUpdateDto extends ICellExecutionStateUpdate {
uri: UriComponents;
cellHandle: number;
}
export interface ICellExecutionCompleteDto extends ICellExecutionComplete {
uri: UriComponents;
cellHandle: number;
}
export type ICellExecuteUpdateDto = ICellExecuteOutputEditDto | ICellExecuteOutputItemEditDto | ICellExecutionStateUpdateDto;
@ -982,9 +974,9 @@ export interface MainThreadNotebookKernelsShape extends IDisposable {
$removeKernel(handle: number): void;
$updateNotebookPriority(handle: number, uri: UriComponents, value: number | undefined): void;
$addExecution(uri: UriComponents, cellHandle: number): void;
$updateExecutions(data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void;
$completeExecution(uri: UriComponents, cellHandle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void;
$createExecution(handle: number, controllerId: string, uri: UriComponents, cellHandle: number): void;
$updateExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void;
$completeExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void;
}
export interface MainThreadNotebookRenderersShape extends IDisposable {

View file

@ -16,9 +16,9 @@ import { ExtHostNotebookKernelsShape, ICellExecuteUpdateDto, IMainContext, INote
import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostCell, ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { ExtHostCell } from 'vs/workbench/api/common/extHostNotebookDocument';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { NotebookCellOutput, NotebookCellExecutionState as ExtHostNotebookCellExecutionState } from 'vs/workbench/api/common/extHostTypes';
import { NotebookCellExecutionState as ExtHostNotebookCellExecutionState, NotebookCellOutput } from 'vs/workbench/api/common/extHostTypes';
import { asWebviewUri } from 'vs/workbench/api/common/shared/webview';
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellExecutionUpdateType } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
@ -108,7 +108,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
const onDidReceiveMessage = new Emitter<{ editor: vscode.NotebookEditor, message: any; }>();
const data: INotebookKernelDto2 = {
id: `${extension.identifier.value}/${id}`,
id: createKernelId(extension, id),
notebookType: viewType,
extensionId: extension.identifier,
extensionLocation: extension.extensionLocation,
@ -218,7 +218,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
that._logService.trace(`NotebookController[${handle}] NOT associated to notebook, associated to THESE notebooks:`, Array.from(associatedNotebooks.keys()).map(u => u.toString()));
throw new Error(`notebook controller is NOT associated to notebook: ${cell.notebook.uri.toString()}`);
}
return that._createNotebookCellExecution(cell);
return that._createNotebookCellExecution(cell, createKernelId(extension, this.id));
},
dispose: () => {
if (!isDisposed) {
@ -348,7 +348,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
// ---
_createNotebookCellExecution(cell: vscode.NotebookCell): vscode.NotebookCellExecution {
_createNotebookCellExecution(cell: vscode.NotebookCell, controllerId: string): vscode.NotebookCellExecution {
if (cell.index < 0) {
throw new Error('CANNOT execute cell that has been REMOVED from notebook');
}
@ -360,7 +360,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
if (this._activeExecutions.has(cellObj.uri)) {
throw new Error(`duplicate execution for ${cellObj.uri}`);
}
const execution = new NotebookCellExecutionTask(cellObj.notebook, cellObj, this._proxy);
const execution = new NotebookCellExecutionTask(controllerId, cellObj, this._proxy);
this._activeExecutions.set(cellObj.uri, execution);
const listener = execution.onDidChangeState(() => {
if (execution.state === NotebookCellExecutionTaskState.Resolved) {
@ -381,6 +381,9 @@ enum NotebookCellExecutionTaskState {
}
class NotebookCellExecutionTask extends Disposable {
private static HANDLE = 0;
private _handle = NotebookCellExecutionTask.HANDLE++;
private _onDidChangeState = new Emitter<void>();
readonly onDidChangeState = this._onDidChangeState.event;
@ -394,7 +397,7 @@ class NotebookCellExecutionTask extends Disposable {
private _executionOrder: number | undefined;
constructor(
private readonly _document: ExtHostNotebookDocument,
controllerId: string,
private readonly _cell: ExtHostCell,
private readonly _proxy: MainThreadNotebookKernelsShape
) {
@ -403,7 +406,7 @@ class NotebookCellExecutionTask extends Disposable {
this._collector = new TimeoutBasedCollector(10, updates => this.update(updates));
this._executionOrder = _cell.internalMetadata.executionOrder;
this._proxy.$addExecution(this._cell.notebook.uri, this._cell.handle);
this._proxy.$createExecution(this._handle, controllerId, this._cell.notebook.uri, this._cell.handle);
}
cancel(): void {
@ -416,7 +419,7 @@ class NotebookCellExecutionTask extends Disposable {
private async update(update: ICellExecuteUpdateDto | ICellExecuteUpdateDto[]): Promise<void> {
const updates = Array.isArray(update) ? update : [update];
return this._proxy.$updateExecutions(new SerializableObjectWithBuffers(updates));
return this._proxy.$updateExecution(this._handle, new SerializableObjectWithBuffers(updates));
}
private verifyStateForOutput() {
@ -429,17 +432,6 @@ class NotebookCellExecutionTask extends Disposable {
}
}
private cellIndexToHandle(cellOrCellIndex: vscode.NotebookCell | undefined): number {
let cell: ExtHostCell | undefined = this._cell;
if (cellOrCellIndex) {
cell = this._document.getCellFromApiCell(cellOrCellIndex);
}
if (!cell) {
throw new Error('INVALID cell');
}
return cell.handle;
}
private validateAndConvertOutputs(items: vscode.NotebookCellOutput[]): NotebookOutputDto[] {
return items.map(output => {
const newOutput = NotebookCellOutput.ensureUniqueMimeTypes(output.items, true);
@ -455,13 +447,10 @@ class NotebookCellExecutionTask extends Disposable {
}
private async updateOutputs(outputs: vscode.NotebookCellOutput | vscode.NotebookCellOutput[], cell: vscode.NotebookCell | undefined, append: boolean): Promise<void> {
const handle = this.cellIndexToHandle(cell);
const outputDtos = this.validateAndConvertOutputs(asArray(outputs));
return this.updateSoon(
{
editType: CellExecutionUpdateType.Output,
uri: this._document.uri,
cellHandle: handle,
append,
outputs: outputDtos
});
@ -471,8 +460,6 @@ class NotebookCellExecutionTask extends Disposable {
items = NotebookCellOutput.ensureUniqueMimeTypes(asArray(items), true);
return this.updateSoon({
editType: CellExecutionUpdateType.OutputItems,
uri: this._document.uri,
cellHandle: this._cell.handle,
items: items.map(extHostTypeConverters.NotebookCellOutputItem.from),
outputId: output.id,
append
@ -489,8 +476,6 @@ class NotebookCellExecutionTask extends Disposable {
that._executionOrder = v;
that.update([{
editType: CellExecutionUpdateType.ExecutionState,
uri: that._document.uri,
cellHandle: that._cell.handle,
executionOrder: that._executionOrder
}]);
},
@ -505,8 +490,6 @@ class NotebookCellExecutionTask extends Disposable {
that.update({
editType: CellExecutionUpdateType.ExecutionState,
uri: that._document.uri,
cellHandle: that._cell.handle,
runStartTime: startTime
});
},
@ -523,9 +506,7 @@ class NotebookCellExecutionTask extends Disposable {
// so we use updateSoon and immediately flush.
that._collector.flush();
that._proxy.$completeExecution(that._document.uri, that._cell.handle, new SerializableObjectWithBuffers({
uri: that._document.uri,
cellHandle: that._cell.handle,
that._proxy.$completeExecution(that._handle, new SerializableObjectWithBuffers({
runEndTime: endTime,
lastRunSuccess: success
}));
@ -601,3 +582,7 @@ class TimeoutBasedCollector<T> {
.finally(() => deferred.complete());
}
}
function createKernelId(extension: IExtensionDescription, id: string): string {
return `${extension.identifier.value}/${id}`;
}

View file

@ -575,7 +575,7 @@ export class InteractiveEditor extends EditorPane {
}
if (this.#lastCell) {
const runState = this.#notebookExecutionStateService.getCellExecutionState(this.#lastCell.uri)?.state;
const runState = this.#notebookExecutionStateService.getCellExecution(this.#lastCell.uri)?.state;
if (runState === NotebookCellExecutionState.Executing) {
return;
}

View file

@ -184,9 +184,9 @@ class NotebookCellPausing extends Disposable implements IWorkbenchContribution {
private editIsPaused(cellUri: URI, isPaused: boolean) {
const parsed = CellUri.parse(cellUri);
if (parsed) {
const exeState = this._notebookExecutionStateService.getCellExecutionState(cellUri);
const exeState = this._notebookExecutionStateService.getCellExecution(cellUri);
if (exeState && (exeState.isPaused !== isPaused || !exeState.didPause)) {
this._notebookExecutionStateService.updateNotebookCellExecution(parsed.notebook, parsed.handle, [{
exeState.update([{
editType: CellExecutionUpdateType.ExecutionState,
didPause: true,
isPaused

View file

@ -15,7 +15,7 @@ import { cellStatusIconError, cellStatusIconSuccess } from 'vs/workbench/contrib
import { errorStateIcon, executingStateIcon, pendingStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellStatusbarAlignment, INotebookCellStatusBarItem, NotebookCellExecutionState, NotebookCellInternalMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellExecutionEntry, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
export class NotebookStatusBarController extends Disposable {
private readonly _visibleCells = new Map<number, IDisposable>();
@ -111,7 +111,7 @@ class ExecutionStateCellStatusBarItem extends Disposable {
* Returns undefined if there should be no change, and an empty array if all items should be removed.
*/
private _getItemsForCell(): INotebookCellStatusBarItem[] | undefined {
const runState = this._executionStateService.getCellExecutionState(this._cell.uri);
const runState = this._executionStateService.getCellExecution(this._cell.uri);
if (this._currentExecutingStateTimer && !runState?.isPaused) {
return;
}
@ -121,7 +121,7 @@ class ExecutionStateCellStatusBarItem extends Disposable {
// Show the execution spinner for a minimum time
if (runState?.state === NotebookCellExecutionState.Executing) {
this._currentExecutingStateTimer = this._register(disposableTimeout(() => {
const runState = this._executionStateService.getCellExecutionState(this._cell.uri);
const runState = this._executionStateService.getCellExecution(this._cell.uri);
this._currentExecutingStateTimer = undefined;
if (runState?.state !== NotebookCellExecutionState.Executing) {
this._update();
@ -132,7 +132,7 @@ class ExecutionStateCellStatusBarItem extends Disposable {
return item ? [item] : [];
}
private _getItemForState(runState: ICellExecutionEntry | undefined, internalMetadata: NotebookCellInternalMetadata): INotebookCellStatusBarItem | undefined {
private _getItemForState(runState: INotebookCellExecution | undefined, internalMetadata: NotebookCellInternalMetadata): INotebookCellStatusBarItem | undefined {
const state = runState?.state;
const { lastRunSuccess } = internalMetadata;
if (!state && lastRunSuccess) {
@ -212,7 +212,7 @@ class TimerCellStatusBarItem extends Disposable {
private async _update() {
let item: INotebookCellStatusBarItem | undefined;
const runState = this._executionStateService.getCellExecutionState(this._cell.uri);
const runState = this._executionStateService.getCellExecution(this._cell.uri);
const state = runState?.state;
if (runState?.didPause) {
item = undefined;

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { ILogService } from 'vs/platform/log/common/log';
import { INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
export class ExecutionContrib extends Disposable implements INotebookEditorContribution {
static id: string = 'workbench.notebook.executionContrib';
constructor(
private readonly _notebookEditor: INotebookEditor,
@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,
@INotebookExecutionService private readonly _notebookExecutionService: INotebookExecutionService,
@ILogService private readonly _logService: ILogService,
@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
) {
super();
this._register(this._notebookKernelService.onDidChangeSelectedNotebooks(e => {
if (e.newKernel && this._notebookEditor.textModel?.uri.toString() === e.notebook.toString()) {
this.cancelAll();
this._notebookExecutionStateService.forceCancelNotebookExecutions(e.notebook);
}
}));
}
private cancelAll(): void {
this._logService.debug(`ExecutionContrib#cancelAll`);
const exes = this._notebookExecutionStateService.getCellExecutionStatesForNotebook(this._notebookEditor.textModel!.uri);
this._notebookExecutionService.cancelNotebookCellHandles(this._notebookEditor.textModel!, exes.map(exe => exe.cellHandle));
}
}
registerNotebookContribution(ExecutionContrib.id, ExecutionContrib);

View file

@ -8,7 +8,7 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { INotebookEditor, INotebookEditorContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { registerNotebookContribution } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellExecutionEntry, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
export class ExecutionEditorProgressController extends Disposable implements INotebookEditorContribution {
static id: string = 'workbench.notebook.executionEditorProgress';
@ -40,7 +40,7 @@ export class ExecutionEditorProgressController extends Disposable implements INo
const executing = this._notebookExecutionStateService.getCellExecutionStatesForNotebook(this._notebookEditor.textModel?.uri)
.filter(exe => exe.state === NotebookCellExecutionState.Executing);
const executionIsVisible = (exe: ICellExecutionEntry) => {
const executionIsVisible = (exe: INotebookCellExecution) => {
for (const range of this._notebookEditor.visibleRanges) {
for (const cell of this._notebookEditor.getCellsInRange(range)) {
if (cell.handle === exe.cellHandle) {

View file

@ -447,7 +447,7 @@ export class NotebookCellOutline extends Disposable implements IOutline<OutlineE
preview = localize('empty', "empty cell");
}
const exeState = !isMarkdown && this._notebookExecutionStateService.getCellExecutionState(cell.uri);
const exeState = !isMarkdown && this._notebookExecutionStateService.getCellExecution(cell.uri);
entries.push(new OutlineEntry(entries.length, 7, cell, preview, !!exeState, exeState ? exeState.isPaused : false));
}

View file

@ -213,7 +213,7 @@ registerAction2(class ClearCellOutputsAction extends NotebookCellAction {
editor.textModel.applyEdits([{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined);
const runState = notebookExecutionStateService.getCellExecutionState(context.cell.uri)?.state;
const runState = notebookExecutionStateService.getCellExecution(context.cell.uri)?.state;
if (runState !== NotebookCellExecutionState.Executing) {
context.notebookEditor.textModel.applyEdits([{
editType: CellEditType.PartialInternalMetadata, index, internalMetadata: {
@ -272,7 +272,7 @@ registerAction2(class ClearAllCellOutputsAction extends NotebookAction {
})), true, undefined, () => undefined, undefined);
const clearExecutionMetadataEdits = editor.textModel.cells.map((cell, index) => {
const runState = notebookExecutionStateService.getCellExecutionState(cell.uri)?.state;
const runState = notebookExecutionStateService.getCellExecution(cell.uri)?.state;
if (runState !== NotebookCellExecutionState.Executing) {
return {
editType: CellEditType.PartialInternalMetadata, index, internalMetadata: {

View file

@ -89,6 +89,7 @@ import 'vs/workbench/contrib/notebook/browser/contrib/troubleshoot/layout';
import 'vs/workbench/contrib/notebook/browser/contrib/codeRenderer/codeRenderer';
import 'vs/workbench/contrib/notebook/browser/contrib/breakpoints/notebookBreakpoints';
import 'vs/workbench/contrib/notebook/browser/contrib/execute/executionEditorProgress';
import 'vs/workbench/contrib/notebook/browser/contrib/execute/execution';
// Diff Editor Contribution
import 'vs/workbench/contrib/notebook/browser/diff/notebookDiffActions';

View file

@ -47,7 +47,7 @@ export class NotebookExecutionService implements INotebookExecutionService {
const cellHandles: number[] = [];
for (const cell of cellsArr) {
const cellExe = this._notebookExecutionStateService.getCellExecutionState(cell.uri);
const cellExe = this._notebookExecutionStateService.getCellExecution(cell.uri);
if (cell.cellKind !== CellKind.Code || !!cellExe) {
continue;
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { combinedDisposable, Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { ResourceMap } from 'vs/base/common/map';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
@ -13,110 +13,135 @@ 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, ICellExecutionEntry, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, ICellExecutionStateUpdate, INotebookCellExecution, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
export class NotebookExecutionStateService extends Disposable implements INotebookExecutionStateService {
declare _serviceBrand: undefined;
private readonly _executions = new ResourceMap<NotebookExecution>();
private readonly _executions = new ResourceMap<Map<number, CellExecution>>();
private readonly _notebookListeners = new ResourceMap<NotebookExecutionListeners>();
private readonly _cellListeners = new ResourceMap<IDisposable>();
private readonly _onDidChangeCellExecution = new Emitter<ICellExecutionStateChangedEvent>();
private readonly _onDidChangeCellExecution = this._register(new Emitter<ICellExecutionStateChangedEvent>());
onDidChangeCellExecution = this._onDidChangeCellExecution.event;
constructor(
@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@ILogService private readonly _logService: ILogService,
@INotebookService private readonly _notebookService: INotebookService,
) {
super();
this._register(this._notebookKernelService.onDidChangeSelectedNotebooks(e => {
if (e.newKernel) {
const notebookExecution = this._executions.get(e.notebook);
if (notebookExecution) {
notebookExecution.cancelAll();
this.checkNotebookExecutionEmpty(e.notebook);
}
}
}));
}
getCellExecutionState(cellUri: URI): ICellExecutionEntry | undefined {
forceCancelNotebookExecutions(notebookUri: URI): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
return;
}
for (const exe of notebookExecutions.values()) {
this._onCellExecutionDidComplete(notebookUri, exe.cellHandle, exe);
}
}
getCellExecution(cellUri: URI): INotebookCellExecution | undefined {
const parsed = CellUri.parse(cellUri);
if (!parsed) {
throw new Error(`Not a cell URI: ${cellUri}`);
}
const exe = this._executions.get(parsed.notebook);
if (exe) {
return exe.getCellExecution(parsed.handle);
const exeMap = this._executions.get(parsed.notebook);
if (exeMap) {
return exeMap.get(parsed.handle);
}
return undefined;
}
getCellExecutionStatesForNotebook(notebook: URI): ICellExecutionEntry[] {
const exe = this._executions.get(notebook);
return (exe?.getCellExecutions() ?? []);
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[] {
const exeMap = this._executions.get(notebook);
return exeMap ? Array.from(exeMap.values()) : [];
}
createNotebookCellExecution(notebook: URI, cellHandle: number): void {
let notebookExecution = this._executions.get(notebook);
if (!notebookExecution) {
notebookExecution = this._instantiationService.createInstance(NotebookExecution, notebook);
this._executions.set(notebook, notebookExecution);
}
const exe = notebookExecution.addExecution(cellHandle);
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebook, cellHandle, exe));
private _onCellExecutionDidChange(notebookUri: URI, cellHandle: number, exe: CellExecution): void {
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe));
}
updateNotebookCellExecution(notebook: URI, cellHandle: number, updates: ICellExecuteUpdate[]): void {
const notebookExecution = this._executions.get(notebook);
if (!notebookExecution) {
this._logService.error(`notebook execution not found for ${notebook}`);
private _onCellExecutionDidComplete(notebookUri: URI, cellHandle: number, exe: CellExecution): void {
const notebookExecutions = this._executions.get(notebookUri);
if (!notebookExecutions) {
this._logService.debug(`NotebookExecutionStateService#_onCellExecutionDidComplete - unknown notebook ${notebookUri.toString()}`);
return;
}
const exe = notebookExecution.updateExecution(cellHandle, updates);
if (exe) {
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebook, cellHandle, exe));
exe.dispose();
const cellUri = CellUri.generate(notebookUri, cellHandle);
this._cellListeners.get(cellUri)?.dispose();
this._cellListeners.delete(cellUri);
notebookExecutions.delete(cellHandle);
if (notebookExecutions.size === 0) {
this._executions.delete(notebookUri);
this._notebookListeners.get(notebookUri)?.dispose();
this._notebookListeners.delete(notebookUri);
}
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle));
}
completeNotebookCellExecution(notebook: URI, cellHandle: number, complete: ICellExecutionComplete): void {
const notebookExecution = this._executions.get(notebook);
if (!notebookExecution) {
this._logService.error(`notebook execution not found for ${notebook}`);
return;
createCellExecution(controllerId: string, notebookUri: URI, cellHandle: number): INotebookCellExecution {
const notebook = this._notebookService.getNotebookTextModel(notebookUri);
if (!notebook) {
throw new Error(`Notebook not found: ${notebookUri.toString()}`);
}
const exe = notebookExecution.completeExecution(cellHandle, complete);
if (exe) {
this.checkNotebookExecutionEmpty(notebook);
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebook, cellHandle));
const kernel = this._notebookKernelService.getMatchingKernel(notebook);
if (!kernel.selected || kernel.selected.id !== controllerId) {
throw new Error(`Kernel is not selected: ${kernel.selected?.id} !== ${controllerId}`);
}
let notebookExecutionMap = this._executions.get(notebookUri);
if (!notebookExecutionMap) {
const listeners = this._instantiationService.createInstance(NotebookExecutionListeners, notebookUri);
this._notebookListeners.set(notebookUri, listeners);
notebookExecutionMap = new Map<number, CellExecution>();
this._executions.set(notebookUri, notebookExecutionMap);
}
let exe = notebookExecutionMap.get(cellHandle);
if (!exe) {
exe = this._createNotebookCellExecution(notebook, cellHandle);
notebookExecutionMap.set(cellHandle, exe);
}
return exe;
}
private checkNotebookExecutionEmpty(notebook: URI): void {
const notebookExecution = this._executions.get(notebook);
if (!notebookExecution) {
return;
}
private _createNotebookCellExecution(notebook: NotebookTextModel, cellHandle: number): CellExecution {
const notebookUri = notebook.uri;
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)));
this._cellListeners.set(CellUri.generate(notebookUri, cellHandle), disposable);
if (notebookExecution.isEmpty()) {
this._logService.debug(`NotebookExecution#dispose ${notebook.toString()}`);
notebookExecution.dispose();
this._executions.delete(notebook);
}
this._onDidChangeCellExecution.fire(new NotebookExecutionEvent(notebookUri, cellHandle, exe));
return exe;
}
override dispose(): void {
super.dispose();
this._executions.forEach(e => e.dispose());
this._executions.forEach(executionMap => {
executionMap.forEach(execution => execution.dispose());
executionMap.clear();
});
this._executions.clear();
this._cellListeners.forEach(disposable => disposable.dispose());
this._notebookListeners.forEach(disposable => disposable.dispose());
}
}
@ -137,16 +162,14 @@ class NotebookExecutionEvent implements ICellExecutionStateChangedEvent {
}
}
class NotebookExecution extends Disposable {
class NotebookExecutionListeners extends Disposable {
private readonly _notebookModel: NotebookTextModel;
private readonly _cellExecutions = new Map<number, CellExecution>();
constructor(
notebook: URI,
@INotebookService private readonly _notebookService: INotebookService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@INotebookExecutionService private readonly _notebookExecutionService: INotebookExecutionService,
@INotebookExecutionStateService private readonly _notebookExecutionStateService: INotebookExecutionStateService,
@ILogService private readonly _logService: ILogService,
) {
super();
@ -162,66 +185,10 @@ class NotebookExecution extends Disposable {
this._register(this._notebookModel.onWillDispose(() => this.onWillDisposeDocument()));
}
getCellExecution(cellHandle: number): CellExecution | undefined {
return this._cellExecutions.get(cellHandle);
}
getCellExecutions(): CellExecution[] {
return Array.from(this._cellExecutions.values());
}
private getCellLog(cellHandle: number): string {
return `${this._notebookModel.uri.toString()}, ${cellHandle}`;
}
isEmpty(): boolean {
return this._cellExecutions.size === 0;
}
cancelAll(): void {
this._logService.debug(`NotebookExecution#cancelAll`);
this._notebookExecutionService.cancelNotebookCellHandles(this._notebookModel, this._cellExecutions.keys());
}
addExecution(cellHandle: number): CellExecution {
this._logService.debug(`NotebookExecution#addExecution ${this.getCellLog(cellHandle)}`);
const execution = this._instantiationService.createInstance(CellExecution, cellHandle, this._notebookModel);
this._cellExecutions.set(cellHandle, execution);
return execution;
}
updateExecution(cellHandle: number, updates: ICellExecuteUpdate[]): CellExecution | undefined {
this.logUpdates(cellHandle, updates);
const execution = this._cellExecutions.get(cellHandle);
if (!execution) {
this._logService.error(`no execution for cell ${cellHandle}`);
return;
}
execution.update(updates);
return execution;
}
private logUpdates(cellHandle: number, updates: ICellExecuteUpdate[]): void {
const updateTypes = updates.map(u => CellExecutionUpdateType[u.editType]).join(', ');
this._logService.debug(`NotebookExecution#updateExecution ${this.getCellLog(cellHandle)}, [${updateTypes}]`);
}
completeExecution(cellHandle: number, complete: ICellExecutionComplete): CellExecution | undefined {
this._logService.debug(`NotebookExecution#completeExecution ${this.getCellLog(cellHandle)}`);
const execution = this._cellExecutions.get(cellHandle);
if (!execution) {
this._logService.error(`no execution for cell ${cellHandle}`);
return;
}
try {
execution.complete(complete);
return execution;
} finally {
this._cellExecutions.delete(cellHandle);
}
private cancelAll(): void {
this._logService.debug(`NotebookExecutionListeners#cancelAll`);
const exes = this._notebookExecutionStateService.getCellExecutionStatesForNotebook(this._notebookModel.uri);
this._notebookExecutionService.cancelNotebookCellHandles(this._notebookModel, exes.map(exe => exe.cellHandle));
}
private onWillDisposeDocument(): void {
@ -230,7 +197,8 @@ class NotebookExecution extends Disposable {
}
private onWillAddRemoveCells(e: NotebookTextModelWillAddRemoveEvent): void {
const handles = new Set(this._cellExecutions.keys());
const notebookExes = this._notebookExecutionStateService.getCellExecutionStatesForNotebook(this._notebookModel.uri);
const handles = new Set(notebookExes.map(exe => exe.cellHandle));
const myDeletedHandles = new Set<number>();
e.rawEvent.changes.forEach(([start, deleteCount]) => {
if (deleteCount) {
@ -256,7 +224,7 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEdit
if (update.editType === CellExecutionUpdateType.Output) {
return {
editType: CellEditType.Output,
handle: update.cellHandle,
handle: cellHandle,
append: update.append,
outputs: update.outputs,
};
@ -268,7 +236,7 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEdit
outputId: update.outputId
};
} else if (update.editType === CellExecutionUpdateType.ExecutionState) {
const newInternalMetadata: Partial<NotebookCellInternalMetadata> = { };
const newInternalMetadata: Partial<NotebookCellInternalMetadata> = {};
if (typeof update.executionOrder !== 'undefined') {
newInternalMetadata.executionOrder = update.executionOrder;
}
@ -285,7 +253,13 @@ function updateToEdit(update: ICellExecuteUpdate, cellHandle: number): ICellEdit
throw new Error('Unknown cell update type');
}
class CellExecution implements ICellExecutionEntry {
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>());
readonly onDidComplete = this._onDidComplete.event;
private _state: NotebookCellExecutionState = NotebookCellExecutionState.Pending;
get state() {
return this._state;
@ -310,6 +284,7 @@ class CellExecution implements ICellExecutionEntry {
private readonly _notebookModel: NotebookTextModel,
@ILogService private readonly _logService: ILogService,
) {
super();
const startExecuteEdit: ICellEditOperation = {
editType: CellEditType.PartialInternalMetadata,
handle: this.cellHandle,
@ -323,7 +298,17 @@ class CellExecution implements ICellExecutionEntry {
this._applyExecutionEdits([startExecuteEdit]);
}
private getCellLog(cellHandle: number): string {
return `${this._notebookModel.uri.toString()}, ${cellHandle}`;
}
private logUpdates(cellHandle: number, updates: ICellExecuteUpdate[]): void {
const updateTypes = updates.map(u => CellExecutionUpdateType[u.editType]).join(', ');
this._logService.debug(`NotebookExecution#updateExecution ${this.getCellLog(cellHandle)}, [${updateTypes}]`);
}
update(updates: ICellExecuteUpdate[]): void {
this.logUpdates(this.cellHandle, updates);
if (updates.some(u => u.editType === CellExecutionUpdateType.ExecutionState)) {
this._state = NotebookCellExecutionState.Executing;
}
@ -337,27 +322,35 @@ class CellExecution implements ICellExecutionEntry {
this._isPaused = (lastIsPausedUpdate as ICellExecutionStateUpdate).isPaused!;
}
const edits = updates.map(update => updateToEdit(update, this.cellHandle));
this._applyExecutionEdits(edits);
const cellModel = this._notebookModel.cells.find(c => c.handle === this.cellHandle);
if (!cellModel) {
this._logService.debug(`CellExecution#update, updating cell not in notebook: ${this._notebookModel.uri.toString()}, ${this.cellHandle}`);
} else {
const edits = updates.map(update => updateToEdit(update, this.cellHandle));
this._applyExecutionEdits(edits);
}
this._onDidUpdate.fire();
}
complete(completionData: ICellExecutionComplete): void {
const cellModel = this._notebookModel.cells.find(c => c.handle === this.cellHandle);
if (!cellModel) {
this._logService.debug(`CellExecution#complete, updating cell not in notebook: ${this._notebookModel.uri.toString()}, ${this.cellHandle}`);
return;
this._logService.debug(`CellExecution#complete, completing cell not in notebook: ${this._notebookModel.uri.toString()}, ${this.cellHandle}`);
} else {
const edit: ICellEditOperation = {
editType: CellEditType.PartialInternalMetadata,
handle: this.cellHandle,
internalMetadata: {
lastRunSuccess: completionData.lastRunSuccess,
runStartTime: this._didPause ? null : cellModel.internalMetadata.runStartTime,
runEndTime: this._didPause ? null : completionData.runEndTime,
}
};
this._applyExecutionEdits([edit]);
}
const edit: ICellEditOperation = {
editType: CellEditType.PartialInternalMetadata,
handle: this.cellHandle,
internalMetadata: {
lastRunSuccess: completionData.lastRunSuccess,
runStartTime: this._didPause ? null : cellModel.internalMetadata.runStartTime,
runEndTime: this._didPause ? null : completionData.runEndTime,
}
};
this._applyExecutionEdits([edit]);
this._onDidComplete.fire();
}
private _applyExecutionEdits(edits: ICellEditOperation[]): void {

View file

@ -127,7 +127,7 @@ export class CellContextKeyManager extends Disposable {
const internalMetadata = this.element.internalMetadata;
this.cellEditable.set(!this.notebookEditor.isReadOnly);
const exeState = this._notebookExecutionStateService.getCellExecutionState(this.element.uri);
const exeState = this._notebookExecutionStateService.getCellExecution(this.element.uri);
if (this.element instanceof MarkupCellViewModel) {
this.cellRunState.reset();
this.cellExecuting.reset();

View file

@ -48,7 +48,7 @@ export class CellProgressBar extends CellPart {
}
if (e.inputCollapsedChanged) {
const exeState = this._notebookExecutionStateService.getCellExecutionState(element.uri);
const exeState = this._notebookExecutionStateService.getCellExecution(element.uri);
if (element.isInputCollapsed) {
this._progressBar.hide();
if (exeState?.state === NotebookCellExecutionState.Executing) {
@ -64,7 +64,7 @@ export class CellProgressBar extends CellPart {
}
private _updateForExecutionState(element: ICellViewModel, e?: ICellExecutionStateChangedEvent): void {
const exeState = e?.changed ?? this._notebookExecutionStateService.getCellExecutionState(element.uri);
const exeState = e?.changed ?? this._notebookExecutionStateService.getCellExecution(element.uri);
const progressBar = element.isInputCollapsed ? this._collapsedProgressBar : this._progressBar;
if (exeState?.state === NotebookCellExecutionState.Executing && !exeState.isPaused) {
showProgressBar(progressBar);

View file

@ -15,7 +15,6 @@ export enum CellExecutionUpdateType {
export interface ICellExecuteOutputEdit {
editType: CellExecutionUpdateType.Output;
cellHandle: number;
append?: boolean;
outputs: IOutputDto[]
}

View file

@ -35,7 +35,7 @@ export interface ICellExecutionEntry {
export interface ICellExecutionStateChangedEvent {
notebook: URI;
cellHandle: number;
changed?: ICellExecutionEntry; // undefined -> execution was completed
changed?: INotebookCellExecution; // undefined -> execution was completed
affectsCell(cell: URI): boolean;
affectsNotebook(notebook: URI): boolean;
}
@ -47,11 +47,19 @@ export interface INotebookExecutionStateService {
onDidChangeCellExecution: Event<ICellExecutionStateChangedEvent>;
getCellExecutionStatesForNotebook(notebook: URI): ICellExecutionEntry[];
getCellExecutionState(cellUri: URI): ICellExecutionEntry | undefined;
createNotebookCellExecution(notebook: URI, cellHandle: number): void;
updateNotebookCellExecution(notebook: URI, cellHandle: number, updates: ICellExecuteUpdate[]): void;
completeNotebookCellExecution(notebook: URI, cellHandle: number, complete: ICellExecutionComplete): void;
forceCancelNotebookExecutions(notebookUri: URI): void
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[];
getCellExecution(cellUri: URI): INotebookCellExecution | undefined;
createCellExecution(controllerId: string, notebook: URI, cellHandle: number): INotebookCellExecution;
}
export interface INotebookCellExecution {
readonly notebook: URI;
readonly cellHandle: number;
readonly state: NotebookCellExecutionState;
readonly didPause: boolean;
readonly isPaused: boolean;
update(updates: ICellExecuteUpdate[]): void;
complete(complete: ICellExecutionComplete): void;
}

View file

@ -479,7 +479,7 @@ export class NotebookOptions extends Disposable {
}
private statusBarIsVisible(internalMetadata: NotebookCellInternalMetadata, cellUri: URI): boolean {
const exe = this.notebookExecutionStateService.getCellExecutionState(cellUri);
const exe = this.notebookExecutionStateService.getCellExecution(cellUri);
if (this._layoutConfiguration.showCellStatusBar === 'visible') {
return true;
} else if (this._layoutConfiguration.showCellStatusBar === 'visibleAfterExecute') {

View file

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { timeout } from 'vs/base/common/async';
import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
@ -20,6 +19,7 @@ import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellEditType, CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { INotebookKernel, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/browser/testNotebookEditor';
@ -29,6 +29,7 @@ suite('NotebookExecutionStateService', () => {
let instantiationService: TestInstantiationService;
let kernelService: INotebookKernelService;
let disposables: DisposableStore;
let testNotebookModel: NotebookTextModel | undefined;
setup(function () {
@ -40,11 +41,15 @@ suite('NotebookExecutionStateService', () => {
override onDidAddNotebookDocument = Event.None;
override onWillRemoveNotebookDocument = Event.None;
override getNotebookTextModels() { return []; }
override getNotebookTextModel(uri: URI): NotebookTextModel | undefined {
return testNotebookModel;
}
});
kernelService = instantiationService.createInstance(NotebookKernelService);
instantiationService.set(INotebookKernelService, kernelService);
instantiationService.set(INotebookExecutionService, instantiationService.createInstance(NotebookExecutionService));
instantiationService.set(INotebookExecutionStateService, instantiationService.createInstance(NotebookExecutionStateService));
});
teardown(() => {
@ -55,13 +60,9 @@ suite('NotebookExecutionStateService', () => {
return _withTestNotebook(cells, (editor, viewModel) => callback(viewModel, viewModel.notebookDocument));
}
test('cancel execution when cell is deleted', async function () {
test('cancel execution when cell is deleted', async function () { // TODO@roblou Should be a test for NotebookExecutionListeners, which can be a standalone contribution
return withTestNotebook([], async viewModel => {
instantiationService.stub(INotebookService, new class extends mock<INotebookService>() {
override getNotebookTextModel(uri: URI): NotebookTextModel | undefined {
return viewModel.notebookDocument;
}
});
testNotebookModel = viewModel.notebookDocument;
let didCancel = false;
const kernel = new class extends TestNotebookKernel {
@ -76,19 +77,64 @@ suite('NotebookExecutionStateService', () => {
}
};
kernelService.registerKernel(kernel);
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
const executionStateService: NotebookExecutionStateService = instantiationService.createInstance(NotebookExecutionStateService);
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
executionStateService.createNotebookCellExecution(viewModel.uri, cell.handle);
executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle);
assert.strictEqual(didCancel, false);
viewModel.notebookDocument.applyEdits([{
editType: CellEditType.Replace, index: 0, count: 1, cells: []
}], true, undefined, () => undefined, undefined, false);
await timeout(0);
assert.strictEqual(didCancel, true);
});
});
test('fires onDidChangeCellExecution when cell is completed while deleted', async function () {
return withTestNotebook([], async viewModel => {
testNotebookModel = viewModel.notebookDocument;
const kernel = new TestNotebookKernel();
kernelService.registerKernel(kernel);
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
const exe = executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle);
let didFire = false;
disposables.add(executionStateService.onDidChangeCellExecution(e => {
didFire = !e.changed;
}));
viewModel.notebookDocument.applyEdits([{
editType: CellEditType.Replace, index: 0, count: 1, cells: []
}], true, undefined, () => undefined, undefined, false);
exe.complete({});
assert.strictEqual(didFire, true);
});
});
test('force-cancel works', async function () {
return withTestNotebook([], async viewModel => {
testNotebookModel = viewModel.notebookDocument;
const kernel = new TestNotebookKernel();
kernelService.registerKernel(kernel);
kernelService.selectKernelForNotebook(kernel, viewModel.notebookDocument);
const executionStateService: INotebookExecutionStateService = instantiationService.get(INotebookExecutionStateService);
const cell = insertCellAtIndex(viewModel, 0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true, true);
executionStateService.createCellExecution(kernel.id, viewModel.uri, cell.handle);
const exe = executionStateService.getCellExecution(cell.uri);
assert.ok(exe);
executionStateService.forceCancelNotebookExecutions(viewModel.uri);
const exe2 = executionStateService.getCellExecution(cell.uri);
assert.strictEqual(exe2, undefined);
});
});
});
class TestNotebookKernel implements INotebookKernel {
@ -110,7 +156,10 @@ class TestNotebookKernel implements INotebookKernel {
throw new Error('Method not implemented.');
}
constructor(opts?: { languages: string[] }) {
constructor(opts?: { languages?: string[], id?: string }) {
this.supportedLanguages = opts?.languages ?? [PLAINTEXT_LANGUAGE_ID];
if (opts?.id) {
this.id = opts?.id;
}
}
}

View file

@ -50,7 +50,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, NotebookCellMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionEntry, ICellExecutionStateChangedEvent, INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
import { ICellExecuteUpdate, ICellExecutionComplete, ICellExecutionStateChangedEvent, INotebookCellExecution, INotebookExecutionStateService } 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';
@ -416,15 +416,19 @@ class TestNotebookExecutionStateService implements INotebookExecutionStateServic
onDidChangeCellExecution = new Emitter<ICellExecutionStateChangedEvent>().event;
getCellExecutionStatesForNotebook(notebook: URI): ICellExecutionEntry[] {
forceCancelNotebookExecutions(notebookUri: URI): void {
}
getCellExecutionStatesForNotebook(notebook: URI): INotebookCellExecution[] {
return [];
}
getCellExecutionState(cellUri: URI): ICellExecutionEntry | undefined {
getCellExecution(cellUri: URI): INotebookCellExecution | undefined {
return undefined;
}
createNotebookCellExecution(notebook: URI, cellHandle: number): void {
createCellExecution(controllerId: string, notebook: URI, cellHandle: number): INotebookCellExecution {
return undefined!;
}
updateNotebookCellExecution(notebook: URI, cellHandle: number, updates: ICellExecuteUpdate[]): void {

View file

@ -6,11 +6,11 @@
import * as assert from 'assert';
import { Barrier } from 'vs/base/common/async';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { URI, UriComponents } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { NullLogService } from 'vs/platform/log/common/log';
import { ICellExecuteUpdateDto, INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ICellExecuteUpdateDto, ICellExecutionCompleteDto, INotebookKernelDto2, MainContext, MainThreadCommandsShape, MainThreadNotebookDocumentsShape, MainThreadNotebookKernelsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
@ -43,13 +43,17 @@ suite('NotebookKernel', function () {
const kernelData = new Map<number, INotebookKernelDto2>();
const disposables = new DisposableStore();
const cellExecuteCreate: { notebook: UriComponents, cell: number }[] = [];
const cellExecuteUpdates: ICellExecuteUpdateDto[] = [];
const cellExecuteComplete: ICellExecutionCompleteDto[] = [];
teardown(function () {
disposables.clear();
});
setup(async function () {
cellExecuteCreate.length = 0;
cellExecuteUpdates.length = 0;
cellExecuteComplete.length = 0;
kernelData.clear();
rpcProtocol = new TestRPCProtocol();
@ -67,9 +71,15 @@ suite('NotebookKernel', function () {
assert.strictEqual(kernelData.has(handle), true);
kernelData.set(handle, { ...kernelData.get(handle)!, ...data, });
}
override $updateExecutions(data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {
override $createExecution(handle: number, controllerId: string, uri: UriComponents, cellHandle: number): void {
cellExecuteCreate.push({ notebook: uri, cell: cellHandle });
}
override $updateExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecuteUpdateDto[]>): void {
cellExecuteUpdates.push(...data.value);
}
override $completeExecution(handle: number, data: SerializableObjectWithBuffers<ICellExecutionCompleteDto>): void {
cellExecuteComplete.push(data.value);
}
});
rpcProtocol.set(MainContext.MainThreadNotebookDocuments, new class extends mock<MainThreadNotebookDocumentsShape>() {