Implement Execution API (#116416)

Implement new execution task API
#106744
Fix #105847
This commit is contained in:
Rob Lourens 2021-03-22 11:27:00 -07:00 committed by GitHub
parent 5a0fe1f6a8
commit 7b96cc4c8b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 1100 additions and 440 deletions

View file

@ -53,79 +53,106 @@ async function withEvent<T>(event: vscode.Event<T>, callback: (e: Promise<T>) =>
await callback(e);
}
const kernel1: vscode.NotebookKernel = {
id: 'mainKernel',
label: 'Notebook Test Kernel',
isPreferred: true,
supportedLanguages: ['typescript', 'javascript'],
executeAllCells: async (_document: vscode.NotebookDocument) => {
const edit = new vscode.WorkspaceEdit();
const kernel1 = new class implements vscode.NotebookKernel {
readonly id = 'mainKernel';
readonly label = 'Notebook Test Kernel';
readonly isPreferred = true;
readonly supportedLanguages = ['typescript', 'javascript'];
edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined)
])]);
return vscode.workspace.applyEdit(edit);
},
cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { },
executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => {
if (!cell) {
cell = document.cells[0];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) {
if (ranges.length > 1 || ranges[0].start + 1 < ranges[0].end) {
// Keeping same behavior... if the full notebook is executed, just execute the first cell
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, 0, 'mainKernel');
if (!task) {
return;
}
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined)
])]);
task.end({ success: true });
return;
}
for (let range of ranges) {
for (let i = range.start; i < range.end; i++) {
await this.runCell(document, i);
}
}
}
private async runCell(document: vscode.NotebookDocument, idx: number) {
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'mainKernel');
if (!task) {
return;
}
task.start();
task.executionOrder = 1;
if (document.uri.path.endsWith('customRenderer.vsctestnb')) {
const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined)
])]);
return vscode.workspace.applyEdit(edit);
return;
}
const edit = new vscode.WorkspaceEdit();
// const previousOutputs = cell.outputs;
edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined)
])]);
return vscode.workspace.applyEdit(edit);
},
cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { }
task.end({ success: true });
}
};
const kernel2: vscode.NotebookKernel = {
id: 'secondaryKernel',
label: 'Notebook Secondary Test Kernel',
isPreferred: false,
supportedLanguages: ['typescript', 'javascript'],
executeAllCells: async (_document: vscode.NotebookDocument) => {
const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined)
])]);
const kernel2 = new class implements vscode.NotebookKernel {
readonly id = 'secondaryKernel';
readonly label = 'Notebook Secondary Test Kernel';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
return vscode.workspace.applyEdit(edit);
},
cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { },
executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => {
if (!cell) {
cell = document.cells[0];
}
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) {
if (ranges.length > 1 || ranges[0].start + 1 < ranges[0].end) {
// Keeping same behavior... if the full notebook is executed, just execute the first cell
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, 0, 'secondaryKernel');
if (!task) {
return;
}
const edit = new vscode.WorkspaceEdit();
if (document.uri.path.endsWith('customRenderer.vsctestnb')) {
edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/custom', ['test 2'], undefined)
])]);
} else {
edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined)
])]);
task.end({ success: true });
return;
}
return vscode.workspace.applyEdit(edit);
},
cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { }
for (let range of ranges) {
for (let i = range.start; i < range.end; i++) {
await this.runCell(document, i);
}
}
}
private async runCell(document: vscode.NotebookDocument, idx: number) {
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'mainKernel');
if (!task) {
return;
}
task.start();
if (document.uri.path.endsWith('customRenderer.vsctestnb')) {
task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/custom', ['test 2'], undefined)
])]);
task.end({ success: true });
return;
}
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined)
])]);
task.end({ success: true });
}
};
class KernelProvider implements vscode.NotebookKernelProvider {
@ -133,7 +160,12 @@ class KernelProvider implements vscode.NotebookKernelProvider {
onDidChangeKernels = this._onDidChangeKernels.event;
private _hasKernels = true;
private readonly _kernels = [kernel1, kernel2];
private readonly _kernels: vscode.NotebookKernel[] = [kernel1, kernel2];
addKernel(kernel: vscode.NotebookKernel): void {
this._kernels.push(kernel);
this._onDidChangeKernels.fire(undefined);
}
provideKernels(): vscode.ProviderResult<vscode.NotebookKernel[]> {
return this._hasKernels ? this._kernels : [];
@ -144,11 +176,13 @@ class KernelProvider implements vscode.NotebookKernelProvider {
this._onDidChangeKernels.fire(undefined);
}
}
let currentKernerProvider: KernelProvider;
let currentKernelProvider: KernelProvider;
suite('Notebook API tests', function () {
const disposables: vscode.Disposable[] = [];
const testDisposables: vscode.Disposable[] = [];
const suiteDisposables: vscode.Disposable[] = [];
suiteTeardown(async function () {
@ -157,12 +191,12 @@ suite('Notebook API tests', function () {
await revertAllDirty();
await closeAllEditors();
disposeAll(disposables);
disposables.length = 0;
disposeAll(suiteDisposables);
suiteDisposables.length = 0;
});
suiteSetup(function () {
disposables.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', {
suiteDisposables.push(vscode.notebook.registerNotebookContentProvider('notebookCoreTest', {
openNotebook: async (_resource: vscode.Uri): Promise<vscode.NotebookData> => {
if (/.*empty\-.*\.vsctestnb$/.test(_resource.path)) {
return {
@ -191,6 +225,7 @@ suite('Notebook API tests', function () {
],
{ testOutputMetadata: true })
],
previousResult: { executionOrder: 5, success: true },
metadata: new vscode.NotebookCellMetadata().with({ custom: { testCellMetadata: 456 } })
}
]
@ -213,9 +248,16 @@ suite('Notebook API tests', function () {
};
}
}));
});
currentKernerProvider = new KernelProvider();
disposables.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, currentKernerProvider));
setup(() => {
currentKernelProvider = new KernelProvider();
testDisposables.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, currentKernelProvider));
});
teardown(() => {
disposeAll(testDisposables);
testDisposables.length = 0;
});
test('shared document in notebook editors', async function () {
@ -391,12 +433,11 @@ suite('Notebook API tests', function () {
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.window.activeNotebookEditor!.edit(editBuilder => {
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 }));
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true }));
});
const document = vscode.window.activeNotebookEditor?.document!;
assert.strictEqual(document.cells.length, 2);
assert.strictEqual(document.cells[0].metadata.executionOrder, 17);
assert.strictEqual(document.cells[0].metadata.inputCollapsed, true);
assert.strictEqual(document.isDirty, true);
@ -410,12 +451,11 @@ suite('Notebook API tests', function () {
const event = asPromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
await vscode.window.activeNotebookEditor!.edit(editBuilder => {
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true, executionOrder: 17 }));
editBuilder.replaceCellMetadata(0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true }));
});
const data = await event;
assert.strictEqual(data.document, vscode.window.activeNotebookEditor?.document);
assert.strictEqual(data.cell.metadata.executionOrder, 17);
assert.strictEqual(data.cell.metadata.inputCollapsed, true);
assert.strictEqual(data.document.isDirty, true);
@ -499,6 +539,8 @@ suite('Notebook API tests', function () {
assert.strictEqual(secondCell!.outputs[0].outputs[0].mime, 'text/plain');
assert.strictEqual(secondCell!.outputs[0].outputs[0].value, 'Hello World');
assert.deepStrictEqual(secondCell!.outputs[0].outputs[0].metadata, { testOutputItemMetadata: true });
assert.strictEqual(secondCell!.previousResult?.executionOrder, 5);
assert.strictEqual(secondCell!.previousResult?.success, true);
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), '');
@ -637,11 +679,11 @@ suite('Notebook API tests', function () {
const cell = editor.document.cells[0];
assert.strictEqual(cell.outputs.length, 0);
currentKernerProvider.setHasKernels(false);
currentKernelProvider.setHasKernels(false);
await vscode.commands.executeCommand('notebook.execute');
assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work
currentKernerProvider.setHasKernels(true);
currentKernelProvider.setHasKernels(true);
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.execute');
@ -737,26 +779,158 @@ suite('Notebook API tests', function () {
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'my output'
]);
vscode.commands.executeCommand('notebook.cell.execute');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'my output'
]);
});
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: 'secondaryKernel' });
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'my second output'
]);
vscode.commands.executeCommand('notebook.cell.execute');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'my second output'
]);
});
await saveAllFilesAndCloseAll(undefined);
});
// });
test('set outputs on cancel', async () => {
const cancelableKernel = new class implements vscode.NotebookKernel {
readonly id = 'cancelableKernel';
readonly label = 'Notebook Cancelable Test Kernel';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) {
const idx = ranges[0].start;
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'cancelableKernel');
if (!task) {
return;
}
task.start();
task.token.onCancellationRequested(async () => {
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Canceled'], undefined)
])]);
task.end({});
});
}
};
currentKernelProvider.addKernel(cancelableKernel);
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: cancelableKernel.id });
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.cell.execute');
await vscode.commands.executeCommand('notebook.cell.cancelExecution');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'Canceled'
]);
});
await saveAllFilesAndCloseAll(undefined);
});
test('set outputs on interrupt', async () => {
const interruptableKernel = new class implements vscode.NotebookKernel {
readonly id = 'interruptableKernel';
readonly label = 'Notebook Interruptable Test Kernel';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
private _task: vscode.NotebookCellExecutionTask | undefined;
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) {
const idx = ranges[0].start;
this._task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'interruptableKernel');
if (!this._task) {
return;
}
this._task.start();
}
async interrupt(_document: vscode.NotebookDocument, _ranges: vscode.NotebookCellRange[]) {
await this._task!.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Interrupted'], undefined)
])]);
this._task!.end({});
}
};
currentKernelProvider.addKernel(interruptableKernel);
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: interruptableKernel.id });
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.cell.execute');
await vscode.commands.executeCommand('notebook.cell.cancelExecution');
await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
assert.strictEqual(cell.outputs[0].outputs.length, 1);
assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain');
assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [
'Interrupted'
]);
});
await saveAllFilesAndCloseAll(undefined);
});
test('onDidChangeCellExecutionState is fired', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
vscode.commands.executeCommand('notebook.cell.execute');
let eventCount = 0;
let resolve: () => void;
const p = new Promise<void>(r => resolve = r);
const listener = vscode.notebook.onDidChangeCellExecutionState(e => {
if (eventCount === 0) {
assert.strictEqual(e.executionState, vscode.NotebookCellExecutionState.Pending, 'should be set to Pending');
} else if (eventCount === 1) {
assert.strictEqual(e.executionState, vscode.NotebookCellExecutionState.Executing, 'should be set to Executing');
assert.strictEqual(cell.outputs.length, 0, 'no outputs yet: ' + JSON.stringify(cell.outputs[0]));
} else if (eventCount === 2) {
assert.strictEqual(e.executionState, vscode.NotebookCellExecutionState.Idle, 'should be set to Idle');
assert.strictEqual(cell.outputs.length, 1, 'should have an output');
resolve();
}
eventCount++;
});
await p;
listener.dispose();
await saveAllFilesAndCloseAll(undefined);
});
// suite('notebook dirty state', () => {
test('notebook open', async function () {
@ -1094,7 +1268,6 @@ suite('Notebook API tests', function () {
await saveAllFilesAndCloseAll(resource);
});
test('#116808, active kernel should not be undefined', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
@ -1108,32 +1281,110 @@ suite('Notebook API tests', function () {
await saveAllFilesAndCloseAll(resource);
});
test('Numeric metadata should get updated correctly', async function () {
test('Output changes are applied once the promise resolves', async function () {
const verifyOutputSyncKernel = new class implements vscode.NotebookKernel {
readonly id = 'verifyOutputSyncKernel';
readonly label = '';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) {
const idx = ranges[0].start;
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, this.id);
if (!task) {
return;
}
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Some output'], undefined)
])]);
assert.strictEqual(document.cells[0].outputs.length, 1);
assert.deepStrictEqual(document.cells[0].outputs[0].outputs[0].value, ['Some output']);
task.end({});
}
};
currentKernelProvider.addKernel(verifyOutputSyncKernel);
const resource = await createRandomFile('', undefined, '.vsctestnb');
const document = await vscode.notebook.openNotebookDocument(resource);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: verifyOutputSyncKernel.id });
await vscode.commands.executeCommand('notebook.cell.execute');
const edit = new vscode.WorkspaceEdit();
const runStartTime = Date.now();
const lastRunDuration = Date.now() + 1000;
const runState = vscode.NotebookCellRunState.Success;
const executionOrder = 1234;
const metadata = document.cells[0].metadata.with({
...document.cells[0].metadata,
runStartTime,
runState,
lastRunDuration,
executionOrder
});
edit.replaceNotebookCellMetadata(document.uri, 0, metadata);
await vscode.workspace.applyEdit(edit);
assert.strictEqual(document.cells[0].metadata.runStartTime, runStartTime);
assert.strictEqual(document.cells[0].metadata.lastRunDuration, lastRunDuration);
assert.strictEqual(document.cells[0].metadata.executionOrder, executionOrder);
assert.strictEqual(document.cells[0].metadata.runState, vscode.NotebookCellRunState.Success);
await saveAllFilesAndCloseAll(undefined);
});
test('previousResult', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
assert.strictEqual(cell.previousResult?.success, undefined);
assert.strictEqual(cell.previousResult?.executionOrder, undefined);
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 1, 'should execute');
assert.ok(cell.previousResult);
assert.strictEqual(cell.previousResult!.success, true);
assert.strictEqual(typeof cell.previousResult!.executionOrder, 'number');
await saveAllFilesAndCloseAll(undefined);
});
test('initialize previousResult', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cells[0];
assert.strictEqual(cell.previousResult?.success, undefined);
assert.strictEqual(cell.previousResult?.executionOrder, undefined);
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(cell.outputs.length, 1, 'should execute');
assert.ok(cell.previousResult);
assert.strictEqual(cell.previousResult!.success, true);
assert.strictEqual(typeof cell.previousResult!.executionOrder, 'number');
await saveAllFilesAndCloseAll(undefined);
});
test('Throws errors for invalid execution tasks', async function () {
let missedError: string | undefined;
const invalidKernel = new class implements vscode.NotebookKernel {
readonly id = 'invalidKernel';
readonly label = '';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, _ranges: vscode.NotebookCellRange[]) {
try {
vscode.notebook.createNotebookCellExecutionTask(document.uri, 1000, this.id);
missedError = 'Expected to throw for invalid index';
return;
} catch (e) { }
try {
vscode.notebook.createNotebookCellExecutionTask(vscode.Uri.file('slkdf'), 0, this.id);
missedError = 'Expected to throw for invalid uri';
return;
} catch (e) { }
}
};
currentKernelProvider.addKernel(invalidKernel);
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: invalidKernel.id });
await vscode.commands.executeCommand('notebook.cell.execute');
assert.strictEqual(missedError, undefined, missedError);
await saveAllFilesAndCloseAll(undefined);
});
// });

View file

@ -62,32 +62,22 @@ export function activate(context: vscode.ExtensionContext): any {
}));
const kernel: vscode.NotebookKernel = {
id: 'notebookSmokeTest',
label: 'notebookSmokeTest',
isPreferred: true,
executeAllCells: async (_document: vscode.NotebookDocument) => {
const edit = new vscode.WorkspaceEdit();
for (let i = 0; i < _document.cells.length; i++) {
edit.replaceNotebookCellOutput(_document.uri, i, [new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined)
])]);
executeCellsRequest: async (document: vscode.NotebookDocument, ranges: vscode.NotebookCellRange[]) => {
const idx = ranges[0].start;
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'notebookSmokeTest');
if (!task) {
return;
}
await vscode.workspace.applyEdit(edit);
},
cancelAllCellsExecution: async () => { },
executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined) => {
if (!_cell) {
_cell = _document.cells[0];
}
const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCellOutput(_document.uri, _cell.index, [new vscode.NotebookCellOutput([
task.start();
task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined)
])]);
await vscode.workspace.applyEdit(edit);
return;
},
cancelCellExecution: async () => { }
task.end({ success: true });
}
};
context.subscriptions.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.smoke-nb' }, {

View file

@ -963,18 +963,6 @@ declare module 'vscode' {
Code = 2
}
export enum NotebookCellRunState {
Running = 1,
Idle = 2,
Success = 3,
Error = 4
}
export enum NotebookRunState {
Running = 1,
Idle = 2
}
export class NotebookCellMetadata {
/**
* Controls whether a cell's editor is editable/readonly.
@ -1003,14 +991,16 @@ declare module 'vscode' {
// run related API, will be removed
readonly hasExecutionOrder?: boolean;
readonly executionOrder?: number;
readonly runState?: NotebookCellRunState;
readonly runStartTime?: number;
readonly lastRunDuration?: number;
constructor(editable?: boolean, breakpointMargin?: boolean, hasExecutionOrder?: boolean, executionOrder?: number, runState?: NotebookCellRunState, runStartTime?: number, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record<string, any>)
constructor(editable?: boolean, breakpointMargin?: boolean, hasExecutionOrder?: boolean, statusMessage?: string, lastRunDuration?: number, inputCollapsed?: boolean, outputCollapsed?: boolean, custom?: Record<string, any>)
with(change: { editable?: boolean | null, breakpointMargin?: boolean | null, hasExecutionOrder?: boolean | null, executionOrder?: number | null, runState?: NotebookCellRunState | null, runStartTime?: number | null, statusMessage?: string | null, lastRunDuration?: number | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, custom?: Record<string, any> | null, }): NotebookCellMetadata;
with(change: { editable?: boolean | null, breakpointMargin?: boolean | null, hasExecutionOrder?: boolean | null, statusMessage?: string | null, lastRunDuration?: number | null, inputCollapsed?: boolean | null, outputCollapsed?: boolean | null, custom?: Record<string, any> | null, }): NotebookCellMetadata;
}
export interface NotebookCellExecutionSummary {
executionOrder?: number;
success?: boolean;
duration?: number;
}
// todo@API support ids https://github.com/jupyter/enhancement-proposals/blob/master/62-cell-id/cell-id.md
@ -1021,6 +1011,7 @@ declare module 'vscode' {
readonly document: TextDocument;
readonly metadata: NotebookCellMetadata
readonly outputs: ReadonlyArray<NotebookCellOutput>;
readonly latestExecutionSummary: NotebookCellExecutionSummary | undefined;
}
export class NotebookDocumentMetadata {
@ -1048,12 +1039,9 @@ declare module 'vscode' {
// todo@API is this a kernel property?
readonly cellHasExecutionOrder: boolean;
// todo@API remove
readonly runState: NotebookRunState;
constructor(editable?: boolean, cellEditable?: boolean, cellHasExecutionOrder?: boolean, custom?: { [key: string]: any; }, trusted?: boolean);
constructor(editable?: boolean, cellEditable?: boolean, cellHasExecutionOrder?: boolean, custom?: { [key: string]: any; }, runState?: NotebookRunState, trusted?: boolean);
with(change: { editable?: boolean | null, cellEditable?: boolean | null, cellHasExecutionOrder?: boolean | null, custom?: { [key: string]: any; } | null, runState?: NotebookRunState | null, trusted?: boolean | null, }): NotebookDocumentMetadata
with(change: { editable?: boolean | null, cellEditable?: boolean | null, cellHasExecutionOrder?: boolean | null, custom?: { [key: string]: any; } | null, trusted?: boolean | null, }): NotebookDocumentMetadata
}
export interface NotebookDocumentContentOptions {
@ -1227,6 +1215,12 @@ declare module 'vscode' {
readonly visibleRanges: ReadonlyArray<NotebookCellRange>;
}
export interface NotebookCellExecutionStateChangeEvent {
readonly document: NotebookDocument;
readonly cell: NotebookCell;
readonly executionState: NotebookCellExecutionState;
}
// todo@API support ids https://github.com/jupyter/enhancement-proposals/blob/master/62-cell-id/cell-id.md
export class NotebookCellData {
kind: NotebookCellKind;
@ -1236,7 +1230,8 @@ declare module 'vscode' {
language: string;
outputs?: NotebookCellOutput[];
metadata?: NotebookCellMetadata;
constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata)
latestExecutionSummary?: NotebookCellExecutionSummary;
constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, latestExecutionSummary?: NotebookCellExecutionSummary);
}
export class NotebookData {
@ -1496,27 +1491,6 @@ declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/106744, NotebookKernel
// todo@API use the NotebookCellExecution-object as a container to model and enforce
// the flow of a cell execution
// kernel -> execute_info
// ext -> createNotebookCellExecution(cell)
// kernel -> done
// exec.dispose();
// export interface NotebookCellExecution {
// dispose(): void;
// clearOutput(): void;
// appendOutput(out: NotebookCellOutput): void;
// replaceOutput(out: NotebookCellOutput): void;
// appendOutputItems(output:string, items: NotebookCellOutputItem[]):void;
// replaceOutputItems(output:string, items: NotebookCellOutputItem[]):void;
// }
// export function createNotebookCellExecution(cell: NotebookCell, startTime?: number): NotebookCellExecution;
// export const onDidStartNotebookCellExecution: Event<any>;
// export const onDidStopNotebookCellExecution: Event<any>;
export interface NotebookKernel {
// todo@API make this mandatory?
@ -1541,14 +1515,86 @@ declare module 'vscode' {
// fired when properties like the supported languages etc change
// onDidChangeProperties?: Event<void>
// @roblourens
// todo@API change to `executeCells(document: NotebookDocument, cells: NotebookCellRange[], context:{isWholeNotebooke: boolean}, token: CancelationToken): void;`
// todo@API interrupt vs cancellation, https://github.com/microsoft/vscode/issues/106741
// interrupt?():void;
executeCell(document: NotebookDocument, cell: NotebookCell): void;
cancelCellExecution(document: NotebookDocument, cell: NotebookCell): void;
executeAllCells(document: NotebookDocument): void;
cancelAllCellsExecution(document: NotebookDocument): void;
/**
* A kernel can optionally implement this which will be called when any "cancel" button is clicked in the document.
*/
interrupt?(document: NotebookDocument): void;
/**
* Called when the user triggers execution of a cell by clicking the run button for a cell, multiple cells,
* or full notebook. The cell will be put into the Pending state when this method is called. If
* createNotebookCellExecutionTask has not been called by the time the promise returned by this method is
* resolved, the cell will be put back into the Idle state.
*/
executeCellsRequest(document: NotebookDocument, ranges: NotebookCellRange[]): Thenable<void>;
}
export interface NotebookCellExecuteStartContext {
// TODO@roblou are we concerned about clock issues with this absolute time?
/**
* The time that execution began, in milliseconds in the Unix epoch. Used to drive the clock
* that shows for how long a cell has been running. If not given, the clock won't be shown.
*/
startTime?: number;
}
export interface NotebookCellExecuteEndContext {
/**
* If true, a green check is shown on the cell status bar.
* If false, a red X is shown.
*/
success?: boolean;
/**
* The total execution time in milliseconds.
*/
duration?: number;
}
/**
* A NotebookCellExecutionTask is how the kernel modifies a notebook cell as it is executing. When
* [`createNotebookCellExecutionTask`](#notebook.createNotebookCellExecutionTask) is called, the cell
* enters the Pending state. When `start()` is called on the execution task, it enters the Executing state. When
* `end()` is called, it enters the Idle state. While in the Executing state, cell outputs can be
* modified with the methods on the run task.
*
* All outputs methods operate on this NotebookCellExecutionTask's cell by default. They optionally take
* a cellIndex parameter that allows them to modify the outputs of other cells. `appendOutputItems` and
* `replaceOutputItems` operate on the output with the given ID, which can be an output on any cell. They
* all resolve once the output edit has been applied.
*/
export interface NotebookCellExecutionTask {
readonly document: NotebookDocument;
readonly cell: NotebookCell;
start(context?: NotebookCellExecuteStartContext): void;
executionOrder: number | undefined;
end(result?: NotebookCellExecuteEndContext): void;
readonly token: CancellationToken;
clearOutput(cellIndex?: number): Thenable<void>;
appendOutput(out: NotebookCellOutput[], cellIndex?: number): Thenable<void>;
replaceOutput(out: NotebookCellOutput[], cellIndex?: number): Thenable<void>;
appendOutputItems(items: NotebookCellOutputItem[], outputId: string): Thenable<void>;
replaceOutputItems(items: NotebookCellOutputItem[], outputId: string): Thenable<void>;
}
export enum NotebookCellExecutionState {
Idle = 1,
Pending = 2,
Executing = 3,
}
export namespace notebook {
/**
* Creates a [`NotebookCellExecutionTask`](#NotebookCellExecutionTask). Should only be called by a kernel. Returns undefined unless requested by the active kernel.
* @param uri The [uri](#Uri) of the notebook document.
* @param index The index of the cell.
* @param kernelId The id of the kernel requesting this run task. If this kernel is not the current active kernel, `undefined` is returned.
*/
export function createNotebookCellExecutionTask(uri: Uri, index: number, kernelId: string): NotebookCellExecutionTask | undefined;
export const onDidChangeCellExecutionState: Event<NotebookCellExecutionStateChangeEvent>;
}
export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern; };

View file

@ -24,7 +24,7 @@ import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/no
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ICellEditOperation, ICellRange, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ICellEditOperation, ICellRange, IImmediateCellEditOperation, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookCellsChangeType, NotebookDataDto, TransientMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -165,6 +165,15 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
return textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined);
}
async $applyEdits(resource: UriComponents, cellEdits: IImmediateCellEditOperation[], computeUndoRedo = true): Promise<void> {
const textModel = this._notebookService.getNotebookTextModel(URI.from(resource));
if (!textModel) {
throw new Error(`Can't apply edits to unknown notebook model: ${resource}`);
}
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
}
private _registerListeners(): void {
// forward changes to dirty state
@ -497,17 +506,18 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
isPreferred: dto.isPreferred,
preloads: dto.preloads?.map(u => URI.revive(u)),
supportedLanguages: dto.supportedLanguages,
implementsInterrupt: dto.implementsInterrupt,
resolve: (uri: URI, editorId: string, token: CancellationToken): Promise<void> => {
this._logService.debug('MainthreadNotebooks.resolveNotebookKernel', uri.path, dto.friendlyId);
return this._proxy.$resolveNotebookKernel(handle, editorId, uri, dto.friendlyId, token);
},
executeNotebookCell: (uri: URI, cellHandle: number | undefined): Promise<void> => {
this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellHandle);
return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellHandle);
executeNotebookCellsRequest: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
this._logService.debug('MainthreadNotebooks.executeNotebookCell', uri.path, dto.friendlyId, cellRanges);
return this._proxy.$executeNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellRanges);
},
cancelNotebookCell: (uri: URI, cellHandle: number | undefined): Promise<void> => {
this._logService.debug('MainthreadNotebooks.cancelNotebookCell', uri.path, dto.friendlyId, cellHandle);
return this._proxy.$cancelNotebookKernelFromProvider(handle, uri, dto.friendlyId, cellHandle);
cancelNotebookCellExecution: (uri: URI, cellRanges: ICellRange[]): Promise<void> => {
this._logService.debug('MainthreadNotebooks.cancelNotebookCellExecution', uri.path, dto.friendlyId, cellRanges);
return this._proxy.$cancelNotebookCellExecution(handle, uri, dto.friendlyId, cellRanges);
}
});
}

View file

@ -1078,6 +1078,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookCells(listener, thisArgs, disposables);
},
onDidChangeCellExecutionState(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeNotebookCellExecutionState(listener, thisArgs, disposables);
},
onDidChangeCellOutputs(listener, thisArgs?, disposables?) {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeCellOutputs(listener, thisArgs, disposables);
@ -1093,6 +1097,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
createCellStatusBarItem(cell: vscode.NotebookCell, alignment?: vscode.NotebookCellStatusBarAlignment, priority?: number): vscode.NotebookCellStatusBarItem {
checkProposedApiEnabled(extension);
return extHostNotebook.createNotebookCellStatusBarItemInternal(cell, alignment, priority);
},
createNotebookCellExecutionTask(uri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
checkProposedApiEnabled(extension);
return extHostNotebook.createNotebookCellExecution(uri, index, kernelId);
}
};
@ -1231,12 +1239,11 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
TimelineItem: extHostTypes.TimelineItem,
NotebookCellRange: extHostTypes.NotebookCellRange,
NotebookCellKind: extHostTypes.NotebookCellKind,
NotebookCellRunState: extHostTypes.NotebookCellRunState,
NotebookCellExecutionState: extHostTypes.NotebookCellExecutionState,
NotebookDocumentMetadata: extHostTypes.NotebookDocumentMetadata,
NotebookCellMetadata: extHostTypes.NotebookCellMetadata,
NotebookCellData: extHostTypes.NotebookCellData,
NotebookData: extHostTypes.NotebookData,
NotebookRunState: extHostTypes.NotebookRunState,
NotebookCellStatusBarAlignment: extHostTypes.NotebookCellStatusBarAlignment,
NotebookEditorRevealType: extHostTypes.NotebookEditorRevealType,
NotebookCellOutput: extHostTypes.NotebookCellOutput,

View file

@ -50,7 +50,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel';
import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types';
import { DebugConfigurationProviderTriggerKind, WorkspaceTrustState } from 'vs/workbench/api/common/extHostTypes';
@ -849,6 +849,7 @@ export interface MainThreadNotebookShape extends IDisposable {
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
$trySaveDocument(uri: UriComponents): Promise<boolean>;
$tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise<boolean>;
$applyEdits(resource: UriComponents, edits: IImmediateCellEditOperation[], computeUndoRedo?: boolean): Promise<void>;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
$setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise<void>;
$tryOpenDocument(uriComponents: UriComponents): Promise<UriComponents>;
@ -1857,6 +1858,7 @@ export interface INotebookKernelInfoDto2 {
isPreferred?: boolean;
preloads?: UriComponents[];
supportedLanguages?: string[]
implementsInterrupt?: boolean;
}
export interface ExtHostNotebookShape {
@ -1864,8 +1866,8 @@ export interface ExtHostNotebookShape {
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined }): void;
$provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]>;
$resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$cancelNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellHandle: number | undefined): Promise<void>;
$executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRanges: ICellRange[]): Promise<void>;
$cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void>;
$onDidReceiveMessage(editorId: string, rendererId: string | undefined, message: unknown): void;
$openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto>;

View file

@ -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
});
}
}

View file

@ -6,7 +6,7 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
import { Schemas } from 'vs/base/common/network';
import { deepFreeze } from 'vs/base/common/objects';
import { deepFreeze, equals } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
@ -51,7 +51,9 @@ export class ExtHostCell {
private _outputs: extHostTypes.NotebookCellOutput[];
private _metadata: extHostTypes.NotebookCellMetadata;
private _previousResult: vscode.NotebookCellExecutionSummary | undefined;
private _internalMetadata: NotebookCellMetadata;
readonly handle: number;
readonly uri: URI;
readonly cellKind: CellKind;
@ -67,7 +69,9 @@ export class ExtHostCell {
this.uri = URI.revive(_cellData.uri);
this.cellKind = _cellData.cellKind;
this._outputs = _cellData.outputs.map(extHostTypeConverters.NotebookCellOutput.to);
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(_cellData.metadata ?? {});
this._internalMetadata = _cellData.metadata ?? {};
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(this._internalMetadata);
this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(this._internalMetadata);
}
dispose() {
@ -75,6 +79,10 @@ export class ExtHostCell {
this._onDidDispose.dispose();
}
get internalMetadata(): NotebookCellMetadata {
return this._internalMetadata;
}
get cell(): vscode.NotebookCell {
if (!this._cell) {
const that = this;
@ -89,6 +97,7 @@ export class ExtHostCell {
document: data.document,
get outputs() { return that._outputs.slice(0); },
get metadata() { return that._metadata; },
get latestExecutionSummary() { return that._previousResult; }
});
}
return this._cell;
@ -110,7 +119,9 @@ export class ExtHostCell {
}
setMetadata(newMetadata: NotebookCellMetadata): void {
this._internalMetadata = newMetadata;
this._metadata = extHostTypeConverters.NotebookCellMetadata.to(newMetadata);
this._previousResult = extHostTypeConverters.NotebookCellPreviousExecutionResult.to(newMetadata);
}
}
@ -118,6 +129,7 @@ export interface INotebookEventEmitter {
emitModelChange(events: vscode.NotebookCellsChangeEvent): void;
emitCellOutputsChange(event: vscode.NotebookCellOutputsChangeEvent): void;
emitCellMetadataChange(event: vscode.NotebookCellMetadataChangeEvent): void;
emitCellExecutionStateChange(event: vscode.NotebookCellExecutionStateChangeEvent): void;
}
@ -308,10 +320,22 @@ export class ExtHostNotebookDocument extends Disposable {
}
}
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata | undefined): void {
private _changeCellMetadata(index: number, newMetadata: NotebookCellMetadata): void {
const cell = this._cells[index];
cell.setMetadata(newMetadata || {});
this._emitter.emitCellMetadataChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell }));
const originalInternalMetadata = cell.internalMetadata;
const originalExtMetadata = cell.cell.metadata;
cell.setMetadata(newMetadata);
const newExtMetadata = cell.cell.metadata;
if (!equals(originalExtMetadata, newExtMetadata)) {
this._emitter.emitCellMetadataChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell }));
}
if (originalInternalMetadata.runState !== newMetadata.runState) {
const executionState = newMetadata.runState ?? extHostTypes.NotebookCellExecutionState.Idle;
this._emitter.emitCellExecutionStateChange(deepFreeze({ document: this.notebookDocument, cell: cell.cell, executionState }));
}
}
getCellFromIndex(index: number): ExtHostCell | undefined {

View file

@ -204,9 +204,9 @@ export class ExtHostNotebookEditor {
const prevIndex = compressedEditsIndex;
const prev = compressedEdits[prevIndex];
if (prev.editType === CellEditType.Replace && editData.cellEdits[i].editType === CellEditType.Replace) {
const edit = editData.cellEdits[i];
if ((edit.editType !== CellEditType.DocumentMetadata) && prev.index === edit.index) {
const edit = editData.cellEdits[i];
if (prev.editType === CellEditType.Replace && edit.editType === CellEditType.Replace) {
if (prev.index === edit.index) {
prev.cells.push(...(editData.cellEdits[i] as ICellReplaceEdit).cells);
prev.count += (editData.cellEdits[i] as ICellReplaceEdit).count;
continue;

View file

@ -568,7 +568,7 @@ export namespace WorkspaceEdit {
metadata: entry.metadata,
resource: entry.uri,
edit: {
editType: notebooks.CellEditType.Metadata,
editType: notebooks.CellEditType.PartialMetadata,
index: entry.index,
metadata: entry.newMetadata
}
@ -594,7 +594,6 @@ export namespace WorkspaceEdit {
resource: entry.uri,
edit: {
editType: notebooks.CellEditType.OutputItems,
index: entry.index,
outputId: entry.outputId,
items: entry.newOutputItems?.map(NotebookCellOutputItem.from) || [],
append: entry.append
@ -1420,7 +1419,7 @@ export namespace NotebookCellRange {
export namespace NotebookCellMetadata {
export function to(data: notebooks.NotebookCellMetadata): types.NotebookCellMetadata {
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.executionOrder, data.runState, data.runStartTime, data.statusMessage, data.lastRunDuration, data.inputCollapsed, data.outputCollapsed, data.custom);
return new types.NotebookCellMetadata(data.editable, data.breakpointMargin, data.hasExecutionOrder, data.statusMessage, data.inputCollapsed, data.outputCollapsed, data.custom);
}
}
@ -1431,9 +1430,26 @@ export namespace NotebookDocumentMetadata {
}
export function to(data: notebooks.NotebookDocumentMetadata): types.NotebookDocumentMetadata {
return new types.NotebookDocumentMetadata(data.editable, data.cellEditable, data.cellHasExecutionOrder, data.custom, data.runState, data.trusted);
return new types.NotebookDocumentMetadata(data.editable, data.cellEditable, data.cellHasExecutionOrder, data.custom, data.trusted);
}
}
export namespace NotebookCellPreviousExecutionResult {
export function to(data: notebooks.NotebookCellMetadata): vscode.NotebookCellExecutionSummary {
return {
duration: data.lastRunDuration,
executionOrder: data.executionOrder,
success: data.lastRunSuccess
};
}
export function from(data: vscode.NotebookCellExecutionSummary): Partial<notebooks.NotebookCellMetadata> {
return {
lastRunSuccess: data.success,
lastRunDuration: data.duration,
executionOrder: data.executionOrder
};
}
}
export namespace NotebookCellKind {
@ -1465,7 +1481,10 @@ export namespace NotebookCellData {
cellKind: NotebookCellKind.from(data.kind),
language: data.language,
source: data.source,
metadata: data.metadata,
metadata: {
...data.metadata,
...NotebookCellPreviousExecutionResult.from(data.latestExecutionSummary ?? {})
},
outputs: data.outputs ? data.outputs.map(NotebookCellOutput.from) : []
};
}

View file

@ -707,7 +707,7 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
}
replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Metadata, index, metadata: cellMetadata } });
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.PartialMetadata, index, metadata: cellMetadata } });
}
// --- text
@ -2930,11 +2930,7 @@ export class NotebookCellMetadata {
readonly editable?: boolean,
readonly breakpointMargin?: boolean,
readonly hasExecutionOrder?: boolean,
readonly executionOrder?: number,
readonly runState?: NotebookCellRunState,
readonly runStartTime?: number,
readonly statusMessage?: string,
readonly lastRunDuration?: number,
readonly inputCollapsed?: boolean,
readonly outputCollapsed?: boolean,
readonly custom?: Record<string, any>,
@ -2944,17 +2940,13 @@ export class NotebookCellMetadata {
editable?: boolean | null,
breakpointMargin?: boolean | null,
hasExecutionOrder?: boolean | null,
executionOrder?: number | null,
runState?: NotebookCellRunState | null,
runStartTime?: number | null,
statusMessage?: string | null,
lastRunDuration?: number | null,
inputCollapsed?: boolean | null,
outputCollapsed?: boolean | null,
custom?: Record<string, any> | null,
}): NotebookCellMetadata {
let { editable, breakpointMargin, hasExecutionOrder, executionOrder, runState, runStartTime, statusMessage, lastRunDuration, inputCollapsed, outputCollapsed, custom } = change;
let { editable, breakpointMargin, hasExecutionOrder, statusMessage, inputCollapsed, outputCollapsed, custom } = change;
if (editable === undefined) {
editable = this.editable;
@ -2971,31 +2963,11 @@ export class NotebookCellMetadata {
} else if (hasExecutionOrder === null) {
hasExecutionOrder = undefined;
}
if (executionOrder === undefined) {
executionOrder = this.executionOrder;
} else if (executionOrder === null) {
executionOrder = undefined;
}
if (runState === undefined) {
runState = this.runState;
} else if (runState === null) {
runState = undefined;
}
if (runStartTime === undefined) {
runStartTime = this.runStartTime;
} else if (runStartTime === null) {
runStartTime = undefined;
}
if (statusMessage === undefined) {
statusMessage = this.statusMessage;
} else if (statusMessage === null) {
statusMessage = undefined;
}
if (lastRunDuration === undefined) {
lastRunDuration = this.lastRunDuration;
} else if (lastRunDuration === null) {
lastRunDuration = undefined;
}
if (inputCollapsed === undefined) {
inputCollapsed = this.inputCollapsed;
} else if (inputCollapsed === null) {
@ -3015,11 +2987,7 @@ export class NotebookCellMetadata {
if (editable === this.editable &&
breakpointMargin === this.breakpointMargin &&
hasExecutionOrder === this.hasExecutionOrder &&
executionOrder === this.executionOrder &&
runState === this.runState &&
runStartTime === this.runStartTime &&
statusMessage === this.statusMessage &&
lastRunDuration === this.lastRunDuration &&
inputCollapsed === this.inputCollapsed &&
outputCollapsed === this.outputCollapsed &&
custom === this.custom
@ -3031,11 +2999,7 @@ export class NotebookCellMetadata {
editable,
breakpointMargin,
hasExecutionOrder,
executionOrder,
runState,
runStartTime,
statusMessage,
lastRunDuration,
inputCollapsed,
outputCollapsed,
custom,
@ -3050,7 +3014,6 @@ export class NotebookDocumentMetadata {
readonly cellEditable: boolean = true,
readonly cellHasExecutionOrder: boolean = true,
readonly custom: { [key: string]: any; } = {},
readonly runState: NotebookRunState = NotebookRunState.Idle,
readonly trusted: boolean = true,
) { }
@ -3059,11 +3022,10 @@ export class NotebookDocumentMetadata {
cellEditable?: boolean | null,
cellHasExecutionOrder?: boolean | null,
custom?: { [key: string]: any; } | null,
runState?: NotebookRunState | null,
trusted?: boolean | null,
}): NotebookDocumentMetadata {
let { editable, cellEditable, cellHasExecutionOrder, custom, runState, trusted } = change;
let { editable, cellEditable, cellHasExecutionOrder, custom, trusted } = change;
if (editable === undefined) {
editable = this.editable;
@ -3085,11 +3047,6 @@ export class NotebookDocumentMetadata {
} else if (custom === null) {
custom = undefined;
}
if (runState === undefined) {
runState = this.runState;
} else if (runState === null) {
runState = undefined;
}
if (trusted === undefined) {
trusted = this.trusted;
} else if (trusted === null) {
@ -3100,7 +3057,6 @@ export class NotebookDocumentMetadata {
cellEditable === this.cellEditable &&
cellHasExecutionOrder === this.cellHasExecutionOrder &&
custom === this.custom &&
runState === this.runState &&
trusted === this.trusted
) {
return this;
@ -3112,7 +3068,6 @@ export class NotebookDocumentMetadata {
cellEditable,
cellHasExecutionOrder,
custom,
runState,
trusted
);
}
@ -3125,13 +3080,15 @@ export class NotebookCellData {
language: string;
outputs?: NotebookCellOutput[];
metadata?: NotebookCellMetadata;
lastExecutionSummary?: vscode.NotebookCellExecutionSummary;
constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata) {
constructor(kind: NotebookCellKind, source: string, language: string, outputs?: NotebookCellOutput[], metadata?: NotebookCellMetadata, lastExecutionSummary?: vscode.NotebookCellExecutionSummary) {
this.kind = kind;
this.source = source;
this.language = language;
this.outputs = outputs ?? [];
this.metadata = metadata;
this.lastExecutionSummary = lastExecutionSummary;
}
}
@ -3187,16 +3144,10 @@ export enum NotebookCellKind {
Code = 2
}
export enum NotebookCellRunState {
Running = 1,
Idle = 2,
Success = 3,
Error = 4
}
export enum NotebookRunState {
Running = 1,
Idle = 2
export enum NotebookCellExecutionState {
Idle = 1,
Pending = 2,
Executing = 3,
}
export enum NotebookCellStatusBarAlignment {

View file

@ -19,9 +19,9 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { CATEGORIES } from 'vs/workbench/common/actions';
import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_CONTENT_COMMAND_ID, getNotebookEditorFromEditorPane, IActiveNotebookEditor, ICellViewModel, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_HAS_OUTPUTS, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_TYPE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_IS_ACTIVE_EDITOR, NOTEBOOK_KERNEL_COUNT, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_HAS_RUNNING_CELL } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellRunState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, SelectionStateType, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientMetadata, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
@ -240,7 +240,11 @@ export abstract class NotebookCellAction<T = INotebookCellActionContext> extends
const executeCellCondition = ContextKeyExpr.or(
ContextKeyExpr.and(
ContextKeyExpr.notEquals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Running]),
ContextKeyExpr.or(
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'idle'),
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'succeeded'),
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'failed'),
),
ContextKeyExpr.greater(NOTEBOOK_KERNEL_COUNT.key, 0)),
NOTEBOOK_CELL_TYPE.isEqualTo('markdown'));
@ -344,16 +348,21 @@ registerAction2(class ExecuteCell extends NotebookCellAction<ICellRange> {
}
});
const cellCancelCondition = ContextKeyExpr.or(
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'executing'),
ContextKeyExpr.equals(NOTEBOOK_CELL_EXECUTION_STATE.key, 'pending'),
);
registerAction2(class CancelExecuteCell extends NotebookCellAction<ICellRange> {
constructor() {
super({
id: CANCEL_CELL_COMMAND_ID,
precondition: ContextKeyExpr.equals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Running]),
precondition: cellCancelCondition,
title: localize('notebookActions.cancel', "Stop Cell Execution"),
icon: icons.stopIcon,
menu: {
id: MenuId.NotebookCellExecute,
when: ContextKeyExpr.equals(NOTEBOOK_CELL_RUN_STATE.key, NotebookCellRunState[NotebookCellRunState.Running]),
when: cellCancelCondition,
group: 'inline'
},
description: {
@ -618,7 +627,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
},
order: -1,
group: 'navigation',
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.toNegated(), executeNotebookCondition)
when: ContextKeyExpr.and(executeNotebookCondition, ContextKeyExpr.or(NOTEBOOK_INTERRUPTIBLE_KERNEL.toNegated(), NOTEBOOK_HAS_RUNNING_CELL.toNegated()))
});
MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
@ -629,7 +638,7 @@ MenuRegistry.appendMenuItem(MenuId.NotebookToolbar, {
},
order: -1,
group: 'navigation',
when: ContextKeyExpr.and(NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK)
when: ContextKeyExpr.and(NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL)
});
registerAction2(class extends NotebookCellAction {
@ -681,7 +690,7 @@ registerAction2(class extends NotebookCellAction {
});
async function runCell(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
if (context.cell.metadata?.runState === NotebookCellRunState.Running) {
if (context.cell.metadata?.runState === NotebookCellExecutionState.Executing) {
return;
}
@ -1300,11 +1309,11 @@ registerAction2(class extends NotebookCellAction {
editor.viewModel.notebookDocument.applyEdits([{ editType: CellEditType.Output, index, outputs: [] }], true, undefined, () => undefined, undefined);
if (context.cell.metadata && context.cell.metadata?.runState !== NotebookCellRunState.Running) {
if (context.cell.metadata && context.cell.metadata?.runState !== NotebookCellExecutionState.Executing) {
context.notebookEditor.viewModel.notebookDocument.applyEdits([{
editType: CellEditType.Metadata, index, metadata: {
...context.cell.metadata,
runState: NotebookCellRunState.Idle,
runState: NotebookCellExecutionState.Idle,
runStartTime: undefined,
lastRunDuration: undefined,
statusMessage: undefined,
@ -1515,11 +1524,11 @@ registerAction2(class extends NotebookAction {
})), true, undefined, () => undefined, undefined);
const clearExecutionMetadataEdits = editor.viewModel.notebookDocument.cells.map((cell, index) => {
if (cell.metadata && cell.metadata?.runState !== NotebookCellRunState.Running) {
if (cell.metadata && cell.metadata?.runState !== NotebookCellExecutionState.Executing) {
return {
editType: CellEditType.Metadata, index, metadata: {
...cell.metadata,
runState: NotebookCellRunState.Idle,
runState: NotebookCellExecutionState.Idle,
runStartTime: undefined,
lastRunDuration: undefined,
statusMessage: undefined,

View file

@ -383,7 +383,6 @@ abstract class AbstractElementRenderer extends Disposable {
private _applySanitizedMetadataChanges(currentMetadata: NotebookCellMetadata, newMetadata: any) {
let result: { [key: string]: any } = {};
let newLangauge: string | undefined = undefined;
try {
const newMetadataObj = JSON.parse(newMetadata);
const keys = new Set([...Object.keys(newMetadataObj)]);
@ -428,25 +427,11 @@ abstract class AbstractElementRenderer extends Disposable {
}
break;
default:
if (key === 'language') {
newLangauge = newMetadataObj[key];
}
result[key] = newMetadataObj[key];
break;
}
}
if (newLangauge !== undefined && newLangauge !== this.cell.modified!.language) {
const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!.textModel);
this.notebookEditor.textModel!.applyEdits(
[{ editType: CellEditType.CellLanguage, index, language: newLangauge }],
true,
undefined,
() => undefined,
undefined
);
}
const index = this.notebookEditor.textModel!.cells.indexOf(this.cell.modified!.textModel);
if (index < 0) {

View file

@ -43,7 +43,7 @@ export const NOTEBOOK_EDITOR_OPEN = new RawContextKey<boolean>('notebookEditorOp
export const NOTEBOOK_CELL_LIST_FOCUSED = new RawContextKey<boolean>('notebookCellListFocused', false);
export const NOTEBOOK_OUTPUT_FOCUSED = new RawContextKey<boolean>('notebookOutputFocused', false);
export const NOTEBOOK_EDITOR_EDITABLE = new RawContextKey<boolean>('notebookEditable', true);
export const NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK = new RawContextKey<boolean>('notebookExecuting', false);
export const NOTEBOOK_HAS_RUNNING_CELL = new RawContextKey<boolean>('notebookHasRunningCell', false);
// Diff Editor Keys
export const IN_NOTEBOOK_TEXT_DIFF_EDITOR = new RawContextKey<boolean>('isInNotebookTextDiffEditor', false);
@ -55,13 +55,15 @@ export const NOTEBOOK_CELL_EDITABLE = new RawContextKey<boolean>('notebookCellEd
export const NOTEBOOK_CELL_FOCUSED = new RawContextKey<boolean>('notebookCellFocused', false); // bool
export const NOTEBOOK_CELL_EDITOR_FOCUSED = new RawContextKey<boolean>('notebookCellEditorFocused', false); // bool
export const NOTEBOOK_CELL_MARKDOWN_EDIT_MODE = new RawContextKey<boolean>('notebookCellMarkdownEditMode', false); // bool
export const NOTEBOOK_CELL_RUN_STATE = new RawContextKey<string>('notebookCellRunState', undefined); // Idle, Running
export type NotebookCellExecutionStateContext = 'idle' | 'pending' | 'executing' | 'succeeded' | 'failed';
export const NOTEBOOK_CELL_EXECUTION_STATE = new RawContextKey<NotebookCellExecutionStateContext>('notebookCellExecutionState', undefined);
export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey<boolean>('notebookCellHasOutputs', false); // bool
export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', false); // bool
export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false); // bool
// Kernels
export const NOTEBOOK_HAS_MULTIPLE_KERNELS = new RawContextKey<boolean>('notebookHasMultipleKernels', false);
export const NOTEBOOK_KERNEL_COUNT = new RawContextKey<number>('notebookKernelCount', 0);
export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey<boolean>('notebookInterruptibleKernel', false);
//#endregion

View file

@ -8,24 +8,25 @@ import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { Memento } from 'vs/workbench/common/memento';
import { ICellViewModel, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { ICellViewModel, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { configureKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellKind, INotebookKernel, NotebookCellRunState, NotebookRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { cellIndexesToRanges, CellKind, ICellRange, INotebookKernel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
const NotebookEditorActiveKernelCache = 'workbench.editor.notebook.activeKernel';
export interface IKernelManagerDelegate {
viewModel: NotebookViewModel | undefined;
onDidChangeViewModel: Event<void>;
getId(): string;
getContributedNotebookProviders(resource?: URI): readonly NotebookProviderInfo[];
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined;
@ -46,9 +47,14 @@ export class NotebookEditorKernelManager extends Disposable {
private _contributedKernelsComputePromise: CancelablePromise<INotebookKernel[]> | null = null;
private _initialKernelComputationDone: boolean = false;
private readonly _notebookExecuting: IContextKey<boolean>;
private readonly _notebookHasMultipleKernels: IContextKey<boolean>;
private readonly _notebookKernelCount: IContextKey<number>;
private readonly _interruptibleKernel: IContextKey<boolean>;
private readonly _someCellRunning: IContextKey<boolean>;
private _cellStateListeners: IDisposable[] = [];
private _executionCount = 0;
private _viewModelDisposables: DisposableStore;
get activeKernel() {
return this._activeKernel;
@ -67,6 +73,8 @@ export class NotebookEditorKernelManager extends Disposable {
return;
}
this._interruptibleKernel.set(!!kernel?.implementsInterrupt);
this._activeKernel = kernel;
this._activeKernelResolvePromise = undefined;
@ -104,9 +112,47 @@ export class NotebookEditorKernelManager extends Disposable {
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
this._notebookExecuting = NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK.bindTo(contextKeyService);
this._notebookHasMultipleKernels = NOTEBOOK_HAS_MULTIPLE_KERNELS.bindTo(contextKeyService);
this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService);
this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService);
this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService);
this._viewModelDisposables = this._register(new DisposableStore());
this._register(this._delegate.onDidChangeViewModel(() => {
this._viewModelDisposables.clear();
this.initCellListeners();
}));
}
private initCellListeners(): void {
dispose(this._cellStateListeners);
this._cellStateListeners = [];
if (!this._delegate.viewModel) {
return;
}
const addCellStateListener = (c: ICellViewModel) => {
return (c as CellViewModel).onDidChangeState(() => {
if (c.metadata?.runState === NotebookCellExecutionState.Pending) {
this._executionCount++;
} else if (c.metadata?.runState === NotebookCellExecutionState.Idle) {
this._executionCount--;
}
this._someCellRunning.set(this._executionCount > 0);
});
};
this._cellStateListeners = this._delegate.viewModel.viewCells.map(addCellStateListener);
this._viewModelDisposables.add(this._delegate.viewModel.onDidChangeViewCells(e => {
e.splices.reverse().forEach(splice => {
const [start, deleted, newCells] = splice;
const deletedCells = this._cellStateListeners.splice(start, deleted, ...newCells.map(addCellStateListener));
dispose(deletedCells);
});
}));
}
public async setKernels(tokenSource: CancellationTokenSource) {
@ -167,15 +213,6 @@ export class NotebookEditorKernelManager extends Disposable {
return result;
}
updateForMetadata(): void {
if (!this._delegate.viewModel) {
return;
}
const notebookMetadata = this._delegate.viewModel.metadata;
this._notebookExecuting.set(notebookMetadata.runState === NotebookRunState.Running);
}
private async _setKernelsFromProviders(provider: NotebookProviderInfo, kernels: INotebookKernel[], tokenSource: CancellationTokenSource) {
const rawAssociations = this._configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId) || [];
const userSetKernelProvider = rawAssociations.filter(e => e.viewType === this._delegate.viewModel?.viewType)[0]?.kernelProvider;
@ -366,12 +403,12 @@ export class NotebookEditorKernelManager extends Disposable {
return;
}
if (this._delegate.viewModel.metadata.runState !== NotebookRunState.Running) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this._delegate.viewModel.uri, undefined);
const fullRange: ICellRange = {
start: 0, end: this._delegate.viewModel.length
};
await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, [fullRange]);
}
async executeNotebook(): Promise<void> {
@ -384,8 +421,11 @@ export class NotebookEditorKernelManager extends Disposable {
return;
}
const fullRange: ICellRange = {
start: 0, end: this._delegate.viewModel.length
};
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this._delegate.viewModel.uri, undefined);
await this._activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, [fullRange]);
}
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> {
@ -398,12 +438,15 @@ export class NotebookEditorKernelManager extends Disposable {
}
const metadata = cell.getEvaluatedMetadata(this._delegate.viewModel.metadata);
if (metadata.runState !== NotebookCellRunState.Running) {
if (metadata.runState === NotebookCellExecutionState.Idle) {
return;
}
await this._ensureActiveKernel();
await this._activeKernel?.cancelNotebookCell!(this._delegate.viewModel.uri, cell.handle);
const idx = this._delegate.viewModel.getCellIndex(cell);
const ranges = cellIndexesToRanges([idx]);
await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, ranges);
}
async executeNotebookCell(cell: ICellViewModel): Promise<void> {
@ -416,8 +459,14 @@ export class NotebookEditorKernelManager extends Disposable {
throw new Error('Cell is not executable: ' + cell.uri);
}
if (!this.activeKernel) {
return;
}
const idx = this._delegate.viewModel.getCellIndex(cell);
const range = cellIndexesToRanges([idx]);
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCell!(this._delegate.viewModel.uri, cell.handle);
await this._activeKernel!.executeNotebookCellsRequest(this._delegate.viewModel.uri, range);
}
private canExecuteNotebook(): boolean {

View file

@ -351,6 +351,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager, <IKernelManagerDelegate>{
getId() { return that.getId(); },
loadKernelPreloads: that._loadKernelPreloads.bind(that),
onDidChangeViewModel: that.onDidChangeModel,
get viewModel() { return that.viewModel; },
getContributedNotebookProviders(resource?: URI) {
return that.notebookService.getContributedNotebookProviders(resource);
@ -967,8 +968,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._editorEditable.set(!!notebookMetadata?.editable);
this._overflowContainer.classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable);
this.getDomNode().classList.toggle('notebook-editor-editable', !!notebookMetadata?.editable);
this._kernelManger.updateForMetadata();
}
private async _resolveWebview(): Promise<BackLayerWebView<ICommonCellInfo> | null> {

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { INotebookTextModel, NotebookCellRunState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_RUN_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_FOCUSED, INotebookEditor, NOTEBOOK_CELL_EDITOR_FOCUSED, CellFocusMode } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookTextModel, NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_CELL_TYPE, NOTEBOOK_VIEW_TYPE, NOTEBOOK_CELL_EDITABLE, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_EXECUTION_STATE, NOTEBOOK_CELL_HAS_OUTPUTS, CellViewModelStateChangeEvent, CellEditState, NOTEBOOK_CELL_INPUT_COLLAPSED, NOTEBOOK_CELL_OUTPUT_COLLAPSED, NOTEBOOK_CELL_FOCUSED, INotebookEditor, NOTEBOOK_CELL_EDITOR_FOCUSED, CellFocusMode, NotebookCellExecutionStateContext } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
@ -17,7 +17,7 @@ export class CellContextKeyManager extends Disposable {
private cellEditable!: IContextKey<boolean>;
private cellFocused!: IContextKey<boolean>;
private cellEditorFocused!: IContextKey<boolean>;
private cellRunState!: IContextKey<string>;
private cellRunState!: IContextKey<NotebookCellExecutionStateContext>;
private cellHasOutputs!: IContextKey<boolean>;
private cellContentCollapsed!: IContextKey<boolean>;
private cellOutputCollapsed!: IContextKey<boolean>;
@ -41,7 +41,7 @@ export class CellContextKeyManager extends Disposable {
this.cellFocused = NOTEBOOK_CELL_FOCUSED.bindTo(this.contextKeyService);
this.cellEditorFocused = NOTEBOOK_CELL_EDITOR_FOCUSED.bindTo(this.contextKeyService);
this.markdownEditMode = NOTEBOOK_CELL_MARKDOWN_EDIT_MODE.bindTo(this.contextKeyService);
this.cellRunState = NOTEBOOK_CELL_RUN_STATE.bindTo(this.contextKeyService);
this.cellRunState = NOTEBOOK_CELL_EXECUTION_STATE.bindTo(this.contextKeyService);
this.cellHasOutputs = NOTEBOOK_CELL_HAS_OUTPUTS.bindTo(this.contextKeyService);
this.cellContentCollapsed = NOTEBOOK_CELL_INPUT_COLLAPSED.bindTo(this.contextKeyService);
this.cellOutputCollapsed = NOTEBOOK_CELL_OUTPUT_COLLAPSED.bindTo(this.contextKeyService);
@ -115,8 +115,20 @@ export class CellContextKeyManager extends Disposable {
const metadata = this.element.getEvaluatedMetadata(this.notebookTextModel.metadata);
this.cellEditable.set(!!metadata.editable);
const runState = metadata.runState ?? NotebookCellRunState.Idle;
this.cellRunState.set(NotebookCellRunState[runState]);
const runState = metadata.runState ?? NotebookCellExecutionState.Idle;
if (runState === NotebookCellExecutionState.Idle) {
if (metadata.lastRunSuccess === true) {
this.cellRunState.set('succeeded');
} else if (metadata.lastRunSuccess === false) {
this.cellRunState.set('failed');
} else {
this.cellRunState.set('idle');
}
} else if (runState === NotebookCellExecutionState.Executing) {
this.cellRunState.set('executing');
} else if (runState === NotebookCellExecutionState.Pending) {
this.cellRunState.set('pending');
}
}
private updateForEditState() {

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Codicons from 'vs/base/common/codicons';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import * as DOM from 'vs/base/browser/dom';
import * as aria from 'vs/base/browser/ui/aria/aria';
@ -48,7 +49,7 @@ import { StatefulMarkdownCell } from 'vs/workbench/contrib/notebook/browser/view
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellRunState, NotebookCellsChangeType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, CellKind, NotebookCellMetadata, NotebookCellExecutionState, NotebookCellsChangeType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CodiconActionViewItem, createAndFillInActionBarActionsWithVerticalSeparators, VerticalSeparator, VerticalSeparatorViewItem } from './cellActionView';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { errorStateIcon, successStateIcon, unfoldIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
@ -889,9 +890,9 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
}
return this.notebookEditor.viewModel.getCellIndex(element);
});
}, element.metadata?.lastRunSuccess);
if (metadata.runState === NotebookCellRunState.Running) {
if (metadata.runState === NotebookCellExecutionState.Executing) {
if (metadata.runStartTime) {
templateData.elementDisposables.add(templateData.timer.start(metadata.runStartTime));
} else {
@ -907,7 +908,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
editorOptions.setGlyphMargin(metadata.breakpointMargin);
}
if (metadata.runState === NotebookCellRunState.Running) {
if (metadata.runState === NotebookCellExecutionState.Executing) {
templateData.progressBar.infinite().show(500);
} else {
templateData.progressBar.hide();
@ -1103,8 +1104,9 @@ export class RunStateRenderer {
private static readonly MIN_SPINNER_TIME = 200;
private spinnerTimer: any | undefined;
private pendingNewState: NotebookCellRunState | undefined;
private lastRunState: NotebookCellRunState | undefined;
private lastRunState: NotebookCellExecutionState | undefined;
private pendingNewState: NotebookCellExecutionState | undefined;
private pendingLastRunSuccess: boolean | undefined;
constructor(private readonly element: HTMLElement) {
DOM.hide(element);
@ -1117,41 +1119,51 @@ export class RunStateRenderer {
}
}
renderState(runState: NotebookCellRunState = NotebookCellRunState.Idle, getCellIndex: () => number) {
renderState(runState: NotebookCellExecutionState = NotebookCellExecutionState.Idle, getCellIndex: () => number, lastRunSuccess: boolean | undefined = undefined) {
if (this.spinnerTimer) {
this.pendingNewState = runState;
this.pendingLastRunSuccess = lastRunSuccess;
return;
}
if (runState === NotebookCellRunState.Success) {
let runStateTooltip: string | undefined;
if (runState === NotebookCellExecutionState.Idle && lastRunSuccess) {
aria.alert(`Code cell at ${getCellIndex()} finishes running successfully`);
DOM.reset(this.element, renderIcon(successStateIcon));
} else if (runState === NotebookCellRunState.Error) {
} else if (runState === NotebookCellExecutionState.Idle && !lastRunSuccess) {
aria.alert(`Code cell at ${getCellIndex()} finishes running with errors`);
DOM.reset(this.element, renderIcon(errorStateIcon));
} else if (runState === NotebookCellRunState.Running) {
if (this.lastRunState !== NotebookCellRunState.Running) {
} else if (runState === NotebookCellExecutionState.Executing) {
runStateTooltip = localize('runStateExecuting', "Executing");
if (this.lastRunState !== NotebookCellExecutionState.Executing) {
aria.alert(`Code cell at ${getCellIndex()} starts running`);
}
DOM.reset(this.element, renderIcon(syncing));
this.spinnerTimer = setTimeout(() => {
this.spinnerTimer = undefined;
if (this.pendingNewState) {
this.renderState(this.pendingNewState, getCellIndex);
if (this.pendingNewState && this.pendingNewState !== runState) {
this.renderState(this.pendingNewState, getCellIndex, this.pendingLastRunSuccess);
this.pendingNewState = undefined;
}
}, RunStateRenderer.MIN_SPINNER_TIME);
} else if (runState === NotebookCellExecutionState.Pending) {
// Not spinning
runStateTooltip = localize('runStatePending', "Pending");
DOM.reset(this.element, renderIcon(Codicons.Codicon.sync));
} else {
this.element.innerText = '';
}
if (runState === NotebookCellRunState.Idle) {
if (runState === NotebookCellExecutionState.Idle && typeof lastRunSuccess !== 'boolean') {
DOM.hide(this.element);
} else {
this.element.style.display = 'flex';
}
if (runStateTooltip) {
this.element.setAttribute('title', runStateTooltip);
}
this.lastRunState = runState;
}
}

View file

@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent, IOutputDto, ICellOutput, IOutputItemDto, ISelectionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent, IOutputDto, ICellOutput, IOutputItemDto, ISelectionState, NullablePartialNotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ITextSnapshot } from 'vs/editor/common/model';
import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement, UndoRedoGroup, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo';
import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit';
@ -317,18 +317,40 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._operationManager.pushStackElement(label, selectionState, undoRedoGroup, this.alternativeVersionId);
}
private _getCellIndexByHandle(handle: number) {
return this.cells.findIndex(c => c.handle === handle);
}
private _getCellIndexWithOutputIdHandle(outputId: string) {
return this.cells.findIndex(c => !!c.outputs.find(o => o.outputId === outputId));
}
applyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, beginSelectionState: ISelectionState | undefined, endSelectionsComputer: () => ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, computeUndoRedo: boolean = true): boolean {
this._eventEmitter.beginDeferredEmit();
this.pushStackElement('edit', beginSelectionState, undoRedoGroup);
const edits = rawEdits.map((edit, index) => {
let cellIndex: number = -1;
if ('index' in edit) {
cellIndex = edit.index;
} else if ('handle' in edit) {
cellIndex = this._getCellIndexByHandle(edit.handle);
this._assertIndex(cellIndex);
} else if ('outputId' in edit) {
cellIndex = this._getCellIndexWithOutputIdHandle(edit.outputId);
this._assertIndex(cellIndex);
} else if (edit.editType !== CellEditType.DocumentMetadata) {
throw new Error('Invalid cell edit');
}
return {
edit,
cellIndex,
end:
(edit.editType === CellEditType.DocumentMetadata)
? undefined
: (edit.editType === CellEditType.Replace ? edit.index + edit.count : edit.index),
: (edit.editType === CellEditType.Replace ? edit.index + edit.count : cellIndex),
originalIndex: index,
};
}).sort((a, b) => {
@ -343,15 +365,15 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return b.end - a.end || b.originalIndex - a.originalIndex;
});
for (const { edit } of edits) {
for (const { edit, cellIndex } of edits) {
switch (edit.editType) {
case CellEditType.Replace:
this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo);
break;
case CellEditType.Output:
//TODO@jrieken,@rebornix no event, no undo stop (?)
this._assertIndex(edit.index);
const cell = this._cells[edit.index];
this._assertIndex(cellIndex);
const cell = this._cells[cellIndex];
if (edit.append) {
this._spliceNotebookCellOutputs(cell.handle, [[cell.outputs.length, 0, edit.outputs.map(op => new NotebookCellOutputTextModel(op))]], computeUndoRedo);
} else {
@ -360,8 +382,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
break;
case CellEditType.OutputItems:
{
this._assertIndex(edit.index);
const cell = this._cells[edit.index];
this._assertIndex(cellIndex);
const cell = this._cells[cellIndex];
if (edit.append) {
this._appendNotebookCellOutputItems(cell.handle, edit.outputId, edit.items);
} else {
@ -374,6 +396,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._assertIndex(edit.index);
this._changeCellMetadata(this._cells[edit.index].handle, edit.metadata, computeUndoRedo);
break;
case CellEditType.PartialMetadata:
this._assertIndex(cellIndex);
this._changeCellMetadataPartial(this._cells[cellIndex].handle, edit.metadata, computeUndoRedo);
break;
case CellEditType.CellLanguage:
this._assertIndex(edit.index);
this._changeCellLanguage(this._cells[edit.index].handle, edit.language, computeUndoRedo);
@ -501,16 +527,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
readonly label = 'Update Notebook Metadata';
undo() {
that._updateNotebookMetadata({
...oldMetadata,
runState: that.metadata.runState
}, false);
that._updateNotebookMetadata(oldMetadata, false);
}
redo() {
that._updateNotebookMetadata({
...metadata,
runState: that.metadata.runState
}, false);
that._updateNotebookMetadata(metadata, false);
}
}(), undefined, undefined);
}
@ -623,9 +643,26 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return true;
}
private _changeCellMetadata(handle: number, metadata: NotebookCellMetadata, computeUndoRedo: boolean) {
const cell = this._cells.find(cell => cell.handle === handle);
private _changeCellMetadataPartial(handle: number, metadata: NullablePartialNotebookCellMetadata, computeUndoRedo: boolean) {
const cell = this._mapping.get(handle);
if (!cell) {
return;
}
const newMetadata: NotebookCellMetadata = {
...cell.metadata
};
let k: keyof NullablePartialNotebookCellMetadata;
for (k in metadata) {
const value = metadata[k] ?? undefined;
newMetadata[k] = value as any; // TS...
}
return this._changeCellMetadata(handle, newMetadata, computeUndoRedo);
}
private _changeCellMetadata(handle: number, metadata: NotebookCellMetadata, computeUndoRedo: boolean) {
const cell = this._mapping.get(handle);
if (!cell) {
return;
}
@ -648,12 +685,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
}
}), undefined, undefined);
}
// should be deferred
cell.metadata = metadata;
} else {
cell.metadata = metadata;
}
// should be deferred
cell.metadata = metadata;
this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeCellMetadata, index: this._cells.indexOf(cell), metadata: cell.metadata, transient: !triggerDirtyChange }, true);
}

View file

@ -61,7 +61,6 @@ export const notebookDocumentMetadataDefaults: Required<NotebookDocumentMetadata
cellEditable: true,
cellHasExecutionOrder: true,
custom: {},
runState: NotebookRunState.Idle,
trusted: true
};
@ -70,15 +69,19 @@ export interface NotebookDocumentMetadata {
cellEditable: boolean;
cellHasExecutionOrder: boolean;
custom?: { [key: string]: unknown };
runState?: NotebookRunState;
trusted: boolean;
}
export enum NotebookCellRunState {
Running = 1,
Idle = 2,
Success = 3,
Error = 4
export enum NotebookCellExecutionState {
Idle = 1,
Pending = 2,
Executing = 3,
}
export interface INotebookCellPreviousExecutionResult {
executionOrder?: number;
success?: boolean;
duration?: number;
}
export interface NotebookCellMetadata {
@ -87,7 +90,8 @@ export interface NotebookCellMetadata {
hasExecutionOrder?: boolean;
executionOrder?: number;
statusMessage?: string;
runState?: NotebookCellRunState;
lastRunSuccess?: boolean;
runState?: NotebookCellExecutionState;
runStartTime?: number;
lastRunDuration?: number;
inputCollapsed?: boolean;
@ -269,12 +273,12 @@ export interface NotebookCellsChangeLanguageEvent {
export interface NotebookCellsChangeMetadataEvent {
readonly kind: NotebookCellsChangeType.ChangeCellMetadata;
readonly index: number;
readonly metadata: NotebookCellMetadata | undefined;
readonly metadata: NotebookCellMetadata;
}
export interface NotebookDocumentChangeMetadataEvent {
readonly kind: NotebookCellsChangeType.ChangeDocumentMetadata;
readonly metadata: NotebookDocumentMetadata | undefined;
readonly metadata: NotebookDocumentMetadata;
}
export interface NotebookDocumentUnknownChangeEvent {
@ -326,7 +330,8 @@ export const enum CellEditType {
Move = 7,
Unknown = 8,
CellContent = 9,
OutputItems = 10
OutputItems = 10,
PartialMetadata = 11
}
export interface ICellDto2 {
@ -351,9 +356,15 @@ export interface ICellOutputEdit {
append?: boolean
}
export interface ICellOutputEditByHandle {
editType: CellEditType.Output;
handle: number;
outputs: IOutputDto[];
append?: boolean
}
export interface ICellOutputItemEdit {
editType: CellEditType.OutputItems;
index: number;
outputId: string;
items: IOutputItemDto[];
append?: boolean;
@ -365,6 +376,21 @@ export interface ICellMetadataEdit {
metadata: NotebookCellMetadata;
}
export type NullablePartialNotebookCellMetadata = {
[Key in keyof Partial<NotebookCellMetadata>]: NotebookCellMetadata[Key] | null
};
export interface ICellPartialMetadataEdit {
editType: CellEditType.PartialMetadata;
index: number;
metadata: Partial<NullablePartialNotebookCellMetadata>;
}
export interface ICellPartialMetadataEditByHandle {
editType: CellEditType.PartialMetadata;
handle: number;
metadata: Partial<NullablePartialNotebookCellMetadata>;
}
export interface ICellLanguageEdit {
editType: CellEditType.CellLanguage;
@ -384,7 +410,8 @@ export interface ICellMoveEdit {
newIdx: number;
}
export type ICellEditOperation = ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellLanguageEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit;
export type IImmediateCellEditOperation = ICellOutputEditByHandle | ICellPartialMetadataEditByHandle | ICellOutputItemEdit;
export type ICellEditOperation = IImmediateCellEditOperation | ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellPartialMetadataEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit | ICellLanguageEdit;
export interface NotebookDataDto {
readonly cells: ICellDto2[];
@ -731,10 +758,11 @@ export interface INotebookKernel {
isPreferred?: boolean;
preloads?: URI[];
supportedLanguages?: string[]
implementsInterrupt?: boolean;
resolve(uri: URI, editorId: string, token: CancellationToken): Promise<void>;
executeNotebookCell(uri: URI, handle: number | undefined): Promise<void>;
cancelNotebookCell(uri: URI, handle: number | undefined): Promise<void>;
executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void>;
cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise<void>;
}
export interface INotebookKernelProvider {

View file

@ -17,7 +17,7 @@ import { NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/not
import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, INotebookKernel, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, ICellRange, INotebookKernel, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices';
@ -29,13 +29,15 @@ suite('NotebookEditorKernelManager', () => {
instantiationService.stub(IQuickInputService, new TestQuickInputService());
const loadKernelPreloads = async () => { };
const onDidChangeViewModel = () => { };
const testDelegate = { loadKernelPreloads, onDidChangeViewModel };
async function withTestNotebook(cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (viewModel: NotebookViewModel, textModel: NotebookTextModel) => void | Promise<void>) {
return _withTestNotebook(cells, (editor) => callback(editor.viewModel, editor.viewModel.notebookDocument));
}
test('ctor', () => {
instantiationService.createInstance(NotebookEditorKernelManager, {});
instantiationService.createInstance(NotebookEditorKernelManager, testDelegate);
const contextKeyService = instantiationService.get(IContextKeyService);
assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0);
@ -45,7 +47,7 @@ suite('NotebookEditorKernelManager', () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } });
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell));
@ -56,7 +58,7 @@ suite('NotebookEditorKernelManager', () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } });
kernelManager.activeKernel = new TestNotebookKernel({ languages: ['testlang'] });
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
@ -68,10 +70,10 @@ suite('NotebookEditorKernelManager', () => {
await withTestNotebook(
[],
async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { viewModel, loadKernelPreloads });
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } });
const kernel = new TestNotebookKernel({ languages: ['javascript'] });
const executeSpy = sinon.spy();
kernel.executeNotebookCell = executeSpy;
kernel.executeNotebookCellsRequest = executeSpy;
kernelManager.activeKernel = kernel;
const cell = viewModel.createCell(0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
@ -94,10 +96,10 @@ class TestNotebookKernel implements INotebookKernel {
preloads?: URI[] | undefined;
supportedLanguages?: string[] | undefined;
async resolve(uri: URI, editorId: string, token: CancellationToken): Promise<void> { }
executeNotebookCell(uri: URI, handle: number | undefined): Promise<void> {
executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void> {
throw new Error('Method not implemented.');
}
cancelNotebookCell(uri: URI, handle: number | undefined): Promise<void> {
cancelNotebookCellExecution(): Promise<void> {
throw new Error('Method not implemented.');
}

View file

@ -253,6 +253,12 @@ suite('NotebookTextModel', () => {
}], true, undefined, () => undefined, undefined);
});
textModel.applyEdits([{
index: 0,
editType: CellEditType.Metadata,
metadata: { executionOrder: 15 },
}], true, undefined, () => undefined, undefined);
textModel.applyEdits([{
index: 0,
editType: CellEditType.Metadata,
@ -261,6 +267,34 @@ suite('NotebookTextModel', () => {
assert.equal(textModel.cells.length, 1);
assert.equal(textModel.cells[0].metadata?.editable, false);
assert.equal(textModel.cells[0].metadata?.executionOrder, undefined);
}
);
});
test('partial metadata', async function () {
await withTestNotebook(
[
['var a = 1;', 'javascript', CellKind.Code, [], { editable: true }],
],
(editor) => {
const textModel = editor.viewModel.notebookDocument;
textModel.applyEdits([{
index: 0,
editType: CellEditType.PartialMetadata,
metadata: { executionOrder: 15 },
}], true, undefined, () => undefined, undefined);
textModel.applyEdits([{
index: 0,
editType: CellEditType.PartialMetadata,
metadata: { editable: false },
}], true, undefined, () => undefined, undefined);
assert.strictEqual(textModel.cells.length, 1);
assert.strictEqual(textModel.cells[0].metadata?.editable, false);
assert.strictEqual(textModel.cells[0].metadata?.executionOrder, 15);
}
);
});

View file

@ -13,7 +13,7 @@ import { mock } from 'vs/base/test/common/mock';
import { IModelAddedData, MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { CellKind, CellUri, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, CellUri, NotebookCellExecutionState, NotebookCellsChangeType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { URI } from 'vs/base/common/uri';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
@ -412,4 +412,30 @@ suite('NotebookCell#Document', function () {
assert.strictEqual(first.document.languageId, 'fooLang');
assert.ok(removedDoc === addedDoc);
});
test('change cell execution state does not trigger onDidChangeMetadata event', async function () {
let didFireOnDidChangeMetadata = false;
let e = extHostNotebooks.onDidChangeCellMetadata(() => {
didFireOnDidChangeMetadata = true;
});
const changeExeState = Event.toPromise(extHostNotebooks.onDidChangeNotebookCellExecutionState);
extHostNotebooks.$acceptModelChanged(notebook.uri, {
versionId: 12, rawEvents: [{
kind: NotebookCellsChangeType.ChangeCellMetadata,
index: 0,
metadata: {
...notebook.getCellFromIndex(0)?.internalMetadata,
...{
runState: NotebookCellExecutionState.Executing
}
}
}]
}, false);
await changeExeState;
assert.strictEqual(didFireOnDidChangeMetadata, false);
e.dispose();
});
});

View file

@ -655,7 +655,6 @@ suite('ExtHostTypes', function () {
assert.strictEqual(obj.cellHasExecutionOrder, notebookDocumentMetadataDefaults.cellHasExecutionOrder);
assert.deepStrictEqual(obj.custom, notebookDocumentMetadataDefaults.custom);
assert.strictEqual(obj.editable, notebookDocumentMetadataDefaults.editable);
assert.strictEqual(obj.runState, notebookDocumentMetadataDefaults.runState);
assert.strictEqual(obj.trusted, notebookDocumentMetadataDefaults.trusted);
});
@ -683,22 +682,4 @@ suite('ExtHostTypes', function () {
assert.strictEqual(newObj.custom, undefined);
});
test('Unable to reset executionOrder of cells #116956', function () {
let obj = new types.NotebookCellMetadata();
assert.strictEqual(obj.executionOrder, undefined);
obj = obj.with({ executionOrder: 23 });
assert.strictEqual(obj.executionOrder, 23);
obj = obj.with({ executionOrder: undefined });
assert.strictEqual(obj.executionOrder, 23);
obj = obj.with({});
assert.strictEqual(obj.executionOrder, 23);
obj = obj.with({ executionOrder: null });
assert.strictEqual(obj.executionOrder, undefined);
});
});