mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-21 17:19:01 +01:00
Implement Execution API (#116416)
Implement new execution task API #106744 Fix #105847
This commit is contained in:
@@ -17,7 +17,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
|
||||
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
|
||||
import { CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellStatusbarAlignment, CellUri, ICellRange, INotebookCellStatusBarEntry, INotebookExclusiveDocumentFilter, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, TransientOptions, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import * as vscode from 'vscode';
|
||||
import { ResourceMap } from 'vs/base/common/map';
|
||||
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
|
||||
@@ -125,7 +125,8 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
detail: kernel.detail,
|
||||
isPreferred: kernel.isPreferred,
|
||||
preloads: kernel.preloads,
|
||||
supportedLanguages: kernel.supportedLanguages
|
||||
supportedLanguages: kernel.supportedLanguages,
|
||||
implementsInterrupt: !!kernel.interrupt
|
||||
};
|
||||
});
|
||||
|
||||
@@ -151,42 +152,25 @@ export class ExtHostNotebookKernelProviderAdapter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cellRange: ICellRange[]): Promise<void> {
|
||||
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
return withToken(token => (kernel.executeCell as any)(document.notebookDocument, cell.cell, token));
|
||||
} else {
|
||||
return withToken(token => (kernel.executeAllCells as any)(document.notebookDocument, token));
|
||||
}
|
||||
const extCellRange = cellRange.map(c => typeConverters.NotebookCellRange.to(c));
|
||||
return kernel.executeCellsRequest(document.notebookDocument, extCellRange);
|
||||
}
|
||||
|
||||
async cancelNotebook(kernelId: string, document: ExtHostNotebookDocument, cell: ExtHostCell | undefined) {
|
||||
async interruptNotebookExecution(kernelId: string, document: ExtHostNotebookDocument): Promise<void> {
|
||||
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
|
||||
|
||||
if (!kernel) {
|
||||
if (!kernel || !kernel.interrupt) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (cell) {
|
||||
return kernel.cancelCellExecution(document.notebookDocument, cell.cell);
|
||||
} else {
|
||||
return kernel.cancelAllCellsExecution(document.notebookDocument);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO@roblou remove 'token' passed to all execute APIs once extensions are updated
|
||||
async function withToken(cb: (token: CancellationToken) => any) {
|
||||
const source = new CancellationTokenSource();
|
||||
try {
|
||||
await cb(source.token);
|
||||
} finally {
|
||||
source.dispose();
|
||||
return kernel.interrupt(document.notebookDocument);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,6 +223,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
readonly onDidChangeCellMetadata = this._onDidChangeCellMetadata.event;
|
||||
private readonly _onDidChangeActiveNotebookEditor = new Emitter<vscode.NotebookEditor | undefined>();
|
||||
readonly onDidChangeActiveNotebookEditor = this._onDidChangeActiveNotebookEditor.event;
|
||||
private readonly _onDidChangeCellExecutionState = new Emitter<vscode.NotebookCellExecutionStateChangeEvent>();
|
||||
readonly onDidChangeNotebookCellExecutionState = this._onDidChangeCellExecutionState.event;
|
||||
|
||||
private _activeNotebookEditor: ExtHostNotebookEditor | undefined;
|
||||
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
|
||||
@@ -260,6 +246,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
|
||||
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
|
||||
|
||||
private _activeExecutions = new ResourceMap<NotebookCellExecutionTask>();
|
||||
|
||||
constructor(
|
||||
mainContext: IMainContext,
|
||||
commands: ExtHostCommands,
|
||||
@@ -481,19 +469,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
await provider.provider.resolveNotebook(document.notebookDocument, webComm.contentProviderComm);
|
||||
}
|
||||
|
||||
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
|
||||
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
|
||||
return adapter.executeNotebook(kernelId, document, cell);
|
||||
});
|
||||
}
|
||||
|
||||
async $cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
const cell = cellHandle !== undefined ? document.getCell(cellHandle) : undefined;
|
||||
|
||||
return adapter.cancelNotebook(kernelId, document, cell);
|
||||
return adapter.executeNotebook(kernelId, document, cellRange);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -540,6 +518,31 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
return VSBuffer.wrap(bytes);
|
||||
}
|
||||
|
||||
async $cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void> {
|
||||
await this._withAdapter(handle, uri, async (adapter, document) => {
|
||||
return adapter.interruptNotebookExecution(kernelId, document);
|
||||
});
|
||||
|
||||
const document = this._documents.get(URI.revive(uri));
|
||||
if (!document) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let range of cellRange) {
|
||||
for (let i = range.start; i < range.end; i++) {
|
||||
const cell = document.getCellFromIndex(i);
|
||||
if (cell) {
|
||||
this.cancelOneNotebookCellExecution(cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private cancelOneNotebookCellExecution(cell: ExtHostCell): void {
|
||||
const execution = this._activeExecutions.get(cell.uri);
|
||||
execution?.cancel();
|
||||
}
|
||||
|
||||
// --- open, save, saveAs, backup
|
||||
|
||||
async $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto> {
|
||||
@@ -729,6 +732,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void {
|
||||
that._onDidChangeCellMetadata.fire(event);
|
||||
},
|
||||
emitCellExecutionStateChange(event: vscode.NotebookCellExecutionStateChangeEvent): void {
|
||||
that._onDidChangeCellExecutionState.fire(event);
|
||||
}
|
||||
},
|
||||
viewType,
|
||||
modelData.metadata ? typeConverters.NotebookDocumentMetadata.to(modelData.metadata) : new extHostTypes.NotebookDocumentMetadata(),
|
||||
@@ -837,6 +843,35 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
|
||||
|
||||
return statusBarItem;
|
||||
}
|
||||
|
||||
createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
|
||||
const document = this.lookupNotebookDocument(docUri);
|
||||
if (!document) {
|
||||
throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`);
|
||||
}
|
||||
|
||||
const cell = document.getCellFromIndex(index);
|
||||
if (!cell) {
|
||||
throw new Error(`Invalid cell uri/index: ${docUri}, ${index}`);
|
||||
}
|
||||
|
||||
// TODO@roblou also validate kernelId, once kernel has moved from editor to document
|
||||
if (this._activeExecutions.has(cell.uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const execution = new NotebookCellExecutionTask(docUri, document, cell, this._proxy);
|
||||
this._activeExecutions.set(cell.uri, execution);
|
||||
const listener = execution.onDidChangeState(() => {
|
||||
if (execution.state === NotebookCellExecutionTaskState.Resolved) {
|
||||
execution.dispose();
|
||||
listener.dispose();
|
||||
this._activeExecutions.delete(cell.uri);
|
||||
}
|
||||
});
|
||||
|
||||
return execution.asApiObject();
|
||||
}
|
||||
}
|
||||
|
||||
export class NotebookCellStatusBarItemInternal extends Disposable {
|
||||
@@ -1013,3 +1048,153 @@ function createNotebookCellStatusBarApiItem(internalItem: NotebookCellStatusBarI
|
||||
dispose() { internalItem.dispose(); }
|
||||
});
|
||||
}
|
||||
|
||||
enum NotebookCellExecutionTaskState {
|
||||
Init,
|
||||
Started,
|
||||
Resolved
|
||||
}
|
||||
|
||||
class NotebookCellExecutionTask extends Disposable {
|
||||
private _onDidChangeState = new Emitter<void>();
|
||||
readonly onDidChangeState = this._onDidChangeState.event;
|
||||
|
||||
private _state = NotebookCellExecutionTaskState.Init;
|
||||
get state(): NotebookCellExecutionTaskState { return this._state; }
|
||||
|
||||
private readonly _tokenSource: CancellationTokenSource;
|
||||
|
||||
private _executionOrder: number | undefined;
|
||||
|
||||
constructor(
|
||||
private readonly _uri: vscode.Uri,
|
||||
private readonly _document: ExtHostNotebookDocument,
|
||||
private readonly _cell: ExtHostCell,
|
||||
private readonly _proxy: MainThreadNotebookShape) {
|
||||
super();
|
||||
this._tokenSource = this._register(new CancellationTokenSource());
|
||||
|
||||
this._executionOrder = _cell.internalMetadata.executionOrder;
|
||||
this.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Pending,
|
||||
lastRunDuration: null,
|
||||
executionOrder: null
|
||||
});
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this._tokenSource.cancel();
|
||||
}
|
||||
|
||||
private async applyEdits(edits: IImmediateCellEditOperation[]): Promise<void> {
|
||||
return this._proxy.$applyEdits(this._uri, edits, false);
|
||||
}
|
||||
|
||||
private verifyStateForOutput() {
|
||||
if (this._state === NotebookCellExecutionTaskState.Init) {
|
||||
throw new Error('Must call start before modifying cell output');
|
||||
}
|
||||
|
||||
if (this._state === NotebookCellExecutionTaskState.Resolved) {
|
||||
throw new Error('Cannot modify cell output after calling resolve');
|
||||
}
|
||||
}
|
||||
|
||||
private mixinMetadata(mixinMetadata: NullablePartialNotebookCellMetadata) {
|
||||
const edits: IImmediateCellEditOperation[] = [
|
||||
{ editType: CellEditType.PartialMetadata, handle: this._cell.handle, metadata: mixinMetadata }
|
||||
];
|
||||
this.applyEdits(edits);
|
||||
}
|
||||
|
||||
private cellIndexToHandle(cellIndex: number | undefined): number | undefined {
|
||||
const cell = typeof cellIndex === 'number' ? this._document.getCellFromIndex(cellIndex) : this._cell;
|
||||
if (!cell) {
|
||||
return;
|
||||
}
|
||||
|
||||
return cell.handle;
|
||||
}
|
||||
|
||||
asApiObject(): vscode.NotebookCellExecutionTask {
|
||||
const that = this;
|
||||
return Object.freeze(<vscode.NotebookCellExecutionTask>{
|
||||
get document() { return that._document.notebookDocument; },
|
||||
get cell() { return that._cell.cell; },
|
||||
|
||||
get executionOrder() { return that._executionOrder; },
|
||||
set executionOrder(v: number | undefined) {
|
||||
that._executionOrder = v;
|
||||
that.mixinMetadata({
|
||||
executionOrder: v
|
||||
});
|
||||
},
|
||||
|
||||
start(context?: vscode.NotebookCellExecuteStartContext): void {
|
||||
if (that._state === NotebookCellExecutionTaskState.Resolved || that._state === NotebookCellExecutionTaskState.Started) {
|
||||
throw new Error('Cannot call start again');
|
||||
}
|
||||
|
||||
that._state = NotebookCellExecutionTaskState.Started;
|
||||
that._onDidChangeState.fire();
|
||||
|
||||
that.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Executing,
|
||||
runStartTime: context?.startTime
|
||||
});
|
||||
},
|
||||
|
||||
end(result?: vscode.NotebookCellExecuteEndContext): void {
|
||||
if (that._state === NotebookCellExecutionTaskState.Resolved) {
|
||||
throw new Error('Cannot call resolve twice');
|
||||
}
|
||||
|
||||
that._state = NotebookCellExecutionTaskState.Resolved;
|
||||
that._onDidChangeState.fire();
|
||||
|
||||
that.mixinMetadata({
|
||||
runState: NotebookCellExecutionState.Idle,
|
||||
lastRunSuccess: result?.success ?? null,
|
||||
lastRunDuration: result?.duration ?? null,
|
||||
});
|
||||
},
|
||||
|
||||
clearOutput(cellIndex?: number): Thenable<void> {
|
||||
that.verifyStateForOutput();
|
||||
return this.replaceOutput([], cellIndex);
|
||||
},
|
||||
|
||||
async appendOutput(outputs: vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
const handle = that.cellIndexToHandle(cellIndex);
|
||||
if (typeof handle !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
return that.applyEdits([{ editType: CellEditType.Output, handle, append: true, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]);
|
||||
},
|
||||
|
||||
async replaceOutput(outputs: vscode.NotebookCellOutput[], cellIndex?: number): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
const handle = that.cellIndexToHandle(cellIndex);
|
||||
if (typeof handle !== 'number') {
|
||||
return;
|
||||
}
|
||||
|
||||
return that.applyEdits([{ editType: CellEditType.Output, handle, outputs: outputs.map(typeConverters.NotebookCellOutput.from) }]);
|
||||
},
|
||||
|
||||
async appendOutputItems(items: vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
return that.applyEdits([{ editType: CellEditType.OutputItems, append: true, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]);
|
||||
},
|
||||
|
||||
async replaceOutputItems(items: vscode.NotebookCellOutputItem[], outputId: string): Promise<void> {
|
||||
that.verifyStateForOutput();
|
||||
return that.applyEdits([{ editType: CellEditType.OutputItems, items: items.map(typeConverters.NotebookCellOutputItem.from), outputId }]);
|
||||
},
|
||||
|
||||
token: that._tokenSource.token
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user