Merge branch 'notebook/dev' into main

This commit is contained in:
rebornix
2021-04-20 13:27:44 -07:00
35 changed files with 979 additions and 2139 deletions
@@ -139,7 +139,7 @@ suite('Notebook Document', function () {
// inserting two new cells // inserting two new cells
{ {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, 0, [{ edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 0), [{
kind: vscode.NotebookCellKind.Markdown, kind: vscode.NotebookCellKind.Markdown,
language: 'markdown', language: 'markdown',
metadata: undefined, metadata: undefined,
@@ -164,8 +164,8 @@ suite('Notebook Document', function () {
// deleting cell 1 and 3 // deleting cell 1 and 3
{ {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, 1, []); edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 1), []);
edit.replaceNotebookCells(document.uri, 2, 3, []); edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(2, 3), []);
const success = await vscode.workspace.applyEdit(edit); const success = await vscode.workspace.applyEdit(edit);
assert.strictEqual(success, true); assert.strictEqual(success, true);
} }
@@ -176,7 +176,7 @@ suite('Notebook Document', function () {
// replacing all cells // replacing all cells
{ {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, 1, [{ edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 1), [{
kind: vscode.NotebookCellKind.Markdown, kind: vscode.NotebookCellKind.Markdown,
language: 'markdown', language: 'markdown',
metadata: undefined, metadata: undefined,
@@ -199,7 +199,7 @@ suite('Notebook Document', function () {
// remove all cells // remove all cells
{ {
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, document.cellCount, []); edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []);
const success = await vscode.workspace.applyEdit(edit); const success = await vscode.workspace.applyEdit(edit);
assert.strictEqual(success, true); assert.strictEqual(success, true);
} }
@@ -212,7 +212,7 @@ suite('Notebook Document', function () {
assert.strictEqual(document.cellCount, 1); assert.strictEqual(document.cellCount, 1);
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, 0, [{ edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, 0), [{
kind: vscode.NotebookCellKind.Markdown, kind: vscode.NotebookCellKind.Markdown,
language: 'markdown', language: 'markdown',
metadata: undefined, metadata: undefined,
@@ -320,7 +320,7 @@ suite('Notebook Document', function () {
assert.strictEqual(notebook.viewType, 'notebook.nbdtest'); assert.strictEqual(notebook.viewType, 'notebook.nbdtest');
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(notebook.uri, 0, 0, [{ edit.replaceNotebookCells(notebook.uri, new vscode.NotebookRange(0, 0), [{
kind: vscode.NotebookCellKind.Markdown, kind: vscode.NotebookCellKind.Markdown,
language: 'markdown', language: 'markdown',
metadata: undefined, metadata: undefined,
@@ -364,6 +364,16 @@ suite('Notebook Document', function () {
assert.strictEqual(opened === closed, true); assert.strictEqual(opened === closed, true);
}); });
test('setTextDocumentLanguage when notebook editor is not open', async function () {
const uri = await utils.createRandomFile('', undefined, '.nbdtest');
const notebook = await vscode.notebook.openNotebookDocument(uri);
const firstCelUri = notebook.cellAt(0).document.uri;
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
let cellDoc = await vscode.workspace.openTextDocument(firstCelUri);
cellDoc = await vscode.languages.setTextDocumentLanguage(cellDoc, 'css');
assert.strictEqual(cellDoc.languageId, 'css');
});
test('#117273, Add multiple outputs', async function () { test('#117273, Add multiple outputs', async function () {
@@ -410,7 +420,7 @@ suite('Notebook Document', function () {
assert.strictEqual(document.isDirty, false); assert.strictEqual(document.isDirty, false);
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, document.getCells().length, []); edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []);
assert.ok(await vscode.workspace.applyEdit(edit)); assert.ok(await vscode.workspace.applyEdit(edit));
assert.strictEqual(document.isDirty, true); assert.strictEqual(document.isDirty, true);
@@ -425,7 +435,7 @@ suite('Notebook Document', function () {
assert.strictEqual(document.isDirty, false); assert.strictEqual(document.isDirty, false);
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.replaceNotebookCells(document.uri, 0, document.getCells().length, []); edit.replaceNotebookCells(document.uri, new vscode.NotebookRange(0, document.cellCount), []);
assert.ok(await vscode.workspace.applyEdit(edit)); assert.ok(await vscode.workspace.applyEdit(edit));
assert.strictEqual(document.isDirty, true); assert.strictEqual(document.isDirty, true);
@@ -8,6 +8,10 @@ import * as assert from 'assert';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors, assertNoRpc } from '../utils'; import { createRandomFile, asPromise, disposeAll, closeAllEditors, revertAllDirty, saveAllEditors, assertNoRpc } from '../utils';
async function createRandomNotebookFile() {
return createRandomFile('', undefined, '.vsctestnb');
}
// Since `workbench.action.splitEditor` command does await properly // Since `workbench.action.splitEditor` command does await properly
// Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves // Notebook editor/document events are not guaranteed to be sent to the ext host when promise resolves
// The workaround here is waiting for the first visible notebook editor change event. // The workaround here is waiting for the first visible notebook editor change event.
@@ -53,44 +57,30 @@ async function withEvent<T>(event: vscode.Event<T>, callback: (e: Promise<T>) =>
await callback(e); await callback(e);
} }
const kernel1 = new class implements vscode.NotebookKernel {
readonly id = 'mainKernel';
readonly label = 'Notebook Test Kernel';
readonly isPreferred = true;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookRange[]) { class Kernel {
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(); readonly controller: vscode.NotebookController;
await task.replaceOutput(new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined)
]));
task.end({ success: true });
return;
}
for (let range of ranges) { constructor(id: string, label: string) {
for (let i = range.start; i < range.end; i++) { this.controller = vscode.notebook.createNotebookController(id, 'notebookCoreTest', label);
await this.runCell(document, i); this.controller.executeHandler = this._execute.bind(this);
} this.controller.isPreferred = true;
this.controller.hasExecutionOrder = true;
this.controller.supportedLanguages = ['typescript', 'javascript'];
}
protected async _execute(cells: vscode.NotebookCell[]): Promise<void> {
for (let cell of cells) {
await this._runCell(cell);
} }
} }
private async runCell(document: vscode.NotebookDocument, idx: number) { protected async _runCell(cell: vscode.NotebookCell) {
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'mainKernel'); const task = this.controller.createNotebookCellExecutionTask(cell);
if (!task) {
return;
}
task.start(); task.start();
task.executionOrder = 1; task.executionOrder = 1;
if (document.uri.path.endsWith('customRenderer.vsctestnb')) { if (cell.notebook.uri.path.endsWith('customRenderer.vsctestnb')) {
await task.replaceOutput([new vscode.NotebookCellOutput([ await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined) new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined)
])]); ])]);
@@ -102,82 +92,8 @@ const kernel1 = new class implements vscode.NotebookKernel {
])]); ])]);
task.end({ success: true }); task.end({ success: true });
} }
};
const kernel2 = new class implements vscode.NotebookKernel {
readonly id = 'secondaryKernel';
readonly label = 'Notebook Secondary Test Kernel';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookRange[]) {
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;
}
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my second 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();
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 {
private _onDidChangeKernels = new vscode.EventEmitter<undefined>();
onDidChangeKernels = this._onDidChangeKernels.event;
private _hasKernels = true;
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 : [];
}
setHasKernels(hasKernels: boolean): void {
this._hasKernels = hasKernels;
this._onDidChangeKernels.fire(undefined);
}
} }
let currentKernelProvider: KernelProvider;
function getFocusedCell(editor?: vscode.NotebookEditor) { function getFocusedCell(editor?: vscode.NotebookEditor) {
return editor ? editor.document.cellAt(editor.selections[0].start) : undefined; return editor ? editor.document.cellAt(editor.selections[0].start) : undefined;
@@ -253,8 +169,27 @@ suite('Notebook API tests', function () {
}); });
setup(() => { setup(() => {
currentKernelProvider = new KernelProvider();
testDisposables.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.vsctestnb' }, currentKernelProvider)); const kernel1 = new Kernel('mainKernel', 'Notebook Test Kernel');
const kernel2 = new class extends Kernel {
constructor() {
super('secondaryKernel', 'Notebook Secondary Test Kernel');
this.controller.isPreferred = false;
this.controller.hasExecutionOrder = false;
}
override async _runCell(cell: vscode.NotebookCell) {
const task = this.controller.createNotebookCellExecutionTask(cell);
task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined)
])]);
task.end({ success: true });
}
};
testDisposables.push(kernel1.controller, kernel2.controller);
}); });
teardown(() => { teardown(() => {
@@ -263,7 +198,7 @@ suite('Notebook API tests', function () {
}); });
test('shared document in notebook editors', async function () { test('shared document in notebook editors', async function () {
const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
let counter = 0; let counter = 0;
const disposables: vscode.Disposable[] = []; const disposables: vscode.Disposable[] = [];
disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => { disposables.push(vscode.notebook.onDidOpenNotebookDocument(() => {
@@ -284,7 +219,7 @@ suite('Notebook API tests', function () {
}); });
test('editor open/close event', async function () { test('editor open/close event', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors); const firstEditorOpen = asPromise(vscode.window.onDidChangeVisibleNotebookEditors);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await firstEditorOpen; await firstEditorOpen;
@@ -295,7 +230,7 @@ suite('Notebook API tests', function () {
}); });
test('editor open/close event 2', async function () { test('editor open/close event 2', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
let count = 0; let count = 0;
const disposables: vscode.Disposable[] = []; const disposables: vscode.Disposable[] = [];
disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => { disposables.push(vscode.window.onDidChangeVisibleNotebookEditors(() => {
@@ -313,7 +248,7 @@ suite('Notebook API tests', function () {
}); });
test('correct cell selection on undo/redo of cell creation', async function () { test('correct cell selection on undo/redo of cell creation', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('undo'); await vscode.commands.executeCommand('undo');
@@ -332,7 +267,7 @@ suite('Notebook API tests', function () {
}); });
test('editor editing event 2', async function () { test('editor editing event 2', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells); const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@@ -384,7 +319,7 @@ suite('Notebook API tests', function () {
}); });
test('editor move cell event', async function () { test('editor move cell event', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
@@ -405,7 +340,7 @@ suite('Notebook API tests', function () {
}); });
test('notebook editor active/visible', async function () { test('notebook editor active/visible', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstEditor = vscode.window.activeNotebookEditor; const firstEditor = vscode.window.activeNotebookEditor;
assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true);
@@ -437,7 +372,7 @@ suite('Notebook API tests', function () {
}); });
test('notebook active editor change', async function () { test('notebook active editor change', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor); const firstEditorOpen = asPromise(vscode.window.onDidChangeActiveNotebookEditor);
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await firstEditorOpen; await firstEditorOpen;
@@ -450,7 +385,7 @@ suite('Notebook API tests', function () {
}); });
test('edit API (replaceMetadata)', async function () { test('edit API (replaceMetadata)', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.window.activeNotebookEditor!.edit(editBuilder => { await vscode.window.activeNotebookEditor!.edit(editBuilder => {
@@ -466,7 +401,7 @@ suite('Notebook API tests', function () {
}); });
test('edit API (replaceMetadata, event)', async function () { test('edit API (replaceMetadata, event)', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const event = asPromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata); const event = asPromise<vscode.NotebookCellMetadataChangeEvent>(vscode.notebook.onDidChangeCellMetadata);
@@ -484,7 +419,7 @@ suite('Notebook API tests', function () {
}); });
test('edit API batch edits', async function () { test('edit API batch edits', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells); const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@@ -502,7 +437,7 @@ suite('Notebook API tests', function () {
}); });
test('edit API batch edits undo/redo', async function () { test('edit API batch edits undo/redo', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells); const cellsChangeEvent = asPromise<vscode.NotebookCellsChangeEvent>(vscode.notebook.onDidChangeNotebookCells);
@@ -528,7 +463,7 @@ suite('Notebook API tests', function () {
}); });
test('initialzation should not emit cell change events.', async function () { test('initialzation should not emit cell change events.', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
let count = 0; let count = 0;
const disposables: vscode.Disposable[] = []; const disposables: vscode.Disposable[] = [];
disposables.push(vscode.notebook.onDidChangeNotebookCells(() => { disposables.push(vscode.notebook.onDidChangeNotebookCells(() => {
@@ -547,7 +482,7 @@ suite('Notebook API tests', function () {
// suite('notebook workflow', () => { // suite('notebook workflow', () => {
test('notebook open', async function () { test('notebook open', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test');
@@ -578,7 +513,7 @@ suite('Notebook API tests', function () {
}); });
test('notebook cell actions', async function () { test('notebook cell actions', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test');
@@ -651,7 +586,7 @@ suite('Notebook API tests', function () {
}); });
test('notebook join cells', async function () { test('notebook join cells', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test');
@@ -674,7 +609,7 @@ suite('Notebook API tests', function () {
}); });
test('move cells will not recreate cells in ExtHost', async function () { test('move cells will not recreate cells in ExtHost', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove');
@@ -691,34 +626,34 @@ suite('Notebook API tests', function () {
await saveFileAndCloseAll(resource); await saveFileAndCloseAll(resource);
}); });
test('document runnable based on kernel count', async () => { // test('document runnable based on kernel count', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); // const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!; // const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); // const cell = editor.document.cellAt(0);
assert.strictEqual(cell.outputs.length, 0); // assert.strictEqual(cell.outputs.length, 0);
currentKernelProvider.setHasKernels(false); // currentKernelProvider.setHasKernels(false);
await vscode.commands.executeCommand('notebook.execute'); // await vscode.commands.executeCommand('notebook.execute');
assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work // assert.strictEqual(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work
currentKernelProvider.setHasKernels(true); // currentKernelProvider.setHasKernels(true);
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { // await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.execute'); // await vscode.commands.executeCommand('notebook.execute');
await event; // await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked // assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked
}); // });
await saveAllFilesAndCloseAll(undefined); // await saveAllFilesAndCloseAll(undefined);
}); // });
// TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied // TODO@rebornix this is wrong, `await vscode.commands.executeCommand('notebook.execute');` doesn't wait until the workspace edit is applied
test.skip('cell execute command takes arguments', async () => { test.skip('cell execute command takes arguments', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
@@ -731,7 +666,7 @@ suite('Notebook API tests', function () {
}); });
test('cell execute command takes arguments 2', async () => { test('cell execute command takes arguments 2', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
@@ -749,7 +684,7 @@ suite('Notebook API tests', function () {
assert.strictEqual(cell.outputs.length, 0, 'should clear'); assert.strictEqual(cell.outputs.length, 0, 'should clear');
}); });
const secondResource = await createRandomFile('', undefined, '.vsctestnb'); const secondResource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
@@ -763,7 +698,7 @@ suite('Notebook API tests', function () {
}); });
test('document execute command takes arguments', async () => { test('document execute command takes arguments', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
@@ -780,7 +715,7 @@ suite('Notebook API tests', function () {
await clearChangeEvent; await clearChangeEvent;
assert.strictEqual(cell.outputs.length, 0, 'should clear'); assert.strictEqual(cell.outputs.length, 0, 'should clear');
const secondResource = await createRandomFile('', undefined, '.vsctestnb'); const secondResource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
@@ -793,15 +728,15 @@ suite('Notebook API tests', function () {
await saveAllFilesAndCloseAll(undefined); await saveAllFilesAndCloseAll(undefined);
}); });
test('cell execute and select kernel', async () => { test('cell execute and select kernel', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
vscode.commands.executeCommand('notebook.cell.execute');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.cell.execute');
await event; await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked 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.length, 1);
@@ -811,9 +746,9 @@ suite('Notebook API tests', function () {
]); ]);
}); });
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: 'secondaryKernel' });
vscode.commands.executeCommand('notebook.cell.execute');
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: 'secondaryKernel' });
await vscode.commands.executeCommand('notebook.cell.execute');
await event; await event;
assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked 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.length, 1);
@@ -827,37 +762,35 @@ suite('Notebook API tests', function () {
}); });
test('set outputs on cancel', async () => { 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.NotebookRange[]) { const cancelableKernel = new class extends Kernel {
const idx = ranges[0].start;
constructor() {
super('cancelableKernel', 'Notebook Cancelable Test Kernel');
this.controller.isPreferred = false;
}
async override _execute(cells: vscode.NotebookCell[]) {
for (const cell of cells) {
const task = this.controller.createNotebookCellExecutionTask(cell);
task.start();
task.token.onCancellationRequested(async () => {
await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Canceled'], undefined)
])]);
task.end({});
});
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 createRandomNotebookFile();
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: cancelableKernel.id }); await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: cancelableKernel.controller.id });
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.cell.execute'); await vscode.commands.executeCommand('notebook.cell.execute');
await vscode.commands.executeCommand('notebook.cell.cancelExecution'); await vscode.commands.executeCommand('notebook.cell.cancelExecution');
@@ -870,30 +803,29 @@ suite('Notebook API tests', function () {
]); ]);
}); });
cancelableKernel.controller.dispose();
await saveAllFilesAndCloseAll(undefined); await saveAllFilesAndCloseAll(undefined);
}); });
test('set outputs on interrupt', async () => { test('set outputs on interrupt', async () => {
const interruptableKernel = new class implements vscode.NotebookKernel { const interruptableKernel = new class extends Kernel {
readonly id = 'interruptableKernel';
readonly label = 'Notebook Interruptable Test Kernel';
readonly isPreferred = false; constructor() {
readonly supportedLanguages = ['typescript', 'javascript']; super('interruptableKernel', 'Notebook Interruptable Test Kernel');
this.controller.isPreferred = false;
this.controller.interruptHandler = this.interrupt.bind(this);
}
private _task: vscode.NotebookCellExecutionTask | undefined; private _task: vscode.NotebookCellExecutionTask | undefined;
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookRange[]) { async override _execute(cells: vscode.NotebookCell[]) {
const idx = ranges[0].start; this._task = this.controller.createNotebookCellExecutionTask(cells[0]);
this._task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'interruptableKernel');
if (!this._task) {
return;
}
this._task.start(); this._task.start();
} }
async interrupt(_document: vscode.NotebookDocument) {
async interrupt() {
await this._task!.replaceOutput([new vscode.NotebookCellOutput([ await this._task!.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Interrupted'], undefined) new vscode.NotebookCellOutputItem('text/plain', ['Interrupted'], undefined)
])]); ])]);
@@ -901,13 +833,12 @@ suite('Notebook API tests', function () {
} }
}; };
currentKernelProvider.addKernel(interruptableKernel); const resource = await createRandomNotebookFile();
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: interruptableKernel.id }); await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-api-tests', id: interruptableKernel.controller.id });
await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => { await withEvent<vscode.NotebookCellOutputsChangeEvent>(vscode.notebook.onDidChangeCellOutputs, async (event) => {
await vscode.commands.executeCommand('notebook.cell.execute'); await vscode.commands.executeCommand('notebook.cell.execute');
await vscode.commands.executeCommand('notebook.cell.cancelExecution'); await vscode.commands.executeCommand('notebook.cell.cancelExecution');
@@ -920,11 +851,12 @@ suite('Notebook API tests', function () {
]); ]);
}); });
interruptableKernel.controller.dispose();
await saveAllFilesAndCloseAll(undefined); await saveAllFilesAndCloseAll(undefined);
}); });
test('onDidChangeCellExecutionState is fired', async () => { test('onDidChangeCellExecutionState is fired', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
@@ -955,7 +887,7 @@ suite('Notebook API tests', function () {
// suite('notebook dirty state', () => { // suite('notebook dirty state', () => {
test('notebook open', async function () { test('notebook open', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test');
@@ -987,7 +919,7 @@ suite('Notebook API tests', function () {
// suite('notebook undo redo', () => { // suite('notebook undo redo', () => {
test('notebook open', async function () { test('notebook open', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test'); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), 'test');
@@ -1028,29 +960,8 @@ suite('Notebook API tests', function () {
await saveFileAndCloseAll(resource); await saveFileAndCloseAll(resource);
}); });
test('change cell language when notebook editor is not open', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstCell = vscode.window.activeNotebookEditor!.document.cellAt(0);
const cellUri = firstCell.document.uri;
await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
let cellDoc = await vscode.workspace.openTextDocument(cellUri);
cellDoc = await vscode.languages.setTextDocumentLanguage(cellDoc, 'css');
assert.strictEqual(cellDoc.languageId, 'css');
});
test('change cell language when notebook editor is open', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstCell = vscode.window.activeNotebookEditor!.document.cellAt(0);
const cellDoc = await vscode.languages.setTextDocumentLanguage(firstCell.document, 'css');
assert.strictEqual(cellDoc.languageId, 'css');
});
test('multiple tabs: dirty + clean', async function () { test('multiple tabs: dirty + clean', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), '');
@@ -1060,7 +971,7 @@ suite('Notebook API tests', function () {
edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit); await vscode.workspace.applyEdit(edit);
const secondResource = await createRandomFile('', undefined, '.vsctestnb'); const secondResource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor');
@@ -1074,7 +985,7 @@ suite('Notebook API tests', function () {
}); });
test('multiple tabs: two dirty tabs and switching', async function () { test('multiple tabs: two dirty tabs and switching', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), '');
@@ -1084,7 +995,7 @@ suite('Notebook API tests', function () {
edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;'); edit.insert(getFocusedCell(vscode.window.activeNotebookEditor)!.document.uri, new vscode.Position(0, 0), 'var abc = 0;');
await vscode.workspace.applyEdit(edit); await vscode.workspace.applyEdit(edit);
const secondResource = await createRandomFile('', undefined, '.vsctestnb'); const secondResource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), ''); assert.strictEqual(getFocusedCell(vscode.window.activeNotebookEditor)?.document.getText(), '');
@@ -1107,7 +1018,7 @@ suite('Notebook API tests', function () {
}); });
test.skip('multiple tabs: different editors with same document', async function () { test.skip('multiple tabs: different editors with same document', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const firstNotebookEditor = vscode.window.activeNotebookEditor; const firstNotebookEditor = vscode.window.activeNotebookEditor;
assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first');
@@ -1127,7 +1038,7 @@ suite('Notebook API tests', function () {
}); });
test('custom metadata should be supported', async function () { test('custom metadata should be supported', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
@@ -1140,7 +1051,7 @@ suite('Notebook API tests', function () {
// TODO@rebornix skip as it crashes the process all the time // TODO@rebornix skip as it crashes the process all the time
test.skip('custom metadata should be supported 2', async function () { test.skip('custom metadata should be supported 2', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); assert.strictEqual(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false);
@@ -1158,7 +1069,7 @@ suite('Notebook API tests', function () {
test('#106657. Opening a notebook from markers view is broken ', async function () { test('#106657. Opening a notebook from markers view is broken ', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const document = vscode.window.activeNotebookEditor?.document!; const document = vscode.window.activeNotebookEditor?.document!;
@@ -1175,7 +1086,7 @@ suite('Notebook API tests', function () {
}); });
test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () { test.skip('Cannot open notebook from cell-uri with vscode.open-command', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const document = vscode.window.activeNotebookEditor?.document!; const document = vscode.window.activeNotebookEditor?.document!;
@@ -1192,7 +1103,7 @@ suite('Notebook API tests', function () {
}); });
test('#97830, #97764. Support switch to other editor types', async function () { test('#97830, #97764. Support switch to other editor types', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow');
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
@@ -1214,7 +1125,7 @@ suite('Notebook API tests', function () {
// open text editor, pin, and then open a notebook // open text editor, pin, and then open a notebook
test('#96105 - dirty editors', async function () { test('#96105 - dirty editors', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default');
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;'); edit.insert(resource, new vscode.Position(0, 0), 'var abc = 0;');
@@ -1236,7 +1147,7 @@ suite('Notebook API tests', function () {
}); });
test('#102423 - copy/paste shares the same text buffer', async function () { test('#102423 - copy/paste shares the same text buffer', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
let activeCell = getFocusedCell(vscode.window.activeNotebookEditor); let activeCell = getFocusedCell(vscode.window.activeNotebookEditor);
@@ -1260,7 +1171,7 @@ suite('Notebook API tests', function () {
test('#116598, output items change event.', async function () { test('#116598, output items change event.', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const edit = new vscode.WorkspaceEdit(); const edit = new vscode.WorkspaceEdit();
@@ -1286,7 +1197,7 @@ suite('Notebook API tests', function () {
}); });
test('#115855 onDidSaveNotebookDocument', async function () { test('#115855 onDidSaveNotebookDocument', async function () {
const resource = await createRandomFile(undefined, undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
const notebook = await vscode.notebook.openNotebookDocument(resource); const notebook = await vscode.notebook.openNotebookDocument(resource);
const editor = await vscode.window.showNotebookDocument(notebook); const editor = await vscode.window.showNotebookDocument(notebook);
@@ -1307,56 +1218,38 @@ suite('Notebook API tests', function () {
assert.strictEqual(notebook.isDirty, false); assert.strictEqual(notebook.isDirty, false);
}); });
test('#116808, active kernel should not be undefined', async function () {
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
await withEvent(vscode.notebook.onDidChangeActiveNotebookKernel, async event => {
await event;
assert.notStrictEqual(vscode.window.activeNotebookEditor?.kernel, undefined);
assert.strictEqual(vscode.window.activeNotebookEditor?.kernel?.id, 'mainKernel');
});
await saveAllFilesAndCloseAll(resource);
});
test('Output changes are applied once the promise resolves', async function () { test('Output changes are applied once the promise resolves', async function () {
const verifyOutputSyncKernel = new class implements vscode.NotebookKernel { const verifyOutputSyncKernel = new class extends Kernel {
readonly id = 'verifyOutputSyncKernel';
readonly label = '';
readonly isPreferred = false;
readonly supportedLanguages = ['typescript', 'javascript'];
async executeCellsRequest(document: vscode.NotebookDocument, ranges: vscode.NotebookRange[]) { constructor() {
const idx = ranges[0].start; super('verifyOutputSyncKernel', '');
this.controller.isPreferred = false;
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, this.id); }
if (!task) {
return;
}
override async _execute(cells: vscode.NotebookCell[]) {
const [cell] = cells;
const task = this.controller.createNotebookCellExecutionTask(cell);
task.start(); task.start();
await task.replaceOutput([new vscode.NotebookCellOutput([ await task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/plain', ['Some output'], undefined) new vscode.NotebookCellOutputItem('text/plain', ['Some output'], undefined)
])]); ])]);
assert.strictEqual(document.cellAt(0).outputs.length, 1); assert.strictEqual(cell.notebook.cellAt(0).outputs.length, 1);
assert.deepStrictEqual(document.cellAt(0).outputs[0].outputs[0].value, ['Some output']); assert.deepStrictEqual(cell.notebook.cellAt(0).outputs[0].outputs[0].value, ['Some output']);
task.end({}); task.end({});
} }
}; };
currentKernelProvider.addKernel(verifyOutputSyncKernel); const resource = await createRandomNotebookFile();
const resource = await createRandomFile('', undefined, '.vsctestnb');
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); 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.selectKernel', { extension: 'vscode.vscode-api-tests', id: verifyOutputSyncKernel.controller.id });
await vscode.commands.executeCommand('notebook.cell.execute'); await vscode.commands.executeCommand('notebook.cell.execute');
await saveAllFilesAndCloseAll(undefined); await saveAllFilesAndCloseAll(undefined);
verifyOutputSyncKernel.controller.dispose();
}); });
test('latestExecutionSummary', async () => { test('latestExecutionSummary', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
@@ -1373,7 +1266,7 @@ suite('Notebook API tests', function () {
}); });
test('initialize latestExecutionSummary', async () => { test('initialize latestExecutionSummary', async () => {
const resource = await createRandomFile('', undefined, '.vsctestnb'); const resource = await createRandomNotebookFile();
await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
const editor = vscode.window.activeNotebookEditor!; const editor = vscode.window.activeNotebookEditor!;
const cell = editor.document.cellAt(0); const cell = editor.document.cellAt(0);
@@ -1385,40 +1278,30 @@ suite('Notebook API tests', function () {
await saveAllFilesAndCloseAll(undefined); await saveAllFilesAndCloseAll(undefined);
}); });
test('Throws errors for invalid execution tasks', async function () {
let missedError: string | undefined;
const invalidKernel = new class implements vscode.NotebookKernel { suite('statusbar', () => {
readonly id = 'invalidKernel'; const emitter = new vscode.EventEmitter<vscode.NotebookCell>();
readonly label = ''; const onDidCallProvide = emitter.event;
readonly isPreferred = false; suiteSetup(() => {
readonly supportedLanguages = ['typescript', 'javascript']; vscode.notebook.registerNotebookCellStatusBarItemProvider({ viewType: 'notebookCoreTest' }, {
async provideCellStatusBarItems(cell: vscode.NotebookCell, _token: vscode.CancellationToken): Promise<vscode.NotebookCellStatusBarItem[]> {
emitter.fire(cell);
return [];
}
});
});
async executeCellsRequest(document: vscode.NotebookDocument, _ranges: vscode.NotebookRange[]) { test('provideCellStatusBarItems called on metadata change', async function () {
try { const provideCalled = asPromise(onDidCallProvide);
vscode.notebook.createNotebookCellExecutionTask(document.uri, 1000, this.id); const resource = await createRandomNotebookFile();
missedError = 'Expected to throw for invalid index'; await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
return; await provideCalled;
} catch (e) { }
try { const edit = new vscode.WorkspaceEdit();
vscode.notebook.createNotebookCellExecutionTask(vscode.Uri.file('slkdf'), 0, this.id); edit.replaceNotebookCellMetadata(resource, 0, new vscode.NotebookCellMetadata().with({ inputCollapsed: true }));
missedError = 'Expected to throw for invalid uri'; vscode.workspace.applyEdit(edit);
return; await provideCalled;
} 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);
}); });
// }); // });
@@ -1430,7 +1313,7 @@ suite('Notebook API tests', function () {
// return; // return;
// } // }
// const resource = await createRandomFile('', undefined, '.vsctestnb'); // const resource = await createRandomNotebookFile();
// await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest');
// assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first');
// const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png')); // const uri = vscode.window.activeNotebookEditor!.asWebviewUri(vscode.Uri.file('./hello.png'));
@@ -58,17 +58,16 @@ export function activate(context: vscode.ExtensionContext): any {
} }
})); }));
const kernel: vscode.NotebookKernel = { const controller = vscode.notebook.createNotebookController(
id: 'notebookSmokeTest', 'notebookSmokeTest',
label: 'notebookSmokeTest', { pattern: '*.smoke-nb' },
isPreferred: true, 'notebookSmokeTest'
executeCellsRequest: async (document: vscode.NotebookDocument, ranges: vscode.NotebookRange[]) => { );
const idx = ranges[0].start;
const task = vscode.notebook.createNotebookCellExecutionTask(document.uri, idx, 'notebookSmokeTest');
if (!task) {
return;
}
controller.isPreferred = true;
controller.executeHandler = (cells) => {
for (const cell of cells) {
const task = controller.createNotebookCellExecutionTask(cell);
task.start(); task.start();
task.replaceOutput([new vscode.NotebookCellOutput([ task.replaceOutput([new vscode.NotebookCellOutput([
new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined)
@@ -77,11 +76,7 @@ export function activate(context: vscode.ExtensionContext): any {
} }
}; };
context.subscriptions.push(vscode.notebook.registerNotebookKernelProvider({ filenamePattern: '*.smoke-nb' }, { context.subscriptions.push(controller);
provideKernels: async () => {
return [kernel];
}
}));
context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.debugAction', async (cell: vscode.NotebookCell) => { context.subscriptions.push(vscode.commands.registerCommand('vscode-notebook-tests.debugAction', async (cell: vscode.NotebookCell) => {
if (cell) { if (cell) {
+107 -96
View File
@@ -1328,18 +1328,6 @@ declare module 'vscode' {
constructor(cells: NotebookCellData[], metadata?: NotebookDocumentMetadata); constructor(cells: NotebookCellData[], metadata?: NotebookDocumentMetadata);
} }
/** @deprecated used NotebookController */
export interface NotebookCommunication {
/** @deprecated used NotebookController */
readonly editorId: string;
/** @deprecated used NotebookController */
readonly onDidReceiveMessage: Event<any>;
/** @deprecated used NotebookController */
postMessage(message: any): Thenable<boolean>;
/** @deprecated used NotebookController */
asWebviewUri(localResource: Uri): Uri;
}
export interface NotebookDocumentShowOptions { export interface NotebookDocumentShowOptions {
viewColumn?: ViewColumn; viewColumn?: ViewColumn;
preserveFocus?: boolean; preserveFocus?: boolean;
@@ -1420,17 +1408,14 @@ declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorEdit //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorEdit
export interface WorkspaceEdit { export interface WorkspaceEdit {
// todo@API add NotebookEdit-type which handles all these cases?
replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void; replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): void;
replaceNotebookCells(uri: Uri, range: NotebookRange, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void;
// todo@API use NotebookCellRange
replaceNotebookCells(uri: Uri, start: number, end: number, cells: NotebookCellData[], metadata?: WorkspaceEditEntryMetadata): void;
replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellMetadata(uri: Uri, index: number, cellMetadata: NotebookCellMetadata, metadata?: WorkspaceEditEntryMetadata): void;
//todo@API remove output modifications?
replaceNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void;
appendNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void; appendNotebookCellOutput(uri: Uri, index: number, outputs: NotebookCellOutput[], metadata?: WorkspaceEditEntryMetadata): void;
// TODO@api
// https://jupyter-protocol.readthedocs.io/en/latest/messaging.html#update-display-data
replaceNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void; replaceNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void;
appendNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void; appendNotebookCellOutputItems(uri: Uri, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void;
} }
@@ -1461,15 +1446,47 @@ declare module 'vscode' {
//#region https://github.com/microsoft/vscode/issues/106744, NotebookSerializer //#region https://github.com/microsoft/vscode/issues/106744, NotebookSerializer
/**
* The notebook serializer enables the editor to open notebook files.
*
* At its core the editor only knows a [notebook data structure](#NotebookData) but not
* how that data structure is written to a file, nor how it is read from a file. The
* notebook serializer bridges this gap by deserializing bytes into notebook data and
* vice versa.
*/
export interface NotebookSerializer { export interface NotebookSerializer {
deserializeNotebook(data: Uint8Array, token: CancellationToken): NotebookData | Thenable<NotebookData>;
/**
* Deserialize contents of a notebook file into the notebook data structure.
*
* @param content Contents of a notebook file.
* @param token A cancellation token.
* @return Notebook data or a thenable that resolves to such.
*/
deserializeNotebook(content: Uint8Array, token: CancellationToken): NotebookData | Thenable<NotebookData>;
/**
* Serialize notebook data into file contents.
*
* @param data A notebook data structure.
* @param token A cancellation token.
* @returns An array of bytes or a thenable that resolves to such.
*/
serializeNotebook(data: NotebookData, token: CancellationToken): Uint8Array | Thenable<Uint8Array>; serializeNotebook(data: NotebookData, token: CancellationToken): Uint8Array | Thenable<Uint8Array>;
} }
export namespace notebook { export namespace notebook {
// todo@API remove output when notebook marks that as transient, same for metadata // todo@API remove output when notebook marks that as transient, same for metadata
export function registerNotebookSerializer(notebookType: string, provider: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable; /**
* Register a [notebook serializer](#NotebookSerializer).
*
* @param notebookType A notebook.
* @param serializer A notebook serialzier.
* @param options Optional context options that define what parts of a notebook should be persisted
* @return A [disposable](#Disposable) that unregisters this serializer when being disposed.
*/
export function registerNotebookSerializer(notebookType: string, serializer: NotebookSerializer, options?: NotebookDocumentContentOptions): Disposable;
} }
//#endregion //#endregion
@@ -1490,18 +1507,28 @@ declare module 'vscode' {
* @param cells The notebook cells to execute * @param cells The notebook cells to execute
* @param controller The controller that the handler is attached to * @param controller The controller that the handler is attached to
*/ */
(this: NotebookController, cells: NotebookCell[], controller: NotebookController): void (this: NotebookController, cells: NotebookCell[], controller: NotebookController): void | Thenable<void>
} }
export interface NotebookInterruptHandler { export interface NotebookInterruptHandler {
(this: NotebookController): void; (this: NotebookController): void | Thenable<void>;
} }
export interface NotebookController { export interface NotebookController {
/**
* The unique identifier of this notebook controller.
*/
readonly id: string; readonly id: string;
// select notebook of a type and/or by file-pattern /**
* The selector allows to narrow down on specific notebook types or
* instances.
*
* For instance `{ viewType: 'notebook.test' }` selects all notebook
* documents of the type `notebook.test`, whereas `{ pattern: '/my/file/test.nb' }`
* selects only the notebook with the path `/my/file/test.nb`.
*/
readonly selector: NotebookSelector; readonly selector: NotebookSelector;
/** /**
@@ -1511,13 +1538,41 @@ declare module 'vscode' {
*/ */
readonly onDidChangeNotebookAssociation: Event<{ notebook: NotebookDocument, selected: boolean }>; readonly onDidChangeNotebookAssociation: Event<{ notebook: NotebookDocument, selected: boolean }>;
// UI properties (get/set) /**
* The human-readable label of this notebook controller.
*/
label: string; label: string;
detail?: string;
/**
* The human-readable description which is rendered less prominent.
*/
description?: string; description?: string;
/**
* The human-readable detail which is rendered less prominent.
*/
detail?: string;
/**
* Marks this notebook controller as preferred.
*
* When multiple notebook controller apply to a single notebook then
* users can select one - preferred controllers will be shows more
* prominent then.
*/
isPreferred?: boolean; isPreferred?: boolean;
/**
* An array of language identifiers that are supported by this
* controller.
*/
supportedLanguages: string[]; supportedLanguages: string[];
/**
* Whether this controller supports execution order so that the
* editor can render placeholders for them.
*/
// todo@API rename to supportsExecutionOrder, usesExecutionOrder
hasExecutionOrder?: boolean; hasExecutionOrder?: boolean;
/** /**
@@ -1526,8 +1581,16 @@ declare module 'vscode' {
*/ */
executeHandler: NotebookExecutionHandler; executeHandler: NotebookExecutionHandler;
// optional kernel interrupt command /**
* The interrupt handler is invoked the interrupt all execution. This is contrary to cancellation (available via
* [`NotebookCellExecutionTask#token`](NotebookCellExecutionTask#token)) and should only be used when
* execution-level cancellation is supported
*/
interruptHandler?: NotebookInterruptHandler interruptHandler?: NotebookInterruptHandler
/**
* Dispose and free associated resources.
*/
dispose(): void; dispose(): void;
/** /**
@@ -1542,8 +1605,25 @@ declare module 'vscode' {
// ipc // ipc
readonly preloads: NotebookKernelPreload[]; readonly preloads: NotebookKernelPreload[];
/**
* An event that fires when a renderer (see `preloads`) has send a message to the controller.
*/
readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>; readonly onDidReceiveMessage: Event<{ editor: NotebookEditor, message: any }>;
/**
* Send a message to the renderer of notebook editors.
*
* Note that only editors showing documents that are bound to this controller
* are receiving the message.
*
* @param message The message to send.
* @param editor A specific editor to send the message to. When `undefined` all applicable editors are receiving the message.
* @returns A promise that resolves to a boolean indicating if the message has been send or not.
*/
postMessage(message: any, editor?: NotebookEditor): Thenable<boolean>; postMessage(message: any, editor?: NotebookEditor): Thenable<boolean>;
asWebviewUri(localResource: Uri): Uri; asWebviewUri(localResource: Uri): Uri;
} }
@@ -1631,45 +1711,6 @@ declare module 'vscode' {
uri: Uri; uri: Uri;
} }
/** @deprecated used NotebookController */
export interface NotebookKernel {
// todo@API make this mandatory?
readonly id?: string;
label: string;
description?: string;
detail?: string;
isPreferred?: boolean;
// todo@API do we need an preload change event?
preloads?: NotebookKernelPreload[];
/**
* languages supported by kernel
* - first is preferred
* - `undefined` means all languages available in the editor
*/
supportedLanguages?: string[];
// todo@API kernel updating itself
// fired when properties like the supported languages etc change
// onDidChangeProperties?: Event<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: NotebookRange[]): Thenable<void>;
}
export interface NotebookCellExecuteStartContext { export interface NotebookCellExecuteStartContext {
/** /**
* The time that execution began, in milliseconds in the Unix epoch. Used to drive the clock * The time that execution began, in milliseconds in the Unix epoch. Used to drive the clock
@@ -1726,12 +1767,7 @@ declare module 'vscode' {
} }
export namespace notebook { export namespace notebook {
/** /** @deprecated use NotebookController */
* 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 function createNotebookCellExecutionTask(uri: Uri, index: number, kernelId: string): NotebookCellExecutionTask | undefined;
export const onDidChangeCellExecutionState: Event<NotebookCellExecutionStateChangeEvent>; export const onDidChangeCellExecutionState: Event<NotebookCellExecutionStateChangeEvent>;
@@ -1739,31 +1775,6 @@ declare module 'vscode' {
export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern; }; export type NotebookFilenamePattern = GlobPattern | { include: GlobPattern; exclude: GlobPattern; };
// todo@API why not for NotebookContentProvider?
export interface NotebookDocumentFilter {
viewType?: string | string[];
filenamePattern?: NotebookFilenamePattern;
}
/** @deprecated used NotebookController */
export interface NotebookKernelProvider<T extends NotebookKernel = NotebookKernel> {
onDidChangeKernels?: Event<NotebookDocument | undefined>;
provideKernels(document: NotebookDocument, token: CancellationToken): ProviderResult<T[]>;
resolveKernel?(kernel: T, document: NotebookDocument, webview: NotebookCommunication, token: CancellationToken): ProviderResult<void>;
}
export interface NotebookEditor {
/** @deprecated kernels are private object*/
readonly kernel?: NotebookKernel;
}
export namespace notebook {
/** @deprecated */
export const onDidChangeActiveNotebookKernel: Event<{ document: NotebookDocument, kernel: NotebookKernel | undefined; }>;
/** @deprecated used NotebookController */
export function registerNotebookKernelProvider(selector: NotebookDocumentFilter, provider: NotebookKernelProvider): Disposable;
}
//#endregion //#endregion
//#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorDecorationType //#region https://github.com/microsoft/vscode/issues/106744, NotebookEditorDecorationType
@@ -3,19 +3,16 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { flatten } from 'vs/base/common/arrays';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { IRelativePattern } from 'vs/base/common/glob'; import { IRelativePattern } from 'vs/base/common/glob';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService';
import { ICellRange, INotebookCellStatusBarItemProvider, INotebookDocumentFilter, INotebookExclusiveDocumentFilter, INotebookKernel, NotebookDataDto, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector';
import { INotebookCellStatusBarItemProvider, INotebookExclusiveDocumentFilter, NotebookDataDto, TransientCellMetadata, TransientDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol'; import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainContext, MainThreadNotebookShape, NotebookExtensionDescription } from '../common/extHost.protocol';
@@ -27,43 +24,25 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
private readonly _proxy: ExtHostNotebookShape; private readonly _proxy: ExtHostNotebookShape;
private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, disposable: IDisposable }>(); private readonly _notebookProviders = new Map<string, { controller: IMainNotebookController, disposable: IDisposable }>();
private readonly _notebookSerializer = new Map<number, IDisposable>(); private readonly _notebookSerializer = new Map<number, IDisposable>();
private readonly _notebookKernelProviders = new Map<number, { extension: NotebookExtensionDescription, emitter: Emitter<URI | undefined>, provider: IDisposable }>();
private readonly _notebookCellStatusBarRegistrations = new Map<number, IDisposable>(); private readonly _notebookCellStatusBarRegistrations = new Map<number, IDisposable>();
constructor( constructor(
extHostContext: IExtHostContext, extHostContext: IExtHostContext,
@INotebookService private readonly _notebookService: INotebookService, @INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorService private readonly _notebookEditorService: INotebookEditorService,
@ILogService private readonly _logService: ILogService,
@INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService, @INotebookCellStatusBarService private readonly _cellStatusBarService: INotebookCellStatusBarService,
) { ) {
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook); this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostNotebook);
this._registerListeners();
} }
dispose(): void { dispose(): void {
this._disposables.dispose(); this._disposables.dispose();
// remove all notebook providers // remove all notebook providers
for (const item of this._notebookProviders.values()) { for (const item of this._notebookProviders.values()) {
item.disposable.dispose(); item.disposable.dispose();
} }
// remove all kernel providers
for (const item of this._notebookKernelProviders.values()) {
item.emitter.dispose();
item.provider.dispose();
}
dispose(this._notebookSerializer.values()); dispose(this._notebookSerializer.values());
} }
private _registerListeners(): void {
this._disposables.add(this._notebookService.onDidChangeNotebookActiveKernel(e => {
this._proxy.$acceptNotebookActiveKernelChange(e);
}));
}
async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: { async $registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string, options: {
transientOutputs: boolean; transientOutputs: boolean;
transientCellMetadata: TransientCellMetadata; transientCellMetadata: TransientCellMetadata;
@@ -89,12 +68,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
transientOptions: contentOptions transientOptions: contentOptions
}; };
}, },
resolveNotebookEditor: async (viewType: string, uri: URI, editorId: string) => {
await this._proxy.$resolveNotebookEditor(viewType, uri, editorId);
},
onDidReceiveMessage: (editorId: string, rendererType: string | undefined, message: unknown) => {
this._proxy.$onDidReceiveMessage(editorId, rendererType, message);
},
save: async (uri: URI, token: CancellationToken) => { save: async (uri: URI, token: CancellationToken) => {
return this._proxy.$saveNotebook(viewType, uri, token); return this._proxy.$saveNotebook(viewType, uri, token);
}, },
@@ -149,55 +122,6 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
this._notebookSerializer.delete(handle); this._notebookSerializer.delete(handle);
} }
async $registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void> {
const emitter = new Emitter<URI | undefined>();
const that = this;
const provider = this._notebookService.registerNotebookKernelProvider({
providerExtensionId: extension.id.value,
providerDescription: extension.description,
onDidChangeKernels: emitter.event,
selector: documentFilter,
provideKernels: async (uri: URI, token: CancellationToken): Promise<INotebookKernel[]> => {
const result: INotebookKernel[] = [];
const kernelsDto = await that._proxy.$provideNotebookKernels(handle, uri, token);
for (const dto of kernelsDto) {
result.push({
id: dto.id,
friendlyId: dto.friendlyId,
label: dto.label,
extension: dto.extension,
localResourceRoot: URI.revive(dto.extensionLocation),
providerHandle: dto.providerHandle,
description: dto.description,
detail: dto.detail,
isPreferred: dto.isPreferred,
preloadProvides: flatten(dto.preloads?.map(p => p.provides) ?? []),
preloadUris: dto.preloads?.map(u => URI.revive(u.uri)) ?? [],
supportedLanguages: dto.supportedLanguages,
implementsInterrupt: dto.implementsInterrupt,
implementsExecutionOrder: true, // todo@jrieken this is temporary and for the OLD API only
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);
},
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);
},
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);
}
});
}
return result;
}
});
this._notebookKernelProviders.set(handle, { extension, emitter, provider });
return;
}
$emitCellStatusBarEvent(eventHandle: number): void { $emitCellStatusBarEvent(eventHandle: number): void {
const emitter = this._notebookCellStatusBarRegistrations.get(eventHandle); const emitter = this._notebookCellStatusBarRegistrations.get(eventHandle);
if (emitter instanceof Emitter) { if (emitter instanceof Emitter) {
@@ -245,29 +169,4 @@ export class MainThreadNotebooks implements MainThreadNotebookShape {
unregisterThing(eventHandle); unregisterThing(eventHandle);
} }
} }
async $unregisterNotebookKernelProvider(handle: number): Promise<void> {
const entry = this._notebookKernelProviders.get(handle);
if (entry) {
entry.emitter.dispose();
entry.provider.dispose();
this._notebookKernelProviders.delete(handle);
}
}
$onNotebookKernelChange(handle: number, uriComponents: UriComponents): void {
const entry = this._notebookKernelProviders.get(handle);
entry?.emitter.fire(uriComponents ? URI.revive(uriComponents) : undefined);
}
async $postMessage(id: string, forRendererId: string | undefined, value: any): Promise<boolean> {
const editor = this._notebookEditorService.getNotebookEditor(id);
if (!editor) {
return false;
}
editor.postMessage(forRendererId, value);
return true;
}
} }
@@ -15,6 +15,7 @@ import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookS
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol'; import { ExtHostContext, ExtHostNotebookShape, IExtHostContext, MainThreadNotebookDocumentsShape } from '../common/extHost.protocol';
import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors'; import { MainThreadNotebooksAndEditors } from 'vs/workbench/api/browser/mainThreadNotebookDocumentsAndEditors';
import { onUnexpectedError } from 'vs/base/common/errors';
export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape { export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsShape {
@@ -139,6 +140,12 @@ export class MainThreadNotebookDocuments implements MainThreadNotebookDocumentsS
throw new Error(`Can't apply edits to unknown notebook model: ${resource}`); throw new Error(`Can't apply edits to unknown notebook model: ${resource}`);
} }
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo); try {
textModel.applyEdits(cellEdits, true, undefined, () => undefined, undefined, computeUndoRedo);
} catch (e) {
// Clearing outputs at the same time as the EH calling append/replaceOutputItems is an expected race, and it should be a no-op.
// And any other failure should not throw back to the extension.
onUnexpectedError(e);
}
} }
} }
@@ -77,17 +77,6 @@ export class MainThreadNotebookEditors implements MainThreadNotebookEditorsShape
this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } }); this._proxy.$acceptEditorPropertiesChanged(editor.getId(), { selections: { selections: editor.getSelections() } });
})); }));
editorDisposables.add(editor.onDidChangeKernel(() => {
if (!editor.hasModel()) {
return;
}
this._proxy.$acceptNotebookActiveKernelChange({
uri: editor.viewModel.uri,
providerHandle: editor.activeKernel?.providerHandle,
kernelFriendlyId: editor.activeKernel?.friendlyId
});
}));
const wrapper = new MainThreadNotebook(editor, editorDisposables); const wrapper = new MainThreadNotebook(editor, editorDisposables);
this._mainThreadEditors.set(editor.getId(), wrapper); this._mainThreadEditors.set(editor.getId(), wrapper);
} }
@@ -11,16 +11,16 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookKernelChangeEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernel2, INotebookKernel2ChangeEvent, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector'; import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector';
import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol'; import { ExtHostContext, ExtHostNotebookKernelsShape, IExtHostContext, INotebookKernelDto2, MainContext, MainThreadNotebookKernelsShape } from '../common/extHost.protocol';
abstract class MainThreadKernel implements INotebookKernel2 { abstract class MainThreadKernel implements INotebookKernel {
private readonly _onDidChange = new Emitter<INotebookKernel2ChangeEvent>(); private readonly _onDidChange = new Emitter<INotebookKernelChangeEvent>();
private readonly preloads: { uri: URI, provides: string[] }[]; private readonly preloads: { uri: URI, provides: string[] }[];
readonly onDidChange: Event<INotebookKernel2ChangeEvent> = this._onDidChange.event; readonly onDidChange: Event<INotebookKernelChangeEvent> = this._onDidChange.event;
readonly id: string; readonly id: string;
readonly selector: NotebookSelector; readonly selector: NotebookSelector;
@@ -61,7 +61,8 @@ abstract class MainThreadKernel implements INotebookKernel2 {
update(data: Partial<INotebookKernelDto2>) { update(data: Partial<INotebookKernelDto2>) {
const event: INotebookKernel2ChangeEvent = Object.create(null);
const event: INotebookKernelChangeEvent = Object.create(null);
if (data.label !== undefined) { if (data.label !== undefined) {
this.label = data.label; this.label = data.label;
event.label = true; event.label = true;
@@ -89,13 +90,8 @@ abstract class MainThreadKernel implements INotebookKernel2 {
this._onDidChange.fire(event); this._onDidChange.fire(event);
} }
abstract executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void>; abstract executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise<void>;
abstract cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise<void>; abstract cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
// old stuff
readonly resolve = () => Promise.resolve();
get friendlyId() { return this.id; }
get providerHandle() { return undefined; }
} }
@extHostNamedCustomer(MainContext.MainThreadNotebookKernels) @extHostNamedCustomer(MainContext.MainThreadNotebookKernels)
@@ -137,7 +133,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
if (!editor.hasModel()) { if (!editor.hasModel()) {
return; return;
} }
const kernel = this._notebookKernelService.getBoundKernel(editor.viewModel.notebookDocument); const { bound: kernel } = this._notebookKernelService.getNotebookKernels(editor.viewModel.notebookDocument);
if (!kernel) { if (!kernel) {
return; return;
} }
@@ -167,7 +163,7 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
if (!editor.hasModel()) { if (!editor.hasModel()) {
continue; continue;
} }
if (this._notebookKernelService.getBoundKernel(editor.viewModel.notebookDocument) !== kernel) { if (this._notebookKernelService.getNotebookKernels(editor.viewModel.notebookDocument).bound !== kernel) {
// different kernel // different kernel
continue; continue;
} }
@@ -190,19 +186,19 @@ export class MainThreadNotebookKernels implements MainThreadNotebookKernelsShape
async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> { async $addKernel(handle: number, data: INotebookKernelDto2): Promise<void> {
const that = this; const that = this;
const kernel = new class extends MainThreadKernel { const kernel = new class extends MainThreadKernel {
async executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void> { async executeNotebookCellsRequest(uri: URI, handles: number[]): Promise<void> {
await that._proxy.$executeCells(handle, uri, ranges); await that._proxy.$executeCells(handle, uri, handles);
} }
async cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise<void> { async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise<void> {
await that._proxy.$cancelCells(handle, uri, ranges); await that._proxy.$cancelCells(handle, uri, handles);
} }
}(data); }(data);
const registration = this._notebookKernelService.registerKernel(kernel); const registration = this._notebookKernelService.registerKernel(kernel);
const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => { const listener = this._notebookKernelService.onDidChangeNotebookKernelBinding(e => {
if (e.oldKernel === kernel) { if (e.oldKernel === kernel.id) {
this._proxy.$acceptSelection(handle, e.notebook, false); this._proxy.$acceptSelection(handle, e.notebook, false);
} else if (e.newKernel === kernel) { } else if (e.newKernel === kernel.id) {
this._proxy.$acceptSelection(handle, e.notebook, true); this._proxy.$acceptSelection(handle, e.notebook, true);
} }
}); });
@@ -142,7 +142,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors)); const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService)); const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits))); const extHostDocumentSaveParticipant = rpcProtocol.set(ExtHostContext.ExtHostDocumentSaveParticipant, new ExtHostDocumentSaveParticipant(extHostLogService, extHostDocuments, rpcProtocol.getProxy(MainContext.MainThreadBulkEdits)));
const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, initData.environment, extHostLogService, extensionStoragePaths)); const extHostNotebook = rpcProtocol.set(ExtHostContext.ExtHostNotebook, new ExtHostNotebookController(rpcProtocol, extHostCommands, extHostDocumentsAndEditors, extHostDocuments, extHostLogService, extensionStoragePaths));
const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook)); const extHostNotebookKernels = rpcProtocol.set(ExtHostContext.ExtHostNotebookKernels, new ExtHostNotebookKernels(rpcProtocol, initData, extHostNotebook));
const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors)); const extHostEditors = rpcProtocol.set(ExtHostContext.ExtHostEditors, new ExtHostEditors(rpcProtocol, extHostDocumentsAndEditors));
const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService)); const extHostTreeViews = rpcProtocol.set(ExtHostContext.ExtHostTreeViews, new ExtHostTreeViews(rpcProtocol.getProxy(MainContext.MainThreadTreeViews), extHostCommands, extHostLogService));
@@ -1057,10 +1057,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension); checkProposedApiEnabled(extension);
return extHostNotebook.notebookDocuments.map(d => d.notebookDocument); return extHostNotebook.notebookDocuments.map(d => d.notebookDocument);
}, },
get onDidChangeActiveNotebookKernel() {
checkProposedApiEnabled(extension);
return extHostNotebook.onDidChangeActiveNotebookKernel;
},
registerNotebookSerializer(viewType, serializer, options) { registerNotebookSerializer(viewType, serializer, options) {
checkProposedApiEnabled(extension); checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options); return extHostNotebook.registerNotebookSerializer(extension, viewType, serializer, options);
@@ -1069,10 +1065,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension); checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options); return extHostNotebook.registerNotebookContentProvider(extension, viewType, provider, options);
}, },
registerNotebookKernelProvider: (selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) => {
checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
},
registerNotebookCellStatusBarItemProvider: (selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) => { registerNotebookCellStatusBarItemProvider: (selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) => {
checkProposedApiEnabled(extension); checkProposedApiEnabled(extension);
return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, selector, provider); return extHostNotebook.registerNotebookCellStatusBarItemProvider(extension, selector, provider);
@@ -50,7 +50,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions, ProvidedPortAttributes } from 'vs/platform/remote/common/tunnel'; 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 { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline';
import { revive } from 'vs/base/common/marshalling'; import { revive } from 'vs/base/common/marshalling';
import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, TransientCellMetadata, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation, INotebookCellStatusBarItem, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, TransientCellMetadata, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto, TransientOptions, IImmediateCellEditOperation, INotebookCellStatusBarItem, TransientDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import { Dto } from 'vs/base/common/types'; import { Dto } from 'vs/base/common/types';
import { DebugConfigurationProviderTriggerKind, TestResultState } from 'vs/workbench/api/common/extHostTypes'; import { DebugConfigurationProviderTriggerKind, TestResultState } from 'vs/workbench/api/common/extHostTypes';
@@ -884,13 +884,9 @@ export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void; $registerNotebookSerializer(handle: number, extension: NotebookExtensionDescription, viewType: string, options: TransientOptions): void;
$unregisterNotebookSerializer(handle: number): void; $unregisterNotebookSerializer(handle: number): void;
$registerNotebookKernelProvider(extension: NotebookExtensionDescription, handle: number, documentFilter: INotebookDocumentFilter): Promise<void>;
$unregisterNotebookKernelProvider(handle: number): Promise<void>;
$registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise<void>; $registerNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined, selector: NotebookSelector): Promise<void>;
$unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise<void>; $unregisterNotebookCellStatusBarItemProvider(handle: number, eventHandle: number | undefined): Promise<void>;
$emitCellStatusBarEvent(eventHandle: number): void; $emitCellStatusBarEvent(eventHandle: number): void;
$onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void;
$postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise<boolean>;
} }
export interface MainThreadNotebookEditorsShape extends IDisposable { export interface MainThreadNotebookEditorsShape extends IDisposable {
@@ -1925,15 +1921,8 @@ export interface INotebookKernelInfoDto2 {
} }
export interface ExtHostNotebookShape extends ExtHostNotebookDocumentsAndEditorsShape, ExtHostNotebookDocumentsShape, ExtHostNotebookEditorsShape { export interface ExtHostNotebookShape extends ExtHostNotebookDocumentsAndEditorsShape, ExtHostNotebookDocumentsShape, ExtHostNotebookEditorsShape {
$resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void>;
$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, cellRanges: ICellRange[]): Promise<void>;
$cancelNotebookCellExecution(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void>;
$provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise<INotebookCellStatusBarListDto | undefined>; $provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise<INotebookCellStatusBarListDto | undefined>;
$releaseNotebookCellStatusBarItems(id: number): void; $releaseNotebookCellStatusBarItems(id: number): 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>; $openNotebook(viewType: string, uri: UriComponents, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<NotebookDataDto>;
$saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>; $saveNotebook(viewType: string, uri: UriComponents, token: CancellationToken): Promise<boolean>;
@@ -1964,8 +1953,8 @@ export interface ExtHostNotebookEditorsShape {
export interface ExtHostNotebookKernelsShape { export interface ExtHostNotebookKernelsShape {
$acceptSelection(handle: number, uri: UriComponents, value: boolean): void; $acceptSelection(handle: number, uri: UriComponents, value: boolean): void;
$executeCells(handle: number, uri: UriComponents, ranges: ICellRange[]): Promise<void>; $executeCells(handle: number, uri: UriComponents, handles: number[]): Promise<void>;
$cancelCells(handle: number, uri: UriComponents, ranges: ICellRange[]): Promise<void>; $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise<void>;
$acceptRendererMessage(handle: number, editorId: string, message: any): void; $acceptRendererMessage(handle: number, editorId: string, message: any): void;
} }
+6 -283
View File
@@ -7,17 +7,15 @@ import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cance
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import * as UUID from 'vs/base/common/uuid';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookEditorViewColumnInfo, INotebookKernelInfoDto2, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostNotebookShape, IMainContext, IModelAddedData, INotebookCellStatusBarListDto, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorAddData, INotebookEditorPropertiesChangeData, INotebookEditorViewColumnInfo, MainContext, MainThreadNotebookDocumentsShape, MainThreadNotebookEditorsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { ILogService } from 'vs/platform/log/common/log'; import { ILogService } from 'vs/platform/log/common/log';
import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; import { CellEditType, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellEditType, ICellRange, INotebookExclusiveDocumentFilter, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, NullablePartialNotebookCellMetadata, IImmediateCellEditOperation } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as vscode from 'vscode'; import type * as vscode from 'vscode';
import { ResourceMap } from 'vs/base/common/map'; import { ResourceMap } from 'vs/base/common/map';
import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument'; import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument';
@@ -30,162 +28,6 @@ import { hash } from 'vs/base/common/hash';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { Cache } from 'vs/workbench/api/common/cache'; import { Cache } from 'vs/workbench/api/common/cache';
class ExtHostWebviewCommWrapper extends Disposable {
private readonly _onDidReceiveDocumentMessage = new Emitter<any>();
private readonly _rendererIdToEmitters = new Map<string, Emitter<any>>();
constructor(
private _editorId: string,
public uri: URI,
private _proxy: MainThreadNotebookShape,
private _webviewInitData: WebviewInitData,
public document: ExtHostNotebookDocument,
) {
super();
}
public onDidReceiveMessage(forRendererId: string | undefined, message: any) {
this._onDidReceiveDocumentMessage.fire(message);
if (forRendererId !== undefined) {
this._rendererIdToEmitters.get(forRendererId)?.fire(message);
}
}
public readonly contentProviderComm: vscode.NotebookCommunication = {
editorId: this._editorId,
onDidReceiveMessage: this._onDidReceiveDocumentMessage.event,
postMessage: (message: any) => this._proxy.$postMessage(this._editorId, undefined, message),
asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri),
};
public getRendererComm(rendererId: string): vscode.NotebookCommunication {
const emitter = new Emitter<any>();
this._rendererIdToEmitters.set(rendererId, emitter);
return {
editorId: this._editorId,
onDidReceiveMessage: emitter.event,
postMessage: (message: any) => this._proxy.$postMessage(this._editorId, rendererId, message),
asWebviewUri: (uri: vscode.Uri) => this._asWebviewUri(uri),
};
}
private _asWebviewUri(localResource: vscode.Uri): vscode.Uri {
return asWebviewUri(this._webviewInitData, this._editorId, localResource);
}
}
export class ExtHostNotebookKernelProviderAdapter extends Disposable {
private _kernelToFriendlyId = new ResourceMap<Map<vscode.NotebookKernel, string>>();
private _friendlyIdToKernel = new ResourceMap<Map<string, vscode.NotebookKernel>>();
constructor(
private readonly _proxy: MainThreadNotebookShape,
private readonly _handle: number,
private readonly _extension: IExtensionDescription,
private readonly _provider: vscode.NotebookKernelProvider
) {
super();
if (this._provider.onDidChangeKernels) {
this._register(this._provider.onDidChangeKernels((e: vscode.NotebookDocument | undefined) => {
const uri = e?.uri;
this._proxy.$onNotebookKernelChange(this._handle, uri);
}));
}
}
async provideKernels(document: ExtHostNotebookDocument, token: vscode.CancellationToken): Promise<INotebookKernelInfoDto2[]> {
const data = await this._provider.provideKernels(document.notebookDocument, token) || [];
const newMap = new Map<vscode.NotebookKernel, string>();
let kernel_unique_pool = 0;
const kernelFriendlyIdCache = new Set<string>();
const kernelToFriendlyId = this._kernelToFriendlyId.get(document.uri);
const transformedData: INotebookKernelInfoDto2[] = data.map(kernel => {
let friendlyId = kernelToFriendlyId?.get(kernel);
if (friendlyId === undefined) {
if (kernel.id && kernelFriendlyIdCache.has(kernel.id)) {
friendlyId = `${this._extension.identifier.value}_${kernel.id}_${kernel_unique_pool++}`;
} else {
friendlyId = `${this._extension.identifier.value}_${kernel.id || UUID.generateUuid()}`;
}
}
newMap.set(kernel, friendlyId);
return {
id: kernel.id,
friendlyId: friendlyId,
label: kernel.label,
extension: this._extension.identifier,
extensionLocation: this._extension.extensionLocation,
providerHandle: this._handle,
description: kernel.description,
detail: kernel.detail,
isPreferred: kernel.isPreferred,
preloads: kernel.preloads?.map(preload => {
// todo@connor4312: back compat on 2020-04-12, remove after transition
if (URI.isUri(preload)) {
preload = { uri: preload, provides: [] };
}
return {
uri: preload.uri, provides: typeof preload.provides === 'string'
? [preload.provides]
: preload.provides === undefined
? []
: preload.provides
};
}),
supportedLanguages: kernel.supportedLanguages,
implementsInterrupt: !!kernel.interrupt
};
});
this._kernelToFriendlyId.set(document.uri, newMap);
const friendlyIdToKernel = new Map<string, vscode.NotebookKernel>();
newMap.forEach((value, key) => {
friendlyIdToKernel.set(value, key);
});
this._friendlyIdToKernel.set(document.uri, friendlyIdToKernel);
return transformedData;
}
getKernelByFriendlyId(uri: URI, kernelId: string) {
return this._friendlyIdToKernel.get(uri)?.get(kernelId);
}
async resolveNotebook(kernelId: string, document: ExtHostNotebookDocument, webview: vscode.NotebookCommunication, token: CancellationToken) {
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
if (kernel && this._provider.resolveKernel) {
return this._provider.resolveKernel(kernel, document.notebookDocument, webview, token);
}
}
async executeNotebook(kernelId: string, document: ExtHostNotebookDocument, cellRange: ICellRange[]): Promise<void> {
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
if (!kernel) {
return;
}
const extCellRange = cellRange.map(c => typeConverters.NotebookRange.to(c));
return kernel.executeCellsRequest(document.notebookDocument, extCellRange);
}
async interruptNotebookExecution(kernelId: string, document: ExtHostNotebookDocument): Promise<void> {
const kernel = this._friendlyIdToKernel.get(document.uri)?.get(kernelId);
if (!kernel || !kernel.interrupt) {
return;
}
return kernel.interrupt(document.notebookDocument);
}
}
export class NotebookEditorDecorationType { export class NotebookEditorDecorationType {
@@ -213,7 +55,6 @@ type NotebookContentProviderData = {
}; };
export class ExtHostNotebookController implements ExtHostNotebookShape { export class ExtHostNotebookController implements ExtHostNotebookShape {
private static _notebookKernelProviderHandlePool: number = 0;
private static _notebookStatusBarItemProviderHandlePool: number = 0; private static _notebookStatusBarItemProviderHandlePool: number = 0;
private readonly _notebookProxy: MainThreadNotebookShape; private readonly _notebookProxy: MainThreadNotebookShape;
@@ -221,11 +62,9 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
private readonly _notebookEditorsProxy: MainThreadNotebookEditorsShape; private readonly _notebookEditorsProxy: MainThreadNotebookEditorsShape;
private readonly _notebookContentProviders = new Map<string, NotebookContentProviderData>(); private readonly _notebookContentProviders = new Map<string, NotebookContentProviderData>();
private readonly _notebookKernelProviders = new Map<number, ExtHostNotebookKernelProviderAdapter>();
private readonly _notebookStatusBarItemProviders = new Map<number, vscode.NotebookCellStatusBarItemProvider>(); private readonly _notebookStatusBarItemProviders = new Map<number, vscode.NotebookCellStatusBarItemProvider>();
private readonly _documents = new ResourceMap<ExtHostNotebookDocument>(); private readonly _documents = new ResourceMap<ExtHostNotebookDocument>();
private readonly _editors = new Map<string, ExtHostNotebookEditor>(); private readonly _editors = new Map<string, ExtHostNotebookEditor>();
private readonly _webviewComm = new Map<string, ExtHostWebviewCommWrapper>();
private readonly _commandsConverter: CommandsConverter; private readonly _commandsConverter: CommandsConverter;
private readonly _onDidChangeNotebookEditorSelection = new Emitter<vscode.NotebookEditorSelectionChangeEvent>(); private readonly _onDidChangeNotebookEditorSelection = new Emitter<vscode.NotebookEditorSelectionChangeEvent>();
readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event; readonly onDidChangeNotebookEditorSelection = this._onDidChangeNotebookEditorSelection.event;
@@ -259,8 +98,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event; onDidCloseNotebookDocument: Event<vscode.NotebookDocument> = this._onDidCloseNotebookDocument.event;
private _onDidSaveNotebookDocument = new Emitter<vscode.NotebookDocument>(); private _onDidSaveNotebookDocument = new Emitter<vscode.NotebookDocument>();
onDidSaveNotebookDocument: Event<vscode.NotebookDocument> = this._onDidSaveNotebookDocument.event; onDidSaveNotebookDocument: Event<vscode.NotebookDocument> = this._onDidSaveNotebookDocument.event;
private _onDidChangeActiveNotebookKernel = new Emitter<{ document: vscode.NotebookDocument, kernel: vscode.NotebookKernel | undefined; }>();
onDidChangeActiveNotebookKernel = this._onDidChangeActiveNotebookKernel.event;
private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>(); private _onDidChangeVisibleNotebookEditors = new Emitter<vscode.NotebookEditor[]>();
onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event; onDidChangeVisibleNotebookEditors = this._onDidChangeVisibleNotebookEditors.event;
@@ -273,7 +110,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
commands: ExtHostCommands, commands: ExtHostCommands,
private _textDocumentsAndEditors: ExtHostDocumentsAndEditors, private _textDocumentsAndEditors: ExtHostDocumentsAndEditors,
private _textDocuments: ExtHostDocuments, private _textDocuments: ExtHostDocuments,
private readonly _webviewInitData: WebviewInitData,
private readonly logService: ILogService, private readonly logService: ILogService,
private readonly _extensionStoragePaths: IExtensionStoragePaths, private readonly _extensionStoragePaths: IExtensionStoragePaths,
) { ) {
@@ -388,23 +224,8 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
}); });
} }
registerNotebookKernelProvider(extension: IExtensionDescription, selector: vscode.NotebookDocumentFilter, provider: vscode.NotebookKernelProvider) {
const handle = ExtHostNotebookController._notebookKernelProviderHandlePool++;
const adapter = new ExtHostNotebookKernelProviderAdapter(this._notebookProxy, handle, extension, provider);
this._notebookKernelProviders.set(handle, adapter);
this._notebookProxy.$registerNotebookKernelProvider({ id: extension.identifier, location: extension.extensionLocation, description: extension.description }, handle, {
viewType: selector.viewType,
filenamePattern: selector.filenamePattern ? typeConverters.NotebookExclusiveDocumentPattern.from(selector.filenamePattern) : undefined
});
return new extHostTypes.Disposable(() => {
adapter.dispose();
this._notebookKernelProviders.delete(handle);
this._notebookProxy.$unregisterNotebookKernelProvider(handle);
});
}
registerNotebookCellStatusBarItemProvider(extension: IExtensionDescription, selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) { registerNotebookCellStatusBarItemProvider(extension: IExtensionDescription, selector: vscode.NotebookSelector, provider: vscode.NotebookCellStatusBarItemProvider) {
const handle = ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++; const handle = ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++;
const eventHandle = typeof provider.onDidChangeCellStatusBarItems === 'function' ? ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++ : undefined; const eventHandle = typeof provider.onDidChangeCellStatusBarItems === 'function' ? ExtHostNotebookController._notebookStatusBarItemProviderHandlePool++ : undefined;
@@ -439,21 +260,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return assertIsDefined(document?.notebookDocument); return assertIsDefined(document?.notebookDocument);
} }
private _withAdapter<T>(handle: number, uri: UriComponents, callback: (adapter: ExtHostNotebookKernelProviderAdapter, document: ExtHostNotebookDocument) => Promise<T>) {
const document = this._documents.get(URI.revive(uri));
if (!document) {
return [];
}
const provider = this._notebookKernelProviders.get(handle);
if (!provider) {
return [];
}
return callback(provider, document);
}
async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise<vscode.NotebookEditor> { async showNotebookDocument(notebookOrUri: vscode.NotebookDocument | URI, options?: vscode.NotebookDocumentShowOptions): Promise<vscode.NotebookEditor> {
@@ -489,22 +295,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
} }
} }
async $provideNotebookKernels(handle: number, uri: UriComponents, token: CancellationToken): Promise<INotebookKernelInfoDto2[]> {
return this._withAdapter<INotebookKernelInfoDto2[]>(handle, uri, (adapter, document) => {
return adapter.provideKernels(document, token);
});
}
async $resolveNotebookKernel(handle: number, editorId: string, uri: UriComponents, kernelId: string, token: CancellationToken): Promise<void> {
await this._withAdapter<void>(handle, uri, async (adapter, document) => {
const webComm = this._webviewComm.get(editorId);
if (webComm) {
await adapter.resolveNotebook(kernelId, document, webComm.contentProviderComm, token);
}
});
}
async $provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise<INotebookCellStatusBarListDto | undefined> { async $provideNotebookCellStatusBarItems(handle: number, uri: UriComponents, index: number, token: CancellationToken): Promise<INotebookCellStatusBarListDto | undefined> {
const provider = this._notebookStatusBarItemProviders.get(handle); const provider = this._notebookStatusBarItemProviders.get(handle);
const revivedUri = URI.revive(uri); const revivedUri = URI.revive(uri);
@@ -536,27 +326,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
this._statusBarCache.delete(cacheId); this._statusBarCache.delete(cacheId);
} }
async $resolveNotebookEditor(viewType: string, uri: UriComponents, editorId: string): Promise<void> {
const provider = this._notebookContentProviders.get(viewType);
const revivedUri = URI.revive(uri);
const document = this._documents.get(revivedUri);
if (!document || !provider) {
return;
}
let webComm = this._webviewComm.get(editorId);
if (!webComm) {
webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._notebookProxy, this._webviewInitData, document);
this._webviewComm.set(editorId, webComm);
}
}
async $executeNotebookKernelFromProvider(handle: number, uri: UriComponents, kernelId: string, cellRange: ICellRange[]): Promise<void> {
await this._withAdapter(handle, uri, async (adapter, document) => {
return adapter.executeNotebook(kernelId, document, cellRange);
});
}
// --- serialize/deserialize // --- serialize/deserialize
private _handlePool = 0; private _handlePool = 0;
@@ -601,26 +370,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return VSBuffer.wrap(bytes); 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);
}
}
}
}
cancelOneNotebookCellExecution(cell: ExtHostCell): void { cancelOneNotebookCellExecution(cell: ExtHostCell): void {
const execution = this._activeExecutions.get(cell.uri); const execution = this._activeExecutions.get(cell.uri);
execution?.cancel(); execution?.cancel();
@@ -666,24 +415,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
return backup.id; return backup.id;
} }
$acceptNotebookActiveKernelChange(event: { uri: UriComponents, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }) {
if (event.providerHandle !== undefined) {
this._withAdapter(event.providerHandle, event.uri, async (adapter, document) => {
const kernel = event.kernelFriendlyId ? adapter.getKernelByFriendlyId(URI.revive(event.uri), event.kernelFriendlyId) : undefined;
this._editors.forEach(editor => {
if (editor.notebookData === document) {
editor._acceptKernel(kernel);
}
});
this._onDidChangeActiveNotebookKernel.fire({ document: document.notebookDocument, kernel });
});
}
}
$onDidReceiveMessage(editorId: string, forRendererType: string | undefined, message: any): void {
this._webviewComm.get(editorId)?.onDidReceiveMessage(forRendererType, message);
}
$acceptModelChanged(uri: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void { $acceptModelChanged(uri: UriComponents, event: NotebookCellsChangedEventDto, isDirty: boolean): void {
const document = this._getNotebookDocument(URI.revive(uri)); const document = this._getNotebookDocument(URI.revive(uri));
document.acceptModelChanged(event, isDirty); document.acceptModelChanged(event, isDirty);
@@ -755,14 +486,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
throw new Error(`editor with id ALREADY EXSIST: ${editorId}`); throw new Error(`editor with id ALREADY EXSIST: ${editorId}`);
} }
const revivedUri = document.uri;
let webComm = this._webviewComm.get(editorId);
if (!webComm) {
webComm = new ExtHostWebviewCommWrapper(editorId, revivedUri, this._notebookProxy, this._webviewInitData, document);
this._webviewComm.set(editorId, webComm);
}
const editor = new ExtHostNotebookEditor( const editor = new ExtHostNotebookEditor(
editorId, editorId,
this._notebookEditorsProxy, this._notebookEditorsProxy,
@@ -912,17 +635,17 @@ export class ExtHostNotebookController implements ExtHostNotebookShape {
createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined { createNotebookCellExecution(docUri: vscode.Uri, index: number, kernelId: string): vscode.NotebookCellExecutionTask | undefined {
const document = this.lookupNotebookDocument(docUri); const document = this.lookupNotebookDocument(docUri);
if (!document) { if (!document) {
throw new Error(`Invalid cell uri / index: ${docUri}, ${index} `); throw new Error(`Invalid uri: ${docUri} `);
} }
const cell = document.getCellFromIndex(index); const cell = document.getCellFromIndex(index);
if (!cell) { if (!cell) {
throw new Error(`Invalid cell uri / index: ${docUri}, ${index} `); throw new Error(`Invalid cell index: ${docUri}, ${index} `);
} }
// TODO@roblou also validate kernelId, once kernel has moved from editor to document // TODO@roblou also validate kernelId, once kernel has moved from editor to document
if (this._activeExecutions.has(cell.uri)) { if (this._activeExecutions.has(cell.uri)) {
return; throw new Error(`duplicate execution for ${cell.uri}`);
} }
const execution = new NotebookCellExecutionTask(docUri, document, cell, this._notebookDocumentsProxy); const execution = new NotebookCellExecutionTask(docUri, document, cell, this._notebookDocumentsProxy);
@@ -89,8 +89,6 @@ export class ExtHostNotebookEditor {
private _viewColumn?: vscode.ViewColumn; private _viewColumn?: vscode.ViewColumn;
private _visible: boolean = false; private _visible: boolean = false;
private _kernel?: vscode.NotebookKernel;
private readonly _hasDecorationsForKey = new Set<string>(); private readonly _hasDecorationsForKey = new Set<string>();
private _editor?: vscode.NotebookEditor; private _editor?: vscode.NotebookEditor;
@@ -136,9 +134,6 @@ export class ExtHostNotebookEditor {
callback(edit); callback(edit);
return that._applyEdit(edit.finalize()); return that._applyEdit(edit.finalize());
}, },
get kernel() {
return that._kernel;
},
setDecorations(decorationType, range) { setDecorations(decorationType, range) {
return that.setDecorations(decorationType, range); return that.setDecorations(decorationType, range);
} }
@@ -147,10 +142,6 @@ export class ExtHostNotebookEditor {
return this._editor; return this._editor;
} }
_acceptKernel(kernel?: vscode.NotebookKernel) {
this._kernel = kernel;
}
get visible(): boolean { get visible(): boolean {
return this._visible; return this._visible;
} }
@@ -10,7 +10,6 @@ import * as vscode from 'vscode';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters';
import { isNonEmptyArray } from 'vs/base/common/arrays'; import { isNonEmptyArray } from 'vs/base/common/arrays';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
@@ -181,7 +180,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message); return that._proxy.$postMessage(handle, editor && that._extHostNotebook.getIdByEditor(editor), message);
}, },
asWebviewUri(uri: URI) { asWebviewUri(uri: URI) {
return asWebviewUri(that._initData.environment, data.id, uri); return asWebviewUri(that._initData.environment, String(handle), uri);
} }
}; };
@@ -199,7 +198,7 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
} }
} }
async $executeCells(handle: number, uri: UriComponents, ranges: ICellRange[]): Promise<void> { async $executeCells(handle: number, uri: UriComponents, handles: number[]): Promise<void> {
const obj = this._kernelData.get(handle); const obj = this._kernelData.get(handle);
if (!obj) { if (!obj) {
// extension can dispose kernels in the meantime // extension can dispose kernels in the meantime
@@ -211,19 +210,22 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
} }
const cells: vscode.NotebookCell[] = []; const cells: vscode.NotebookCell[] = [];
for (let range of ranges) { for (let cellHandle of handles) {
cells.push(...document.notebookDocument.getCells(extHostTypeConverters.NotebookRange.to(range))); const cell = document.getCell(cellHandle);
if (cell) {
cells.push(cell.cell);
}
} }
try { try {
obj.controller.executeHandler.call(obj.controller, cells, obj.controller); await obj.controller.executeHandler.call(obj.controller, cells, obj.controller);
} catch (err) { } catch (err) {
// //
console.error(err); console.error(err);
} }
} }
async $cancelCells(handle: number, uri: UriComponents, ranges: ICellRange[]): Promise<void> { async $cancelCells(handle: number, uri: UriComponents, handles: number[]): Promise<void> {
const obj = this._kernelData.get(handle); const obj = this._kernelData.get(handle);
if (!obj) { if (!obj) {
// extension can dispose kernels in the meantime // extension can dispose kernels in the meantime
@@ -234,16 +236,14 @@ export class ExtHostNotebookKernels implements ExtHostNotebookKernelsShape {
throw new Error('MISSING notebook'); throw new Error('MISSING notebook');
} }
if (obj.controller.interruptHandler) { if (obj.controller.interruptHandler) {
obj.controller.interruptHandler.call(obj.controller); await obj.controller.interruptHandler.call(obj.controller);
} }
// we do both? interrupt and cancellation or should we be selective? // we do both? interrupt and cancellation or should we be selective?
for (const range of ranges) { for (let cellHandle of handles) {
for (let i = range.start; i < range.end; i++) { const cell = document.getCell(cellHandle);
const cell = document.getCellFromIndex(i); if (cell) {
if (cell) { this._extHostNotebook.cancelOneNotebookCellExecution(cell);
this._extHostNotebook.cancelOneNotebookCellExecution(cell);
}
} }
} }
} }
+45 -3
View File
@@ -677,9 +677,32 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: value }, notebookMetadata: value }); this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.DocumentMetadata, metadata: value }, notebookMetadata: value });
} }
replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { replaceNotebookCells(uri: URI, range: vscode.NotebookRange, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void;
if (start !== end || cells.length > 0) { replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void;
this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells, metadata }); replaceNotebookCells(uri: URI, startOrRange: number | vscode.NotebookRange, endOrCells: number | vscode.NotebookCellData[], cellsOrMetadata?: vscode.NotebookCellData[] | vscode.WorkspaceEditEntryMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void {
let start: number | undefined;
let end: number | undefined;
let cellData: vscode.NotebookCellData[] = [];
let workspaceEditMetadata: vscode.WorkspaceEditEntryMetadata | undefined;
if (NotebookRange.isNotebookRange(startOrRange) && NotebookCellData.isNotebookCellDataArray(endOrCells) && !NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) {
start = startOrRange.start;
end = startOrRange.end;
cellData = endOrCells;
workspaceEditMetadata = cellsOrMetadata;
} else if (typeof startOrRange === 'number' && typeof endOrCells === 'number' && NotebookCellData.isNotebookCellDataArray(cellsOrMetadata)) {
start = startOrRange;
end = endOrCells;
cellData = cellsOrMetadata;
workspaceEditMetadata = metadata;
}
if (start === undefined || end === undefined) {
throw new Error('Invalid arguments');
}
if (start !== end || cellData.length > 0) {
this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells: cellData, metadata: workspaceEditMetadata });
} }
} }
@@ -2897,6 +2920,16 @@ export enum ColorThemeKind {
//#region Notebook //#region Notebook
export class NotebookRange { export class NotebookRange {
static isNotebookRange(thing: any): thing is vscode.NotebookRange {
if (thing instanceof NotebookRange) {
return true;
}
if (!thing) {
return false;
}
return typeof (<NotebookRange>thing).start === 'number'
&& typeof (<NotebookRange>thing).end === 'number';
}
private _start: number; private _start: number;
private _end: number; private _end: number;
@@ -3063,6 +3096,15 @@ export class NotebookDocumentMetadata {
export class NotebookCellData { export class NotebookCellData {
static isNotebookCellDataArray(value: unknown): value is vscode.NotebookCellData[] {
return Array.isArray(value) && (<unknown[]>value).every(elem => NotebookCellData.isNotebookCellData(elem));
}
static isNotebookCellData(value: unknown): value is vscode.NotebookCellData {
// return value instanceof NotebookCellData;
return true;
}
kind: NotebookCellKind; kind: NotebookCellKind;
source: string; source: string;
language: string; language: string;
@@ -19,18 +19,19 @@ import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_INPUT_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, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { BaseCellRenderTemplate, CellEditState, CellFocusMode, EXECUTE_CELL_COMMAND_ID, EXPAND_CELL_INPUT_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, CHANGE_CELL_LANGUAGE, QUIT_EDIT_CELL_COMMAND_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellEditType, CellKind, ICellEditOperation, ICellRange, INotebookDocumentFilter, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientCellMetadata, TransientDocumentMetadata, SelectionStateType, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellEditType, CellKind, ICellEditOperation, ICellRange, isDocumentExcludePattern, NotebookCellMetadata, NotebookCellExecutionState, NOTEBOOK_EDITOR_CURSOR_BOUNDARY, TransientCellMetadata, TransientDocumentMetadata, SelectionStateType, ICellReplaceEdit } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import * as icons from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput'; import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebookEditorInput';
import { EditorsOrder } from 'vs/workbench/common/editor'; import { EditorsOrder } from 'vs/workbench/common/editor';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { Iterable } from 'vs/base/common/iterator';
// Notebook Commands // Notebook Commands
const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute'; const EXECUTE_NOTEBOOK_COMMAND_ID = 'notebook.execute';
@@ -426,7 +427,7 @@ registerAction2(class CancelExecuteCell extends NotebookCellAction<ICellRange> {
} }
async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> { async runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void> {
return context.notebookEditor.cancelNotebookCellExecution(context.cell); return context.notebookEditor.cancelNotebookCells(Iterable.single(context.cell));
} }
}); });
@@ -568,7 +569,7 @@ registerAction2(class extends NotebookAction {
group?.pinEditor(editor.editor); group?.pinEditor(editor.editor);
} }
return context.notebookEditor.executeNotebook(); return context.notebookEditor.executeNotebookCells();
} }
}); });
@@ -610,7 +611,7 @@ registerAction2(class CancelNotebook extends NotebookAction {
} }
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> { async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
return context.notebookEditor.cancelNotebookExecution(); return context.notebookEditor.cancelNotebookCells();
} }
}); });
@@ -690,7 +691,12 @@ async function runCell(accessor: ServicesAccessor, context: INotebookCellActionC
} }
} }
return context.notebookEditor.executeNotebookCell(context.cell); if (context.cell.cellKind === CellKind.Markdown) {
context.notebookEditor.focusNotebookCell(context.cell, 'container');
return;
} else {
return context.notebookEditor.executeNotebookCells(Iterable.single(context.cell));
}
} }
export async function changeCellToKind(kind: CellKind, context: INotebookCellActionContext, language?: string): Promise<ICellViewModel | null> { export async function changeCellToKind(kind: CellKind, context: INotebookCellActionContext, language?: string): Promise<ICellViewModel | null> {
@@ -1731,20 +1737,6 @@ CommandsRegistry.registerCommand('_resolveNotebookContentProvider', (accessor, a
}); });
}); });
CommandsRegistry.registerCommand('_resolveNotebookKernelProviders', async (accessor, args): Promise<{
extensionId: string;
description?: string;
selector: INotebookDocumentFilter;
}[]> => {
const notebookService = accessor.get<INotebookService>(INotebookService);
const providers = await notebookService.getContributedNotebookKernelProviders();
return providers.map(provider => ({
extensionId: provider.providerExtensionId,
description: provider.providerDescription,
selector: provider.selector
}));
});
CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, args: { CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, args: {
viewType: string; viewType: string;
uri: UriComponents; uri: UriComponents;
@@ -1756,14 +1748,12 @@ CommandsRegistry.registerCommand('_resolveNotebookKernels', async (accessor, arg
isPreferred?: boolean; isPreferred?: boolean;
preloads?: URI[]; preloads?: URI[];
}[]> => { }[]> => {
const notebookService = accessor.get<INotebookService>(INotebookService); const notebookKernelService = accessor.get(INotebookKernelService);
const uri = URI.revive(args.uri as UriComponents); const uri = URI.revive(args.uri as UriComponents);
const source = new CancellationTokenSource(); const kernels = notebookKernelService.getNotebookKernels({ uri, viewType: args.viewType });
const kernels = await notebookService.getNotebookKernels(args.viewType, uri, source.token);
source.dispose();
return kernels.map(provider => ({ return kernels.all.map(provider => ({
id: provider.friendlyId, id: provider.id,
label: provider.label, label: provider.label,
description: provider.description, description: provider.description,
detail: provider.detail, detail: provider.detail,
@@ -7,21 +7,22 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { IQuickInputButton, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions'; import { NOTEBOOK_ACTIONS_CATEGORY } from 'vs/workbench/contrib/notebook/browser/contrib/coreActions';
import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getNotebookEditorFromEditorPane, INotebookEditor, NOTEBOOK_IS_ACTIVE_EDITOR } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, MutableDisposable } from 'vs/base/common/lifecycle';
import { IStatusbarEntry, IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar'; import { IStatusbarEntryAccessor, IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { configureKernelIcon, selectKernelIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { INotebookKernel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
registerAction2(class extends Action2 { registerAction2(class extends Action2 {
constructor() { constructor() {
@@ -58,90 +59,60 @@ registerAction2(class extends Action2 {
} }
async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise<void> { async run(accessor: ServicesAccessor, context?: { id: string, extension: string }): Promise<void> {
const editorService = accessor.get<IEditorService>(IEditorService); const notebookKernelService = accessor.get(INotebookKernelService);
const quickInputService = accessor.get<IQuickInputService>(IQuickInputService); const editorService = accessor.get(IEditorService);
const configurationService = accessor.get<IConfigurationService>(IConfigurationService); const quickInputService = accessor.get(IQuickInputService);
const configurationService = accessor.get(IConfigurationService);
const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane); const editor = getNotebookEditorFromEditorPane(editorService.activeEditorPane);
if (!editor) { if (!editor || !editor.hasModel()) {
return; return;
} }
if (!editor.hasModel()) { if (context && (typeof context.id !== 'string' || typeof context.extension !== 'string')) {
// validate context: id & extension MUST be strings
context = undefined;
}
const notebook = editor.viewModel.notebookDocument;
const { bound, all } = notebookKernelService.getNotebookKernels(notebook);
if (bound && context && bound.id === context.id && ExtensionIdentifier.equals(bound.extension, context.extension)) {
// current kernel is wanted kernel -> done
return; return;
} }
const activeKernel = editor.activeKernel; let newKernel: INotebookKernel | undefined;
if (context) {
const picker = quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>(); for (let candidate of all) {
picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook"); if (candidate.id === context.id && ExtensionIdentifier.equals(candidate.extension, context.extension)) {
picker.matchOnDetail = true; newKernel = candidate;
break;
}
if (context && context.id) { }
} else {
picker.show();
} }
picker.busy = true; if (!newKernel) {
type KernelPick = IQuickPickItem & { kernel: INotebookKernel };
const tokenSource = new CancellationTokenSource(); const configButton: IQuickInputButton = {
const availableKernels = await editor.beginComputeContributedKernels(); iconClass: ThemeIcon.asClassName(configureKernelIcon),
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel.viewType)
const selectedKernel = availableKernels.length ? availableKernels.find(
kernel => kernel.id && context?.id && kernel.id === context?.id && kernel.extension.value === context?.extension
) : undefined;
if (selectedKernel) {
editor.activeKernel = selectedKernel;
return selectedKernel.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token);
} else {
picker.show();
}
const picks: QuickPickInput<IQuickPickItem & { run(): void; kernelProviderId?: string; }>[] = [...availableKernels].map((a) => {
return {
id: a.friendlyId,
label: a.label,
picked: a.friendlyId === activeKernel?.friendlyId,
description:
a.description
? a.description
: a.extension.value + (a.friendlyId === activeKernel?.friendlyId
? nls.localize('currentActiveKernel', " (Currently Active)")
: ''),
detail: a.detail,
kernelProviderId: a.extension.value,
run: async () => {
editor.activeKernel = a;
a.resolve(editor.viewModel.uri, editor.getId(), tokenSource.token);
},
buttons: [{
iconClass: ThemeIcon.asClassName(configureKernelIcon),
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", editor.viewModel.viewType)
}]
}; };
}); const picks = all.map(kernel => {
return <KernelPick>{
picker.items = picks; kernel,
picker.busy = false; picked: kernel.id === bound?.id,
picker.activeItems = picks.filter(pick => (pick as IQuickPickItem).picked) as (IQuickPickItem & { run(): void; kernelProviderId?: string; })[]; label: kernel.label,
description: kernel.description,
const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => { detail: kernel.detail,
picker.onDidAccept(() => { buttons: [configButton]
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined); };
picker.dispose();
}); });
const pick = await quickInputService.pick(picks, {
onDidTriggerItemButton: (context) => {
newKernel = context.item.kernel;
picker.onDidTriggerItemButton(e => { const newAssociation: NotebookKernelProviderAssociation = { viewType: notebook.viewType, kernelProvider: context.item.kernel.extension.value };
const pick = e.item;
const id = pick.id;
resolve(pick); // open the view
picker.dispose();
// And persist the setting
if (pick && id && pick.kernelProviderId) {
const newAssociation: NotebookKernelProviderAssociation = { viewType: editor.viewModel.viewType, kernelProvider: pick.kernelProviderId };
const currentAssociations = [...configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId)]; const currentAssociations = [...configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId)];
// First try updating existing association // First try updating existing association
@@ -160,71 +131,75 @@ registerAction2(class extends Action2 {
} }
}); });
}); if (pick) {
newKernel = pick.kernel;
}
}
tokenSource.dispose(); if (newKernel) {
return pickedItem?.run(); notebookKernelService.updateNotebookKernelBinding(notebook, newKernel);
}
} }
}); });
export class KernelStatus extends Disposable implements IWorkbenchContribution { export class KernelStatus extends Disposable implements IWorkbenchContribution {
private readonly _editorDisposable = this._register(new DisposableStore()); private readonly _editorDisposables = this._register(new DisposableStore());
private readonly _kernelInfoElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>()); private readonly _kernelInfoElement = this._register(new MutableDisposable<IStatusbarEntryAccessor>());
constructor( constructor(
@IEditorService private readonly _editorService: IEditorService, @IEditorService private readonly _editorService: IEditorService,
@IStatusbarService private readonly _statusbarService: IStatusbarService, @IStatusbarService private readonly _statusbarService: IStatusbarService,
@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
) { ) {
super(); super();
this._register(this._editorService.onDidActiveEditorChange(() => this._updateStatusbar())); this._register(this._editorService.onDidActiveEditorChange(() => this._updateStatusbar()));
} }
private _updateStatusbar() { private _updateStatusbar() {
this._editorDisposable.clear(); this._editorDisposables.clear();
const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane); const activeEditor = getNotebookEditorFromEditorPane(this._editorService.activeEditorPane);
if (activeEditor) { if (!activeEditor) {
this._editorDisposable.add(activeEditor.onDidChangeKernel(() => { // not a notebook -> clean-up, done
this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount);
}));
this._editorDisposable.add(activeEditor.onDidChangeAvailableKernels(() => {
this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount);
}));
this._showKernelStatus(activeEditor.activeKernel, activeEditor.availableKernelCount);
} else {
this._kernelInfoElement.clear();
}
}
private static readonly _chooseKernelEntry: IStatusbarEntry = {
text: nls.localize('choose', "Choose Kernel"),
ariaLabel: nls.localize('choose', "Choose Kernel"),
tooltip: nls.localize('tooltop', "Choose kernel for current notebook"),
command: 'notebook.selectKernel'
};
private _showKernelStatus(kernel: INotebookKernel | undefined, availableKernelCount: number) {
if (availableKernelCount === 0) {
this._kernelInfoElement.clear(); this._kernelInfoElement.clear();
return; return;
} }
let entry: IStatusbarEntry; const updateStatus = () => {
const notebook = activeEditor.viewModel?.notebookDocument;
if (notebook) {
const info = this._notebookKernelService.getNotebookKernels(notebook);
this._showKernelStatus(info.bound, info.all);
} else {
this._kernelInfoElement.clear();
}
};
if (kernel) { this._editorDisposables.add(this._notebookKernelService.onDidAddKernel(updateStatus));
entry = { this._editorDisposables.add(this._notebookKernelService.onDidChangeNotebookKernelBinding(updateStatus));
text: `$(notebook-kernel-select) ${kernel.label}`, this._editorDisposables.add(activeEditor.onDidChangeModel(updateStatus));
ariaLabel: kernel.label, updateStatus();
tooltip: kernel.description ?? kernel.detail ?? kernel.label, }
command: availableKernelCount > 1 ? 'notebook.selectKernel' : undefined,
}; private _showKernelStatus(boundKernel: INotebookKernel | undefined, availableKernels: INotebookKernel[]) {
} else {
entry = KernelStatus._chooseKernelEntry; if (availableKernels.length === 0) {
this._kernelInfoElement.clear();
return;
}
if (!boundKernel) {
boundKernel = availableKernels[0];
} }
this._kernelInfoElement.value = this._statusbarService.addEntry( this._kernelInfoElement.value = this._statusbarService.addEntry(
entry, {
text: `$(notebook-kernel-select) ${boundKernel.label}`,
ariaLabel: boundKernel.label,
tooltip: boundKernel.description ?? boundKernel.detail ?? boundKernel.label,
command: availableKernels.length > 1 ? 'notebook.selectKernel' : undefined,
},
'notebook.selectKernel', 'notebook.selectKernel',
nls.localize('notebook.info', "Notebook Kernel Info"), nls.localize('notebook.info', "Notebook Kernel Info"),
StatusbarAlignment.RIGHT, StatusbarAlignment.RIGHT,
@@ -407,6 +407,7 @@
font-size: 12px; font-size: 12px;
display: flex; display: flex;
position: relative; position: relative;
overflow: hidden;
} }
.monaco-workbench .notebookOverlay.cell-statusbar-hidden .cell-statusbar-container { .monaco-workbench .notebookOverlay.cell-statusbar-hidden .cell-statusbar-container {
@@ -424,6 +425,10 @@
z-index: 26; z-index: 26;
} }
.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right {
flex-direction: row-reverse;
}
.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right .cell-contributed-items { .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-right .cell-contributed-items {
justify-content: flex-end; justify-content: flex-end;
} }
@@ -440,7 +445,9 @@
white-space: pre; white-space: pre;
height: 21px; /* Editor outline is -1px in, don't overlap */ height: 21px; /* Editor outline is -1px in, don't overlap */
padding: 0px 6px; margin: 0px 6px;
overflow: hidden;
text-overflow: clip;
} }
.monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command { .monaco-workbench .notebookOverlay .cell-statusbar-container .cell-status-item.cell-status-item-has-command {
@@ -953,7 +960,6 @@
.monaco-workbench .notebookOverlay .output .error, .monaco-workbench .notebookOverlay .output .error,
.monaco-workbench .notebookOverlay .output .output-plaintext { .monaco-workbench .notebookOverlay .output .output-plaintext {
margin: 4px 0;
overflow-x: auto; overflow-x: auto;
} }
@@ -63,7 +63,6 @@ export const NOTEBOOK_CELL_HAS_OUTPUTS = new RawContextKey<boolean>('notebookCel
export const NOTEBOOK_CELL_INPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellInputIsCollapsed', 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 export const NOTEBOOK_CELL_OUTPUT_COLLAPSED = new RawContextKey<boolean>('notebookCellOutputIsCollapsed', false); // bool
// Kernels // 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_KERNEL_COUNT = new RawContextKey<number>('notebookKernelCount', 0);
export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey<boolean>('notebookInterruptibleKernel', false); export const NOTEBOOK_INTERRUPTIBLE_KERNEL = new RawContextKey<boolean>('notebookInterruptibleKernel', false);
@@ -374,11 +373,8 @@ export interface INotebookEditor extends ICommonNotebookEditor {
*/ */
readonly onDidChangeModel: Event<NotebookTextModel | undefined>; readonly onDidChangeModel: Event<NotebookTextModel | undefined>;
readonly onDidFocusEditorWidget: Event<void>; readonly onDidFocusEditorWidget: Event<void>;
activeKernel: INotebookKernel | undefined;
readonly availableKernelCount: number;
readonly onDidScroll: Event<void>; readonly onDidScroll: Event<void>;
readonly onDidChangeAvailableKernels: Event<void>;
readonly onDidChangeKernel: Event<void>;
readonly onDidChangeActiveCell: Event<void>; readonly onDidChangeActiveCell: Event<void>;
isDisposed: boolean; isDisposed: boolean;
dispose(): void; dispose(): void;
@@ -417,11 +413,6 @@ export interface INotebookEditor extends ICommonNotebookEditor {
*/ */
getOutputRenderer(): OutputRenderer; getOutputRenderer(): OutputRenderer;
/**
* Fetch the contributed kernels for this notebook
*/
beginComputeContributedKernels(): Promise<INotebookKernel[]>;
/** /**
* Insert a new cell around `cell` * Insert a new cell around `cell`
*/ */
@@ -459,25 +450,17 @@ export interface INotebookEditor extends ICommonNotebookEditor {
focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void; focusNextNotebookCell(cell: ICellViewModel, focus: 'editor' | 'container' | 'output'): void;
/** readonly activeKernel: INotebookKernel | undefined;
* Execute the given notebook cell
*/
executeNotebookCell(cell: ICellViewModel): Promise<void>;
/** /**
* Cancel the cell execution * Execute the given notebook cells
*/ */
cancelNotebookCellExecution(cell: ICellViewModel): void; executeNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void>
/** /**
* Executes all notebook cells in order * Cancel the given notebook cells
*/ */
executeNotebook(): Promise<void>; cancelNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void>
/**
* Cancel the notebook execution
*/
cancelNotebookExecution(): void;
/** /**
* Get current active cell * Get current active cell
@@ -26,7 +26,6 @@ import { IEditorDropService } from 'vs/workbench/services/editor/browser/editorD
import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroup, IEditorGroupsService, GroupsOrder } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { NotebookEditorOptions, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorOptions, NOTEBOOK_EDITOR_ID } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { IBorrowValue, INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { clearMarks, getAndClearMarks, mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { IFileService } from 'vs/platform/files/common/files'; import { IFileService } from 'vs/platform/files/common/files';
@@ -59,7 +58,6 @@ export class NotebookEditor extends EditorPane {
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService, @IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IEditorDropService private readonly _editorDropService: IEditorDropService, @IEditorDropService private readonly _editorDropService: IEditorDropService,
@INotificationService private readonly _notificationService: INotificationService, @INotificationService private readonly _notificationService: INotificationService,
@INotebookService private readonly _notebookService: INotebookService,
@INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService, @INotebookEditorService private readonly _notebookWidgetService: INotebookEditorService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService, @IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IFileService private readonly fileService: IFileService, @IFileService private readonly fileService: IFileService,
@@ -189,8 +187,7 @@ export class NotebookEditor extends EditorPane {
return; return;
} }
await this._notebookService.resolveNotebookEditor(model.viewType, model.resource, this._widget.value!.getId());
mark(input.resource, 'webviewCommLoaded');
const viewState = this._loadNotebookEditorViewState(input); const viewState = this._loadNotebookEditorViewState(input);
@@ -3,510 +3,62 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls'; import { Disposable } from 'vs/base/common/lifecycle';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput'; import { ICellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; import { CellKind, INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { ICommandService } from 'vs/platform/commands/common/commands';
import { Emitter, Event } from 'vs/base/common/event'; import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
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_HAS_MULTIPLE_KERNELS, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, getRanges } 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 { 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;
getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]>;
loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel): Promise<void>;
}
export class NotebookEditorKernelManager extends Disposable { export class NotebookEditorKernelManager extends Disposable {
private _isDisposed: boolean = false;
private _activeKernelExecuted: boolean = false;
private _activeKernel: INotebookKernel | undefined = undefined;
private readonly _onDidChangeKernel = this._register(new Emitter<void>());
readonly onDidChangeKernel: Event<void> = this._onDidChangeKernel.event;
private readonly _onDidChangeAvailableKernels = this._register(new Emitter<void>());
readonly onDidChangeAvailableKernels: Event<void> = this._onDidChangeAvailableKernels.event;
private _contributedKernelsComputePromise: CancelablePromise<INotebookKernel[]> | null = null;
private _initialKernelComputationDone: boolean = false;
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;
}
set activeKernel(kernel: INotebookKernel | undefined) {
if (this._isDisposed) {
return;
}
if (!this._delegate.viewModel) {
return;
}
if (this._activeKernel === kernel) {
return;
}
this._interruptibleKernel.set(!!kernel?.implementsInterrupt);
this._activeKernel = kernel;
this._activeKernelResolvePromise = undefined;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
memento[this._delegate.viewModel.viewType] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
this._onDidChangeKernel.fire();
if (this._activeKernel) {
this._delegate.loadKernelPreloads(this._activeKernel.localResourceRoot, this._activeKernel);
}
}
private _activeKernelResolvePromise: Promise<void> | undefined = undefined;
private _kernelCount: number = 0;
get availableKernelCount() {
return this._kernelCount;
}
private readonly _activeKernelMemento: Memento;
constructor( constructor(
private readonly _delegate: IKernelManagerDelegate, @ICommandService private readonly _commandService: ICommandService,
@IStorageService storageService: IStorageService, @INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
@IContextKeyService contextKeyService: IContextKeyService, ) {
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IConfigurationService private readonly _configurationService: IConfigurationService,) {
super(); super();
this._activeKernelMemento = new Memento(NotebookEditorActiveKernelCache, storageService);
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 { getActiveKernel(notebook: INotebookTextModel): INotebookKernel | undefined {
dispose(this._cellStateListeners); const info = this._notebookKernelService.getNotebookKernels(notebook);
this._cellStateListeners = []; return info.bound ?? info.all[0];
if (!this._delegate.viewModel) {
return;
}
const addCellStateListener = (c: ICellViewModel) => {
return (c as CellViewModel).onDidChangeState(e => {
if (!e.runStateChanged) {
return;
}
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(refresh: boolean, tokenSource: CancellationTokenSource) { async executeNotebookCells(notebook: INotebookTextModel, cells: Iterable<ICellViewModel>): Promise<void> {
if (!this._delegate.viewModel) { if (!notebook.metadata.trusted) {
return; return;
} }
if (!refresh && this._activeKernel !== undefined && this._activeKernelExecuted) { let kernel = this.getActiveKernel(notebook);
// kernel already executed, we should not change it automatically if (!kernel) {
await this._commandService.executeCommand('notebook.selectKernel');
kernel = this.getActiveKernel(notebook);
}
if (!kernel) {
return; return;
} }
const provider = this._delegate.getContributedNotebookProvider(this._delegate.viewModel.viewType) || this._delegate.getContributedNotebookProviders(this._delegate.viewModel.uri)[0]; const cellHandles: number[] = [];
const availableKernels = await this.beginComputeContributedKernels(); for (const cell of cells) {
if (cell.cellKind !== CellKind.Code) {
if (tokenSource.token.isCancellationRequested) { continue;
return;
}
this._kernelCount = availableKernels.length;
this._notebookKernelCount.set(this._kernelCount);
this._notebookHasMultipleKernels.set(this._kernelCount > 1);
this._onDidChangeAvailableKernels.fire();
let activeKernelStillExist = false;
if (this._activeKernel?.friendlyId) {
for (let candidateKernel of availableKernels) {
if (this._activeKernel.friendlyId === candidateKernel.friendlyId) {
// exiting kernel still exists but might have changed. Only update the object and call it a day
this._activeKernel = candidateKernel;
activeKernelStillExist = true;
break;
}
} }
} if (!kernel.supportedLanguages.includes(cell.language)) {
continue;
if (activeKernelStillExist) {
// the kernel still exist, we don't want to modify the selection otherwise user's temporary preference is lost
this._onDidChangeKernel.fire();
return;
}
if (availableKernels.length) {
return this._setKernelsFromProviders(provider, availableKernels, tokenSource);
}
this._initialKernelComputationDone = true;
tokenSource.dispose();
}
async beginComputeContributedKernels() {
if (this._contributedKernelsComputePromise) {
return this._contributedKernelsComputePromise;
}
this._contributedKernelsComputePromise = createCancelablePromise(token => {
return this._delegate.getNotebookKernels(this._delegate.viewModel!.viewType, this._delegate.viewModel!.uri, token);
});
const result = await this._contributedKernelsComputePromise;
this._initialKernelComputationDone = true;
this._contributedKernelsComputePromise = null;
return result;
}
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;
const memento = this._activeKernelMemento.getMemento(StorageScope.GLOBAL, StorageTarget.MACHINE);
if (userSetKernelProvider) {
const filteredKernels = kernels.filter(kernel => kernel.extension.value === userSetKernelProvider);
if (filteredKernels.length) {
const cachedKernelId = memento[provider.id];
this.activeKernel =
filteredKernels.find(kernel => kernel.isPreferred)
|| filteredKernels.find(kernel => kernel.friendlyId === cachedKernelId)
|| filteredKernels[0];
} else {
this.activeKernel = undefined;
} }
cellHandles.push(cell.handle);
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
await this._activeKernelResolvePromise;
if (tokenSource.token.isCancellationRequested) {
return;
}
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
} }
// choose a preferred kernel if (cellHandles.length > 0) {
const kernelsFromSameExtension = kernels.filter(kernel => kernel.extension.value === provider.providerExtensionId); this._notebookKernelService.updateNotebookKernelBinding(notebook, kernel);
if (kernelsFromSameExtension.length) { await kernel.executeNotebookCellsRequest(notebook.uri, cellHandles);
const cachedKernelId = memento[provider.id];
const preferedKernel = kernelsFromSameExtension.find(kernel => kernel.isPreferred)
|| kernelsFromSameExtension.find(kernel => kernel.friendlyId === cachedKernelId)
|| kernelsFromSameExtension[0];
this.activeKernel = preferedKernel;
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel);
}
if (tokenSource.token.isCancellationRequested) {
return;
}
await preferedKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
memento[provider.id] = this._activeKernel?.friendlyId;
this._activeKernelMemento.saveMemento();
tokenSource.dispose();
return;
}
// the provider doesn't have a builtin kernel, choose a kernel
this.activeKernel = kernels[0];
if (this.activeKernel) {
await this._delegate.loadKernelPreloads(this.activeKernel.localResourceRoot, this.activeKernel);
if (tokenSource.token.isCancellationRequested) {
return;
}
await this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
if (tokenSource.token.isCancellationRequested) {
return;
}
}
tokenSource.dispose();
}
private async _ensureActiveKernel() {
if (this._activeKernel) {
return;
}
if (this._activeKernelResolvePromise) {
await this._activeKernelResolvePromise;
if (this._activeKernel) {
return;
}
}
if (!this._initialKernelComputationDone) {
await this.setKernels(false, new CancellationTokenSource());
if (this._activeKernel) {
return;
}
}
// pick active kernel
const picker = this._quickInputService.createQuickPick<(IQuickPickItem & { run(): void; kernelProviderId?: string })>();
picker.placeholder = nls.localize('notebook.runCell.selectKernel', "Select a notebook kernel to run this notebook");
picker.matchOnDetail = true;
const tokenSource = new CancellationTokenSource();
const availableKernels = await this.beginComputeContributedKernels();
const picks: QuickPickInput<IQuickPickItem & { run(): void; kernelProviderId?: string; }>[] = availableKernels.map((a) => {
return {
id: a.friendlyId,
label: a.label,
picked: false,
description:
a.description
? a.description
: a.extension.value,
detail: a.detail,
kernelProviderId: a.extension.value,
run: async () => {
this.activeKernel = a;
this._activeKernelResolvePromise = this.activeKernel.resolve(this._delegate.viewModel!.uri, this._delegate.getId(), tokenSource.token);
},
buttons: [{
iconClass: ThemeIcon.asClassName(configureKernelIcon),
tooltip: nls.localize('notebook.promptKernel.setDefaultTooltip', "Set as default kernel provider for '{0}'", this._delegate.viewModel!.viewType)
}]
};
});
picker.items = picks;
picker.busy = false;
const pickedItem = await new Promise<(IQuickPickItem & { run(): void; kernelProviderId?: string; }) | undefined>(resolve => {
picker.onDidAccept(() => {
resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0] : undefined);
picker.dispose();
});
picker.onDidTriggerItemButton(e => {
const pick = e.item;
const id = pick.id;
resolve(pick); // open the view
picker.dispose();
// And persist the setting
if (pick && id && pick.kernelProviderId) {
const newAssociation: NotebookKernelProviderAssociation = { viewType: this._delegate.viewModel!.viewType, kernelProvider: pick.kernelProviderId };
const currentAssociations = [...this._configurationService.getValue<NotebookKernelProviderAssociations>(notebookKernelProviderAssociationsSettingId)];
// First try updating existing association
for (let i = 0; i < currentAssociations.length; ++i) {
const existing = currentAssociations[i];
if (existing.viewType === newAssociation.viewType) {
currentAssociations.splice(i, 1, newAssociation);
this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
return;
}
}
// Otherwise, create a new one
currentAssociations.unshift(newAssociation);
this._configurationService.updateValue(notebookKernelProviderAssociationsSettingId, currentAssociations);
}
});
});
tokenSource.dispose();
if (pickedItem) {
await pickedItem.run();
}
return;
}
async cancelNotebookExecution(): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
await this._ensureActiveKernel();
const fullRange: ICellRange = {
start: 0, end: this._delegate.viewModel.length
};
await this._activeKernel?.cancelNotebookCellExecution!(this._delegate.viewModel.uri, [fullRange]);
}
async executeNotebook(): Promise<void> {
if (!this._delegate.viewModel) {
return;
}
await this._ensureActiveKernel();
if (!this.canExecuteNotebook()) {
return;
}
const codeCellRanges = getRanges(this._delegate.viewModel.viewCells, cell => cell.cellKind === CellKind.Code);
if (codeCellRanges.length) {
this._activeKernelExecuted = true;
await this._activeKernel?.executeNotebookCellsRequest(this._delegate.viewModel.uri, codeCellRanges);
} }
} }
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> { async cancelNotebookCells(notebook: INotebookTextModel, cells: Iterable<ICellViewModel>): Promise<void> {
if (!this._delegate.viewModel) { let kernel = this.getActiveKernel(notebook);
return; if (kernel) {
await kernel.cancelNotebookCellExecution(notebook.uri, Array.from(cells, cell => cell.handle));
} }
if (cell.cellKind !== CellKind.Code) {
return;
}
const metadata = cell.getEvaluatedMetadata(this._delegate.viewModel.metadata);
if (metadata.runState === NotebookCellExecutionState.Idle) {
return;
}
await this._ensureActiveKernel();
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> {
if (!this._delegate.viewModel) {
return;
}
await this._ensureActiveKernel();
if (!this.canExecuteCell(cell)) {
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!.executeNotebookCellsRequest(this._delegate.viewModel.uri, range);
}
private canExecuteNotebook(): boolean {
if (!this.activeKernel) {
return false;
}
if (!this._delegate.viewModel?.trusted) {
return false;
}
return true;
}
private canExecuteCell(cell: ICellViewModel): boolean {
if (!this.activeKernel) {
return false;
}
if (cell.cellKind !== CellKind.Code) {
return false;
}
if (!this.activeKernel.supportedLanguages) {
return true;
}
if (this.activeKernel.supportedLanguages.includes(cell.language)) {
return true;
}
return false;
} }
} }
@@ -10,12 +10,11 @@ import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent
import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { SequencerByKey } from 'vs/base/common/async'; import { SequencerByKey } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { Color, RGBA } from 'vs/base/common/color'; import { Color, RGBA } from 'vs/base/common/color';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { combinedDisposable, Disposable, DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { extname } from 'vs/base/common/resources'; import { extname, isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import 'vs/css!./media/notebook'; import 'vs/css!./media/notebook';
@@ -47,7 +46,7 @@ import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN
import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_ID, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations'; import { NotebookDecorationCSSRules, NotebookRefCountedStyleSheet } from 'vs/workbench/contrib/notebook/browser/notebookEditorDecorations';
import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions';
import { IKernelManagerDelegate, NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService';
import { errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons'; import { errorStateIcon, successStateIcon } from 'vs/workbench/contrib/notebook/browser/notebookIcons';
import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
@@ -61,9 +60,7 @@ import { NotebookEventDispatcher, NotebookLayoutChangedEvent } from 'vs/workbenc
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { CellViewModel, IModelDecorationsChangeAccessor, INotebookEditorViewState, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, CellToolbarLocKey, ExperimentalUseMarkdownRenderer, ICellRange, INotebookKernel, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellKind, CellToolbarLocKey, ExperimentalUseMarkdownRenderer, ICellRange, SelectionStateType, ShowCellStatusBarKey } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator'; import { editorGutterModifiedBackground } from 'vs/workbench/contrib/scm/browser/dirtydiffDecorator';
import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -76,6 +73,8 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
import { isWeb } from 'vs/base/common/platform'; import { isWeb } from 'vs/base/common/platform';
import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance'; import { mark } from 'vs/workbench/contrib/notebook/common/notebookPerformance';
import { readFontInfo } from 'vs/editor/browser/config/configuration'; import { readFontInfo } from 'vs/editor/browser/config/configuration';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/notebookEditorWidgetContextKeys';
const $ = DOM.$; const $ = DOM.$;
@@ -267,27 +266,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
return this._notebookViewModel?.notebookDocument; return this._notebookViewModel?.notebookDocument;
} }
get onDidChangeKernel(): Event<void> {
return this._kernelManger.onDidChangeKernel;
}
get onDidChangeAvailableKernels(): Event<void> {
return this._kernelManger.onDidChangeAvailableKernels;
}
get activeKernel() {
return this._kernelManger.activeKernel;
}
set activeKernel(value) {
this._kernelManger.activeKernel = value;
}
private _currentKernelTokenSource: CancellationTokenSource | undefined = undefined;
get availableKernelCount() {
return this._kernelManger.availableKernelCount;
}
private readonly _onDidChangeActiveEditor = this._register(new Emitter<this>()); private readonly _onDidChangeActiveEditor = this._register(new Emitter<this>());
readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event; readonly onDidChangeActiveEditor: Event<this> = this._onDidChangeActiveEditor.event;
@@ -333,8 +311,8 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IStorageService storageService: IStorageService, @IStorageService storageService: IStorageService,
@IAccessibilityService accessibilityService: IAccessibilityService, @IAccessibilityService accessibilityService: IAccessibilityService,
@INotebookService private notebookService: INotebookService,
@INotebookEditorService private readonly notebookEditorService: INotebookEditorService, @INotebookEditorService private readonly notebookEditorService: INotebookEditorService,
@INotebookKernelService notebookKernelService: INotebookKernelService,
@IEditorService private readonly editorService: IEditorService, @IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService, @IConfigurationService private readonly configurationService: IConfigurationService,
@IContextKeyService contextKeyService: IContextKeyService, @IContextKeyService contextKeyService: IContextKeyService,
@@ -356,22 +334,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer); this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer);
this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])); this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
const that = this; this._register(instantiationService.createInstance(NotebookEditorContextKeys, this));
this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager, <IKernelManagerDelegate>{
getId() { return that.getId(); }, this._kernelManger = instantiationService.createInstance(NotebookEditorKernelManager);
loadKernelPreloads: that._loadKernelPreloads.bind(that), this._register(notebookKernelService.onDidChangeNotebookKernelBinding(e => {
onDidChangeViewModel: that.onDidChangeModel, if (isEqual(e.notebook, this.viewModel?.uri)) {
get viewModel() { return that.viewModel; }, this._loadKernelPreloads();
getContributedNotebookProviders(resource?: URI) {
return that.notebookService.getContributedNotebookProviders(resource);
},
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined {
return that.notebookService.getContributedNotebookProvider(viewType);
},
getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]> {
return that.notebookService.getNotebookKernels(viewType, resource, token);
} }
}); }));
this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService); this._memento = new Memento(NOTEBOOK_EDITOR_ID, storageService);
@@ -888,24 +858,12 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this.restoreListViewState(viewState); this.restoreListViewState(viewState);
} }
// load preloads for matching kernel
this._loadKernelPreloads();
// clear state // clear state
this._dndController?.clearGlobalDragState(); this._dndController?.clearGlobalDragState();
this._currentKernelTokenSource = new CancellationTokenSource();
this._localStore.add(this._currentKernelTokenSource);
// we don't await for it, otherwise it will slow down the file opening
this._setKernels(false, this._currentKernelTokenSource);
this._localStore.add(this.notebookService.onDidChangeKernels(async (e) => {
if (e && e.toString() !== this.textModel?.uri.toString()) {
// kernel update is not for current document.
return;
}
this._currentKernelTokenSource?.cancel();
this._currentKernelTokenSource = new CancellationTokenSource();
await this._setKernels(true, this._currentKernelTokenSource);
}));
this._localStore.add(this._list.onDidChangeFocus(() => { this._localStore.add(this._list.onDidChangeFocus(() => {
this.updateContextKeysOnFocusChange(); this.updateContextKeysOnFocusChange();
})); }));
@@ -998,28 +956,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
this._list.clear(); this._list.clear();
} }
async beginComputeContributedKernels() {
return this._kernelManger.beginComputeContributedKernels();
}
private async _setKernels(refresh: boolean, tokenSource: CancellationTokenSource) {
if (this.viewModel) {
this._kernelManger.setKernels(refresh, tokenSource);
}
}
private async _loadKernelPreloads(extensionLocation: URI, kernel: INotebookKernel) {
const preloadUris = kernel.preloadUris;
if (!preloadUris.length) {
return;
}
if (!this._webview?.isResolved()) {
await this._resolveWebview();
}
this._webview?.updateKernelPreloads([extensionLocation], kernel.preloadUris);
}
private _updateForOptions(): void { private _updateForOptions(): void {
if (!this.hasModel()) { if (!this.hasModel()) {
@@ -1072,13 +1008,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
} }
}); });
this._localStore.add(this._webview.onMessage(e => {
if (this.viewModel) {
this.notebookService.onDidReceiveMessage(this.viewModel.viewType, this.getId(), e.forRenderer, e.message);
this._onDidReceiveMessage.fire(e);
}
}));
resolve(this._webview); resolve(this._webview);
}); });
@@ -1716,30 +1645,46 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
//#endregion //#endregion
//#region Kernel/Execution //#region Kernel/Execution
async cancelNotebookExecution(): Promise<void> {
return this._kernelManger.cancelNotebookExecution();
}
async executeNotebook(): Promise<void> { private async _loadKernelPreloads() {
return this._kernelManger.executeNotebook(); const kernel = this.activeKernel;
} if (!kernel) {
return;
async cancelNotebookCellExecution(cell: ICellViewModel): Promise<void> { }
return this._kernelManger.cancelNotebookCellExecution(cell); const preloadUris = kernel.preloadUris;
} if (!preloadUris.length) {
async executeNotebookCell(cell: ICellViewModel): Promise<void> {
if (!this.viewModel) {
return; return;
} }
// TODO@roblourens, don't use the "execute" command for this if (!this._webview?.isResolved()) {
if (cell.cellKind === CellKind.Markdown) { await this._resolveWebview();
this.focusNotebookCell(cell, 'container');
return;
} }
return this._kernelManger.executeNotebookCell(cell); this._webview?.updateKernelPreloads([kernel.localResourceRoot], kernel.preloadUris);
}
get activeKernel() {
return this.viewModel && this._kernelManger.getActiveKernel(this.viewModel.notebookDocument);
}
async cancelNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void> {
if (!this.hasModel()) {
return;
}
if (!cells) {
cells = this.viewModel.viewCells;
}
return this._kernelManger.cancelNotebookCells(this.viewModel.notebookDocument, cells);
}
async executeNotebookCells(cells?: Iterable<ICellViewModel>): Promise<void> {
if (!this.hasModel()) {
return;
}
if (!cells) {
cells = this.viewModel.viewCells;
}
return this._kernelManger.executeNotebookCells(this.viewModel.notebookDocument, cells);
} }
//#endregion //#endregion
@@ -1811,7 +1756,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor
const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1; const nextIndex = ui ? this.viewModel.getNextVisibleCellIndex(index) : index + 1;
let language; let language;
if (type === CellKind.Code) { if (type === CellKind.Code) {
const supportedLanguages = this._kernelManger.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes(); const supportedLanguages = this.activeKernel?.supportedLanguages ?? this.modeService.getRegisteredModes();
const defaultLanguage = supportedLanguages[0] || 'plaintext'; const defaultLanguage = supportedLanguages[0] || 'plaintext';
if (cell?.cellKind === CellKind.Code) { if (cell?.cellKind === CellKind.Code) {
language = cell.language; language = cell.language;
@@ -0,0 +1,98 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICellViewModel, NOTEBOOK_HAS_RUNNING_CELL, NOTEBOOK_INTERRUPTIBLE_KERNEL, NOTEBOOK_KERNEL_COUNT, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookCellExecutionState } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
export class NotebookEditorContextKeys {
private readonly _notebookKernelCount: IContextKey<number>;
private readonly _interruptibleKernel: IContextKey<boolean>;
private readonly _someCellRunning: IContextKey<boolean>;
private readonly _disposables = new DisposableStore();
private readonly _viewModelDisposables = new DisposableStore();
private readonly _cellStateListeners: IDisposable[] = [];
constructor(
private readonly _editor: INotebookEditor,
@INotebookKernelService private readonly _notebookKernelService: INotebookKernelService,
@IContextKeyService contextKeyService: IContextKeyService,
) {
this._notebookKernelCount = NOTEBOOK_KERNEL_COUNT.bindTo(contextKeyService);
this._interruptibleKernel = NOTEBOOK_INTERRUPTIBLE_KERNEL.bindTo(contextKeyService);
this._someCellRunning = NOTEBOOK_HAS_RUNNING_CELL.bindTo(contextKeyService);
this._disposables.add(_editor.onDidChangeModel(() => this._initCellListeners()));
this._initCellListeners();
this._updateKernelContext();
this._disposables.add(_notebookKernelService.onDidAddKernel(this._updateKernelContext, this));
this._disposables.add(_notebookKernelService.onDidChangeNotebookKernelBinding(this._updateKernelContext, this));
}
dispose(): void {
this._disposables.dispose();
this._viewModelDisposables.dispose();
this._notebookKernelCount.reset();
this._interruptibleKernel.reset();
this._someCellRunning.reset();
}
private _initCellListeners(): void {
this._viewModelDisposables.clear();
dispose(this._cellStateListeners);
this._cellStateListeners.length = 0;
if (!this._editor.hasModel()) {
return;
}
let executionCount = 0;
const addCellStateListener = (c: ICellViewModel) => {
return (c as CellViewModel).onDidChangeState(e => {
if (!e.runStateChanged) {
return;
}
if (c.metadata?.runState === NotebookCellExecutionState.Pending) {
executionCount++;
} else if (c.metadata?.runState === NotebookCellExecutionState.Idle) {
executionCount--;
}
this._someCellRunning.set(executionCount > 0);
});
};
for (const cell of this._editor.viewModel.viewCells) {
this._cellStateListeners.push(addCellStateListener(cell));
}
this._viewModelDisposables.add(this._editor.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);
});
}));
}
private _updateKernelContext(): void {
if (!this._editor.hasModel()) {
this._notebookKernelCount.reset();
this._interruptibleKernel.reset();
return;
}
const { bound, all } = this._notebookKernelService.getNotebookKernels(this._editor.viewModel.notebookDocument);
this._notebookKernelCount.set(all.length);
this._interruptibleKernel.set(bound?.implementsInterrupt ?? false);
}
}
@@ -4,32 +4,68 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { DisposableStore, dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { INotebookKernelBindEvent, INotebookKernel2, INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService'; import { INotebookKernelBindEvent, INotebookKernelService, INotebookTextModelLike } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { score } from 'vs/workbench/contrib/notebook/common/notebookSelector'; import { score } from 'vs/workbench/contrib/notebook/common/notebookSelector';
import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LRUCache } from 'vs/base/common/map';
import { Registry } from 'vs/platform/registry/common/platform'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ResourceMap } from 'vs/base/common/map'; import { runWhenIdle } from 'vs/base/common/async';
import { INotebookEditorService } from 'vs/workbench/contrib/notebook/browser/notebookEditorService'; import { ILogService } from 'vs/platform/log/common/log';
import { isEqual } from 'vs/base/common/resources';
interface IKernelInfo {
kernel: INotebookKernel;
score: number;
}
class ScoreInfo {
constructor(private readonly _anchor: INotebookTextModelLike) { }
equals(candidate: INotebookTextModelLike): boolean {
return this._anchor.viewType === candidate.viewType && isEqual(this._anchor.uri, candidate.uri);
}
}
export class NotebookKernelService implements INotebookKernelService { export class NotebookKernelService implements INotebookKernelService {
declare _serviceBrand: undefined; declare _serviceBrand: undefined;
private readonly _kernels = new Map<string, INotebookKernel2>(); private static _storageKey = 'notebook.kernelBindings';
private readonly _kernelBindings = new ResourceMap<INotebookKernel2>();
private readonly _kernels = new Map<string, IKernelInfo>();
private readonly _kernelBindings = new LRUCache<string, string>(1000, 0.7);
private _scoreInfo?: ScoreInfo;
private readonly _onDidChangeNotebookKernelBinding = new Emitter<INotebookKernelBindEvent>(); private readonly _onDidChangeNotebookKernelBinding = new Emitter<INotebookKernelBindEvent>();
private readonly _onDidAddKernel = new Emitter<INotebookKernel2>(); private readonly _onDidAddKernel = new Emitter<INotebookKernel>();
private readonly _onDidRemoveKernel = new Emitter<INotebookKernel2>(); private readonly _onDidRemoveKernel = new Emitter<INotebookKernel>();
readonly onDidChangeNotebookKernelBinding: Event<INotebookKernelBindEvent> = this._onDidChangeNotebookKernelBinding.event; readonly onDidChangeNotebookKernelBinding: Event<INotebookKernelBindEvent> = this._onDidChangeNotebookKernelBinding.event;
readonly onDidAddKernel: Event<INotebookKernel2> = this._onDidAddKernel.event; readonly onDidAddKernel: Event<INotebookKernel> = this._onDidAddKernel.event;
readonly onDidRemoveKernel: Event<INotebookKernel2> = this._onDidRemoveKernel.event; readonly onDidRemoveKernel: Event<INotebookKernel> = this._onDidRemoveKernel.event;
constructor(
@IStorageService private _storageService: IStorageService,
@ILogService logService: ILogService,
) {
try {
const value = _storageService.get(NotebookKernelService._storageKey, StorageScope.WORKSPACE, '[]');
const data = JSON.parse(value);
this._kernelBindings.fromJSON(data);
} catch {
logService.warn('FAILED to restore kernel bindings');
}
}
private _persistBindings(): void {
runWhenIdle(() => {
const raw = JSON.stringify(this._kernelBindings);
this._storageService.store(NotebookKernelService._storageKey, raw, StorageScope.WORKSPACE, StorageTarget.MACHINE);
}, 100);
}
dispose() { dispose() {
this._onDidChangeNotebookKernelBinding.dispose(); this._onDidChangeNotebookKernelBinding.dispose();
@@ -38,154 +74,80 @@ export class NotebookKernelService implements INotebookKernelService {
this._kernels.clear(); this._kernels.clear();
} }
registerKernel(kernel: INotebookKernel2): IDisposable { registerKernel(kernel: INotebookKernel): IDisposable {
if (this._kernels.has(kernel.id)) { if (this._kernels.has(kernel.id)) {
throw new Error(`NOTEBOOK CONTROLLER with id '${kernel.id}' already exists`); throw new Error(`NOTEBOOK CONTROLLER with id '${kernel.id}' already exists`);
} }
this._kernels.set(kernel.id, kernel); this._scoreInfo = undefined;
this._kernels.set(kernel.id, { kernel, score: -1 });
this._onDidAddKernel.fire(kernel); this._onDidAddKernel.fire(kernel);
return toDisposable(() => { return toDisposable(() => {
this._scoreInfo = undefined;
if (this._kernels.delete(kernel.id)) { if (this._kernels.delete(kernel.id)) {
this._onDidRemoveKernel.fire(kernel); this._onDidRemoveKernel.fire(kernel);
} }
for (let [uri, candidate] of this._kernelBindings) { for (let [uri, candidate] of Array.from(this._kernelBindings)) {
if (candidate === kernel) { if (candidate === kernel.id) {
this._kernelBindings.delete(uri); this._kernelBindings.delete(uri);
this._onDidChangeNotebookKernelBinding.fire({ notebook: uri, oldKernel: kernel, newKernel: undefined }); this._onDidChangeNotebookKernelBinding.fire({ notebook: URI.parse(uri), oldKernel: kernel.id, newKernel: undefined });
} }
} }
}); });
} }
getMatchingKernels(notebook: INotebookTextModel): INotebookKernel2[] { getNotebookKernels(notebook: INotebookTextModelLike): { bound: INotebookKernel | undefined, all: INotebookKernel[] } {
const result: INotebookKernel2[] = [];
for (const kernel of this._kernels.values()) { // update score if needed
if (score(kernel.selector, notebook.uri, notebook.viewType) > 0) { if (!this._scoreInfo?.equals(notebook)) {
result.push(kernel); for (let item of this._kernels.values()) {
item.score = score(item.kernel.selector, notebook.uri, notebook.viewType);
} }
this._scoreInfo = new ScoreInfo(notebook);
} }
const boundKernel = this._kernelBindings.get(notebook.uri);
return result.sort((a, b) => { // all applicable kernels
// (1) binding a kernel const all = Array.from(this._kernels.values())
if (a === boundKernel) { .filter(item => item.score > 0)
return -1; .sort((a, b) => {
} else if (b === boundKernel) { // (1) sort by preference
return 1; if (a.kernel.isPreferred !== b.kernel.isPreferred) {
} if (a.kernel.isPreferred) {
// (2) preferring a kernel return -1;
if (a.isPreferred === b.isPreferred) { } else {
return 0; return 1;
} else if (a.isPreferred) { }
return -1; }
} else { // (2) sort by score
return 1; if (b.score !== a.score) {
} return b.score - a.score;
}); }
// (3) sort by name
return a.kernel.label.localeCompare(b.kernel.label);
})
.map(item => item.kernel);
// bound kernel
const boundId = this._kernelBindings.get(notebook.uri.toString());
const bound = boundId ? this._kernels.get(boundId)?.kernel : undefined;
return { all, bound };
} }
// a notebook has one kernel, a kernel has N notebooks // a notebook has one kernel, a kernel has N notebooks
// notebook <-1----N-> kernel // notebook <-1----N-> kernel
updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel2 | undefined): void { updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel | undefined): void {
const oldKernel = this._kernelBindings.get(notebook.uri); const key = notebook.uri.toString();
if (oldKernel !== kernel) { const oldKernel = this._kernelBindings.get(key);
if (oldKernel !== kernel?.id) {
if (kernel) { if (kernel) {
this._kernelBindings.set(notebook.uri, kernel); this._kernelBindings.set(key, kernel.id);
} else { } else {
this._kernelBindings.delete(notebook.uri); this._kernelBindings.delete(key);
} }
this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel }); this._onDidChangeNotebookKernelBinding.fire({ notebook: notebook.uri, oldKernel, newKernel: kernel?.id });
this._persistBindings();
} }
} }
getBoundKernel(notebook: INotebookTextModel): INotebookKernel2 | undefined {
return this._kernelBindings.get(notebook.uri);
}
} }
// below is some GLUE code to bridge managed kernels into the existing kernel pull
// world. this should eventually disappear
class KernelAdaptorBridge implements IWorkbenchContribution {
readonly dispose: () => void;
constructor(
@INotebookKernelService notebookKernelService: INotebookKernelService,
@INotebookService notebookService: INotebookService,
@INotebookEditorService notebookEditorService: INotebookEditorService
) {
const disposables = new DisposableStore();
const emitter = new Emitter<URI | undefined>();
const kernels = new Map<INotebookKernel2, IDisposable>();
disposables.add(notebookKernelService.onDidAddKernel(kernel => {
const reg = kernel.onDidChange(() => emitter.fire(undefined));
kernels.set(kernel, reg);
emitter.fire(undefined);
}));
disposables.add(notebookKernelService.onDidRemoveKernel(kernel => {
const reg = kernels.get(kernel);
if (reg) {
reg.dispose();
kernels.delete(kernel);
emitter.fire(undefined);
}
}));
// kernel -> provider
const registration = notebookService.registerNotebookKernelProvider({
onDidChangeKernels: emitter.event,
providerExtensionId: 'notAnExtension',
selector: { filenamePattern: '**/*' },
async provideKernels(uri: URI) {
const model = notebookService.getNotebookTextModel(uri);
if (!model) {
return [];
}
return notebookKernelService.getMatchingKernels(model);
}
});
// kernel binding
const editorListener = new Map<string, IDisposable>();
disposables.add(notebookEditorService.onDidAddNotebookEditor(e => {
const r1 = e.onDidChangeKernel(() => {
if (!e.viewModel) {
return;
}
let kernel: INotebookKernel2 | undefined;
if (e.activeKernel) {
for (const candidate of kernels.keys()) {
if (e.activeKernel.friendlyId === candidate.id) {
kernel = candidate;
break;
}
}
}
notebookKernelService.updateNotebookKernelBinding(e.viewModel.notebookDocument, kernel);
});
editorListener.set(e.getId(), r1);
}));
disposables.add(notebookEditorService.onDidRemoveNotebookEditor(e => {
editorListener.get(e.getId())?.dispose();
editorListener.delete(e.getId());
}));
this.dispose = () => {
dispose(editorListener.values());
disposables.dispose();
emitter.dispose();
registration.dispose();
};
}
}
Registry
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
.registerWorkbenchContribution(KernelAdaptorBridge, LifecyclePhase.Ready);
@@ -6,8 +6,6 @@
import * as glob from 'vs/base/common/glob'; import * as glob from 'vs/base/common/glob';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser';
import { flatten } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Emitter, Event } from 'vs/base/common/event'; import { Emitter, Event } from 'vs/base/common/event';
import { Iterable } from 'vs/base/common/iterator'; import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
@@ -24,10 +22,10 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr
import { Memento } from 'vs/workbench/common/memento'; import { Memento } from 'vs/workbench/common/memento';
import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint'; import { INotebookEditorContribution, notebookMarkdownRendererExtensionPoint, notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorOptions, updateEditorTopPadding } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookViewTypesExtensionRegistry, updateNotebookKernelProvideAssociationSchema } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookKernel, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, notebookDocumentFilterMatch, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellUri, DisplayOrderKey, INotebookExclusiveDocumentFilter, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, NotebookDataDto, NotebookEditorPriority, NotebookRendererMatch, NotebookTextDiffEditorPreview, RENDERER_NOT_AVAILABLE, sortMimeTypes, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer';
import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
@@ -44,44 +42,6 @@ import { NotebookEditorInput } from 'vs/workbench/contrib/notebook/common/notebo
import { ContributedEditorPriority, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService'; import { ContributedEditorPriority, priorityToRank } from 'vs/workbench/services/editor/common/editorOverrideService';
import { IEditorOverrideService } from 'vs/workbench/services/editor/browser/editorOverrideService'; import { IEditorOverrideService } from 'vs/workbench/services/editor/browser/editorOverrideService';
export class NotebookKernelProviderInfoStore {
private readonly _notebookKernelProviders: INotebookKernelProvider[] = [];
add(provider: INotebookKernelProvider) {
this._notebookKernelProviders.push(provider);
this._updateProviderExtensionsInfo();
return toDisposable(() => {
const idx = this._notebookKernelProviders.indexOf(provider);
if (idx >= 0) {
this._notebookKernelProviders.splice(idx, 1);
}
this._updateProviderExtensionsInfo();
});
}
get(viewType: string, resource: URI) {
return this._notebookKernelProviders.filter(provider => notebookDocumentFilterMatch(provider.selector, viewType, resource));
}
getContributedKernelProviders() {
return [...this._notebookKernelProviders];
}
private _updateProviderExtensionsInfo() {
NotebookKernelProviderAssociationRegistry.extensionIds.length = 0;
NotebookKernelProviderAssociationRegistry.extensionDescriptions.length = 0;
this._notebookKernelProviders.forEach(provider => {
NotebookKernelProviderAssociationRegistry.extensionIds.push(provider.providerExtensionId);
NotebookKernelProviderAssociationRegistry.extensionDescriptions.push(provider.providerDescription || '');
});
updateNotebookKernelProvideAssociationSchema();
}
}
export class NotebookProviderInfoStore extends Disposable { export class NotebookProviderInfoStore extends Disposable {
private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors'; private static readonly CUSTOM_EDITORS_STORAGE_ID = 'notebookEditors';
@@ -338,7 +298,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
private readonly _notebookProviderInfoStore: NotebookProviderInfoStore; private readonly _notebookProviderInfoStore: NotebookProviderInfoStore;
private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore); private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore);
private readonly _markdownRenderersInfos = new Set<INotebookMarkdownRendererInfo>(); private readonly _markdownRenderersInfos = new Set<INotebookMarkdownRendererInfo>();
private readonly _notebookKernelProviderInfoStore: NotebookKernelProviderInfoStore = new NotebookKernelProviderInfoStore();
private readonly _models = new ResourceMap<ModelData>(); private readonly _models = new ResourceMap<ModelData>();
private readonly _onDidAddNotebookDocument = this._register(new Emitter<NotebookTextModel>()); private readonly _onDidAddNotebookDocument = this._register(new Emitter<NotebookTextModel>());
@@ -577,36 +536,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return result; return result;
} }
registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable {
const d = this._notebookKernelProviderInfoStore.add(provider);
const kernelChangeEventListener = provider.onDidChangeKernels((e) => {
this._onDidChangeKernels.fire(e);
});
this._onDidChangeKernels.fire(undefined);
return toDisposable(() => {
kernelChangeEventListener.dispose();
d.dispose();
this._onDidChangeKernels.fire(undefined);
});
}
async getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]> {
const filteredProvider = this._notebookKernelProviderInfoStore.get(viewType, resource);
const result = new Array<INotebookKernel[]>(filteredProvider.length);
const promises = filteredProvider.map(async (provider, index) => {
const data = await provider.provideKernels(resource, token);
result[index] = data;
});
await Promise.all(promises);
return flatten(result);
}
async getContributedNotebookKernelProviders(): Promise<INotebookKernelProvider[]> {
const kernelProviders = this._notebookKernelProviderInfoStore.getContributedKernelProviders();
return kernelProviders;
}
getRendererInfo(rendererId: string): INotebookRendererInfo | undefined { getRendererInfo(rendererId: string): INotebookRendererInfo | undefined {
return this._notebookRenderersInfoStore.get(rendererId); return this._notebookRenderersInfoStore.get(rendererId);
} }
@@ -739,22 +668,6 @@ export class NotebookService extends Disposable implements INotebookService, IEd
return ret; return ret;
} }
// --- data provider IPC (deprecated)
async resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void> {
const entry = this._notebookProviders.get(viewType);
if (entry instanceof ComplexNotebookProviderInfo) {
entry.controller.resolveNotebookEditor(viewType, uri, editorId);
}
}
onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: any): void {
const provider = this._notebookProviders.get(viewType);
if (provider instanceof ComplexNotebookProviderInfo) {
return provider.controller.onDidReceiveMessage(editorId, rendererType, message);
}
}
// --- copy & paste // --- copy & paste
setToCopy(items: NotebookCellTextModel[], isCopy: boolean) { setToCopy(items: NotebookCellTextModel[], isCopy: boolean) {
@@ -44,6 +44,9 @@ export class CellEditorStatusBar extends Disposable {
private readonly rightContributedItemsContainer: HTMLElement; private readonly rightContributedItemsContainer: HTMLElement;
private readonly itemsDisposable: DisposableStore; private readonly itemsDisposable: DisposableStore;
private items: CellStatusBarItem[] = [];
private width: number = 0;
private currentContext: INotebookCellActionContext | undefined; private currentContext: INotebookCellActionContext | undefined;
protected readonly _onDidClick: Emitter<IClickTarget> = this._register(new Emitter<IClickTarget>()); protected readonly _onDidClick: Emitter<IClickTarget> = this._register(new Emitter<IClickTarget>());
readonly onDidClick: Event<IClickTarget> = this._onDidClick.event; readonly onDidClick: Event<IClickTarget> = this._onDidClick.event;
@@ -103,7 +106,15 @@ export class CellEditorStatusBar extends Disposable {
} }
layout(width: number): void { layout(width: number): void {
this.width = width;
this.statusBarContainer.style.width = `${width}px`; this.statusBarContainer.style.width = `${width}px`;
const maxItemWidth = this.getMaxItemWidth();
this.items.forEach(item => item.maxWidth = maxItemWidth);
}
private getMaxItemWidth() {
return this.width / 2;
} }
private async updateStatusBarItems() { private async updateStatusBarItems() {
@@ -130,14 +141,15 @@ export class CellEditorStatusBar extends Disposable {
items.sort((itemA, itemB) => { items.sort((itemA, itemB) => {
return (itemB.priority ?? 0) - (itemA.priority ?? 0); return (itemB.priority ?? 0) - (itemA.priority ?? 0);
}); });
items.forEach(item => {
const itemView = this.itemsDisposable.add(this.instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item)); const maxItemWidth = this.getMaxItemWidth();
if (item.alignment === CellStatusbarAlignment.Left) { const leftItems = items.filter(item => item.alignment === CellStatusbarAlignment.Left)
this.leftContributedItemsContainer.appendChild(itemView.container); .map(item => this.itemsDisposable.add(this.instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item, maxItemWidth)));
} else { const rightItems = items.filter(item => item.alignment === CellStatusbarAlignment.Right).reverse()
this.rightContributedItemsContainer.appendChild(itemView.container); .map(item => this.itemsDisposable.add(this.instantiationService.createInstance(CellStatusBarItem, this.currentContext!, item, maxItemWidth)));
} leftItems.forEach(itemView => this.leftContributedItemsContainer.appendChild(itemView.container));
}); rightItems.forEach(itemView => this.rightContributedItemsContainer.appendChild(itemView.container));
this.items = [...leftItems, ...rightItems];
} }
} }
@@ -145,15 +157,20 @@ class CellStatusBarItem extends Disposable {
readonly container = $('.cell-status-item'); readonly container = $('.cell-status-item');
set maxWidth(v: number) {
this.container.style.maxWidth = v + 'px';
}
constructor( constructor(
private readonly _context: INotebookCellActionContext, private readonly _context: INotebookCellActionContext,
private readonly _itemModel: INotebookCellStatusBarItem, private readonly _itemModel: INotebookCellStatusBarItem,
maxWidth: number | undefined,
@ITelemetryService private readonly telemetryService: ITelemetryService, @ITelemetryService private readonly telemetryService: ITelemetryService,
@ICommandService private readonly commandService: ICommandService, @ICommandService private readonly commandService: ICommandService,
@INotificationService private readonly notificationService: INotificationService @INotificationService private readonly notificationService: INotificationService
) { ) {
super(); super();
new SimpleIconLabel(this.container).text = this._itemModel.text; new SimpleIconLabel(this.container).text = this._itemModel.text.replace(/\n/g, ' ');
if (this._itemModel.opacity) { if (this._itemModel.opacity) {
this.container.style.opacity = this._itemModel.opacity; this.container.style.opacity = this._itemModel.opacity;
@@ -163,6 +180,10 @@ class CellStatusBarItem extends Disposable {
this.container.classList.add('cell-status-item-show-when-active'); this.container.classList.add('cell-status-item-show-when-active');
} }
if (typeof maxWidth === 'number') {
this.maxWidth = maxWidth;
}
let ariaLabel: string; let ariaLabel: string;
let role: string | undefined; let role: string | undefined;
if (this._itemModel.accessibilityInformation) { if (this._itemModel.accessibilityInformation) {
@@ -15,6 +15,7 @@ import { hash } from 'vs/base/common/hash';
import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer';
import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
import { TextModel } from 'vs/editor/common/model/textModel';
export class NotebookCellTextModel extends Disposable implements ICell { export class NotebookCellTextModel extends Disposable implements ICell {
private _onDidChangeOutputs = new Emitter<NotebookCellOutputsSplice[]>(); private _onDidChangeOutputs = new Emitter<NotebookCellOutputsSplice[]>();
@@ -88,7 +89,9 @@ export class NotebookCellTextModel extends Disposable implements ICell {
this._register(this._textBuffer.onDidChangeContent(() => { this._register(this._textBuffer.onDidChangeContent(() => {
this._hash = null; this._hash = null;
this._onDidChangeContent.fire(); if (!this._textModel) {
this._onDidChangeContent.fire();
}
})); }));
return this._textBuffer; return this._textBuffer;
@@ -96,13 +99,19 @@ export class NotebookCellTextModel extends Disposable implements ICell {
private _hash: number | null = null; private _hash: number | null = null;
private _versionId: number = 1;
private _alternativeId: number = 1;
get alternativeId(): number {
return this._alternativeId;
}
private _textModelDisposables = new DisposableStore(); private _textModelDisposables = new DisposableStore();
private _textModel: model.ITextModel | undefined = undefined; private _textModel: TextModel | undefined = undefined;
get textModel(): model.ITextModel | undefined { get textModel(): TextModel | undefined {
return this._textModel; return this._textModel;
} }
set textModel(m: model.ITextModel | undefined) { set textModel(m: TextModel | undefined) {
if (this._textModel === m) { if (this._textModel === m) {
return; return;
} }
@@ -118,6 +127,16 @@ export class NotebookCellTextModel extends Disposable implements ICell {
this.language = e.newLanguage; this.language = e.newLanguage;
})); }));
this._textModelDisposables.add(this._textModel.onWillDispose(() => this.textModel = undefined)); this._textModelDisposables.add(this._textModel.onWillDispose(() => this.textModel = undefined));
this._textModelDisposables.add(this._textModel.onDidChangeContent(() => {
if (this._textModel) {
this._versionId = this._textModel.getVersionId();
this._alternativeId = this._textModel.getAlternativeVersionId();
}
this._onDidChangeContent.fire();
}));
this._textModel._overwriteVersionId(this._versionId);
this._textModel._overwriteAlternativeVersionId(this._versionId);
} }
} }
@@ -18,6 +18,7 @@ import { Schemas } from 'vs/base/common/network';
import { isEqual } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/resources';
import { IModeService } from 'vs/editor/common/services/modeService'; import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModel } from 'vs/editor/common/model'; import { ITextModel } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
class StackOperation implements IWorkspaceUndoRedoElement { class StackOperation implements IWorkspaceUndoRedoElement {
@@ -26,17 +27,17 @@ class StackOperation implements IWorkspaceUndoRedoElement {
private _operations: IUndoRedoElement[] = []; private _operations: IUndoRedoElement[] = [];
private _beginSelectionState: ISelectionState | undefined = undefined; private _beginSelectionState: ISelectionState | undefined = undefined;
private _resultSelectionState: ISelectionState | undefined = undefined; private _resultSelectionState: ISelectionState | undefined = undefined;
private _beginAlternativeVersionId: number; private _beginAlternativeVersionId: string;
private _resultAlternativeVersionId: number; private _resultAlternativeVersionId: string;
constructor( constructor(
readonly resource: URI, readonly resource: URI,
readonly label: string, readonly label: string,
readonly undoRedoGroup: UndoRedoGroup | undefined, readonly undoRedoGroup: UndoRedoGroup | undefined,
private _delayedEmitter: DelayedEmitter, private _delayedEmitter: DelayedEmitter,
private _postUndoRedo: (alternativeVersionId: number) => void, private _postUndoRedo: (alternativeVersionId: string) => void,
selectionState: ISelectionState | undefined, selectionState: ISelectionState | undefined,
beginAlternativeVersionId: number beginAlternativeVersionId: string
) { ) {
this.type = UndoRedoElementType.Workspace; this.type = UndoRedoElementType.Workspace;
this._beginSelectionState = selectionState; this._beginSelectionState = selectionState;
@@ -51,7 +52,7 @@ class StackOperation implements IWorkspaceUndoRedoElement {
return this._operations.length === 0; return this._operations.length === 0;
} }
pushEndState(alternativeVersionId: number, selectionState: ISelectionState | undefined) { pushEndState(alternativeVersionId: string, selectionState: ISelectionState | undefined) {
this._resultAlternativeVersionId = alternativeVersionId; this._resultAlternativeVersionId = alternativeVersionId;
this._resultSelectionState = selectionState; this._resultSelectionState = selectionState;
} }
@@ -89,11 +90,11 @@ export class NotebookOperationManager {
private _undoService: IUndoRedoService, private _undoService: IUndoRedoService,
private _resource: URI, private _resource: URI,
private _delayedEmitter: DelayedEmitter, private _delayedEmitter: DelayedEmitter,
private _postUndoRedo: (alternativeVersionId: number) => void private _postUndoRedo: (alternativeVersionId: string) => void
) { ) {
} }
pushStackElement(label: string, selectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, alternativeVersionId: number) { pushStackElement(label: string, selectionState: ISelectionState | undefined, undoRedoGroup: UndoRedoGroup | undefined, alternativeVersionId: string) {
if (this._pendingStackOperation) { if (this._pendingStackOperation) {
this._pendingStackOperation.pushEndState(alternativeVersionId, selectionState); this._pendingStackOperation.pushEndState(alternativeVersionId, selectionState);
if (!this._pendingStackOperation.isEmpty) { if (!this._pendingStackOperation.isEmpty) {
@@ -199,7 +200,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
/** /**
* Unlike, versionId, this can go down (via undo) or go to previous values (via redo) * Unlike, versionId, this can go down (via undo) or go to previous values (via redo)
*/ */
private _alternativeVersionId: number = 0; private _alternativeVersionId: string = '1';
private _operationManager: NotebookOperationManager; private _operationManager: NotebookOperationManager;
private _eventEmitter: DelayedEmitter; private _eventEmitter: DelayedEmitter;
@@ -215,7 +216,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
return this._versionId; return this._versionId;
} }
get alternativeVersionId(): number { get alternativeVersionId(): string {
return this._alternativeVersionId; return this._alternativeVersionId;
} }
@@ -235,7 +236,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._initialize(cells); this._initialize(cells);
const maybeUpdateCellTextModel = (textModel: ITextModel) => { const maybeUpdateCellTextModel = (textModel: ITextModel) => {
if (textModel.uri.scheme === Schemas.vscodeNotebookCell) { if (textModel.uri.scheme === Schemas.vscodeNotebookCell && textModel instanceof TextModel) {
const cellUri = CellUri.parse(textModel.uri); const cellUri = CellUri.parse(textModel.uri);
if (cellUri && isEqual(cellUri.notebook, this.uri)) { if (cellUri && isEqual(cellUri.notebook, this.uri)) {
const cellIdx = this._getCellIndexByHandle(cellUri.handle); const cellIdx = this._getCellIndexByHandle(cellUri.handle);
@@ -259,7 +260,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._undoService, this._undoService,
uri, uri,
this._eventEmitter, this._eventEmitter,
(alternativeVersionId: number) => { (alternativeVersionId: string) => {
this._increaseVersionId(); this._increaseVersionId();
this._overwriteAlternativeVersionId(alternativeVersionId); this._overwriteAlternativeVersionId(alternativeVersionId);
} }
@@ -287,6 +288,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
} }
this._cells.splice(0, 0, ...mainCells); this._cells.splice(0, 0, ...mainCells);
this._alternativeVersionId = this._generateAlternativeId();
}
private _generateAlternativeId() {
return this.cells.map(cell => cell.handle + ',' + cell.alternativeId).join(';');
} }
override dispose() { override dispose() {
@@ -327,6 +333,23 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
this.pushStackElement('edit', beginSelectionState, undoRedoGroup); this.pushStackElement('edit', beginSelectionState, undoRedoGroup);
try {
this._doApplyEdits(rawEdits, synchronous, computeUndoRedo);
return true;
} finally {
// Update selection and versionId after applying edits.
const endSelections = endSelectionsComputer();
this._increaseVersionId();
// Finalize undo element
this.pushStackElement('edit', endSelections, undefined);
// Broadcast changes
this._eventEmitter.endDeferredEmit(endSelections);
}
}
private _doApplyEdits(rawEdits: ICellEditOperation[], synchronous: boolean, computeUndoRedo: boolean = true): void {
const edits = rawEdits.map((edit, index) => { const edits = rawEdits.map((edit, index) => {
let cellIndex: number = -1; let cellIndex: number = -1;
if ('index' in edit) { if ('index' in edit) {
@@ -368,7 +391,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo); this._replaceCells(edit.index, edit.count, edit.cells, synchronous, computeUndoRedo);
break; break;
case CellEditType.Output: case CellEditType.Output:
//TODO@jrieken,@rebornix no event, no undo stop (?)
this._assertIndex(cellIndex); this._assertIndex(cellIndex);
const cell = this._cells[cellIndex]; const cell = this._cells[cellIndex];
if (edit.append) { if (edit.append) {
@@ -409,19 +431,6 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
break; break;
} }
} }
/**
* Update selection and versionId after applying edits.
*/
const endSelections = endSelectionsComputer();
this._increaseVersionId();
// Finalize undo element
this.pushStackElement('edit', endSelections, undefined);
// Broadcast changes
this._eventEmitter.endDeferredEmit(endSelections);
return true;
} }
private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean): void { private _replaceCells(index: number, count: number, cellDtos: ICellDto2[], synchronous: boolean, computeUndoRedo: boolean): void {
@@ -450,7 +459,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
this._modeService this._modeService
); );
const textModel = this._modelService.getModel(cellUri); const textModel = this._modelService.getModel(cellUri);
if (textModel) { if (textModel && textModel instanceof TextModel) {
cell.textModel = textModel; cell.textModel = textModel;
cell.language = cellDto.language; cell.language = cellDto.language;
} }
@@ -495,10 +504,10 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
private _increaseVersionId(): void { private _increaseVersionId(): void {
this._versionId = this._versionId + 1; this._versionId = this._versionId + 1;
this._alternativeVersionId = this.versionId; this._alternativeVersionId = this._generateAlternativeId();
} }
private _overwriteAlternativeVersionId(newAlternativeVersionId: number): void { private _overwriteAlternativeVersionId(newAlternativeVersionId: string): void {
this._alternativeVersionId = newAlternativeVersionId; this._alternativeVersionId = newAlternativeVersionId;
} }
@@ -765,37 +765,36 @@ export function notebookDocumentFilterMatch(filter: INotebookDocumentFilter, vie
return false; return false;
} }
export interface INotebookKernelChangeEvent {
label?: true;
description?: true;
detail?: true;
isPreferred?: true;
supportedLanguages?: true;
hasExecutionOrder?: true;
}
export interface INotebookKernel { export interface INotebookKernel {
/** @deprecated */ readonly id: string;
providerHandle?: number; readonly selector: NotebookSelector;
/** @deprecated */ readonly onDidChange: Event<Readonly<INotebookKernelChangeEvent>>;
resolve(uri: URI, editorId: string, token: CancellationToken): Promise<void>; readonly extension: ExtensionIdentifier;
readonly localResourceRoot: URI;
readonly preloadUris: URI[];
readonly preloadProvides: string[];
id?: string;
friendlyId: string;
label: string; label: string;
extension: ExtensionIdentifier;
localResourceRoot: URI;
description?: string; description?: string;
detail?: string; detail?: string;
isPreferred?: boolean; isPreferred?: boolean;
preloadUris: URI[]; supportedLanguages: string[]
preloadProvides: string[];
supportedLanguages?: string[]
implementsInterrupt?: boolean; implementsInterrupt?: boolean;
implementsExecutionOrder?: boolean; implementsExecutionOrder?: boolean;
executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void>; executeNotebookCellsRequest(uri: URI, cellHandles: number[]): Promise<void>;
cancelNotebookCellExecution(uri: URI, ranges: ICellRange[]): Promise<void>; cancelNotebookCellExecution(uri: URI, cellHandles: number[]): Promise<void>;
}
export interface INotebookKernelProvider {
providerExtensionId: string;
providerDescription?: string;
selector: INotebookDocumentFilter;
onDidChangeKernels: Event<URI | undefined>;
provideKernels(uri: URI, token: CancellationToken): Promise<INotebookKernel[]>;
} }
export interface INotebookCellStatusBarItemProvider { export interface INotebookCellStatusBarItemProvider {
@@ -16,12 +16,10 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService';
import { ResourceMap } from 'vs/base/common/map'; import { ResourceMap } from 'vs/base/common/map';
import { NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy';
class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> { class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IResolvedNotebookEditorModel>> {
private readonly _workingCopyManager: IFileWorkingCopyManager<NotebookFileWorkingCopyModel>; private readonly _workingCopyManagers = new Map<string, IFileWorkingCopyManager<NotebookFileWorkingCopyModel>>();
private readonly _modelListener = new Map<IResolvedNotebookEditorModel, IDisposable>(); private readonly _modelListener = new Map<IResolvedNotebookEditorModel, IDisposable>();
private readonly _onDidSaveNotebook = new Emitter<URI>(); private readonly _onDidSaveNotebook = new Emitter<URI>();
@@ -38,29 +36,13 @@ class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IReso
@ILogService private readonly _logService: ILogService, @ILogService private readonly _logService: ILogService,
) { ) {
super(); super();
this._workingCopyManager = <any>_instantiationService.createInstance(
FileWorkingCopyManager,
// TODO@jrieken TODO@rebornix consider to enable a `typeId` that is
// specific for custom editors. Using a distinct `typeId` allows the
// working copy to have any resource (including file based resources)
// even if other working copies exist with the same resource.
//
// IMPORTANT: changing the `typeId` has an impact on backups for this
// working copy. Any value that is not the empty string will be used
// as seed to the backup. Only change the `typeId` if you have implemented
// a fallback solution to resolve any existing backups that do not have
// this seed.
NO_TYPE_ID,
new NotebookFileWorkingCopyModelFactory(_notebookService)
);
} }
dispose(): void { dispose(): void {
this._onDidSaveNotebook.dispose(); this._onDidSaveNotebook.dispose();
this._onDidChangeDirty.dispose(); this._onDidChangeDirty.dispose();
dispose(this._modelListener.values()); dispose(this._modelListener.values());
this._workingCopyManager.dispose(); dispose(this._workingCopyManagers.values());
} }
isDirty(resource: URI): boolean { isDirty(resource: URI): boolean {
@@ -78,7 +60,15 @@ class NotebookModelReferenceCollection extends ReferenceCollection<Promise<IReso
result = await model.load(); result = await model.load();
} else if (info instanceof SimpleNotebookProviderInfo) { } else if (info instanceof SimpleNotebookProviderInfo) {
const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, viewType, this._workingCopyManager); let workingCopyManager = this._workingCopyManagers.get(viewType);
if (!workingCopyManager) {
workingCopyManager = <IFileWorkingCopyManager<NotebookFileWorkingCopyModel>><any>this._instantiationService.createInstance(
FileWorkingCopyManager,
viewType,
new NotebookFileWorkingCopyModelFactory(this._notebookService)
);
}
const model = this._instantiationService.createInstance(SimpleNotebookEditorModel, uri, viewType, workingCopyManager);
result = await model.load(); result = await model.load();
} else { } else {
@@ -6,52 +6,34 @@
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookKernel, INotebookTextModel } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookSelector';
export interface INotebookKernel2ChangeEvent {
label?: true;
description?: true;
detail?: true;
isPreferred?: true;
supportedLanguages?: true;
hasExecutionOrder?: true;
}
export interface INotebookKernel2 extends INotebookKernel {
readonly id: string;
readonly selector: NotebookSelector
readonly extension: ExtensionIdentifier;
readonly onDidChange: Event<INotebookKernel2ChangeEvent>;
}
export interface INotebookKernelBindEvent { export interface INotebookKernelBindEvent {
notebook: URI; notebook: URI;
oldKernel: INotebookKernel2 | undefined; oldKernel: string | undefined;
newKernel: INotebookKernel2 | undefined; newKernel: string | undefined;
} }
export interface INotebookTextModelLike { uri: URI; viewType: string; }
export const INotebookKernelService = createDecorator<INotebookKernelService>('INotebookKernelService'); export const INotebookKernelService = createDecorator<INotebookKernelService>('INotebookKernelService');
export interface INotebookKernelService { export interface INotebookKernelService {
_serviceBrand: undefined; _serviceBrand: undefined;
readonly onDidAddKernel: Event<INotebookKernel2>; readonly onDidAddKernel: Event<INotebookKernel>;
readonly onDidRemoveKernel: Event<INotebookKernel2>; readonly onDidRemoveKernel: Event<INotebookKernel>;
readonly onDidChangeNotebookKernelBinding: Event<INotebookKernelBindEvent>; readonly onDidChangeNotebookKernelBinding: Event<INotebookKernelBindEvent>;
registerKernel(kernel: INotebookKernel2): IDisposable; registerKernel(kernel: INotebookKernel): IDisposable;
getMatchingKernels(notebook: INotebookTextModel): INotebookKernel2[];
getNotebookKernels(notebook: INotebookTextModelLike): { bound: INotebookKernel | undefined, all: INotebookKernel[] }
/** /**
* Bind a notebook document to a kernel. A notebook is only bound to one kernel * Bind a notebook document to a kernel. A notebook is only bound to one kernel
* but a kernel can be bound to many notebooks (depending on its configuration) * but a kernel can be bound to many notebooks (depending on its configuration)
*/ */
updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel2 | undefined): void; updateNotebookKernelBinding(notebook: INotebookTextModel, kernel: INotebookKernel | undefined): void;
getBoundKernel(notebook: INotebookTextModel): INotebookKernel2 | undefined
} }
@@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol'; import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Event } from 'vs/base/common/event'; import { Event } from 'vs/base/common/event';
import { INotebookRendererInfo, INotebookKernelProvider, INotebookKernel, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookRendererInfo, NotebookDataDto, TransientOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel';
@@ -22,8 +22,6 @@ export const INotebookService = createDecorator<INotebookService>('notebookServi
export interface IMainNotebookController { export interface IMainNotebookController {
viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; }; viewOptions?: { displayName: string; filenamePattern: (string | IRelativePattern | INotebookExclusiveDocumentFilter)[]; exclusive: boolean; };
options: TransientOptions; options: TransientOptions;
resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void>;
onDidReceiveMessage(editorId: string, rendererType: string | undefined, message: any): void;
open(uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>; open(uri: URI, backupId: string | undefined, untitledDocumentData: VSBuffer | undefined, token: CancellationToken): Promise<{ data: NotebookDataDto, transientOptions: TransientOptions; }>;
save(uri: URI, token: CancellationToken): Promise<boolean>; save(uri: URI, token: CancellationToken): Promise<boolean>;
@@ -74,9 +72,6 @@ export interface INotebookService {
getMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[]; getMimeTypeInfo(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined, output: IOutputDto): readonly IOrderedMimeType[];
registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable;
getNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise<INotebookKernel[]>;
getContributedNotebookKernelProviders(): Promise<INotebookKernelProvider[]>;
getRendererInfo(id: string): INotebookRendererInfo | undefined; getRendererInfo(id: string): INotebookRendererInfo | undefined;
getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[]; getMarkdownRendererInfo(): INotebookMarkdownRendererInfo[];
@@ -92,11 +87,6 @@ export interface INotebookService {
getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined; getContributedNotebookProvider(viewType: string): NotebookProviderInfo | undefined;
getNotebookProviderResourceRoots(): URI[]; getNotebookProviderResourceRoots(): URI[];
onDidReceiveMessage(viewType: string, editorId: string, rendererType: string | undefined, message: unknown): void;
setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void; setToCopy(items: NotebookCellTextModel[], isCopy: boolean): void;
getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined; getToCopy(): { items: NotebookCellTextModel[], isCopy: boolean; } | undefined;
// editor events
resolveNotebookEditor(viewType: string, uri: URI, editorId: string): Promise<void>;
} }
@@ -5,49 +5,41 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as sinon from 'sinon'; import * as sinon from 'sinon';
import { CancellationToken } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { assertThrowsAsync } from 'vs/base/test/common/utils'; import { assertThrowsAsync } from 'vs/base/test/common/utils';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { NOTEBOOK_KERNEL_COUNT } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager'; import { NotebookEditorKernelManager } from 'vs/workbench/contrib/notebook/browser/notebookEditorKernelManager';
import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, ICellRange, INotebookKernel, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CellKind, INotebookKernel, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { setupInstantiationService, withTestNotebook as _withTestNotebook } from 'vs/workbench/contrib/notebook/test/testNotebookEditor';
import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
import { TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event';
import { INotebookKernelService } from 'vs/workbench/contrib/notebook/common/notebookKernelService';
import { NotebookKernelService } from 'vs/workbench/contrib/notebook/browser/notebookKernelServiceImpl';
import { NullLogService } from 'vs/platform/log/common/log';
suite('NotebookEditorKernelManager', () => { suite('NotebookEditorKernelManager', () => {
const instantiationService = setupInstantiationService(); const instantiationService = setupInstantiationService();
instantiationService.stub(IStorageService, new TestStorageService()); const kernelService = new NotebookKernelService(new TestStorageService(), new NullLogService());
instantiationService.stub(IContextKeyService, new MockContextKeyService()); instantiationService.set(INotebookKernelService, kernelService);
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>) { 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)); return _withTestNotebook(cells, (editor) => callback(editor.viewModel, editor.viewModel.notebookDocument));
} }
test('ctor', () => { // test('ctor', () => {
instantiationService.createInstance(NotebookEditorKernelManager, testDelegate); // instantiationService.createInstance(NotebookEditorKernelManager, { activeKernel: undefined, viewModel: undefined });
const contextKeyService = instantiationService.get(IContextKeyService); // const contextKeyService = instantiationService.get(IContextKeyService);
assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0); // assert.strictEqual(contextKeyService.getContextKeyValue(NOTEBOOK_KERNEL_COUNT.key), 0);
}); // });
test('cell is not runnable when no kernel is selected', async () => { test('cell is not runnable when no kernel is selected', async () => {
await withTestNotebook( await withTestNotebook(
[], [],
async (viewModel) => { async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } }); const kernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { activeKernel: undefined, viewModel });
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell)); await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell));
@@ -58,11 +50,12 @@ suite('NotebookEditorKernelManager', () => {
await withTestNotebook( await withTestNotebook(
[], [],
async (viewModel) => { async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } });
kernelManager.activeKernel = new TestNotebookKernel({ languages: ['testlang'] });
const d = kernelService.registerKernel(new TestNotebookKernel({ languages: ['testlang'] }));
const kernelManager = instantiationService.createInstance(NotebookEditorKernelManager);
const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); const cell = viewModel.createCell(1, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell)); await assertThrowsAsync(async () => await kernelManager.executeNotebookCell(cell));
d.dispose();
}); });
}); });
@@ -70,41 +63,41 @@ suite('NotebookEditorKernelManager', () => {
await withTestNotebook( await withTestNotebook(
[], [],
async (viewModel) => { async (viewModel) => {
const kernelManager: NotebookEditorKernelManager = instantiationService.createInstance(NotebookEditorKernelManager, { ...testDelegate, ...{ viewModel } });
const kernel = new TestNotebookKernel({ languages: ['javascript'] }); const kernel = new TestNotebookKernel({ languages: ['javascript'] });
const d = kernelService.registerKernel(kernel);
const kernelManager = instantiationService.createInstance(NotebookEditorKernelManager);
const executeSpy = sinon.spy(); const executeSpy = sinon.spy();
kernel.executeNotebookCellsRequest = executeSpy; kernel.executeNotebookCellsRequest = executeSpy;
kernelManager.activeKernel = kernel;
const cell = viewModel.createCell(0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true); const cell = viewModel.createCell(0, 'var c = 3', 'javascript', CellKind.Code, {}, [], true);
await kernelManager.executeNotebookCell(cell); await kernelManager.executeNotebookCells(viewModel.notebookDocument, [cell]);
assert.strictEqual(executeSpy.calledOnce, true); assert.strictEqual(executeSpy.calledOnce, true);
d.dispose();
}); });
}); });
}); });
class TestNotebookKernel implements INotebookKernel { class TestNotebookKernel implements INotebookKernel {
id?: string | undefined; id: string = 'test';
friendlyId: string = '';
label: string = ''; label: string = '';
selector = '*';
onDidChange = Event.None;
extension: ExtensionIdentifier = new ExtensionIdentifier('test'); extension: ExtensionIdentifier = new ExtensionIdentifier('test');
localResourceRoot: URI = URI.file('/test'); localResourceRoot: URI = URI.file('/test');
providerHandle?: number | undefined;
description?: string | undefined; description?: string | undefined;
detail?: string | undefined; detail?: string | undefined;
isPreferred?: boolean | undefined; isPreferred?: boolean | undefined;
preloadUris: URI[] = []; preloadUris: URI[] = [];
preloadProvides: string[] = []; preloadProvides: string[] = [];
supportedLanguages?: string[] | undefined; supportedLanguages: string[] = [];
async resolve(uri: URI, editorId: string, token: CancellationToken): Promise<void> { } executeNotebookCellsRequest(): Promise<void> {
executeNotebookCellsRequest(uri: URI, ranges: ICellRange[]): Promise<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
cancelNotebookCellExecution(): Promise<void> { cancelNotebookCellExecution(): Promise<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
constructor(opts?: { languages?: string[] }) { constructor(opts?: { languages: string[] }) {
this.supportedLanguages = opts?.languages; this.supportedLanguages = opts?.languages ?? ['text/plain'];
} }
} }
@@ -54,7 +54,7 @@ suite('NotebookCell#Document', function () {
return URI.from({ scheme: 'test', path: generateUuid() }); return URI.from({ scheme: 'test', path: generateUuid() });
} }
}; };
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, new NullLogService(), extHostStoragePaths);
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() { let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { } // async openNotebook() { }
}); });
@@ -51,7 +51,7 @@ suite('NotebookConcatDocument', function () {
return URI.from({ scheme: 'test', path: generateUuid() }); return URI.from({ scheme: 'test', path: generateUuid() });
} }
}; };
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths); extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, new NullLogService(), extHostStoragePaths);
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() { let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { } // async openNotebook() { }
}); });
@@ -70,14 +70,12 @@ suite('NotebookConcatDocument', function () {
}], }],
versionId: 0 versionId: 0
}], }],
addedEditors: [ addedEditors: [{
{ documentUri: notebookUri,
documentUri: notebookUri, id: '_notebook_editor_0',
id: '_notebook_editor_0', selections: [{ start: 0, end: 1 }],
selections: [{ start: 0, end: 1 }], visibleRanges: []
visibleRanges: [] }]
}
]
}); });
extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' }); extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
@@ -1,109 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { TestRPCProtocol } from 'vs/workbench/test/browser/api/testRPCProtocol';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { NullLogService } from 'vs/platform/log/common/log';
import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook';
import { ExtHostNotebookDocument } from 'vs/workbench/api/common/extHostNotebookDocument';
import { URI } from 'vs/base/common/uri';
import { CellKind, CellUri } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import * as vscode from 'vscode';
import { mock } from 'vs/workbench/test/common/workbenchTestServices';
import { MainContext, MainThreadCommandsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { generateUuid } from 'vs/base/common/uuid';
import { CancellationToken } from 'vs/base/common/cancellation';
suite('NotebookKernel', function () {
let rpcProtocol: TestRPCProtocol;
let notebook: ExtHostNotebookDocument;
let extHostDocumentsAndEditors: ExtHostDocumentsAndEditors;
let extHostDocuments: ExtHostDocuments;
let extHostNotebooks: ExtHostNotebookController;
const notebookUri = URI.parse('test:///notebook.file');
const disposables = new DisposableStore();
setup(async function () {
disposables.clear();
rpcProtocol = new TestRPCProtocol();
rpcProtocol.set(MainContext.MainThreadCommands, new class extends mock<MainThreadCommandsShape>() {
override $registerCommand() { }
});
rpcProtocol.set(MainContext.MainThreadNotebook, new class extends mock<MainThreadNotebookShape>() {
async override $registerNotebookProvider() { }
async override $unregisterNotebookProvider() { }
async override $registerNotebookKernelProvider() { }
async override $unregisterNotebookKernelProvider() { }
});
extHostDocumentsAndEditors = new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService());
extHostDocuments = new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors);
const extHostStoragePaths = new class extends mock<IExtensionStoragePaths>() {
override workspaceValue() {
return URI.from({ scheme: 'test', path: generateUuid() });
}
};
extHostNotebooks = new ExtHostNotebookController(rpcProtocol, new ExtHostCommands(rpcProtocol, new NullLogService()), extHostDocumentsAndEditors, extHostDocuments, { isExtensionDevelopmentDebug: false, webviewCspSource: '', webviewResourceRoot: '' }, new NullLogService(), extHostStoragePaths);
let reg = extHostNotebooks.registerNotebookContentProvider(nullExtensionDescription, 'test', new class extends mock<vscode.NotebookContentProvider>() {
// async openNotebook() { }
});
const kernels = [new class extends mock<vscode.NotebookKernel>() {
override id = 'first';
}, new class extends mock<vscode.NotebookKernel>() {
override id = 'second';
}];
let kernelReg = extHostNotebooks.registerNotebookKernelProvider(nullExtensionDescription, { viewType: 'test' }, new class extends mock<vscode.NotebookKernelProvider>() {
async override provideKernels() { return kernels; }
});
// init
extHostNotebooks.$acceptDocumentAndEditorsDelta({
addedDocuments: [{
uri: notebookUri,
viewType: 'test',
cells: [{
handle: 0,
uri: CellUri.generate(notebookUri, 0),
source: ['console.log'],
eol: '\n',
language: 'javascript',
cellKind: CellKind.Code,
outputs: [],
}],
versionId: 0
}],
addedEditors: [
{
documentUri: notebookUri,
id: '_notebook_editor_0',
selections: [{ start: 0, end: 1 }],
visibleRanges: []
}
]
});
extHostNotebooks.$acceptDocumentAndEditorsDelta({ newActiveEditor: '_notebook_editor_0' });
notebook = extHostNotebooks.notebookDocuments[0]!;
disposables.add(reg);
disposables.add(kernelReg);
disposables.add(notebook);
disposables.add(extHostDocuments);
});
test('provide kernels', async function () {
const dto = await extHostNotebooks.$provideNotebookKernels(0, notebook.uri, CancellationToken.None);
assert.deepStrictEqual(dto.map(kernel => kernel.id), ['first', 'second']);
});
});