diff --git a/extensions/vscode-notebook-tests/src/notebook.test.ts b/extensions/vscode-notebook-tests/src/notebook.test.ts index cb252a20b71..ca66acd63b6 100644 --- a/extensions/vscode-notebook-tests/src/notebook.test.ts +++ b/extensions/vscode-notebook-tests/src/notebook.test.ts @@ -87,12 +87,29 @@ async function saveAllFilesAndCloseAll(resource: vscode.Uri | undefined) { await documentClosed; } +async function updateCellMetadata(uri: vscode.Uri, cell: vscode.NotebookCell, newMetadata: vscode.NotebookCellMetadata) { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellMetadata(uri, cell.index, newMetadata); + await vscode.workspace.applyEdit(edit); +} + +async function updateNotebookMetadata(uri: vscode.Uri, newMetadata: vscode.NotebookDocumentMetadata) { + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookMetadata(uri, newMetadata); + await vscode.workspace.applyEdit(edit); +} + +async function withEvent(event: vscode.Event, callback: (e: Promise) => Promise) { + const e = getEventOncePromise(event); + await callback(e); +} + function assertInitalState() { // no-op unless we figure out why some documents are opened after the editor is closed - // assert.equal(vscode.window.activeNotebookEditor, undefined); - // assert.equal(vscode.notebook.notebookDocuments.length, 0); - // assert.equal(vscode.notebook.visibleNotebookEditors.length, 0); + // assert.strictEqual(vscode.window.activeNotebookEditor, undefined); + // assert.strictEqual(vscode.notebook.notebookDocuments.length, 0); + // assert.strictEqual(vscode.notebook.visibleNotebookEditors.length, 0); } suite('Notebook API tests', () => { @@ -187,12 +204,12 @@ suite('Notebook API tests', () => { counter--; })); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(counter, 1); + assert.strictEqual(counter, 1); await splitEditor(); - assert.equal(counter, 1); + assert.strictEqual(counter, 1); await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - assert.equal(counter, 0); + assert.strictEqual(counter, 0); disposables.forEach(d => d.dispose()); }); @@ -221,13 +238,13 @@ suite('Notebook API tests', () => { })); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(count, 1); + assert.strictEqual(count, 1); await splitEditor(); - assert.equal(count, 2); + assert.strictEqual(count, 2); await vscode.commands.executeCommand('workbench.action.closeAllEditors'); - assert.equal(count, 0); + assert.strictEqual(count, 0); }); test('editor editing event 2', async function () { @@ -239,9 +256,9 @@ suite('Notebook API tests', () => { const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); const cellChangeEventRet = await cellsChangeEvent; - assert.equal(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); - assert.equal(cellChangeEventRet.changes.length, 1); - assert.deepEqual(cellChangeEventRet.changes[0], { + assert.strictEqual(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); + assert.strictEqual(cellChangeEventRet.changes.length, 1); + assert.deepStrictEqual(cellChangeEventRet.changes[0], { start: 1, deletedCount: 0, deletedItems: [], @@ -255,7 +272,7 @@ suite('Notebook API tests', () => { const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.moveUp'); const moveCellEventRet = await moveCellEvent; - assert.deepEqual(moveCellEventRet, { + assert.deepStrictEqual(moveCellEventRet, { document: vscode.window.activeNotebookEditor!.document, changes: [ { @@ -276,25 +293,25 @@ suite('Notebook API tests', () => { const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); await vscode.commands.executeCommand('notebook.cell.execute'); const cellOutputsAddedRet = await cellOutputChange; - assert.deepEqual(cellOutputsAddedRet, { + assert.deepStrictEqual(cellOutputsAddedRet, { document: vscode.window.activeNotebookEditor!.document, cells: [vscode.window.activeNotebookEditor!.document.cells[0]] }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); + assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); await vscode.commands.executeCommand('notebook.cell.clearOutputs'); const cellOutputsCleardRet = await cellOutputClear; - assert.deepEqual(cellOutputsCleardRet, { + assert.deepStrictEqual(cellOutputsCleardRet, { document: vscode.window.activeNotebookEditor!.document, cells: [vscode.window.activeNotebookEditor!.document.cells[0]] }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); + assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); // const cellChangeLanguage = getEventOncePromise(vscode.notebook.onDidChangeCellLanguage); // await vscode.commands.executeCommand('notebook.cell.changeToMarkdown'); // const cellChangeLanguageRet = await cellChangeLanguage; - // assert.deepEqual(cellChangeLanguageRet, { + // assert.deepStrictEqual(cellChangeLanguageRet, { // document: vscode.window.activeNotebookEditor!.document, // cells: vscode.window.activeNotebookEditor!.document.cells[0], // language: 'markdown' @@ -313,11 +330,11 @@ suite('Notebook API tests', () => { await vscode.commands.executeCommand('notebook.focusTop'); const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); const moveChange = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); await vscode.commands.executeCommand('notebook.cell.moveDown'); const ret = await moveChange; - assert.deepEqual(ret, { + assert.deepStrictEqual(ret, { document: vscode.window.activeNotebookEditor?.document, changes: [ { @@ -340,7 +357,7 @@ suite('Notebook API tests', () => { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstEditor = vscode.window.activeNotebookEditor; - assert.equal(firstEditor?.document.cells.length, 1); + assert.strictEqual(firstEditor?.document.cells.length, 1); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeAllEditors'); @@ -358,7 +375,7 @@ suite('Notebook API tests', () => { assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); assert.notStrictEqual(firstEditor, secondEditor); assert.strictEqual(firstEditor && vscode.window.visibleNotebookEditors.indexOf(firstEditor) >= 0, true); - assert.equal(vscode.window.visibleNotebookEditors.length, 2); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); const untitledEditorChange = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); await vscode.commands.executeCommand('workbench.action.files.newUntitledFile'); @@ -367,13 +384,13 @@ suite('Notebook API tests', () => { assert.notStrictEqual(firstEditor, vscode.window.activeNotebookEditor); assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) < 0, true); assert.notStrictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.equal(vscode.window.visibleNotebookEditors.length, 1); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 1); const activeEditorClose = getEventOncePromise(vscode.window.onDidChangeActiveNotebookEditor); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); await activeEditorClose; assert.strictEqual(secondEditor, vscode.window.activeNotebookEditor); - assert.equal(vscode.window.visibleNotebookEditors.length, 2); + assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); assert.strictEqual(secondEditor && vscode.window.visibleNotebookEditors.indexOf(secondEditor) >= 0, true); await vscode.commands.executeCommand('workbench.action.files.save'); @@ -435,10 +452,11 @@ suite('Notebook API tests', () => { // consuming is OLD api (for now) const [output] = document.cells[0].outputs; - assert.strictEqual(output.outputKind, vscode.CellOutputKind.Rich); - assert.strictEqual((output).data['application/foo'], 'bar'); - assert.deepStrictEqual((output).data['application/json'], { data: true }); - assert.deepStrictEqual((output).metadata, { custom: { 'application/json': { metadata: true } } }); + assert.strictEqual(output.outputs.length, 2); + assert.strictEqual(output.outputs[0].mime, 'application/foo'); + assert.strictEqual(output.outputs[0].value, 'bar'); + assert.strictEqual(output.outputs[1].mime, 'application/json'); + assert.deepStrictEqual(output.outputs[1].value, { data: true }); await saveAllFilesAndCloseAll(undefined); }); @@ -449,14 +467,18 @@ suite('Notebook API tests', () => { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('foo', 'bar') + ])]); }); const document = vscode.window.activeNotebookEditor?.document!; assert.strictEqual(document.isDirty, true); assert.strictEqual(document.cells.length, 1); assert.strictEqual(document.cells[0].outputs.length, 1); - assert.strictEqual(document.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + assert.strictEqual(document.cells[0].outputs[0].outputs.length, 1); + assert.strictEqual(document.cells[0].outputs[0].outputs[0].mime, 'foo'); + assert.strictEqual(document.cells[0].outputs[0].outputs[0].value, 'bar'); await saveAllFilesAndCloseAll(undefined); }); @@ -468,15 +490,20 @@ suite('Notebook API tests', () => { const outputChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); await vscode.window.activeNotebookEditor!.edit(editBuilder => { - editBuilder.replaceCellOutput(0, [{ outputKind: vscode.CellOutputKind.Rich, data: { foo: 'bar' } }]); + editBuilder.replaceCellOutput(0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('foo', 'bar') + ])]); }); const value = await outputChangeEvent; assert.strictEqual(value.document === vscode.window.activeNotebookEditor?.document, true); assert.strictEqual(value.document.isDirty, true); assert.strictEqual(value.cells.length, 1); - assert.strictEqual(value.cells[0].outputs.length, 1); - assert.strictEqual(value.cells[0].outputs[0].outputKind, vscode.CellOutputKind.Rich); + assert.strictEqual(value.document.cells.length, 1); + assert.strictEqual(value.document.cells[0].outputs.length, 1); + assert.strictEqual(value.document.cells[0].outputs[0].outputs.length, 1); + assert.strictEqual(value.document.cells[0].outputs[0].outputs[0].mime, 'foo'); + assert.strictEqual(value.document.cells[0].outputs[0].outputs[0].value, 'bar'); await saveAllFilesAndCloseAll(undefined); }); @@ -706,7 +733,7 @@ suite('Notebook API tests', () => { })); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(count, 0); + assert.strictEqual(count, 0); disposables.forEach(d => d.dispose()); @@ -719,19 +746,19 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const activeCell = vscode.window.activeNotebookEditor!.selection; assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -741,69 +768,69 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); // ---- insert cell below and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); // ---- insert cell above and focus ---- // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); let activeCell = vscode.window.activeNotebookEditor!.selection; assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); // ---- focus bottom ---- // await vscode.commands.executeCommand('notebook.focusBottom'); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2); // ---- focus top and then copy down ---- // await vscode.commands.executeCommand('notebook.focusTop'); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.copyDown'); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), 'test'); await vscode.commands.executeCommand('notebook.cell.delete'); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), ''); // ---- focus top and then copy up ---- // await vscode.commands.executeCommand('notebook.focusTop'); await vscode.commands.executeCommand('notebook.cell.copyUp'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 4); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 4); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); // ---- move up and down ---- // await vscode.commands.executeCommand('notebook.cell.moveDown'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1, + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1, `first move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); // await vscode.commands.executeCommand('notebook.cell.moveDown'); // activeCell = vscode.window.activeNotebookEditor!.selection; - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 2, // `second move down, active cell ${vscode.window.activeNotebookEditor!.selection!.uri.toString()}, ${vscode.window.activeNotebookEditor!.selection!.document.getText()}`); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), ''); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[1].document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[2].document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells[3].document.getText(), ''); // ---- ---- // @@ -815,12 +842,12 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); const edit = new vscode.WorkspaceEdit(); edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); @@ -829,7 +856,7 @@ suite('notebook workflow', () => { await vscode.commands.executeCommand('notebook.cell.joinAbove'); await cellsChangeEvent; - assert.deepEqual(vscode.window.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); + assert.deepStrictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText().split(/\r\n|\r|\n/), ['test', 'var abc = 0;']); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -844,37 +871,37 @@ suite('notebook workflow', () => { await vscode.commands.executeCommand('notebook.focusTop'); const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 0); await vscode.commands.executeCommand('notebook.cell.moveDown'); await vscode.commands.executeCommand('notebook.cell.moveDown'); const newActiveCell = vscode.window.activeNotebookEditor!.selection; - assert.deepEqual(activeCell, newActiveCell); + assert.deepStrictEqual(activeCell, newActiveCell); await saveFileAndCloseAll(resource); // TODO@rebornix, there are still some events order issue. - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(newActiveCell!), 2); }); // test.only('document metadata is respected', async function () { // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); // const editor = vscode.window.activeNotebookEditor!; - // assert.equal(editor.document.cells.length, 1); + // assert.strictEqual(editor.document.cells.length, 1); // editor.document.metadata.editable = false; // await editor.edit(builder => builder.delete(0)); - // assert.equal(editor.document.cells.length, 1, 'should not delete cell'); // Not editable, no effect + // assert.strictEqual(editor.document.cells.length, 1, 'should not delete cell'); // Not editable, no effect // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.equal(editor.document.cells.length, 1, 'should not insert cell'); // Not editable, no effect + // assert.strictEqual(editor.document.cells.length, 1, 'should not insert cell'); // Not editable, no effect // editor.document.metadata.editable = true; // await editor.edit(builder => builder.delete(0)); - // assert.equal(editor.document.cells.length, 0, 'should delete cell'); // Editable, it worked + // assert.strictEqual(editor.document.cells.length, 0, 'should delete cell'); // Editable, it worked // await editor.edit(builder => builder.insert(0, 'test', 'python', vscode.CellKind.Code, [], undefined)); - // assert.equal(editor.document.cells.length, 1, 'should insert cell'); // Editable, it worked + // assert.strictEqual(editor.document.cells.length, 1, 'should insert cell'); // Editable, it worked // // await vscode.commands.executeCommand('workbench.action.files.save'); // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -884,26 +911,26 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; await vscode.commands.executeCommand('notebook.focusTop'); const cell = editor.document.cells[0]; - assert.equal(cell.outputs.length, 0); + assert.strictEqual(cell.outputs.length, 0); let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - cell.metadata.runnable = false; + await updateCellMetadata(resource, cell, { ...cell.metadata, runnable: false }); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(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 metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellMetadata); - cell.metadata.runnable = true; + await updateCellMetadata(resource, cell, { ...cell.metadata, runnable: true }); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -913,57 +940,86 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; const cell = editor.document.cells[0]; - assert.equal(cell.outputs.length, 0); - let metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = false; - await metadataChangeEvent; + assert.strictEqual(cell.outputs.length, 0); + + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: false }); + await event; + }); await vscode.commands.executeCommand('notebook.execute'); - assert.equal(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 - metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + await event; + }); - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); - test('cell execute command takes arguments', async () => { + + // 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 () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; const cell = editor.document.cells[0]; await vscode.commands.executeCommand('notebook.execute'); - assert.equal(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 - const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; - await metadataChangeEvent; + await vscode.commands.executeCommand('workbench.action.files.save'); + await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); + }); - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + test('cell execute command takes arguments 2', async () => { + assertInitalState(); + const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + const editor = vscode.window.activeNotebookEditor!; + const cell = editor.document.cells[0]; - const clearChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.clearOutputs'); - await clearChangeEvent; - assert.equal(cell.outputs.length, 0, 'should clear'); + await withEvent(vscode.notebook.onDidChangeNotebookDocumentMetadata, async event => { + updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); + await event; + }); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async event => { + await vscode.commands.executeCommand('notebook.cell.clearOutputs'); + await event; + assert.strictEqual(cell.outputs.length, 0, 'should clear'); + }); const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.equal(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.cell.execute', { start: 0, end: 1 }, resource); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + }); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -975,30 +1031,35 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; const cell = editor.document.cells[0]; - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 0, 'should not execute'); // not runnable, didn't work - const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; + updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); await metadataChangeEvent; + assert.strictEqual(editor.document.metadata.runnable, true); - await vscode.commands.executeCommand('notebook.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute'); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + }); - const clearChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + const clearChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); await vscode.commands.executeCommand('notebook.cell.clearOutputs'); await clearChangeEvent; - assert.equal(cell.outputs.length, 0, 'should clear'); + assert.strictEqual(cell.outputs.length, 0, 'should clear'); const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - await vscode.commands.executeCommand('notebook.execute', resource); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.equal(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + + await withEvent(vscode.notebook.onDidChangeCellOutputs, async (event) => { + await vscode.commands.executeCommand('notebook.execute', resource); + await event; + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(vscode.window.activeNotebookEditor?.document.uri.fsPath, secondResource.fsPath); + }); await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -1010,30 +1071,31 @@ suite('notebook workflow', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); const editor = vscode.window.activeNotebookEditor!; const cell = editor.document.cells[0]; const metadataChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookDocumentMetadata); - editor.document.metadata.runnable = true; + updateNotebookMetadata(editor.document.uri, { ...editor.document.metadata, runnable: true }); await metadataChangeEvent; await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.deepEqual((cell.outputs[0] as vscode.CellDisplayOutput).data, { - 'text/plain': [ - 'my output' - ] - }); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].outputs.length, 1); + assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); + assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ + 'my output' + ]); - await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-notebook-tests', id: 'secondaryKernel' }) + await vscode.commands.executeCommand('notebook.selectKernel', { extension: 'vscode.vscode-notebook-tests', id: 'secondaryKernel' }); await vscode.commands.executeCommand('notebook.cell.execute'); - assert.equal(cell.outputs.length, 1, 'should execute'); // runnable, it worked - assert.deepEqual((cell.outputs[0] as vscode.CellDisplayOutput).data, { - 'text/plain': [ - 'my second output' - ] - }); + assert.strictEqual(cell.outputs.length, 1, 'should execute'); // runnable, it worked + assert.strictEqual(cell.outputs[0].outputs.length, 1); + assert.strictEqual(cell.outputs[0].outputs[0].mime, 'text/plain'); + assert.deepStrictEqual(cell.outputs[0].outputs[0].value, [ + 'my second output' + ]); + await vscode.commands.executeCommand('workbench.action.files.save'); await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); }); @@ -1044,27 +1106,30 @@ suite('notebook dirty state', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - const edit = new vscode.WorkspaceEdit(); - edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;'); - await vscode.workspace.applyEdit(edit); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + await withEvent(vscode.workspace.onDidChangeTextDocument, async event => { + const edit = new vscode.WorkspaceEdit(); + edit.insert(activeCell!.uri, new vscode.Position(0, 0), 'var abc = 0;'); + await vscode.workspace.applyEdit(edit); + await event; + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + }); await saveFileAndCloseAll(resource); }); @@ -1075,19 +1140,19 @@ suite('notebook undo redo', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const activeCell = vscode.window.activeNotebookEditor!.selection; - assert.notEqual(vscode.window.activeNotebookEditor!.selection, undefined); - assert.equal(activeCell!.document.getText(), ''); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.notStrictEqual(vscode.window.activeNotebookEditor!.selection, undefined); + assert.strictEqual(activeCell!.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); // modify the second cell, delete it @@ -1095,87 +1160,87 @@ suite('notebook undo redo', () => { edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); await vscode.commands.executeCommand('notebook.cell.delete'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); // undo should bring back the deleted cell, and revert to previous content and selection await vscode.commands.executeCommand('undo'); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); // redo // await vscode.commands.executeCommand('notebook.redo'); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(vscode.window.activeNotebookEditor!.selection!), 1); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); await saveFileAndCloseAll(resource); }); - test.skip('execute and then undo redo', async function () { - assertInitalState(); - const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); - await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); + // test.skip('execute and then undo redo', async function () { + // assertInitalState(); + // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); + // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - const cellChangeEventRet = await cellsChangeEvent; - assert.equal(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); - assert.equal(cellChangeEventRet.changes.length, 1); - assert.deepEqual(cellChangeEventRet.changes[0], { - start: 1, - deletedCount: 0, - deletedItems: [], - items: [ - vscode.window.activeNotebookEditor!.document.cells[1] - ] - }); + // const cellsChangeEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); + // const cellChangeEventRet = await cellsChangeEvent; + // assert.strictEqual(cellChangeEventRet.document, vscode.window.activeNotebookEditor?.document); + // assert.strictEqual(cellChangeEventRet.changes.length, 1); + // assert.deepStrictEqual(cellChangeEventRet.changes[0], { + // start: 1, + // deletedCount: 0, + // deletedItems: [], + // items: [ + // vscode.window.activeNotebookEditor!.document.cells[1] + // ] + // }); - const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; + // const secondCell = vscode.window.activeNotebookEditor!.document.cells[1]; - const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); - await vscode.commands.executeCommand('notebook.cell.moveUp'); - const moveCellEventRet = await moveCellEvent; - assert.deepEqual(moveCellEventRet, { - document: vscode.window.activeNotebookEditor!.document, - changes: [ - { - start: 1, - deletedCount: 1, - deletedItems: [secondCell], - items: [] - }, - { - start: 0, - deletedCount: 0, - deletedItems: [], - items: [vscode.window.activeNotebookEditor?.document.cells[0]] - } - ] - }); + // const moveCellEvent = getEventOncePromise(vscode.notebook.onDidChangeNotebookCells); + // await vscode.commands.executeCommand('notebook.cell.moveUp'); + // const moveCellEventRet = await moveCellEvent; + // assert.deepStrictEqual(moveCellEventRet, { + // document: vscode.window.activeNotebookEditor!.document, + // changes: [ + // { + // start: 1, + // deletedCount: 1, + // deletedItems: [secondCell], + // items: [] + // }, + // { + // start: 0, + // deletedCount: 0, + // deletedItems: [], + // items: [vscode.window.activeNotebookEditor?.document.cells[0]] + // } + // ] + // }); - const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('notebook.cell.execute'); - const cellOutputsAddedRet = await cellOutputChange; - assert.deepEqual(cellOutputsAddedRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 1); + // const cellOutputChange = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + // await vscode.commands.executeCommand('notebook.cell.execute'); + // const cellOutputsAddedRet = await cellOutputChange; + // assert.deepStrictEqual(cellOutputsAddedRet, { + // document: vscode.window.activeNotebookEditor!.document, + // cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + // }); + // assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 1); - const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); - await vscode.commands.executeCommand('undo'); - const cellOutputsCleardRet = await cellOutputClear; - assert.deepEqual(cellOutputsCleardRet, { - document: vscode.window.activeNotebookEditor!.document, - cells: [vscode.window.activeNotebookEditor!.document.cells[0]] - }); - assert.equal(cellOutputsAddedRet.cells[0].outputs.length, 0); + // const cellOutputClear = getEventOncePromise(vscode.notebook.onDidChangeCellOutputs); + // await vscode.commands.executeCommand('undo'); + // const cellOutputsCleardRet = await cellOutputClear; + // assert.deepStrictEqual(cellOutputsCleardRet, { + // document: vscode.window.activeNotebookEditor!.document, + // cells: [vscode.window.activeNotebookEditor!.document.cells[0]] + // }); + // assert.strictEqual(cellOutputsAddedRet.cells[0].outputs.length, 0); - await saveFileAndCloseAll(resource); - }); + // await saveFileAndCloseAll(resource); + // }); }); @@ -1184,7 +1249,7 @@ suite('notebook working copy', () => { // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); @@ -1192,10 +1257,10 @@ suite('notebook working copy', () => { // // close active editor from command will revert the file // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - // assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); // await vscode.commands.executeCommand('workbench.action.files.save'); // await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); @@ -1205,17 +1270,17 @@ suite('notebook working copy', () => { // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); // await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); // await vscode.commands.executeCommand('default:type', { text: 'var abc = 0;' }); // await vscode.commands.executeCommand('workbench.action.files.revert'); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - // assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); - // assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 1); - // assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[0], vscode.window.activeNotebookEditor?.selection); + // assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 1); + // assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'test'); // await vscode.commands.executeCommand('workbench.action.files.saveAll'); // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); @@ -1226,7 +1291,7 @@ suite('notebook working copy', () => { const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const edit = new vscode.WorkspaceEdit(); @@ -1238,11 +1303,11 @@ suite('notebook working copy', () => { await vscode.commands.executeCommand('workbench.action.closeActiveEditor'); // make sure that the previous dirty editor is still restored in the extension host and no data loss - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); await saveFileAndCloseAll(resource); }); @@ -1252,7 +1317,7 @@ suite('notebook working copy', () => { const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); await vscode.commands.executeCommand('notebook.cell.insertCodeCellAbove'); const edit = new vscode.WorkspaceEdit(); @@ -1262,23 +1327,23 @@ suite('notebook working copy', () => { const secondResource = await createRandomFile('', undefined, 'second', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); await vscode.commands.executeCommand('notebook.cell.insertCodeCellBelow'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); // switch to the first editor await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 3); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), 'var abc = 0;'); // switch to the second editor await vscode.commands.executeCommand('vscode.openWith', secondResource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true); - assert.equal(vscode.window.activeNotebookEditor?.selection !== undefined, true); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); - assert.deepEqual(vscode.window.activeNotebookEditor?.document.cells.length, 2); - assert.equal(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection !== undefined, true); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells[1], vscode.window.activeNotebookEditor?.selection); + assert.deepStrictEqual(vscode.window.activeNotebookEditor?.document.cells.length, 2); + assert.strictEqual(vscode.window.activeNotebookEditor?.selection?.document.getText(), ''); await saveAllFilesAndCloseAll(secondResource); // await vscode.commands.executeCommand('workbench.action.files.saveAll'); @@ -1291,18 +1356,18 @@ suite('notebook working copy', () => { const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); const firstNotebookEditor = vscode.window.activeNotebookEditor; - assert.equal(firstNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(firstNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(firstNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(firstNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(firstNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(firstNotebookEditor!.selection?.language, 'typescript'); await splitEditor(); const secondNotebookEditor = vscode.window.activeNotebookEditor; - assert.equal(secondNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(secondNotebookEditor!.selection?.document.getText(), 'test'); - assert.equal(secondNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(secondNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(secondNotebookEditor!.selection?.document.getText(), 'test'); + assert.strictEqual(secondNotebookEditor!.selection?.language, 'typescript'); assert.notEqual(firstNotebookEditor, secondNotebookEditor); - assert.equal(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); + assert.strictEqual(firstNotebookEditor?.document, secondNotebookEditor?.document, 'split notebook editors share the same document'); // assert.notEqual(firstNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png')), secondNotebookEditor?.asWebviewUri(vscode.Uri.file('./hello.png'))); await saveAllFilesAndCloseAll(resource); @@ -1317,10 +1382,10 @@ suite('metadata', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.equal(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + 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!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await saveFileAndCloseAll(resource); }); @@ -1331,16 +1396,16 @@ suite('metadata', () => { assertInitalState(); const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.document.metadata.custom!['testMetadata'] as boolean, false); - assert.equal(vscode.window.activeNotebookEditor!.selection?.metadata.custom!['testCellMetadata'] as number, 123); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + 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!.selection?.metadata.custom!['testCellMetadata'] as number, 123); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); // TODO see #101462 // await vscode.commands.executeCommand('notebook.cell.copyDown'); // const activeCell = vscode.window.activeNotebookEditor!.selection; - // assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - // assert.equal(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); + // assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + // assert.strictEqual(activeCell?.metadata.custom!['testCellMetadata'] as number, 123); await saveFileAndCloseAll(resource); }); @@ -1350,9 +1415,9 @@ suite('regression', () => { // test('microsoft/vscode-github-issue-notebooks#26. Insert template cell in the new empty document', async function () { // assertInitalState(); // await vscode.commands.executeCommand('workbench.action.files.newUntitledFile', { "viewType": "notebookCoreTest" }); - // assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); - // assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + // assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), ''); + // assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); // }); @@ -1401,12 +1466,12 @@ suite('regression', () => { edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - assert.equal(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); - assert.equal(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); + assert.strictEqual(vscode.window.activeNotebookEditor !== undefined, true, 'notebook first'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.document.getText(), 'var abc = 0;'); + assert.strictEqual(vscode.window.activeNotebookEditor!.selection?.language, 'typescript'); await vscode.commands.executeCommand('vscode.openWith', resource, 'default'); - assert.equal(vscode.window.activeTextEditor?.document.uri.path, resource.path); + assert.strictEqual(vscode.window.activeTextEditor?.document.uri.path, resource.path); await vscode.commands.executeCommand('workbench.action.closeAllEditors'); }); @@ -1442,19 +1507,19 @@ suite('regression', () => { await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); let activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(activeCell?.document.getText(), 'test'); + assert.strictEqual(activeCell?.document.getText(), 'test'); await vscode.commands.executeCommand('notebook.cell.copyDown'); await vscode.commands.executeCommand('notebook.cell.edit'); activeCell = vscode.window.activeNotebookEditor!.selection; - assert.equal(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); - assert.equal(activeCell?.document.getText(), 'test'); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.indexOf(activeCell!), 1); + assert.strictEqual(activeCell?.document.getText(), 'test'); const edit = new vscode.WorkspaceEdit(); edit.insert(vscode.window.activeNotebookEditor!.selection!.uri, new vscode.Position(0, 0), 'var abc = 0;'); await vscode.workspace.applyEdit(edit); - assert.equal(vscode.window.activeNotebookEditor!.document.cells.length, 2); + assert.strictEqual(vscode.window.activeNotebookEditor!.document.cells.length, 2); assert.notEqual(vscode.window.activeNotebookEditor!.document.cells[0].document.getText(), vscode.window.activeNotebookEditor!.document.cells[1].document.getText()); await vscode.commands.executeCommand('workbench.action.closeAllEditors'); @@ -1470,9 +1535,9 @@ suite('webview', () => { // const resource = await createRandomFile('', undefined, 'first', '.vsctestnb'); // await vscode.commands.executeCommand('vscode.openWith', resource, 'notebookCoreTest'); - // assert.equal(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')); - // assert.equal(uri.scheme, 'vscode-webview-resource'); + // assert.strictEqual(uri.scheme, 'vscode-webview-resource'); // await vscode.commands.executeCommand('workbench.action.closeAllEditors'); // }); diff --git a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts b/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts index bf3f05e35aa..686397a0f87 100644 --- a/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookSmokeTestMain.ts @@ -70,14 +70,14 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { label: 'notebookSmokeTest', isPreferred: true, executeAllCells: async (_document: vscode.NotebookDocument) => { + const edit = new vscode.WorkspaceEdit(); for (let i = 0; i < _document.cells.length; i++) { - _document.cells[i].outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/html': ['test output'] - } - }]; + edit.replaceNotebookCellOutput(_document.uri, i, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) + ])]); } + + await vscode.workspace.applyEdit(edit); }, cancelAllCellsExecution: async () => { }, executeCell: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell | undefined) => { @@ -85,12 +85,11 @@ export function smokeTestActivate(context: vscode.ExtensionContext): any { _cell = _document.cells[0]; } - _cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/html': ['test output'] - } - }]; + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(_document.uri, _cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/html', ['test output'], undefined) + ])]); + await vscode.workspace.applyEdit(edit); return; }, cancelCellExecution: async () => { } diff --git a/extensions/vscode-notebook-tests/src/notebookTestMain.ts b/extensions/vscode-notebook-tests/src/notebookTestMain.ts index 9b2b91a012a..32d25475aa9 100644 --- a/extensions/vscode-notebook-tests/src/notebookTestMain.ts +++ b/extensions/vscode-notebook-tests/src/notebookTestMain.ts @@ -61,15 +61,12 @@ export function activate(context: vscode.ExtensionContext): any { label: 'Notebook Test Kernel', isPreferred: true, executeAllCells: async (_document: vscode.NotebookDocument) => { - const cell = _document.cells[0]; + const edit = new vscode.WorkspaceEdit(); - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my output'] - } - }]; - return; + edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) + ])]); + return vscode.workspace.applyEdit(edit); }, cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { @@ -78,26 +75,21 @@ export function activate(context: vscode.ExtensionContext): any { } if (document.uri.path.endsWith('customRenderer.vsctestnb')) { - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/custom': 'test' - } - }]; + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/custom', ['test'], undefined) + ])]); - return; + return vscode.workspace.applyEdit(edit); } + const edit = new vscode.WorkspaceEdit(); // const previousOutputs = cell.outputs; - const newOutputs: vscode.CellOutput[] = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my output'] - } - }]; + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my output'], undefined) + ])]); - cell.outputs = newOutputs; - return; + return vscode.workspace.applyEdit(edit); }, cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } }; @@ -107,15 +99,12 @@ export function activate(context: vscode.ExtensionContext): any { label: 'Notebook Secondary Test Kernel', isPreferred: false, executeAllCells: async (_document: vscode.NotebookDocument) => { - const cell = _document.cells[0]; + const edit = new vscode.WorkspaceEdit(); + edit.replaceNotebookCellOutput(_document.uri, 0, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + ])]); - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my second output'] - } - }]; - return; + return vscode.workspace.applyEdit(edit); }, cancelAllCellsExecution: async (_document: vscode.NotebookDocument) => { }, executeCell: async (document: vscode.NotebookDocument, cell: vscode.NotebookCell | undefined) => { @@ -123,26 +112,19 @@ export function activate(context: vscode.ExtensionContext): any { cell = document.cells[0]; } - if (document.uri.path.endsWith('customRenderer.vsctestnb')) { - cell.outputs = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/custom': 'test 2' - } - }]; + const edit = new vscode.WorkspaceEdit(); - return; + if (document.uri.path.endsWith('customRenderer.vsctestnb')) { + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/custom', ['test 2'], undefined) + ])]); + } else { + edit.replaceNotebookCellOutput(document.uri, cell.index, [new vscode.NotebookCellOutput([ + new vscode.NotebookCellOutputItem('text/plain', ['my second output'], undefined) + ])]); } - const newOutputs: vscode.CellOutput[] = [{ - outputKind: vscode.CellOutputKind.Rich, - data: { - 'text/plain': ['my second output'] - } - }]; - - cell.outputs = newOutputs; - return; + return vscode.workspace.applyEdit(edit); }, cancelCellExecution: async (_document: vscode.NotebookDocument, _cell: vscode.NotebookCell) => { } }; diff --git a/src/main.js b/src/main.js index 618c45bbf3c..0541fb4c4a0 100644 --- a/src/main.js +++ b/src/main.js @@ -38,70 +38,8 @@ app.setPath('userData', userDataPath); // Configure static command line arguments const argvConfig = configureCommandlineSwitchesSync(args); -// If a crash-reporter-directory is specified we store the crash reports -// in the specified directory and don't upload them to the crash server. -let crashReporterDirectory = args['crash-reporter-directory']; -let submitURL = ''; -if (crashReporterDirectory) { - crashReporterDirectory = path.normalize(crashReporterDirectory); - - if (!path.isAbsolute(crashReporterDirectory)) { - console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); - app.exit(1); - } - - if (!fs.existsSync(crashReporterDirectory)) { - try { - fs.mkdirSync(crashReporterDirectory); - } catch (error) { - console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); - app.exit(1); - } - } - - // Crashes are stored in the crashDumps directory by default, so we - // need to change that directory to the provided one - console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); - app.setPath('crashDumps', crashReporterDirectory); -} else { - const appCenter = product.appCenter; - // Disable Appcenter crash reporting if - // * --crash-reporter-directory is specified - // * enable-crash-reporter runtime argument is set to 'false' - // * --disable-crash-reporter command line parameter is set - if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { - const isWindows = (process.platform === 'win32'); - const isLinux = (process.platform === 'linux'); - const crashReporterId = argvConfig['crash-reporter-id']; - const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; - if (uuidPattern.test(crashReporterId)) { - submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin; - submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); - // Send the id for child node process that are explicitly starting crash reporter. - // For vscode this is ExtensionHost process currently. - const argv = process.argv; - const endOfArgsMarkerIndex = argv.indexOf('--'); - if (endOfArgsMarkerIndex === -1) { - argv.push('--crash-reporter-id', crashReporterId); - } else { - // if the we have an argument "--" (end of argument marker) - // we cannot add arguments at the end. rather, we add - // arguments before the "--" marker. - argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId); - } - } - } -} - -// Start crash reporter for all processes -const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; -const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; -crashReporter.start({ - companyName: companyName, - productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, - submitURL, - uploadToServer: !crashReporterDirectory -}); +// Configure crash reporter +configureCrashReporter(); // Set logs path before app 'ready' event if running portable // to ensure that no 'logs' folder is created on disk at a @@ -118,29 +56,14 @@ setCurrentWorkingDirectory(); protocol.registerSchemesAsPrivileged([ { scheme: 'vscode-webview', - privileges: { - standard: true, - secure: true, - supportFetchAPI: true, - corsEnabled: true, - } + privileges: { standard: true, secure: true, supportFetchAPI: true, corsEnabled: true } }, { scheme: 'vscode-webview-resource', - privileges: { - secure: true, - standard: true, - supportFetchAPI: true, - corsEnabled: true, - } + privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true } }, { scheme: 'vscode-file', - privileges: { - secure: true, - standard: true, - supportFetchAPI: true, - corsEnabled: true - } + privileges: { secure: true, standard: true, supportFetchAPI: true, corsEnabled: true } } ]); @@ -381,6 +304,77 @@ function getArgvConfigPath() { return path.join(os.homedir(), dataFolderName, 'argv.json'); } +function configureCrashReporter() { + + // If a crash-reporter-directory is specified we store the crash reports + // in the specified directory and don't upload them to the crash server. + let crashReporterDirectory = args['crash-reporter-directory']; + let submitURL = ''; + if (crashReporterDirectory) { + crashReporterDirectory = path.normalize(crashReporterDirectory); + + if (!path.isAbsolute(crashReporterDirectory)) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory must be absolute.`); + app.exit(1); + } + + if (!fs.existsSync(crashReporterDirectory)) { + try { + fs.mkdirSync(crashReporterDirectory); + } catch (error) { + console.error(`The path '${crashReporterDirectory}' specified for --crash-reporter-directory does not seem to exist or cannot be created.`); + app.exit(1); + } + } + + // Crashes are stored in the crashDumps directory by default, so we + // need to change that directory to the provided one + console.log(`Found --crash-reporter-directory argument. Setting crashDumps directory to be '${crashReporterDirectory}'`); + app.setPath('crashDumps', crashReporterDirectory); + } + + // Otherwise we configure the crash reporter from product.json + else { + const appCenter = product.appCenter; + // Disable Appcenter crash reporting if + // * --crash-reporter-directory is specified + // * enable-crash-reporter runtime argument is set to 'false' + // * --disable-crash-reporter command line parameter is set + if (appCenter && argvConfig['enable-crash-reporter'] && !args['disable-crash-reporter']) { + const isWindows = (process.platform === 'win32'); + const isLinux = (process.platform === 'linux'); + const crashReporterId = argvConfig['crash-reporter-id']; + const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; + if (uuidPattern.test(crashReporterId)) { + submitURL = isWindows ? appCenter[process.arch === 'ia32' ? 'win32-ia32' : 'win32-x64'] : isLinux ? appCenter[`linux-x64`] : appCenter.darwin; + submitURL = submitURL.concat('&uid=', crashReporterId, '&iid=', crashReporterId, '&sid=', crashReporterId); + // Send the id for child node process that are explicitly starting crash reporter. + // For vscode this is ExtensionHost process currently. + const argv = process.argv; + const endOfArgsMarkerIndex = argv.indexOf('--'); + if (endOfArgsMarkerIndex === -1) { + argv.push('--crash-reporter-id', crashReporterId); + } else { + // if the we have an argument "--" (end of argument marker) + // we cannot add arguments at the end. rather, we add + // arguments before the "--" marker. + argv.splice(endOfArgsMarkerIndex, 0, '--crash-reporter-id', crashReporterId); + } + } + } + } + + // Start crash reporter for all processes + const productName = (product.crashReporter ? product.crashReporter.productName : undefined) || product.nameShort; + const companyName = (product.crashReporter ? product.crashReporter.companyName : undefined) || 'Microsoft'; + crashReporter.start({ + companyName: companyName, + productName: process.env['VSCODE_DEV'] ? `${productName} Dev` : productName, + submitURL, + uploadToServer: !crashReporterDirectory + }); +} + /** * @param {import('./vs/platform/environment/common/argv').NativeParsedArgs} cliArgs * @returns {string | null} diff --git a/src/vs/base/browser/ui/checkbox/checkbox.ts b/src/vs/base/browser/ui/checkbox/checkbox.ts index f09bfc0d984..cd9e4a2718c 100644 --- a/src/vs/base/browser/ui/checkbox/checkbox.ts +++ b/src/vs/base/browser/ui/checkbox/checkbox.ts @@ -19,6 +19,7 @@ export interface ICheckboxOpts extends ICheckboxStyles { readonly icon?: CSSIcon; readonly title: string; readonly isChecked: boolean; + readonly notFocusable?: boolean; } export interface ICheckboxStyles { @@ -51,7 +52,8 @@ export class CheckboxActionViewItem extends BaseActionViewItem { this.checkbox = new Checkbox({ actionClassName: this._action.class, isChecked: this._action.checked, - title: this._action.label + title: this._action.label, + notFocusable: true }); this.disposables.add(this.checkbox); this.disposables.add(this.checkbox.onChange(() => this._action.checked = !!this.checkbox && this.checkbox.checked, this)); @@ -113,7 +115,9 @@ export class Checkbox extends Widget { this.domNode = document.createElement('div'); this.domNode.title = this._opts.title; this.domNode.classList.add(...classes); - this.domNode.tabIndex = 0; + if (!this._opts.notFocusable) { + this.domNode.tabIndex = 0; + } this.domNode.setAttribute('role', 'checkbox'); this.domNode.setAttribute('aria-checked', String(this._checked)); this.domNode.setAttribute('aria-label', this._opts.title); diff --git a/src/vs/base/common/errors.ts b/src/vs/base/common/errors.ts index 8c8e874e853..6043f469353 100644 --- a/src/vs/base/common/errors.ts +++ b/src/vs/base/common/errors.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IAction } from 'vs/base/common/actions'; + export interface ErrorListenerCallback { (error: any): void; } @@ -221,3 +223,31 @@ export class NotSupportedError extends Error { } } } + +export class ExpectedError extends Error { + readonly isExpected = true; +} + +export interface IErrorOptions { + actions?: ReadonlyArray; +} + +export interface IErrorWithActions { + actions?: ReadonlyArray; +} + +export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { + const candidate = obj as IErrorWithActions | undefined; + + return candidate instanceof Error && Array.isArray(candidate.actions); +} + +export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions { + const result = new Error(message); + + if (options.actions) { + (result as IErrorWithActions).actions = options.actions; + } + + return result; +} diff --git a/src/vs/base/common/errorsWithActions.ts b/src/vs/base/common/errorsWithActions.ts deleted file mode 100644 index fa92b7f4526..00000000000 --- a/src/vs/base/common/errorsWithActions.ts +++ /dev/null @@ -1,28 +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 { IAction } from 'vs/base/common/actions'; - -export interface IErrorOptions { - actions?: ReadonlyArray; -} - -export interface IErrorWithActions { - actions?: ReadonlyArray; -} - -export function isErrorWithActions(obj: unknown): obj is IErrorWithActions { - return obj instanceof Error && Array.isArray((obj as IErrorWithActions).actions); -} - -export function createErrorWithActions(message: string, options: IErrorOptions = Object.create(null)): Error & IErrorWithActions { - const result = new Error(message); - - if (options.actions) { - (result).actions = options.actions; - } - - return result; -} diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 47ee3460726..aabe12df5e0 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -3,11 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import product from 'vs/platform/product/common/product'; import * as fs from 'fs'; import { release } from 'os'; import { gracefulify } from 'graceful-fs'; import { ipcRenderer } from 'electron'; +import product from 'vs/platform/product/common/product'; import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; import { StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; @@ -40,7 +40,7 @@ import { NodeCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/co import { LanguagePackCachedDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner'; import { StorageDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner'; import { LogsDataCleaner } from 'vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { MessagePortMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { DiagnosticsService } from 'vs/platform/diagnostics/node/diagnosticsService'; diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index ff58b3f5664..fe6e388cbd7 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -3,16 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; import { release } from 'os'; import { statSync } from 'fs'; +import { app, ipcMain, systemPreferences, contentTracing, protocol, BrowserWindow, dialog, session } from 'electron'; import { IProcessEnvironment, isWindows, isMacintosh, isLinux, isLinuxSnap } from 'vs/base/common/platform'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; import { IWindowOpenable } from 'vs/platform/windows/common/windows'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv'; import { IUpdateService } from 'vs/platform/update/common/update'; -import { UpdateChannel } from 'vs/platform/update/electron-main/updateIpc'; +import { UpdateChannel } from 'vs/platform/update/common/updateIpc'; import { getDelayedChannel, StaticRouter, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { Server as ElectronIPCServer } from 'vs/base/parts/ipc/electron-main/ipc.electron'; import { Server as NodeIPCServer } from 'vs/base/parts/ipc/node/ipc.net'; @@ -90,6 +90,10 @@ import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/commo import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService'; import { once } from 'vs/base/common/functional'; +/** + * The main VS Code application. There will only ever be one instance, + * even if the user starts many instances (e.g. from the command line). + */ export class CodeApplication extends Disposable { private windowsMainService: IWindowsMainService | undefined; private dialogMainService: IDialogMainService | undefined; diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f9de03dfc32..d7c5594da19 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -33,7 +33,7 @@ import { CodeApplication } from 'vs/code/electron-main/app'; import { getPathLabel, mnemonicButtonLabel } from 'vs/base/common/labels'; import { SpdLogLogger } from 'vs/platform/log/node/spdlogLog'; import { BufferLogService } from 'vs/platform/log/common/bufferLog'; -import { setUnexpectedErrorHandler } from 'vs/base/common/errors'; +import { ExpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; import { IThemeMainService, ThemeMainService } from 'vs/platform/theme/electron-main/themeMainService'; import { once } from 'vs/base/common/functional'; import { ISignService } from 'vs/platform/sign/common/sign'; @@ -47,7 +47,6 @@ import { ITunnelService } from 'vs/platform/remote/common/tunnel'; import { TunnelService } from 'vs/platform/remote/node/tunnelService'; import { IProductService } from 'vs/platform/product/common/productService'; import { IPathWithLineAndColumn, isValidBasename, parseLineAndColumnAware, sanitizeFilePath } from 'vs/base/common/extpath'; -import { isNumber } from 'vs/base/common/types'; import { rtrim, trim } from 'vs/base/common/strings'; import { basename, join, resolve } from 'vs/base/common/path'; import { coalesce, distinct } from 'vs/base/common/arrays'; @@ -56,10 +55,14 @@ import { toErrorMessage } from 'vs/base/common/errorMessage'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { LoggerService } from 'vs/platform/log/node/loggerService'; -class ExpectedError extends Error { - readonly isExpected = true; -} - +/** + * The main VS Code entry point. + * + * Note: This class can exist more than once for example when VS Code is already + * running and a second instance is started from the command line. It will always + * try to communicate with an existing instance to prevent that 2 VS Code instances + * are running at the same time. + */ class CodeMain { main(): void { @@ -101,21 +104,15 @@ class CodeMain { private async startup(args: NativeParsedArgs): Promise { - // We need to buffer the spdlog logs until we are sure - // we are the only instance running, otherwise we'll have concurrent - // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) - const bufferLogService = new BufferLogService(); + // Create services + const [instantiationService, instanceEnvironment, environmentService, configurationService, stateService, bufferLogService] = this.createServices(args); - const [instantiationService, instanceEnvironment, environmentService] = this.createServices(args, bufferLogService); try { // Init services await instantiationService.invokeFunction(async accessor => { - const configurationService = accessor.get(IConfigurationService); - const stateService = accessor.get(IStateService); - try { - await this.initServices(environmentService, configurationService as ConfigurationService, stateService as StateService); + await this.initServices(environmentService, configurationService, stateService); } catch (error) { // Show a dialog for errors that can be resolved by the user @@ -130,14 +127,19 @@ class CodeMain { const logService = accessor.get(ILogService); const lifecycleMainService = accessor.get(ILifecycleMainService); const fileService = accessor.get(IFileService); - const configurationService = accessor.get(IConfigurationService); + // Create the main IPC server by trying to be the server + // If this throws an error it means we are not the first + // instance of VS Code running and so we would quit. const mainIpcServer = await this.doStartup(args, logService, environmentService, lifecycleMainService, instantiationService, true); + // Delay creation of spdlog for perf reasons (https://github.com/microsoft/vscode/issues/72906) bufferLogService.logger = new SpdLogLogger('main', join(environmentService.logsPath, 'main.log'), true, bufferLogService.getLevel()); + + // Lifecycle once(lifecycleMainService.onWillShutdown)(() => { fileService.dispose(); - (configurationService as ConfigurationService).dispose(); + configurationService.dispose(); }); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); @@ -147,35 +149,76 @@ class CodeMain { } } - private createServices(args: NativeParsedArgs, bufferLogService: BufferLogService): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService] { + private createServices(args: NativeParsedArgs): [IInstantiationService, IProcessEnvironment, IEnvironmentMainService, ConfigurationService, StateService, BufferLogService] { const services = new ServiceCollection(); + // Environment const environmentService = new EnvironmentMainService(args); const instanceEnvironment = this.patchEnvironment(environmentService); // Patch `process.env` with the instance's environment services.set(IEnvironmentService, environmentService); services.set(IEnvironmentMainService, environmentService); + // Log: We need to buffer the spdlog logs until we are sure + // we are the only instance running, otherwise we'll have concurrent + // log file access on Windows (https://github.com/microsoft/vscode/issues/41218) + const bufferLogService = new BufferLogService(); const logService = new MultiplexLogService([new ConsoleMainLogger(getLogLevel(environmentService)), bufferLogService]); process.once('exit', () => logService.dispose()); services.set(ILogService, logService); + // Files const fileService = new FileService(logService); services.set(IFileService, fileService); const diskFileSystemProvider = new DiskFileSystemProvider(logService); fileService.registerProvider(Schemas.file, diskFileSystemProvider); + // Logger services.set(ILoggerService, new LoggerService(logService, fileService)); - services.set(IConfigurationService, new ConfigurationService(environmentService.settingsResource, fileService)); + // Configuration + const configurationService = new ConfigurationService(environmentService.settingsResource, fileService); + services.set(IConfigurationService, configurationService); + + // Lifecycle services.set(ILifecycleMainService, new SyncDescriptor(LifecycleMainService)); - services.set(IStateService, new SyncDescriptor(StateService)); + + // State + const stateService = new StateService(environmentService, logService); + services.set(IStateService, stateService); + + // Request services.set(IRequestService, new SyncDescriptor(RequestMainService)); + + // Themes services.set(IThemeMainService, new SyncDescriptor(ThemeMainService)); + + // Signing services.set(ISignService, new SyncDescriptor(SignService)); + + // Product services.set(IProductService, { _serviceBrand: undefined, ...product }); + + // Tunnel services.set(ITunnelService, new SyncDescriptor(TunnelService)); - return [new InstantiationService(services, true), instanceEnvironment, environmentService]; + return [new InstantiationService(services, true), instanceEnvironment, environmentService, configurationService, stateService, bufferLogService]; + } + + private patchEnvironment(environmentService: IEnvironmentMainService): IProcessEnvironment { + const instanceEnvironment: IProcessEnvironment = { + VSCODE_IPC_HOOK: environmentService.mainIPCHandle + }; + + ['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => { + const value = process.env[key]; + if (typeof value === 'string') { + instanceEnvironment[key] = value; + } + }); + + Object.assign(process.env, instanceEnvironment); + + return instanceEnvironment; } private initServices(environmentService: IEnvironmentMainService, configurationService: ConfigurationService, stateService: StateService): Promise { @@ -199,23 +242,6 @@ class CodeMain { return Promise.all([environmentServiceInitialization, configurationServiceInitialization, stateServiceInitialization]); } - private patchEnvironment(environmentService: IEnvironmentMainService): IProcessEnvironment { - const instanceEnvironment: IProcessEnvironment = { - VSCODE_IPC_HOOK: environmentService.mainIPCHandle - }; - - ['VSCODE_NLS_CONFIG', 'VSCODE_PORTABLE'].forEach(key => { - const value = process.env[key]; - if (typeof value === 'string') { - instanceEnvironment[key] = value; - } - }); - - Object.assign(process.env, instanceEnvironment); - - return instanceEnvironment; - } - private async doStartup(args: NativeParsedArgs, logService: ILogService, environmentService: IEnvironmentMainService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means @@ -405,7 +431,7 @@ class CodeMain { lifecycleMainService.kill(exitCode); } - //#region Helpers + //#region Path Helpers private validatePaths(args: NativeParsedArgs): NativeParsedArgs { @@ -486,11 +512,11 @@ class CodeMain { private toPath(pathWithLineAndCol: IPathWithLineAndColumn): string { const segments = [pathWithLineAndCol.path]; - if (isNumber(pathWithLineAndCol.line)) { + if (typeof pathWithLineAndCol.line === 'number') { segments.push(String(pathWithLineAndCol.line)); } - if (isNumber(pathWithLineAndCol.column)) { + if (typeof pathWithLineAndCol.column === 'number') { segments.push(String(pathWithLineAndCol.column)); } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 6d339e85815..75b1c60e454 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -22,11 +22,12 @@ import BaseHtml from 'vs/code/electron-sandbox/issue/issueReporterPage'; import { localize } from 'vs/nls'; import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/common/issue'; import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { Codicon } from 'vs/base/common/codicons'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; const MAX_URL_LENGTH = 2045; diff --git a/src/vs/platform/contextkey/browser/contextKeyService.ts b/src/vs/platform/contextkey/browser/contextKeyService.ts index df2e5b70dbe..d55aea9bb41 100644 --- a/src/vs/platform/contextkey/browser/contextKeyService.ts +++ b/src/vs/platform/contextkey/browser/contextKeyService.ts @@ -10,7 +10,7 @@ import { TernarySearchTree } from 'vs/base/common/map'; import { distinct } from 'vs/base/common/objects'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; +import { IContext, IContextKey, IContextKeyChangeEvent, IContextKeyService, IContextKeyServiceTarget, IReadableSet, SET_CONTEXT_COMMAND_ID, ContextKeyExpression, RawContextKey, ContextKeyInfo } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingResolver } from 'vs/platform/keybinding/common/keybindingResolver'; const KEYBINDING_CONTEXT_ATTR = 'data-keybinding-context'; @@ -501,3 +501,16 @@ function findContextAttr(domNode: IContextKeyServiceTarget | null): number { CommandsRegistry.registerCommand(SET_CONTEXT_COMMAND_ID, function (accessor, contextKey: any, contextValue: any) { accessor.get(IContextKeyService).createKey(String(contextKey), contextValue); }); + +CommandsRegistry.registerCommand('_generateContextKeyInfo', function () { + const result: ContextKeyInfo[] = []; + const seen = new Set(); + for (let info of RawContextKey.all()) { + if (!seen.has(info.key)) { + seen.add(info.key); + result.push(info); + } + } + result.sort((a, b) => a.key.localeCompare(b.key)); + console.log(JSON.stringify(result, undefined, 2)); +}); diff --git a/src/vs/platform/contextkey/common/contextkey.ts b/src/vs/platform/contextkey/common/contextkey.ts index fde5606ea23..56e09f911fa 100644 --- a/src/vs/platform/contextkey/common/contextkey.ts +++ b/src/vs/platform/contextkey/common/contextkey.ts @@ -1257,13 +1257,28 @@ export class ContextKeyOrExpr implements IContextKeyExpression { } } +export interface ContextKeyInfo { + readonly key: string; + readonly type: string; + readonly description?: string; +} + export class RawContextKey extends ContextKeyDefinedExpr { + private static _info: ContextKeyInfo[] = []; + + static all(): IterableIterator { + return RawContextKey._info.values(); + } + private readonly _defaultValue: T | undefined; - constructor(key: string, defaultValue: T | undefined) { + constructor(key: string, defaultValue: T | undefined, description?: string) { super(key); this._defaultValue = defaultValue; + + // collect all context keys into a central place + RawContextKey._info.push({ key, description, type: typeof defaultValue }); } public bindTo(target: IContextKeyService): IContextKey { diff --git a/src/vs/platform/driver/electron-browser/driver.ts b/src/vs/platform/driver/electron-browser/driver.ts index a06299b41c4..169d7365701 100644 --- a/src/vs/platform/driver/electron-browser/driver.ts +++ b/src/vs/platform/driver/electron-browser/driver.ts @@ -6,7 +6,7 @@ import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/node/driver'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { timeout } from 'vs/base/common/async'; import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; diff --git a/src/vs/platform/instantiation/common/extensions.ts b/src/vs/platform/instantiation/common/extensions.ts index a6848e16ac1..3c970fa305a 100644 --- a/src/vs/platform/instantiation/common/extensions.ts +++ b/src/vs/platform/instantiation/common/extensions.ts @@ -8,8 +8,14 @@ import { ServiceIdentifier, BrandedService } from './instantiation'; const _registry: [ServiceIdentifier, SyncDescriptor][] = []; -export function registerSingleton(id: ServiceIdentifier, ctor: new (...services: Services) => T, supportsDelayedInstantiation?: boolean): void { - _registry.push([id, new SyncDescriptor(ctor as new (...args: any[]) => T, [], supportsDelayedInstantiation)]); +export function registerSingleton(id: ServiceIdentifier, ctor: new (...services: Services) => T, supportsDelayedInstantiation?: boolean): void; +export function registerSingleton(id: ServiceIdentifier, descriptor: SyncDescriptor): void; +export function registerSingleton(id: ServiceIdentifier, ctorOrDescriptor: { new(...services: Services): T } | SyncDescriptor, supportsDelayedInstantiation?: boolean): void { + if (!(ctorOrDescriptor instanceof SyncDescriptor)) { + ctorOrDescriptor = new SyncDescriptor(ctorOrDescriptor as new (...args: any[]) => T, [], supportsDelayedInstantiation); + } + + _registry.push([id, ctorOrDescriptor]); } export function getSingletonServiceDescriptors(): [ServiceIdentifier, SyncDescriptor][] { diff --git a/src/vs/platform/ipc/electron-browser/mainProcessService.ts b/src/vs/platform/ipc/electron-browser/mainProcessService.ts index 9253eddb605..276420e848c 100644 --- a/src/vs/platform/ipc/electron-browser/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-browser/mainProcessService.ts @@ -5,7 +5,7 @@ import { IChannel, IServerChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { Server as MessagePortServer } from 'vs/base/parts/ipc/electron-browser/ipc.mp'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; /** * An implementation of `IMainProcessService` that leverages MessagePorts. diff --git a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts b/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts index 22e6149ee95..837b3fb4a20 100644 --- a/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts +++ b/src/vs/platform/ipc/electron-sandbox/mainProcessService.ts @@ -6,18 +6,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { Client as IPCElectronClient } from 'vs/base/parts/ipc/electron-sandbox/ipc.electron'; import { Disposable } from 'vs/base/common/lifecycle'; -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; - -export const IMainProcessService = createDecorator('mainProcessService'); - -export interface IMainProcessService { - - readonly _serviceBrand: undefined; - - getChannel(channelName: string): IChannel; - - registerChannel(channelName: string, channel: IServerChannel): void; -} +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; /** * An implementation of `IMainProcessService` that leverages Electron's IPC. diff --git a/src/vs/platform/ipc/electron-sandbox/services.ts b/src/vs/platform/ipc/electron-sandbox/services.ts new file mode 100644 index 00000000000..e32ba3b95b1 --- /dev/null +++ b/src/vs/platform/ipc/electron-sandbox/services.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel, ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { createDecorator, ServiceIdentifier } from 'vs/platform/instantiation/common/instantiation'; + +type ChannelClientCtor = { new(channel: IChannel): T }; +type Remote = { getChannel(channelName: string): IChannel; }; + +abstract class RemoteServiceStub { + constructor( + channelName: string, + channelClientCtor: ChannelClientCtor | undefined, + remote: Remote + ) { + const channel = remote.getChannel(channelName); + + if (channelClientCtor) { + return new channelClientCtor(channel); + } else { + return ProxyChannel.toService(channel); + } + } +} + +export interface IRemoteServiceOptions { + readonly channelClientCtor?: ChannelClientCtor; + readonly supportsDelayedInstantiation?: boolean; +} + +//#region Main Process + +export const IMainProcessService = createDecorator('mainProcessService'); + +export interface IMainProcessService { + readonly _serviceBrand: undefined; + getChannel(channelName: string): IChannel; + registerChannel(channelName: string, channel: IServerChannel): void; +} + +class MainProcessRemoteServiceStub extends RemoteServiceStub { + constructor(channelName: string, channelClientCtor: ChannelClientCtor | undefined, @IMainProcessService ipcService: IMainProcessService) { + super(channelName, channelClientCtor, ipcService); + } +} + +export function registerMainProcessRemoteService(id: ServiceIdentifier, channelName: string, options: IRemoteServiceOptions = {}): void { + registerSingleton(id, new SyncDescriptor(MainProcessRemoteServiceStub, [channelName, options.channelClientCtor], options.supportsDelayedInstantiation)); +} + +//#endregion + +//#region Shared Process + +export const ISharedProcessService = createDecorator('sharedProcessService'); + +export interface ISharedProcessService { + readonly _serviceBrand: undefined; + getChannel(channelName: string): IChannel; + registerChannel(channelName: string, channel: IServerChannel): void; +} + +class SharedProcessRemoteServiceStub extends RemoteServiceStub { + constructor(channelName: string, channelClientCtor: ChannelClientCtor | undefined, @ISharedProcessService ipcService: ISharedProcessService) { + super(channelName, channelClientCtor, ipcService); + } +} + +export function registerSharedProcessRemoteService(id: ServiceIdentifier, channelName: string, options: IRemoteServiceOptions = {}): void { + registerSingleton(id, new SyncDescriptor(SharedProcessRemoteServiceStub, [channelName, options.channelClientCtor], options.supportsDelayedInstantiation)); +} + +//#endregion diff --git a/src/vs/platform/ipc/electron-sandbox/sharedProcessService.ts b/src/vs/platform/ipc/electron-sandbox/sharedProcessService.ts index d3f157a5815..87539339df7 100644 --- a/src/vs/platform/ipc/electron-sandbox/sharedProcessService.ts +++ b/src/vs/platform/ipc/electron-sandbox/sharedProcessService.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { ipcMessagePort } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { Client as MessagePortClient } from 'vs/base/parts/ipc/common/ipc.mp'; @@ -12,16 +11,7 @@ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { generateUuid } from 'vs/base/common/uuid'; import { ILogService } from 'vs/platform/log/common/log'; import { Disposable } from 'vs/base/common/lifecycle'; - -export const ISharedProcessService = createDecorator('sharedProcessService'); - -export interface ISharedProcessService { - - readonly _serviceBrand: undefined; - - getChannel(channelName: string): IChannel; - registerChannel(channelName: string, channel: IServerChannel): void; -} +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; export class SharedProcessService extends Disposable implements ISharedProcessService { diff --git a/src/vs/platform/native/electron-sandbox/nativeHostService.ts b/src/vs/platform/native/electron-sandbox/nativeHostService.ts index cc52246b294..37b92510abe 100644 --- a/src/vs/platform/native/electron-sandbox/nativeHostService.ts +++ b/src/vs/platform/native/electron-sandbox/nativeHostService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; // @ts-ignore: interface is implemented via proxy diff --git a/src/vs/platform/update/common/updateIpc.ts b/src/vs/platform/update/common/updateIpc.ts new file mode 100644 index 00000000000..26dcaa321b1 --- /dev/null +++ b/src/vs/platform/update/common/updateIpc.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; +import { Emitter, Event } from 'vs/base/common/event'; +import { IUpdateService, State } from 'vs/platform/update/common/update'; + +export class UpdateChannel implements IServerChannel { + + constructor(private service: IUpdateService) { } + + listen(_: unknown, event: string): Event { + switch (event) { + case 'onStateChange': return this.service.onStateChange; + } + + throw new Error(`Event not found: ${event}`); + } + + call(_: unknown, command: string, arg?: any): Promise { + switch (command) { + case 'checkForUpdates': return this.service.checkForUpdates(arg); + case 'downloadUpdate': return this.service.downloadUpdate(); + case 'applyUpdate': return this.service.applyUpdate(); + case 'quitAndInstall': return this.service.quitAndInstall(); + case '_getInitialState': return Promise.resolve(this.service.state); + case 'isLatestVersion': return this.service.isLatestVersion(); + } + + throw new Error(`Call not found: ${command}`); + } +} + +export class UpdateChannelClient implements IUpdateService { + + declare readonly _serviceBrand: undefined; + + private readonly _onStateChange = new Emitter(); + readonly onStateChange: Event = this._onStateChange.event; + + private _state: State = State.Uninitialized; + get state(): State { return this._state; } + set state(state: State) { + this._state = state; + this._onStateChange.fire(state); + } + + constructor(private readonly channel: IChannel) { + this.channel.listen('onStateChange')(state => this.state = state); + this.channel.call('_getInitialState').then(state => this.state = state); + } + + checkForUpdates(context: any): Promise { + return this.channel.call('checkForUpdates', context); + } + + downloadUpdate(): Promise { + return this.channel.call('downloadUpdate'); + } + + applyUpdate(): Promise { + return this.channel.call('applyUpdate'); + } + + quitAndInstall(): Promise { + return this.channel.call('quitAndInstall'); + } + + isLatestVersion(): Promise { + return this.channel.call('isLatestVersion'); + } +} diff --git a/src/vs/platform/update/electron-main/updateIpc.ts b/src/vs/platform/update/electron-main/updateIpc.ts deleted file mode 100644 index 6dd824ba48a..00000000000 --- a/src/vs/platform/update/electron-main/updateIpc.ts +++ /dev/null @@ -1,34 +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 { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IUpdateService } from 'vs/platform/update/common/update'; - -export class UpdateChannel implements IServerChannel { - - constructor(private service: IUpdateService) { } - - listen(_: unknown, event: string): Event { - switch (event) { - case 'onStateChange': return this.service.onStateChange; - } - - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'checkForUpdates': return this.service.checkForUpdates(arg); - case 'downloadUpdate': return this.service.downloadUpdate(); - case 'applyUpdate': return this.service.applyUpdate(); - case 'quitAndInstall': return this.service.quitAndInstall(); - case '_getInitialState': return Promise.resolve(this.service.state); - case 'isLatestVersion': return this.service.isLatestVersion(); - } - - throw new Error(`Call not found: ${command}`); - } -} \ No newline at end of file diff --git a/src/vs/platform/userDataSync/common/userDataSyncService.ts b/src/vs/platform/userDataSync/common/userDataSyncService.ts index ad5494bc422..cf0d022f8ca 100644 --- a/src/vs/platform/userDataSync/common/userDataSyncService.ts +++ b/src/vs/platform/userDataSync/common/userDataSyncService.ts @@ -195,8 +195,29 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ try { await synchroniser.sync(manifest, syncHeaders); } catch (e) { - this.handleSynchronizerError(e, synchroniser.resource); - this._syncErrors.push([synchroniser.resource, UserDataSyncError.toUserDataSyncError(e)]); + + if (e instanceof UserDataSyncError) { + // Bail out for following errors + switch (e.code) { + case UserDataSyncErrorCode.TooLarge: + throw new UserDataSyncError(e.message, e.code, synchroniser.resource); + case UserDataSyncErrorCode.TooManyRequests: + case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter: + case UserDataSyncErrorCode.LocalTooManyRequests: + case UserDataSyncErrorCode.Gone: + case UserDataSyncErrorCode.UpgradeRequired: + case UserDataSyncErrorCode.IncompatibleRemoteContent: + case UserDataSyncErrorCode.IncompatibleLocalContent: + throw e; + } + } + + // Log and report other errors and continue + const userDataSyncError = UserDataSyncError.toUserDataSyncError(e); + this.reportUserDataSyncError(userDataSyncError, executionId); + this.logService.error(e); + this.logService.error(`${synchroniser.resource}: ${toErrorMessage(e)}`); + this._syncErrors.push([synchroniser.resource, userDataSyncError]); } } @@ -371,26 +392,6 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ } } - private handleSynchronizerError(e: Error, source: SyncResource): void { - if (e instanceof UserDataSyncError) { - switch (e.code) { - case UserDataSyncErrorCode.TooLarge: - throw new UserDataSyncError(e.message, e.code, source); - - case UserDataSyncErrorCode.TooManyRequests: - case UserDataSyncErrorCode.TooManyRequestsAndRetryAfter: - case UserDataSyncErrorCode.LocalTooManyRequests: - case UserDataSyncErrorCode.Gone: - case UserDataSyncErrorCode.UpgradeRequired: - case UserDataSyncErrorCode.IncompatibleRemoteContent: - case UserDataSyncErrorCode.IncompatibleLocalContent: - throw e; - } - } - this.logService.error(e); - this.logService.error(`${source}: ${toErrorMessage(e)}`); - } - private reportUserDataSyncError(userDataSyncError: UserDataSyncError, executionId: string) { this.telemetryService.publicLog2<{ code: string, service: string, url?: string, resource?: string, executionId?: string }, SyncErrorClassification>('sync/error', { code: userDataSyncError.code, url: userDataSyncError instanceof UserDataSyncStoreError ? userDataSyncError.url : undefined, resource: userDataSyncError.resource, executionId, service: this.userDataSyncStoreManagementService.userDataSyncStore!.url.toString() }); diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts index 373d3a905e0..a33fa80aad2 100644 --- a/src/vs/vscode.proposed.d.ts +++ b/src/vs/vscode.proposed.d.ts @@ -1008,55 +1008,20 @@ declare module 'vscode' { Idle = 2 } + // TODO@API + // make this a class, allow modified using with-pattern export interface NotebookCellMetadata { /** * Controls whether a cell's editor is editable/readonly. */ editable?: boolean; - /** - * Controls if the cell is executable. - * This metadata is ignored for markdown cell. - */ - runnable?: boolean; - /** * Controls if the cell has a margin to support the breakpoint UI. * This metadata is ignored for markdown cell. */ breakpointMargin?: boolean; - /** - * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. - * Defaults to true. - */ - hasExecutionOrder?: boolean; - - /** - * The order in which this cell was executed. - */ - executionOrder?: number; - - /** - * A status message to be shown in the cell's status bar - */ - statusMessage?: string; - - /** - * The cell's current run state - */ - runState?: NotebookCellRunState; - - /** - * If the cell is running, the time at which the cell started running - */ - runStartTime?: number; - - /** - * The total duration of the cell's last run - */ - lastRunDuration?: number; - /** * Whether a code cell's editor is collapsed */ @@ -1081,13 +1046,16 @@ declare module 'vscode' { readonly cellKind: CellKind; readonly document: TextDocument; readonly language: string; + readonly outputs: readonly NotebookCellOutput[]; + readonly metadata: NotebookCellMetadata; /** @deprecated use WorkspaceEdit.replaceCellOutput */ - outputs: CellOutput[]; + // outputs: CellOutput[]; // readonly outputs2: NotebookCellOutput[]; /** @deprecated use WorkspaceEdit.replaceCellMetadata */ - metadata: NotebookCellMetadata; + // metadata: NotebookCellMetadata; } + export interface NotebookDocumentMetadata { /** * Controls if users can add or delete cells @@ -1095,30 +1063,11 @@ declare module 'vscode' { */ editable?: boolean; - /** - * Controls whether the full notebook can be run at once. - * Defaults to true - */ - runnable?: boolean; - /** * Default value for [cell editable metadata](#NotebookCellMetadata.editable). * Defaults to true. */ cellEditable?: boolean; - - /** - * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). - * Defaults to true. - */ - cellRunnable?: boolean; - - /** - * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). - * Defaults to true. - */ - cellHasExecutionOrder?: boolean; - displayOrder?: GlobPattern[]; /** @@ -1126,11 +1075,6 @@ declare module 'vscode' { */ custom?: { [key: string]: any; }; - /** - * The document's current run state - */ - runState?: NotebookRunState; - /** * Whether the document is trusted, default to true * When false, insecure outputs like HTML, JavaScript, SVG will not be rendered. @@ -1166,10 +1110,15 @@ declare module 'vscode' { readonly isUntitled: boolean; readonly cells: ReadonlyArray; readonly contentOptions: NotebookDocumentContentOptions; + // todo@API + // - move to kernel -> control runnable state of a cell + // - remove from this type languages: string[]; - metadata: NotebookDocumentMetadata; + readonly metadata: NotebookDocumentMetadata; } + // todo@API maybe have a NotebookCellPosition sibling + // todo@API should be a class export interface NotebookCellRange { readonly start: number; /** @@ -1241,18 +1190,6 @@ declare module 'vscode' { readonly onDidDispose: Event; } - // todo@API stale? - export interface NotebookOutputSelector { - mimeTypes?: string[]; - } - - // todo@API stale? - export interface NotebookRenderRequest { - output: CellDisplayOutput; - mimeType: string; - outputId: string; - } - export interface NotebookDocumentMetadataChangeEvent { readonly document: NotebookDocument; } @@ -1273,17 +1210,6 @@ declare module 'vscode' { readonly changes: ReadonlyArray; } - // todo@API stale? - export interface NotebookCellMoveEvent { - - /** - * The affected document. - */ - readonly document: NotebookDocument; - readonly index: number; - readonly newIndex: number; - } - export interface NotebookCellOutputsChangeEvent { /** @@ -1326,7 +1252,7 @@ declare module 'vscode' { readonly source: string; readonly language: string; // todo@API maybe use a separate data type? - readonly outputs: CellOutput[]; + readonly outputs: NotebookCellOutput[]; readonly metadata: NotebookCellMetadata | undefined; } @@ -1416,86 +1342,29 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/106744, NotebookCellOutput - export enum CellOutputKind { - Text = 1, - Error = 2, - Rich = 3 - } - - export interface CellStreamOutput { - outputKind: CellOutputKind.Text; - text: string; - } - - export interface CellErrorOutput { - outputKind: CellOutputKind.Error; - /** - * Exception Name - */ - ename: string; - /** - * Exception Value - */ - evalue: string; - /** - * Exception call stack - */ - traceback: string[]; - } - - export interface NotebookCellOutputMetadata { - /** - * Additional attributes of a cell metadata. - */ - custom?: { [key: string]: any; }; - } - - export interface CellDisplayOutput { - outputKind: CellOutputKind.Rich; - /** - * { mime_type: value } - * - * Example: - * ```json - * { - * "outputKind": vscode.CellOutputKind.Rich, - * "data": { - * "text/html": [ - * "

Hello

" - * ], - * "text/plain": [ - * "" - * ] - * } - * } - */ - data: { [key: string]: any; }; - - readonly metadata?: NotebookCellOutputMetadata; - } - - export type CellOutput = CellStreamOutput | CellErrorOutput | CellDisplayOutput; - + // code specific mime types + // application/x.notebook.error-traceback + // application/x.notebook.stream export class NotebookCellOutputItem { + // todo@API + // add factory functions for common mime types + // static textplain(value:string): NotebookCellOutputItem; + // static errortrace(value:any): NotebookCellOutputItem; + readonly mime: string; readonly value: unknown; - readonly metadata?: Record; + readonly metadata?: Record; - constructor(mime: string, value: unknown, metadata?: Record); + constructor(mime: string, value: unknown, metadata?: Record); } // @jrieken //TODO@API add execution count to cell output? export class NotebookCellOutput { - readonly id: string; readonly outputs: NotebookCellOutputItem[]; - - constructor(outputs: NotebookCellOutputItem[]); - - //TODO@jrieken HACK to workaround dependency issues... - toJSON(): any; + constructor(outputs: NotebookCellOutputItem[], id?: string); } //#endregion @@ -1504,22 +1373,24 @@ declare module 'vscode' { export interface WorkspaceEdit { replaceNotebookMetadata(uri: Uri, value: NotebookDocumentMetadata): 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; - replaceNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; - appendNotebookCellOutput(uri: Uri, index: number, outputs: (NotebookCellOutput | CellOutput)[], metadata?: WorkspaceEditEntryMetadata): void; + replaceNotebookCellOutput(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 - // replaceNotebookCellOutput(uri: Uri, index: number, outputId:string, outputs: NotebookCellOutputItem[], metadata?: WorkspaceEditEntryMetadata): void; - // appendNotebookCellOutput(uri: Uri, index: number, outputId:string, outputs: 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; } export interface NotebookEditorEdit { replaceMetadata(value: NotebookDocumentMetadata): void; replaceCells(start: number, end: number, cells: NotebookCellData[]): void; - replaceCellOutput(index: number, outputs: (NotebookCellOutput | CellOutput)[]): void; + replaceCellOutput(index: number, outputs: NotebookCellOutput[]): void; replaceCellMetadata(index: number, metadata: NotebookCellMetadata): void; } @@ -1612,6 +1483,98 @@ declare module 'vscode' { //#region https://github.com/microsoft/vscode/issues/106744, NotebookKernel + export interface NotebookDocumentMetadata { + + /** + * Controls whether the full notebook can be run at once. + * Defaults to true + */ + // todo@API infer from kernel + // todo@API remove + runnable?: boolean; + + /** + * Default value for [cell runnable metadata](#NotebookCellMetadata.runnable). + * Defaults to true. + */ + cellRunnable?: boolean; + + /** + * Default value for [cell hasExecutionOrder metadata](#NotebookCellMetadata.hasExecutionOrder). + * Defaults to true. + */ + cellHasExecutionOrder?: boolean; + + /** + * The document's current run state + */ + runState?: NotebookRunState; + } + + // todo@API use the NotebookCellExecution-object as a container to model and enforce + // the flow of a cell execution + + // kernel -> execute_info + // ext -> createNotebookCellExecution(cell) + // kernel -> done + // exec.dispose(); + + // export interface NotebookCellExecution { + // dispose(): void; + // clearOutput(): void; + // appendOutput(out: NotebookCellOutput): void; + // replaceOutput(out: NotebookCellOutput): void; + // appendOutputItems(output:string, items: NotebookCellOutputItem[]):void; + // replaceOutputItems(output:string, items: NotebookCellOutputItem[]):void; + // } + + // export function createNotebookCellExecution(cell: NotebookCell, startTime?: number): NotebookCellExecution; + // export const onDidStartNotebookCellExecution: Event; + // export const onDidStopNotebookCellExecution: Event; + + export interface NotebookCellMetadata { + + /** + * Controls if the cell is executable. + * This metadata is ignored for markdown cell. + */ + // todo@API infer from kernel + runnable?: boolean; + + /** + * Whether the [execution order](#NotebookCellMetadata.executionOrder) indicator will be displayed. + * Defaults to true. + */ + hasExecutionOrder?: boolean; + + /** + * The order in which this cell was executed. + */ + executionOrder?: number; + + /** + * A status message to be shown in the cell's status bar + */ + // todo@API duplicates status bar API + statusMessage?: string; + + /** + * The cell's current run state + */ + runState?: NotebookCellRunState; + + /** + * If the cell is running, the time at which the cell started running + */ + runStartTime?: number; + + /** + * The total duration of the cell's last run + */ + // todo@API depends on having output + lastRunDuration?: number; + } + export interface NotebookKernel { readonly id?: string; label: string; @@ -1619,6 +1582,12 @@ declare module 'vscode' { detail?: string; isPreferred?: boolean; preloads?: Uri[]; + + // todo@API + // languages supported by kernel + // first is preferred + // languages: string[]; + // @roblourens // todo@API change to `executeCells(document: NotebookDocument, cells: NotebookCellRange[], context:{isWholeNotebooke: boolean}, token: CancelationToken): void;` // todo@API interrupt vs cancellation, https://github.com/microsoft/vscode/issues/106741 diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts index 1a2b21cf966..7e09d9d1bbf 100644 --- a/src/vs/workbench/api/browser/mainThreadNotebook.ts +++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts @@ -23,7 +23,7 @@ import { INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookB import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { INotebookCellStatusBarService } from 'vs/workbench/contrib/notebook/common/notebookCellStatusBarService'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, NotebookCellOutputsSplice, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, CellEditType, DisplayOrderKey, ICellEditOperation, ICellRange, IEditor, IMainCellDto, INotebookDecorationRenderOptions, INotebookDocumentFilter, INotebookEditorModel, INotebookExclusiveDocumentFilter, NotebookCellsChangeType, NOTEBOOK_DISPLAY_ORDER, TransientMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookEditorModelResolverService } from 'vs/workbench/contrib/notebook/common/notebookEditorModelResolverService'; import { IMainNotebookController, INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IEditorGroup, IEditorGroupsService, preferredSideBySideGroupDirection } from 'vs/workbench/services/editor/common/editorGroupsService'; @@ -569,29 +569,6 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo textModel?.updateLanguages(languages); } - async $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise { - this.logService.debug('MainThreadNotebooks#spliceNotebookCellOutputs', resource.path, cellHandle); - const textModel = this._notebookService.getNotebookTextModel(URI.from(resource)); - - if (!textModel) { - return; - } - - const cell = textModel.cells.find(cell => cell.handle === cellHandle); - - if (!cell) { - return; - } - - textModel.applyEdits(textModel.versionId, [ - { - editType: CellEditType.OutputsSplice, - index: textModel.cells.indexOf(cell), - splices - } - ], true, undefined, () => undefined, undefined); - } - async $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise { const editor = this._notebookService.getNotebookEditor(editorId) as INotebookEditor | undefined; if (editor?.isNotebookEditor) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 3b658592ed8..865ded63659 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -1257,10 +1257,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I // checkProposedApiEnabled(extension); return extHostTypes.CellKind; }, - get CellOutputKind() { - // checkProposedApiEnabled(extension); - return extHostTypes.CellOutputKind; - }, get NotebookCellRunState() { // checkProposedApiEnabled(extension); return extHostTypes.NotebookCellRunState; diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 9faf3f0c43d..43e0853342d 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -50,7 +50,7 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService'; import { TunnelCreationOptions, TunnelProviderFeatures, TunnelOptions } from 'vs/platform/remote/common/tunnel'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor, InternalTimelineOptions } from 'vs/workbench/contrib/timeline/common/timeline'; import { revive } from 'vs/base/common/marshalling'; -import { IProcessedOutput, INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookDisplayOrder, NotebookCellMetadata, NotebookDocumentMetadata, ICellEditOperation, NotebookCellsChangedEventDto, NotebookDataDto, IMainCellDto, INotebookDocumentFilter, INotebookKernelInfoDto2, TransientMetadata, INotebookCellStatusBarEntry, ICellRange, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { Dto } from 'vs/base/common/types'; import { ISerializableEnvironmentVariableCollection } from 'vs/workbench/contrib/terminal/common/environmentVariable'; @@ -745,7 +745,7 @@ export interface ICellDto { source: string[]; language: string; cellKind: CellKind; - outputs: IProcessedOutput[]; + outputs: IOutputDto[]; metadata?: NotebookCellMetadata; } @@ -758,7 +758,7 @@ export type NotebookCellsSplice = [ export type NotebookCellOutputsSplice = [ number /* start */, number /* delete count */, - IProcessedOutput[] + IOutputDto[] ]; export enum NotebookEditorRevealType { @@ -790,7 +790,6 @@ export interface MainThreadNotebookShape extends IDisposable { $onNotebookKernelChange(handle: number, uri: UriComponents | undefined): void; $tryApplyEdits(viewType: string, resource: UriComponents, modelVersionId: number, edits: ICellEditOperation[]): Promise; $updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise; - $spliceNotebookCellOutputs(viewType: string, resource: UriComponents, cellHandle: number, splices: NotebookCellOutputsSplice[]): Promise; $postMessage(editorId: string, forRendererId: string | undefined, value: any): Promise; $setStatusBarEntry(id: number, statusBarEntry: INotebookCellStatusBarEntryDto): Promise; $tryOpenDocument(uriComponents: UriComponents, viewType?: string): Promise; diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts index daa276d8c86..3ca6106e7e7 100644 --- a/src/vs/workbench/api/common/extHostNotebook.ts +++ b/src/vs/workbench/api/common/extHostNotebook.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; 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 { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorPropertiesChangeData, MainContext, MainThreadBulkEditsShape, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostNotebookShape, ICommandDto, IMainContext, IModelAddedData, INotebookDocumentPropertiesChangeData, INotebookDocumentsAndEditorsDelta, INotebookDocumentShowOptions, INotebookEditorPropertiesChangeData, MainContext, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ILogService } from 'vs/platform/log/common/log'; import { CommandsConverter, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; @@ -17,7 +17,7 @@ import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePa import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview'; -import { addIdToOutput, CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookExclusiveDocumentFilter, INotebookKernelInfoDto2, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellStatusbarAlignment, CellUri, INotebookCellStatusBarEntry, INotebookDisplayOrder, INotebookExclusiveDocumentFilter, INotebookKernelInfoDto2, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookDataDto, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; import { ResourceMap } from 'vs/base/common/map'; import { ExtHostCell, ExtHostNotebookDocument } from './extHostNotebookDocument'; @@ -214,7 +214,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private static _notebookKernelProviderHandlePool: number = 0; private readonly _proxy: MainThreadNotebookShape; - private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape; private readonly _notebookContentProviders = new Map(); private readonly _notebookKernelProviders = new Map(); private readonly _documents = new ResourceMap(); @@ -271,7 +270,6 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN private readonly _extensionStoragePaths: IExtensionStoragePaths, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadNotebook); - this._mainThreadBulkEdits = mainContext.getProxy(MainContext.MainThreadBulkEdits); this._commandsConverter = commands.converter; commands.registerArgumentProcessor({ @@ -456,10 +454,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN ...data.metadata }, languages: data.languages, - cells: data.cells.map(cell => ({ - ...cell, - outputs: cell.outputs.map(o => addIdToOutput(o)) - })), + cells: data.cells.map(typeConverters.NotebookCellData.from), }; } @@ -711,7 +706,7 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN } const that = this; - const document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, this._mainThreadBulkEdits, { + const document = new ExtHostNotebookDocument(this._proxy, this._documentsAndEditors, { emitModelChange(event: vscode.NotebookCellsChangeEvent): void { that._onDidChangeNotebookCells.fire(event); }, diff --git a/src/vs/workbench/api/common/extHostNotebookDocument.ts b/src/vs/workbench/api/common/extHostNotebookDocument.ts index d5708a31d9c..b20fb518865 100644 --- a/src/vs/workbench/api/common/extHostNotebookDocument.ts +++ b/src/vs/workbench/api/common/extHostNotebookDocument.ts @@ -5,39 +5,17 @@ import { Emitter, Event } from 'vs/base/common/event'; import { hash } from 'vs/base/common/hash'; -import { Disposable, DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore, dispose } from 'vs/base/common/lifecycle'; import { Schemas } from 'vs/base/common/network'; import { joinPath } from 'vs/base/common/resources'; import { ISplice } from 'vs/base/common/sequence'; import { URI } from 'vs/base/common/uri'; -import * as UUID from 'vs/base/common/uuid'; -import { CellKind, INotebookDocumentPropertiesChangeData, IWorkspaceCellEditDto, MainThreadBulkEditsShape, MainThreadNotebookShape, NotebookCellOutputsSplice, WorkspaceEditType } from 'vs/workbench/api/common/extHost.protocol'; +import { CellKind, INotebookDocumentPropertiesChangeData, MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import { ExtHostDocumentsAndEditors, IExtHostModelAddedData } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { CellEditType, CellOutputKind, diff, IMainCellDto, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import * as extHostTypeConverters from 'vs/workbench/api/common/extHostTypeConverters'; +import { IMainCellDto, IOutputDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; - -interface IObservable { - proxy: T; - onDidChange: Event; -} - -function getObservable(obj: T): IObservable { - const onDidChange = new Emitter(); - const proxy = new Proxy(obj, { - set(target: T, p: PropertyKey, value: any, _receiver: any): boolean { - target[p as keyof T] = value; - onDidChange.fire(); - return true; - } - }); - - return { - proxy, - onDidChange: onDidChange.event - }; -} - class RawContentChangeEvent { constructor(readonly start: number, readonly deletedCount: number, readonly deletedItems: ExtHostCell[], readonly items: ExtHostCell[]) { } @@ -69,14 +47,12 @@ export class ExtHostCell extends Disposable { private _onDidDispose = new Emitter(); readonly onDidDispose: Event = this._onDidDispose.event; - private _onDidChangeOutputs = new Emitter[]>(); - readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; + private _onDidChangeOutputs = new Emitter[]>(); + readonly onDidChangeOutputs: Event[]> = this._onDidChangeOutputs.event; - private _outputs: any[]; - private _outputMapping = new WeakMap(); + private _outputs: IOutputDto[]; private _metadata: vscode.NotebookCellMetadata; - private _metadataChangeListener: IDisposable; readonly handle: number; readonly uri: URI; @@ -85,7 +61,6 @@ export class ExtHostCell extends Disposable { private _cell: vscode.NotebookCell | undefined; constructor( - private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape, private readonly _notebook: ExtHostNotebookDocument, private readonly _extHostDocument: ExtHostDocumentsAndEditors, private readonly _cellData: IMainCellDto, @@ -97,16 +72,9 @@ export class ExtHostCell extends Disposable { this.cellKind = _cellData.cellKind; this._outputs = _cellData.outputs; - for (const output of this._outputs) { - this._outputMapping.set(output, output.outputId); - delete output.outputId; - } - const observableMetadata = getObservable(_cellData.metadata ?? {}); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._updateMetadata(); - })); + + this._metadata = _cellData.metadata ?? {}; } get cell(): vscode.NotebookCell { @@ -123,13 +91,10 @@ export class ExtHostCell extends Disposable { cellKind: this._cellData.cellKind, document: data.document, get language() { return data!.document.languageId; }, - get outputs() { return that._outputs; }, - set outputs(value) { that._updateOutputs(value); }, + get outputs() { return that._outputs.map(extHostTypeConverters.NotebookCellOutput.to); }, + set outputs(_value) { throw new Error('Use WorkspaceEdit to update cell outputs.'); }, get metadata() { return that._metadata; }, - set metadata(value) { - that.setMetadata(value); - that._updateMetadata(); - }, + set metadata(_value) { throw new Error('Use WorkspaceEdit to update cell metadata.'); }, }); } return this._cell; @@ -140,61 +105,12 @@ export class ExtHostCell extends Disposable { this._onDidDispose.fire(); } - setOutputs(newOutputs: vscode.CellOutput[]): void { + setOutputs(newOutputs: IOutputDto[]): void { this._outputs = newOutputs; } - private _updateOutputs(newOutputs: vscode.CellOutput[]) { - const rawDiffs = diff(this._outputs || [], newOutputs || [], (a) => { - return this._outputMapping.has(a); - }); - - const transformedDiffs: ISplice[] = rawDiffs.map(diff => { - for (let i = diff.start; i < diff.start + diff.deleteCount; i++) { - this._outputMapping.delete(this._outputs[i]); - } - - return { - deleteCount: diff.deleteCount, - start: diff.start, - toInsert: diff.toInsert.map((output): IProcessedOutput => { - if (output.outputKind === CellOutputKind.Rich) { - const uuid = UUID.generateUuid(); - this._outputMapping.set(output, uuid); - return { ...output, outputId: uuid }; - } - - this._outputMapping.set(output, undefined); - return output; - }) - }; - }); - - this._outputs = newOutputs; - this._onDidChangeOutputs.fire(transformedDiffs); - } - setMetadata(newMetadata: vscode.NotebookCellMetadata): void { - // Don't apply metadata defaults here, 'undefined' means 'inherit from document metadata' - this._metadataChangeListener.dispose(); - const observableMetadata = getObservable(newMetadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._updateMetadata(); - })); - } - - private _updateMetadata(): Promise { - const index = this._notebook.notebookDocument.cells.indexOf(this.cell); - const edit: IWorkspaceCellEditDto = { - _type: WorkspaceEditType.Cell, - metadata: undefined, - resource: this._notebook.uri, - notebookVersionId: this._notebook.notebookDocument.version, - edit: { editType: CellEditType.Metadata, index, metadata: this._metadata } - }; - - return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] }); + this._metadata = newMetadata; } } @@ -221,8 +137,8 @@ export class ExtHostNotebookDocument extends Disposable { private _cellDisposableMapping = new Map(); private _notebook: vscode.NotebookDocument | undefined; - private _metadata: Required; - private _metadataChangeListener: IDisposable; + // private _metadata: Required; + // private _metadataChangeListener: IDisposable; private _versionId = 0; private _isDirty: boolean = false; private _backupCounter = 1; @@ -233,21 +149,14 @@ export class ExtHostNotebookDocument extends Disposable { constructor( private readonly _proxy: MainThreadNotebookShape, private readonly _documentsAndEditors: ExtHostDocumentsAndEditors, - private readonly _mainThreadBulkEdits: MainThreadBulkEditsShape, private readonly _emitter: INotebookEventEmitter, private readonly _viewType: string, private readonly _contentOptions: vscode.NotebookDocumentContentOptions, - metadata: Required, + private _metadata: Required, public readonly uri: URI, private readonly _storagePath: URI | undefined ) { super(); - - const observableMetadata = getObservable(metadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._tryUpdateMetadata(); - })); } dispose() { @@ -256,36 +165,6 @@ export class ExtHostNotebookDocument extends Disposable { dispose(this._cellDisposableMapping.values()); } - private _updateMetadata(newMetadata: Required) { - this._metadataChangeListener.dispose(); - newMetadata = { - ...notebookDocumentMetadataDefaults, - ...newMetadata - }; - if (this._metadataChangeListener) { - this._metadataChangeListener.dispose(); - } - - const observableMetadata = getObservable(newMetadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._tryUpdateMetadata(); - })); - - this._tryUpdateMetadata(); - } - - private _tryUpdateMetadata() { - const edit: IWorkspaceCellEditDto = { - _type: WorkspaceEditType.Cell, - metadata: undefined, - edit: { editType: CellEditType.DocumentMetadata, metadata: this._metadata }, - resource: this.uri, - notebookVersionId: this.notebookDocument.version, - }; - - return this._mainThreadBulkEdits.$tryApplyWorkspaceEdit({ edits: [edit] }); - } get notebookDocument(): vscode.NotebookDocument { if (!this._notebook) { @@ -301,7 +180,7 @@ export class ExtHostNotebookDocument extends Disposable { get languages() { return that._languages; }, set languages(value: string[]) { that._trySetLanguages(value); }, get metadata() { return that._metadata; }, - set metadata(value: Required) { that._updateMetadata(value); }, + set metadata(_value: Required) { throw new Error('Use WorkspaceEdit to update metadata.'); }, get contentOptions() { return that._contentOptions; } }); } @@ -336,17 +215,7 @@ export class ExtHostNotebookDocument extends Disposable { ...notebookDocumentMetadataDefaults, ...data.metadata }; - - if (this._metadataChangeListener) { - this._metadataChangeListener.dispose(); - } - - const observableMetadata = getObservable(newMetadata); - this._metadata = observableMetadata.proxy; - this._metadataChangeListener = this._register(observableMetadata.onDidChange(() => { - this._tryUpdateMetadata(); - })); - + this._metadata = newMetadata; this._emitter.emitDocumentMetadataChange({ document: this.notebookDocument }); } @@ -383,7 +252,7 @@ export class ExtHostNotebookDocument extends Disposable { const cellDtos = splice[2]; const newCells = cellDtos.map(cell => { - const extCell = new ExtHostCell(this._mainThreadBulkEdits, this, this._documentsAndEditors, cell); + const extCell = new ExtHostCell(this, this._documentsAndEditors, cell); if (!initialization) { addedCellDocuments.push(ExtHostCell.asModelAddData(this.notebookDocument, cell)); @@ -395,12 +264,6 @@ export class ExtHostNotebookDocument extends Disposable { this._cellDisposableMapping.set(extCell.handle, store); } - const store = this._cellDisposableMapping.get(extCell.handle)!; - - store.add(extCell.onDidChangeOutputs((diffs) => { - this.eventuallyUpdateCellOutputs(extCell, diffs); - })); - return extCell; }); @@ -450,7 +313,7 @@ export class ExtHostNotebookDocument extends Disposable { }); } - private _setCellOutputs(index: number, outputs: IProcessedOutput[]): void { + private _setCellOutputs(index: number, outputs: IOutputDto[]): void { const cell = this._cells[index]; cell.setOutputs(outputs); this._emitter.emitCellOutputsChange({ document: this.notebookDocument, cells: [cell.cell] }); @@ -469,23 +332,6 @@ export class ExtHostNotebookDocument extends Disposable { this._emitter.emitCellMetadataChange(event); } - async eventuallyUpdateCellOutputs(cell: ExtHostCell, diffs: ISplice[]) { - const outputDtos: NotebookCellOutputsSplice[] = diffs.map(diff => { - const outputs = diff.toInsert; - return [diff.start, diff.deleteCount, outputs]; - }); - - if (!outputDtos.length) { - return; - } - - await this._proxy.$spliceNotebookCellOutputs(this._viewType, this.uri, cell.handle, outputDtos); - this._emitter.emitCellOutputsChange({ - document: this.notebookDocument, - cells: [cell.cell] - }); - } - getCell(cellHandle: number): ExtHostCell | undefined { return this._cells.find(cell => cell.handle === cellHandle); } diff --git a/src/vs/workbench/api/common/extHostNotebookEditor.ts b/src/vs/workbench/api/common/extHostNotebookEditor.ts index b7cfa39c6c0..9afae7d5be3 100644 --- a/src/vs/workbench/api/common/extHostNotebookEditor.ts +++ b/src/vs/workbench/api/common/extHostNotebookEditor.ts @@ -8,10 +8,16 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { MainThreadNotebookShape } from 'vs/workbench/api/common/extHost.protocol'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { addIdToOutput, CellEditType, ICellEditOperation, ICellReplaceEdit, INotebookEditData, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import * as extHostConverter from 'vs/workbench/api/common/extHostTypeConverters'; +import { CellEditType, ICellEditOperation, ICellReplaceEdit, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import * as vscode from 'vscode'; import { ExtHostNotebookDocument } from './extHostNotebookDocument'; +interface INotebookEditData { + documentVersionId: number; + cellEdits: ICellEditOperation[]; +} + class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { private readonly _documentVersionId: number; @@ -54,17 +60,13 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { }); } - replaceCellOutput(index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[]): void { + replaceCellOutput(index: number, outputs: vscode.NotebookCellOutput[]): void { this._throwIfFinalized(); this._collectedEdits.push({ editType: CellEditType.Output, index, outputs: outputs.map(output => { - if (extHostTypes.NotebookCellOutput.isNotebookCellOutput(output)) { - return addIdToOutput(output.toJSON()); - } else { - return addIdToOutput(output); - } + return extHostConverter.NotebookCellOutput.from(output); }) }); } @@ -78,12 +80,7 @@ class NotebookEditorCellEditBuilder implements vscode.NotebookEditorEdit { editType: CellEditType.Replace, index: from, count: to - from, - cells: cells.map(data => { - return { - ...data, - outputs: data.outputs.map(output => addIdToOutput(output)), - }; - }) + cells: cells.map(extHostConverter.NotebookCellData.from) }); } } diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index 4bcef3df475..5f211700488 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -31,7 +31,7 @@ import { coalesce, isNonEmptyArray } from 'vs/base/common/arrays'; import { RenderLineNumbersType } from 'vs/editor/common/config/editorOptions'; import { CommandsConverter } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; -import { CellOutputKind, IDisplayOutput, INotebookDecorationRenderOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, ICellDto2, INotebookDecorationRenderOptions, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITestItem, ITestState } from 'vs/workbench/contrib/testing/common/testCollection'; export interface PositionLike { @@ -546,6 +546,60 @@ export namespace WorkspaceEdit { notebookMetadata: entry.notebookMetadata, notebookVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version }); + + } else if (entry._type === types.FileEditType.CellOutput) { + if (entry.newOutputs) { + result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.Cell, + metadata: entry.metadata, + resource: entry.uri, + edit: { + editType: CellEditType.Output, + index: entry.index, + append: entry.append, + outputs: entry.newOutputs.map(NotebookCellOutput.from) + } + }); + } + // todo@joh merge metadata and output edit? + if (entry.newMetadata) { + result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.Cell, + metadata: entry.metadata, + resource: entry.uri, + edit: { + editType: CellEditType.Metadata, + index: entry.index, + metadata: entry.newMetadata + } + }); + } + } else if (entry._type === types.FileEditType.CellReplace) { + result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.Cell, + metadata: entry.metadata, + resource: entry.uri, + notebookVersionId: notebooks?.lookupNotebookDocument(entry.uri)?.notebookDocument.version, + edit: { + editType: CellEditType.Replace, + index: entry.index, + count: entry.count, + cells: entry.cells.map(NotebookCellData.from) + } + }); + } else if (entry._type === types.FileEditType.CellOutputItem) { + result.edits.push({ + _type: extHostProtocol.WorkspaceEditType.Cell, + metadata: entry.metadata, + resource: entry.uri, + edit: { + editType: CellEditType.OutputItems, + index: entry.index, + outputId: entry.outputId, + items: entry.newOutputItems?.map(item => ({ mime: item.mime, value: item.value, metadata: item.metadata })) || [], + append: entry.append + } + }); } } } @@ -1290,22 +1344,54 @@ export namespace LanguageSelector { } } -export namespace NotebookCellOutput { - export function from(output: types.NotebookCellOutput): IDisplayOutput { - return output.toJSON(); - } -} +export namespace NotebookCellData { -export namespace NotebookCellOutputItem { - export function from(output: types.NotebookCellOutputItem): IDisplayOutput { + export function from(data: vscode.NotebookCellData): ICellDto2 { return { - outputKind: CellOutputKind.Rich, - data: { [output.mime]: output.value }, - metadata: output.metadata && { custom: output.metadata } + cellKind: data.cellKind, + language: data.language, + source: data.source, + metadata: data.metadata, + outputs: data.outputs.map(output => ({ + outputId: output.id, outputs: (output.outputs || []).map(op => ({ + mime: op.mime, + value: op.value, + metadata: op.metadata + })) + })) }; } } +export namespace NotebookCellOutput { + export function from(output: types.NotebookCellOutput): IOutputDto { + + const data = Object.create(null); + const custom = Object.create(null); + + for (let item of output.outputs) { + data[item.mime] = item.value; + custom[item.mime] = item.metadata; + } + + return { + outputId: output.id, + outputs: (output.outputs || []).map(op => ({ + mime: op.mime, + value: op.value, + metadata: op.metadata + })) || [], + // metadata: isEmptyObject(custom) ? undefined : { custom } + }; + } + + export function to(output: IOutputDto): vscode.NotebookCellOutput { + const items: types.NotebookCellOutputItem[] = output.outputs.map(op => new types.NotebookCellOutputItem(op.mime, op.value, op.metadata)); + return new types.NotebookCellOutput(items, output.outputId); + } +} + + export namespace NotebookExclusiveDocumentPattern { export function from(pattern: { include: vscode.GlobPattern | undefined, exclude: vscode.GlobPattern | undefined }): { include: string | types.RelativePattern | undefined, exclude: string | types.RelativePattern | undefined }; export function from(pattern: vscode.GlobPattern): string | types.RelativePattern; diff --git a/src/vs/workbench/api/common/extHostTypes.ts b/src/vs/workbench/api/common/extHostTypes.ts index 0ead4e754aa..dac847fc2ae 100644 --- a/src/vs/workbench/api/common/extHostTypes.ts +++ b/src/vs/workbench/api/common/extHostTypes.ts @@ -13,7 +13,7 @@ import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; import { FileSystemProviderErrorCode, markAsFileSystemProviderError } from 'vs/platform/files/common/files'; import { RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver'; -import { addIdToOutput, CellEditType, ICellEditOperation, ICellOutputEdit, ITransformedDisplayOutputDto, notebookDocumentMetadataDefaults, NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, ICellEditOperation, notebookDocumentMetadataDefaults, NOTEBOOK_DISPLAY_ORDER } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import type * as vscode from 'vscode'; function es5ClassCompat(target: Function): any { @@ -585,7 +585,10 @@ export interface IFileOperationOptions { export const enum FileEditType { File = 1, Text = 2, - Cell = 3 + Cell = 3, + CellOutput = 4, + CellReplace = 5, + CellOutputItem = 6 } export interface IFileOperation { @@ -611,13 +614,45 @@ export interface IFileCellEdit { metadata?: vscode.WorkspaceEditEntryMetadata; } +export interface ICellEdit { + _type: FileEditType.CellReplace; + metadata?: vscode.WorkspaceEditEntryMetadata; + uri: URI; + index: number; + count: number; + cells: vscode.NotebookCellData[]; +} + +export interface ICellOutputEdit { + _type: FileEditType.CellOutput; + uri: URI; + index: number; + append: boolean; + newOutputs?: NotebookCellOutput[]; + newMetadata?: vscode.NotebookCellMetadata; + metadata?: vscode.WorkspaceEditEntryMetadata; +} + +export interface ICellOutputItemsEdit { + _type: FileEditType.CellOutputItem; + uri: URI; + index: number; + outputId: string; + append: boolean; + newOutputItems?: NotebookCellOutputItem[]; + metadata?: vscode.WorkspaceEditEntryMetadata; +} + + +type WorkspaceEditEntry = IFileOperation | IFileTextEdit | IFileCellEdit | ICellEdit | ICellOutputEdit | ICellOutputItemsEdit; + @es5ClassCompat export class WorkspaceEdit implements vscode.WorkspaceEdit { - private readonly _edits = new Array(); + private readonly _edits: WorkspaceEditEntry[] = []; - _allEntries(): ReadonlyArray { + _allEntries(): ReadonlyArray { return this._edits; } @@ -643,32 +678,33 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit { replaceNotebookCells(uri: URI, start: number, end: number, cells: vscode.NotebookCellData[], metadata?: vscode.WorkspaceEditEntryMetadata): void { if (start !== end || cells.length > 0) { - this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit: { editType: CellEditType.Replace, index: start, count: end - start, cells: cells.map(cell => ({ ...cell, outputs: cell.outputs.map(output => addIdToOutput(output)) })) } }); + this._edits.push({ _type: FileEditType.CellReplace, uri, index: start, count: end - start, cells, metadata }); } } - replaceNotebookCellOutput(uri: URI, index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + replaceNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { this._editNotebookCellOutput(uri, index, false, outputs, metadata); } - appendNotebookCellOutput(uri: URI, index: number, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + appendNotebookCellOutput(uri: URI, index: number, outputs: vscode.NotebookCellOutput[], metadata?: vscode.WorkspaceEditEntryMetadata): void { this._editNotebookCellOutput(uri, index, true, outputs, metadata); } - private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: (vscode.NotebookCellOutput | vscode.CellOutput)[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { - const edit: ICellOutputEdit = { - editType: CellEditType.Output, - index, - append, - outputs: outputs.map(output => { - if (NotebookCellOutput.isNotebookCellOutput(output)) { - return output.toJSON(); - } else { - return addIdToOutput(output); - } - }) - }; - this._edits.push({ _type: FileEditType.Cell, metadata, uri, edit }); + replaceNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + this._editNotebookCellOutputItems(uri, index, outputId, false, items, metadata); + } + + appendNotebookCellOutputItems(uri: URI, index: number, outputId: string, items: NotebookCellOutputItem[], metadata?: vscode.WorkspaceEditEntryMetadata): void { + this._editNotebookCellOutputItems(uri, index, outputId, true, items, metadata); + } + + private _editNotebookCellOutputItems(uri: URI, index: number, id: string, append: boolean, items: vscode.NotebookCellOutputItem[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { + this._edits.push({ _type: FileEditType.CellOutputItem, metadata, uri, index, outputId: id, append, newOutputItems: items }); + } + + private _editNotebookCellOutput(uri: URI, index: number, append: boolean, outputs: vscode.NotebookCellOutput[], metadata: vscode.WorkspaceEditEntryMetadata | undefined): void { + let newOutputs: NotebookCellOutput[] = outputs; + this._edits.push({ _type: FileEditType.CellOutput, metadata, uri, index, append, newOutputs, newMetadata: undefined }); } replaceNotebookCellMetadata(uri: URI, index: number, cellMetadata: vscode.NotebookCellMetadata, metadata?: vscode.WorkspaceEditEntryMetadata): void { @@ -2888,39 +2924,15 @@ export class NotebookCellOutputItem { constructor( readonly mime: string, readonly value: unknown, // JSON'able - readonly metadata?: Record + readonly metadata?: any ) { } } export class NotebookCellOutput { - - static isNotebookCellOutput(obj: unknown): obj is vscode.NotebookCellOutput { - return obj instanceof NotebookCellOutput; - } - - readonly id: string = generateUuid(); - - constructor(readonly outputs: NotebookCellOutputItem[]) { } - - toJSON(): ITransformedDisplayOutputDto { - let data: { [key: string]: unknown; } = {}; - let custom: { [key: string]: unknown; } = {}; - let hasMetadata = false; - - for (let item of this.outputs) { - data[item.mime] = item.value; - if (item.metadata) { - custom[item.mime] = item.metadata; - hasMetadata = true; - } - } - return { - outputId: this.id, - outputKind: CellOutputKind.Rich, - data, - metadata: hasMetadata ? { custom } : undefined - }; - } + constructor( + readonly outputs: NotebookCellOutputItem[], + readonly id: string = generateUuid() + ) { } } export enum CellKind { @@ -2928,12 +2940,6 @@ export enum CellKind { Code = 2 } -export enum CellOutputKind { - Text = 1, - Error = 2, - Rich = 3 -} - export enum NotebookCellRunState { Running = 1, Idle = 2, diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index eb1a73e9c9a..1ad4ee402dc 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -22,7 +22,7 @@ import { EditorControl } from 'vs/workbench/browser/parts/editor/editorControl'; import { IEditorProgressService } from 'vs/platform/progress/common/progress'; import { EditorProgressIndicator } from 'vs/workbench/services/progress/browser/progressIndicator'; import { localize } from 'vs/nls'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { isErrorWithActions, isPromiseCanceledError } from 'vs/base/common/errors'; import { dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { Severity, INotificationService } from 'vs/platform/notification/common/notification'; import { toErrorMessage } from 'vs/base/common/errorMessage'; @@ -40,7 +40,6 @@ import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions' import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; -import { isErrorWithActions, IErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { withNullAsUndefined, withUndefinedAsNull } from 'vs/base/common/types'; import { hash } from 'vs/base/common/hash'; @@ -1022,7 +1021,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Extract possible error actions from the error let errorActions: ReadonlyArray | undefined = undefined; if (isErrorWithActions(error)) { - errorActions = (error as IErrorWithActions).actions; + errorActions = error.actions; } // If the context is USER, we try to show a modal dialog instead of a background notification diff --git a/src/vs/workbench/common/notifications.ts b/src/vs/workbench/common/notifications.ts index e7df7148660..bc3d63845c5 100644 --- a/src/vs/workbench/common/notifications.ts +++ b/src/vs/workbench/common/notifications.ts @@ -7,9 +7,8 @@ import { INotification, INotificationHandle, INotificationActions, INotification import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { isErrorWithActions, isPromiseCanceledError } from 'vs/base/common/errors'; import { Action } from 'vs/base/common/actions'; -import { isErrorWithActions } from 'vs/base/common/errorsWithActions'; import { equals } from 'vs/base/common/arrays'; import { parseLinkedText, LinkedText } from 'vs/base/common/linkedText'; diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts index 8c4bd5e0a68..94cfd235857 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/displayChangeRemeasureFonts.ts @@ -7,7 +7,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IDisplayMainService } from 'vs/platform/display/common/displayMainService'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { clearAllFontInfos } from 'vs/editor/browser/config/configuration'; diff --git a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts index ef56ceaf206..a257c4d65bb 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointsView.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointsView.ts @@ -465,7 +465,7 @@ class ExceptionBreakpointsRenderer implements IListRenderer boolean, event: Event): Event { diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 35380f13cee..de9ca55bc9f 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -11,7 +11,6 @@ import * as errors from 'vs/base/common/errors'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { IExtensionHostDebugService, IOpenExtensionWindowResult } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; @@ -690,7 +689,7 @@ export class RawDebugSession implements IDisposable { const url = error?.url; if (error && url) { const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info"); - return createErrorWithActions(userMessage, { + return errors.createErrorWithActions(userMessage, { actions: [new Action('debug.moreInfo', label, undefined, true, () => { this.openerService.open(URI.parse(url)); return Promise.resolve(null); diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index b468a45a486..2173f184850 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -411,6 +411,7 @@ export interface IExceptionBreakpoint extends IEnablement { readonly filter: string; readonly label: string; readonly condition: string | undefined; + readonly description: string | undefined; } export interface IDataBreakpoint extends IBaseBreakpoint { diff --git a/src/vs/workbench/contrib/debug/common/debugModel.ts b/src/vs/workbench/contrib/debug/common/debugModel.ts index 68184d87ccb..28e44620827 100644 --- a/src/vs/workbench/contrib/debug/common/debugModel.ts +++ b/src/vs/workbench/contrib/debug/common/debugModel.ts @@ -865,7 +865,15 @@ export class DataBreakpoint extends BaseBreakpoint implements IDataBreakpoint { export class ExceptionBreakpoint extends Enablement implements IExceptionBreakpoint { - constructor(public filter: string, public label: string, enabled: boolean, public supportsCondition: boolean, public condition: string | undefined) { + constructor( + public filter: string, + public label: string, + enabled: boolean, + public supportsCondition: boolean, + public condition: string | undefined, + public description: string | undefined, + public conditionDescription: string | undefined + ) { super(enabled, generateUuid()); } @@ -1074,14 +1082,15 @@ export class DebugModel implements IDebugModel { setExceptionBreakpoints(data: DebugProtocol.ExceptionBreakpointsFilter[]): void { if (data) { - if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) => exbp.filter === data[i].filter && exbp.label === data[i].label && exbp.supportsCondition === data[i].supportsCondition)) { + if (this.exceptionBreakpoints.length === data.length && this.exceptionBreakpoints.every((exbp, i) => + exbp.filter === data[i].filter && exbp.label === data[i].label && exbp.supportsCondition === data[i].supportsCondition && exbp.conditionDescription === data[i].conditionDescription && exbp.description === data[i].description)) { // No change return; } this.exceptionBreakpoints = data.map(d => { const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop(); - return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default, !!d.supportsCondition, ebp?.condition); + return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : !!d.default, !!d.supportsCondition, ebp?.condition, d.description, d.conditionDescription); }); this._onDidChangeBreakpoints.fire(undefined); } diff --git a/src/vs/workbench/contrib/debug/common/debugStorage.ts b/src/vs/workbench/contrib/debug/common/debugStorage.ts index f99a939ce12..5c6f5205c14 100644 --- a/src/vs/workbench/contrib/debug/common/debugStorage.ts +++ b/src/vs/workbench/contrib/debug/common/debugStorage.ts @@ -58,7 +58,7 @@ export class DebugStorage { let result: ExceptionBreakpoint[] | undefined; try { result = JSON.parse(this.storageService.get(DEBUG_EXCEPTION_BREAKPOINTS_KEY, StorageScope.WORKSPACE, '[]')).map((exBreakpoint: any) => { - return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition); + return new ExceptionBreakpoint(exBreakpoint.filter, exBreakpoint.label, exBreakpoint.enabled, exBreakpoint.supportsCondition, exBreakpoint.condition, exBreakpoint.description, exBreakpoint.conditionDescription); }); } catch (e) { } diff --git a/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts index 5b504f89cbf..5eafc40d532 100644 --- a/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-sandbox/extensionHostDebugService.ts @@ -5,7 +5,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 163c4430ba3..4c1c975a732 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/extensionsViewlet'; import { localize } from 'vs/nls'; import { timeout, Delayer, Promises } from 'vs/base/common/async'; -import { isPromiseCanceledError } from 'vs/base/common/errors'; +import { createErrorWithActions, isPromiseCanceledError } from 'vs/base/common/errors'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { Event } from 'vs/base/common/event'; @@ -44,7 +44,6 @@ import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { SuggestEnabledInput, attachSuggestEnabledInputBoxStyler } from 'vs/workbench/contrib/codeEditor/browser/suggestEnabledInput/suggestEnabledInput'; import { alert } from 'vs/base/browser/ui/aria/aria'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; import { Registry } from 'vs/platform/registry/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts index be8f1ac3c27..b984e11f93f 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViews.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; -import { isPromiseCanceledError, getErrorMessage } from 'vs/base/common/errors'; +import { isPromiseCanceledError, getErrorMessage, createErrorWithActions } from 'vs/base/common/errors'; import { PagedModel, IPagedModel, IPager, DelayedPagedModel } from 'vs/base/common/paging'; import { SortBy, SortOrder, IQueryOptions } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionManagementServer, IExtensionManagementServerService, EnablementState, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -35,7 +35,6 @@ import { coalesce, distinct, flatten } from 'vs/base/common/arrays'; import { IExperimentService, IExperiment, ExperimentActionType } from 'vs/workbench/contrib/experiments/common/experimentService'; import { alert } from 'vs/base/browser/ui/aria/aria'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IAction, Action, Separator } from 'vs/base/common/actions'; import { ExtensionIdentifier, IExtensionDescription, isLanguagePackExtension } from 'vs/platform/extensions/common/extensions'; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index cc4762f968f..45fc3d5bb79 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -24,7 +24,7 @@ import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-sandbox/extensionsActions'; import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionRecommendationNotificationService } from 'vs/platform/extensionRecommendations/common/extensionRecommendations'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ExtensionRecommendationNotificationServiceChannel } from 'vs/platform/extensionRecommendations/electron-sandbox/extensionRecommendationsIpc'; import { Codicon } from 'vs/base/common/codicons'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts index 42fb39ed911..6230d2b22d9 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionRecommendationsService.test.ts @@ -40,7 +40,7 @@ import { IExperimentService } from 'vs/workbench/contrib/experiments/common/expe import { TestExperimentService } from 'vs/workbench/contrib/experiments/test/electron-browser/experimentService.test'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService, ILogService } from 'vs/platform/log/common/log'; import { IFileService } from 'vs/platform/files/common/files'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts index 86b1a268ac9..0c5e51b8469 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsActions.test.ts @@ -35,7 +35,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; import { ExtensionIdentifier, IExtensionContributions, ExtensionType, IExtensionDescription, IExtension } from 'vs/platform/extensions/common/extensions'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ILabelService, IFormatterChangeEvent } from 'vs/platform/label/common/label'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts index 4a0dfa20ab0..8a64229deaf 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsViews.test.ts @@ -37,7 +37,7 @@ import { IExperimentService, ExperimentState, ExperimentActionType, ExperimentSe import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; import { ExtensionType, IExtension, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService'; import { IMenuService } from 'vs/platform/actions/common/actions'; diff --git a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index 93370b60998..c87416016e7 100644 --- a/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/contrib/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -37,7 +37,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType, IExtension, ExtensionKind } from 'vs/platform/extensions/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { IProductService } from 'vs/platform/product/common/productService'; import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; diff --git a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts index cd7fb061e4e..6bbeff0f38a 100644 --- a/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/browser/editors/textFileEditor.ts @@ -27,7 +27,7 @@ import { ScrollType } from 'vs/editor/common/editorCommon'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { createErrorWithActions } from 'vs/base/common/errors'; import { EditorActivation, IEditorOptions } from 'vs/platform/editor/common/editor'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { IExplorerService } from 'vs/workbench/contrib/files/browser/files'; diff --git a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts index 145efa74298..d79e4a08201 100644 --- a/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts +++ b/src/vs/workbench/contrib/files/electron-sandbox/textFileEditor.ts @@ -8,7 +8,7 @@ import { TextFileEditor } from 'vs/workbench/contrib/files/browser/editors/textF import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput'; import { EditorOptions } from 'vs/workbench/common/editor'; import { FileOperationError, FileOperationResult, IFileService, MIN_MAX_MEMORY_SIZE_MB, FALLBACK_MAX_MEMORY_SIZE_MB } from 'vs/platform/files/common/files'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { createErrorWithActions } from 'vs/base/common/errors'; import { toAction } from 'vs/base/common/actions'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index d7a23d525c8..235dd429996 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -14,7 +14,7 @@ import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/wi import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditorWidget'; import { IModelService } from 'vs/editor/common/services/modelService'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { CellEditType, CellUri, IProcessedOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellEditType, CellUri, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; @@ -564,8 +564,8 @@ abstract class AbstractElementRenderer extends Disposable { } } - private _getFormatedOutputJSON(outputs: IProcessedOutput[]) { - return JSON.stringify(outputs, undefined, '\t'); + private _getFormatedOutputJSON(outputs: IOutputDto[]) { + return JSON.stringify(outputs.map(op => ({ outputs: op.outputs })), undefined, '\t'); } private _buildOutputEditor() { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts index 74a0010fcaf..e063a800f4a 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementOutputs.ts @@ -9,7 +9,7 @@ import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { DiffElementViewModelBase, SideBySideDiffElementViewModel } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { DiffSide, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; -import { ICellOutputViewModel, IDisplayOutputViewModel, IRenderOutput, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellOutputViewModel, IRenderOutput, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { BUILTIN_RENDERER_ID, NotebookCellOutputsSplice } from 'vs/workbench/contrib/notebook/common/notebookCommon'; @@ -49,38 +49,38 @@ export class OutputElement extends Disposable { const outputItemDiv = document.createElement('div'); let result: IRenderOutput | undefined = undefined; - if (this.output.isDisplayOutput()) { - const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel); - const pickedMimeTypeRenderer = mimeTypes[pick]; - if (mimeTypes.length > 1) { - outputItemDiv.style.position = 'relative'; - const mimeTypePicker = DOM.$('.multi-mimetype-output'); - mimeTypePicker.classList.add(...ThemeIcon.asClassNameArray(mimetypeIcon)); - mimeTypePicker.tabIndex = 0; - mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", mimeTypes.map(mimeType => mimeType.mimeType).join(', ')); - outputItemDiv.appendChild(mimeTypePicker); - this.resizeListener.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { - if (e.leftButton) { - e.preventDefault(); - e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output as IDisplayOutputViewModel); - } - })); + const [mimeTypes, pick] = this.output.resolveMimeTypes(this._notebookTextModel); + const pickedMimeTypeRenderer = mimeTypes[pick]; + if (mimeTypes.length > 1) { + outputItemDiv.style.position = 'relative'; + const mimeTypePicker = DOM.$('.multi-mimetype-output'); + mimeTypePicker.classList.add(...ThemeIcon.asClassNameArray(mimetypeIcon)); + mimeTypePicker.tabIndex = 0; + mimeTypePicker.title = nls.localize('mimeTypePicker', "Choose a different output mimetype, available mimetypes: {0}", mimeTypes.map(mimeType => mimeType.mimeType).join(', ')); + outputItemDiv.appendChild(mimeTypePicker); + this.resizeListener.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => { + if (e.leftButton) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output); + } + })); - this.resizeListener.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { - const event = new StandardKeyboardEvent(e); - if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { - e.preventDefault(); - e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output as IDisplayOutputViewModel); - } - }))); - } + this.resizeListener.add((DOM.addDisposableListener(mimeTypePicker, DOM.EventType.KEY_DOWN, async e => { + const event = new StandardKeyboardEvent(e); + if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { + e.preventDefault(); + e.stopPropagation(); + await this.pickActiveMimeTypeRenderer(this._notebookTextModel, this.output); + } + }))); + } - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); + const innerContainer = DOM.$('.output-inner-container'); + DOM.append(outputItemDiv, innerContainer); + if (mimeTypes.length !== 0) { if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { const renderer = this._notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); result = renderer @@ -91,12 +91,6 @@ export class OutputElement extends Disposable { } this.output.pickedMimeType = pick; - } else { - // for text and error, there is no mimetype - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); - - result = this._notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this._notebookTextModel.uri); } this.domNode = outputItemDiv; @@ -113,7 +107,7 @@ export class OutputElement extends Disposable { this._outputContainer.appendChild(outputItemDiv); } - if (result.type !== RenderOutputType.None) { + if (result.type !== RenderOutputType.Mainframe) { // this.viewCell.selfSizeMonitoring = true; this._notebookEditor.createInset( this._diffElementViewModel, @@ -157,7 +151,7 @@ export class OutputElement extends Disposable { elementSizeObserver.startObserving(); this.resizeListener.add(elementSizeObserver); this.updateHeight(index, clientHeight); - } else if (result.type === RenderOutputType.None) { // no-op if it's a webview + } else if (result.type === RenderOutputType.Mainframe) { // no-op if it's a webview const clientHeight = Math.ceil(outputItemDiv.clientHeight); this.updateHeight(index, clientHeight); @@ -166,7 +160,7 @@ export class OutputElement extends Disposable { } } - private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: IDisplayOutputViewModel) { + private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: ICellOutputViewModel) { const [mimeTypes, currIndex] = viewModel.resolveMimeTypes(notebookTextModel); const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ @@ -294,9 +288,7 @@ export class OutputContainer extends Disposable { removedKeys.push(key); // remove element from DOM this._outputContainer.removeChild(value.domNode); - if (key.isDisplayOutput()) { - this._editor.removeInset(this._diffElementViewModel, this._nestedCellViewModel, key, this._diffSide); - } + this._editor.removeInset(this._diffElementViewModel, this._nestedCellViewModel, key, this._diffSide); } }); @@ -334,19 +326,14 @@ export class OutputContainer extends Disposable { showOutputs() { for (let index = 0; index < this._nestedCellViewModel.outputsViewModels.length; index++) { const currOutput = this._nestedCellViewModel.outputsViewModels[index]; - - if (currOutput.isDisplayOutput()) { - // always add to the end - this._editor.showInset(this._diffElementViewModel, currOutput.cellViewModel, currOutput, this._diffSide); - } + // always add to the end + this._editor.showInset(this._diffElementViewModel, currOutput.cellViewModel, currOutput, this._diffSide); } } hideOutputs() { this._outputEntries.forEach((outputElement, cellOutputViewModel) => { - if (cellOutputViewModel.isDisplayOutput()) { - this._editor.hideInset(this._diffElementViewModel, this._nestedCellViewModel, cellOutputViewModel); - } + this._editor.hideInset(this._diffElementViewModel, this._nestedCellViewModel, cellOutputViewModel); }); } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts index 4d10ed22097..faa6b794a57 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffElementViewModel.ts @@ -316,7 +316,7 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { } checkIfOutputsModified() { - return !this.mainDocumentTextModel.transientOptions.transientOutputs && hash(this.original?.outputs ?? []) !== hash(this.modified?.outputs ?? []); + return !this.mainDocumentTextModel.transientOptions.transientOutputs && hash(this.original?.outputs.map(op => op.outputs) ?? []) !== hash(this.modified?.outputs.map(op => op.outputs) ?? []); } checkMetadataIfModified(): boolean { @@ -370,7 +370,7 @@ export class SideBySideDiffElementViewModel extends DiffElementViewModelBase { } getNestedCellViewModel(diffSide: DiffSide): DiffNestedCellViewModel { - throw new Error('Method not implemented.'); + return diffSide === DiffSide.Original ? this.original : this.modified; } getCellByUri(cellUri: URI): IGenericCellViewModel { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css index 407465444d7..370fa55a783 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiff.css @@ -189,7 +189,7 @@ width: 100%; padding: 4px 8px 4px 32px; box-sizing: border-box; - overflow-x: hidden; + overflow: hidden; } .monaco-workbench .notebook-text-diff-editor .cell-body.full .output-info-container.modified .output-view-container .output-view-container-left { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts index 611b0f264b0..dd0920e45d3 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DiffElementViewModelBase } from 'vs/workbench/contrib/notebook/browser/diff/diffElementViewModel'; import { Event } from 'vs/base/common/event'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; @@ -28,15 +28,15 @@ export interface IDiffCellInfo extends ICommonCellInfo { export interface INotebookTextDiffEditor extends ICommonNotebookEditor { readonly textModel?: NotebookTextModel; onMouseUp: Event<{ readonly event: MouseEvent; readonly target: DiffElementViewModelBase; }>; - onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel, output: IDisplayOutputViewModel }>; + onDidDynamicOutputRendered: Event<{ cell: IGenericCellViewModel, output: ICellOutputViewModel }>; getOverflowContainerDomNode(): HTMLElement; getLayoutInfo(): NotebookLayoutInfo; layoutNotebookCell(cell: DiffElementViewModelBase, height: number): void; getOutputRenderer(): OutputRenderer; createInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IInsetRenderOutput, getOffset: () => number, diffSide: DiffSide): void; - showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, diffSide: DiffSide): void; - removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IDisplayOutputViewModel, diffSide: DiffSide): void; - hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: IDisplayOutputViewModel): void; + showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide): void; + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel, diffSide: DiffSide): void; + hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: IDiffNestedCellViewModel, output: ICellOutputViewModel): void; /** * Trigger the editor to scroll from scroll event programmatically */ @@ -44,7 +44,7 @@ export interface INotebookTextDiffEditor extends ICommonNotebookEditor { getCellByInfo(cellInfo: ICommonCellInfo): IGenericCellViewModel; focusNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; focusNextNotebookCell(cell: IGenericCellViewModel, focus: 'editor' | 'container' | 'output'): void; - updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void; + updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, height: number, isInit: boolean): void; deltaCellOutputContainerClassNames(diffSide: DiffSide, cellId: string, added: string[], removed: string[]): void; } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts index 13f8707fe7f..81390f3d9ae 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookTextDiffEditor.ts @@ -23,7 +23,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { getPixelRatio, getZoomLevel } from 'vs/base/browser/browser'; -import { CellEditState, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, ICellOutputViewModel, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, NotebookLayoutInfo } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { DiffSide, DIFF_CELL_MARGIN, IDiffCellInfo, INotebookTextDiffEditor } from 'vs/workbench/contrib/notebook/browser/diff/notebookDiffEditorBrowser'; import { Emitter } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -74,7 +74,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _revealFirst: boolean; private readonly _insetModifyQueueByOutputId = new SequencerByKey(); - protected _onDidDynamicOutputRendered = new Emitter<{ cell: IGenericCellViewModel, output: IDisplayOutputViewModel }>(); + protected _onDidDynamicOutputRendered = new Emitter<{ cell: IGenericCellViewModel, output: ICellOutputViewModel }>(); onDidDynamicOutputRendered = this._onDidDynamicOutputRendered.event; private _localStore: DisposableStore = this._register(new DisposableStore()); @@ -113,7 +113,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD // throw new Error('Method not implemented.'); } - updateOutputHeight(cellInfo: IDiffCellInfo, output: IDisplayOutputViewModel, outputHeight: number, isInit: boolean): void { + updateOutputHeight(cellInfo: IDiffCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void { const diffElement = cellInfo.diffElement; const cell = this.getCellByInfo(cellInfo); const outputIndex = cell.outputsViewModels.indexOf(output); @@ -244,7 +244,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD if (activeWebview.insetMapping) { const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; - const removedItems: IDisplayOutputViewModel[] = []; + const removedItems: ICellOutputViewModel[] = []; activeWebview.insetMapping.forEach((value, key) => { const cell = getActiveNestedCell(value.cellInfo.diffElement); if (!cell) { @@ -610,7 +610,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return cellInfo.diffElement.getCellByUri(cellInfo.cellUri); } - removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, diffSide: DiffSide) { + removeInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -625,7 +625,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }); } - showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: IDisplayOutputViewModel, diffSide: DiffSide) { + showInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, displayOutput: ICellOutputViewModel, diffSide: DiffSide) { this._insetModifyQueueByOutputId.queue(displayOutput.model.outputId + (diffSide === DiffSide.Modified ? '-right' : 'left'), async () => { const activeWebview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; if (!activeWebview) { @@ -644,7 +644,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD }); } - hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: IDisplayOutputViewModel) { + hideInset(cellDiffViewModel: DiffElementViewModelBase, cellViewModel: DiffNestedCellViewModel, output: ICellOutputViewModel) { this._modifiedWebview?.hideInset(output); this._originalWebview?.hideInset(output); } diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 84b8e680814..50e545cf130 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -164,7 +164,7 @@ box-sizing: border-box; } -.monaco-workbench .notebookOverlay .output > div.foreground > .output-inner-container { +.monaco-workbench .notebookOverlay .output > div.foreground.output-inner-container { width: 100%; padding: 4px 8px; box-sizing: border-box; diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index b865c49985d..a5d6464c50c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -72,9 +72,6 @@ import 'vs/workbench/contrib/notebook/browser/contrib/status/editorStatus'; import 'vs/workbench/contrib/notebook/browser/diff/notebookDiffActions'; // Output renderers registration - -import 'vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform'; -import 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; import 'vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform'; /*--------------------------------------------------------------------------------------------- */ diff --git a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts index 497a6b80843..8f209eb0aed 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts @@ -22,7 +22,7 @@ import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/outpu import { RunStateRenderer, TimerRenderer } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, IProcessedOutput, NotebookCellMetadata, NotebookDocumentMetadata, IEditor, INotebookKernelInfo2, ICellRange, IOrderedMimeType, ITransformedDisplayOutputDto, INotebookRendererInfo, IErrorOutput, IStreamOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, NotebookDocumentMetadata, IEditor, INotebookKernelInfo2, ICellRange, IOrderedMimeType, INotebookRendererInfo, ICellOutput, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { IMenu } from 'vs/platform/actions/common/actions'; @@ -75,14 +75,15 @@ export const EXECUTE_CELL_COMMAND_ID = 'notebook.cell.execute'; //#region Output related types export const enum RenderOutputType { - None, + Mainframe, Html, Extension } -export interface IRenderNoOutput { - type: RenderOutputType.None; +export interface IRenderMainframeOutput { + type: RenderOutputType.Mainframe; hasDynamicHeight: boolean; + supportAppend?: boolean; } export interface IRenderPlainHtmlOutput { @@ -100,32 +101,28 @@ export interface IRenderOutputViaExtension { } export type IInsetRenderOutput = IRenderPlainHtmlOutput | IRenderOutputViaExtension; -export type IRenderOutput = IRenderNoOutput | IInsetRenderOutput; +export type IRenderOutput = IRenderMainframeOutput | IInsetRenderOutput; export const outputHasDynamicHeight = (o: IRenderOutput) => o.type !== RenderOutputType.Extension && o.hasDynamicHeight; export interface ICellOutputViewModel { cellViewModel: IGenericCellViewModel; - model: IProcessedOutput; - isDisplayOutput(): this is IDisplayOutputViewModel; - isErrorOutput(): this is IErrorOutputViewModel; - isStreamOutput(): this is IStreamOutputViewModel; + /** + * When rendering an output, `model` should always be used as we convert legacy `text/error` output to `display_data` output under the hood. + */ + model: ICellOutput; + resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number]; + pickedMimeType: number; + supportAppend(): boolean; + toRawJSON(): any; } export interface IDisplayOutputViewModel extends ICellOutputViewModel { - model: ITransformedDisplayOutputDto; resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number]; pickedMimeType: number; } -export interface IErrorOutputViewModel extends ICellOutputViewModel { - model: IErrorOutput; -} - -export interface IStreamOutputViewModel extends ICellOutputViewModel { - model: IStreamOutput; -} //#endregion @@ -718,6 +715,7 @@ export function isCodeCellRenderTemplate(templateData: BaseCellRenderTemplate): } export interface IOutputTransformContribution { + getMimetypes(): string[]; /** * Dispose this contribution. */ @@ -728,7 +726,7 @@ export interface IOutputTransformContribution { * This call is allowed to have side effects, such as placing output * directly into the container element. */ - render(output: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput; + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput; } export interface CellFindMatch { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index 150a340969f..895fa91331c 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -41,7 +41,7 @@ import { IEditorMemento } from 'vs/workbench/common/editor'; import { Memento, MementoObject } from 'vs/workbench/common/memento'; import { PANEL_BORDER } from 'vs/workbench/common/theme'; import { BOTTOM_CELL_TOOLBAR_GAP, BOTTOM_CELL_TOOLBAR_HEIGHT, CELL_BOTTOM_MARGIN, CELL_MARGIN, CELL_OUTPUT_PADDING, CELL_RUN_GUTTER, CELL_TOP_MARGIN, CODE_CELL_LEFT_MARGIN, COLLAPSED_INDICATOR_HEIGHT, SCROLLABLE_ELEMENT_PADDING_TOP } from 'vs/workbench/contrib/notebook/browser/constants'; -import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CellEditState, CellFocusMode, IActiveNotebookEditor, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IGenericCellViewModel, IInsetRenderOutput, INotebookCellList, INotebookCellOutputLayoutInfo, INotebookDeltaDecoration, INotebookEditor, INotebookEditorContribution, INotebookEditorContributionDescription, INotebookEditorCreationOptions, INotebookEditorMouseEvent, NotebookEditorOptions, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_EXECUTING_NOTEBOOK, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_EDITOR_RUNNABLE, NOTEBOOK_HAS_MULTIPLE_KERNELS, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookEditorExtensionsRegistry } from 'vs/workbench/contrib/notebook/browser/notebookEditorExtensions'; import { NotebookKernelProviderAssociation, NotebookKernelProviderAssociations, notebookKernelProviderAssociationsSettingId } from 'vs/workbench/contrib/notebook/browser/notebookKernelAssociation'; import { NotebookCellList } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList'; @@ -1080,7 +1080,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor if (this._webview?.insetMapping) { const updateItems: IDisplayOutputLayoutUpdateRequest[] = []; - const removedItems: IDisplayOutputViewModel[] = []; + const removedItems: ICellOutputViewModel[] = []; this._webview?.insetMapping.forEach((value, key) => { const cell = this.viewModel?.getCellByHandle(value.cellInfo.cellHandle); if (!cell || !(cell instanceof CodeCellViewModel)) { @@ -2041,10 +2041,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor } removeInset(output: ICellOutputViewModel) { - if (!output.isDisplayOutput()) { - return; - } - this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => { if (!this._webview || !this._webviewResolved) { return; @@ -2058,10 +2054,6 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return; } - if (!output.isDisplayOutput()) { - return; - } - this._insetModifyQueueByOutputId.queue(output.model.outputId, async () => { this._webview!.hideInset(output); }); @@ -2100,7 +2092,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditor return this.viewModel?.viewCells.find(vc => vc.handle === cellHandle) as CodeCellViewModel; } - updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, outputHeight: number, isInit: boolean): void { + updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, outputHeight: number, isInit: boolean): void { const cell = this.viewModel?.viewCells.find(vc => vc.handle === cellInfo.cellHandle); if (cell && cell instanceof CodeCellViewModel) { const outputIndex = cell.outputsViewModels.indexOf(output); diff --git a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts index e97746c124c..c382e015505 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookRegistry.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { BrandedService, IConstructorSignature1 } from 'vs/platform/instantiation/common/instantiation'; import { ICommonNotebookEditor, IOutputTransformContribution } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; @@ -11,7 +10,6 @@ export type IOutputTransformCtor = IConstructorSignature1(id: string, kind: CellOutputKind, ctor: { new(editor: ICommonNotebookEditor, ...services: Services): IOutputTransformContribution }): void { - this.outputTransforms.push({ id: id, kind: kind, ctor: ctor as IOutputTransformCtor }); + registerOutputTransform(id: string, ctor: { new(editor: ICommonNotebookEditor, ...services: Services): IOutputTransformContribution }): void { + this.outputTransforms.push({ id: id, ctor: ctor as IOutputTransformCtor }); } getOutputTransformContributions(): IOutputTransformDescription[] { diff --git a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts index cff6eeabd4f..8587d657663 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts @@ -32,7 +32,7 @@ import { NotebookKernelProviderAssociationRegistry, NotebookViewTypesExtensionRe import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, CellOutputKind, DisplayOrderKey, ICellEditOperation, IDisplayOutput, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, ITransformedDisplayOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ACCESSIBLE_NOTEBOOK_DISPLAY_ORDER, BUILTIN_RENDERER_ID, CellEditType, CellKind, DisplayOrderKey, ICellEditOperation, INotebookDecorationRenderOptions, INotebookKernelInfo2, INotebookKernelProvider, INotebookMarkdownRendererInfo, INotebookRendererInfo, INotebookTextModel, IOrderedMimeType, IOutputDto, mimeTypeIsAlwaysSecure, mimeTypeSupportedByCore, notebookDocumentFilterMatch, NotebookEditorPriority, NOTEBOOK_DISPLAY_ORDER, RENDERER_NOT_AVAILABLE, sortMimeTypes } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookMarkdownRenderer'; import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer'; import { NotebookEditorDescriptor, NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider'; @@ -525,14 +525,11 @@ export class NotebookService extends Disposable implements INotebookService, ICu language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - return { - ...output, - outputId: UUID.generateUuid() - }; - } - - return output; + return { + ...output, + // paste should generate new outputId + outputId: UUID.generateUuid() + }; }), metadata: { editable: cell.metadata?.editable, @@ -564,14 +561,10 @@ export class NotebookService extends Disposable implements INotebookService, ICu language: cell.language, cellKind: cell.cellKind, outputs: cell.outputs.map(output => { - if (output.outputKind === CellOutputKind.Rich) { - return { - ...output, - outputId: UUID.generateUuid() - }; - } - - return output; + return { + ...output, + outputId: UUID.generateUuid() + }; }), metadata: { editable: cell.metadata?.editable, @@ -829,13 +822,20 @@ export class NotebookService extends Disposable implements INotebookService, ICu return Iterable.map(this._models.values(), data => data.model); } - getMimeTypeInfo(textModel: NotebookTextModel, output: ITransformedDisplayOutputDto): readonly IOrderedMimeType[] { + getMimeTypeInfo(textModel: NotebookTextModel, output: IOutputDto): readonly IOrderedMimeType[] { // TODO@rebornix no string[] casting return this._getOrderedMimeTypes(textModel, output, textModel.metadata.displayOrder as string[] ?? []); } - private _getOrderedMimeTypes(textModel: NotebookTextModel, output: IDisplayOutput, documentDisplayOrder: string[]): IOrderedMimeType[] { - const mimeTypes = Object.keys(output.data); + private _getOrderedMimeTypes(textModel: NotebookTextModel, output: IOutputDto, documentDisplayOrder: string[]): IOrderedMimeType[] { + const mimeTypeSet = new Set(); + let mimeTypes: string[] = []; + output.outputs.forEach(op => { + if (!mimeTypeSet.has(op.mime)) { + mimeTypeSet.add(op.mime); + mimeTypes.push(op.mime); + } + }); const coreDisplayOrder = this._displayOrder; const sorted = sortMimeTypes(mimeTypes, coreDisplayOrder?.userOrder || [], documentDisplayOrder, coreDisplayOrder?.defaultOrder || []); diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts index 39da33ff413..3593c7ca2ff 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/outputRenderer.ts @@ -11,14 +11,15 @@ import { URI } from 'vs/base/common/uri'; export class OutputRenderer { protected readonly _contributions: { [key: string]: IOutputTransformContribution; }; - protected readonly _mimeTypeMapping: { [key: number]: IOutputTransformContribution; }; + protected readonly _renderers: IOutputTransformContribution[]; + private _richMimeTypeRenderers = new Map(); constructor( notebookEditor: ICommonNotebookEditor, private readonly instantiationService: IInstantiationService ) { this._contributions = {}; - this._mimeTypeMapping = {}; + this._renderers = []; const contributions = NotebookRegistry.getOutputTransformContributions(); @@ -26,7 +27,9 @@ export class OutputRenderer { try { const contribution = this.instantiationService.createInstance(desc.ctor, notebookEditor); this._contributions[desc.id] = contribution; - this._mimeTypeMapping[desc.kind] = contribution; + contribution.getMimetypes().forEach(mimetype => { + this._richMimeTypeRenderers.set(mimetype, contribution); + }); } catch (err) { onUnexpectedError(err); } @@ -34,20 +37,39 @@ export class OutputRenderer { } renderNoop(viewModel: ICellOutputViewModel, container: HTMLElement): IRenderOutput { - const output = viewModel.model; const contentNode = document.createElement('p'); - contentNode.innerText = `No renderer could be found for output. It has the following output type: ${output.outputKind}`; + contentNode.innerText = `No renderer could be found for output.`; container.appendChild(contentNode); - return { type: RenderOutputType.None, hasDynamicHeight: false }; + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false }; } render(viewModel: ICellOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI | undefined): IRenderOutput { - const output = viewModel.model; - const transform = this._mimeTypeMapping[output.outputKind]; + if (!viewModel.model.outputs.length) { + return this.renderNoop(viewModel, container); + } - if (transform) { - return transform.render(viewModel, container, preferredMimeType, notebookUri); + if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { + const contentNode = document.createElement('p'); + const mimeTypes = viewModel.model.outputs.map(op => op.mime); + + const mimeTypesMessage = mimeTypes.join(', '); + + if (preferredMimeType) { + contentNode.innerText = `No renderer could be found for MIME type: ${preferredMimeType}`; + } else { + contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; + } + + container.appendChild(contentNode); + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false }; + } + + const renderer = this._richMimeTypeRenderers.get(preferredMimeType); + const items = viewModel.model.outputs.filter(op => op.mime === preferredMimeType); + + if (items.length && renderer) { + return renderer.render(viewModel, items, container, notebookUri); } else { return this.renderNoop(viewModel, container); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts index 6a52ef64a97..e0d909423bc 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform.ts @@ -3,22 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import { RGBA, Color } from 'vs/base/common/color'; import { ansiColorIdentifiers } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { ICommonNotebookEditor, IErrorOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -export class ErrorTransform implements IOutputTransformContribution { - constructor( - _editor: ICommonNotebookEditor, - @IThemeService private readonly themeService: IThemeService - ) { - } - - render(viewModel: IErrorOutputViewModel, container: HTMLElement): IRenderOutput { - const output = viewModel.model; +export class ErrorTransform { + static render(output: any, container: HTMLElement, themeService: IThemeService): IRenderOutput { const header = document.createElement('div'); const headerMessage = output.ename && output.evalue ? `${output.ename}: ${output.evalue}` @@ -31,20 +22,15 @@ export class ErrorTransform implements IOutputTransformContribution { traceback.classList.add('traceback'); if (output.traceback) { for (let j = 0; j < output.traceback.length; j++) { - traceback.appendChild(handleANSIOutput(output.traceback[j], this.themeService)); + traceback.appendChild(handleANSIOutput(output.traceback[j], themeService)); } } container.appendChild(traceback); container.classList.add('error'); - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - dispose(): void { + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false }; } } -NotebookRegistry.registerOutputTransform('notebook.output.error', CellOutputKind.Error, ErrorTransform); - /** * @param text The content to stylize. * @returns An {@link HTMLSpanElement} that contains the potentially stylized text. diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts index bf27a0f05cb..526f00c091c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/richTransform.ts @@ -3,10 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; import * as DOM from 'vs/base/browser/dom'; -import { ICommonNotebookEditor, IDisplayOutputViewModel, IOutputTransformContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellOutputViewModel, ICommonNotebookEditor, IOutputTransformContribution as IOutputRendererContribution, IRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { isArray } from 'vs/base/common/types'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IEditorOptions } from 'vs/editor/common/config/editorOptions'; @@ -21,65 +20,30 @@ import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/vi import { IOpenerService } from 'vs/platform/opener/common/opener'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { ErrorTransform } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/errorTransform'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -class RichRenderer implements IOutputTransformContribution { - private _richMimeTypeRenderers = new Map IRenderOutput>(); +function getStringValue(data: unknown): string { + return isArray(data) ? data.join('') : String(data); +} + +class JSONRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['application/json']; + } constructor( public notebookEditor: ICommonNotebookEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @IModelService private readonly modelService: IModelService, @IModeService private readonly modeService: IModeService, - @IThemeService private readonly themeService: IThemeService, - @IOpenerService readonly openerService: IOpenerService, - @ITextFileService readonly textFileService: ITextFileService, ) { - this._richMimeTypeRenderers.set('application/json', this.renderJSON.bind(this)); - this._richMimeTypeRenderers.set('application/javascript', this.renderJavaScript.bind(this)); - this._richMimeTypeRenderers.set('text/html', this.renderHTML.bind(this)); - this._richMimeTypeRenderers.set('image/svg+xml', this.renderSVG.bind(this)); - this._richMimeTypeRenderers.set('text/markdown', this.renderMarkdown.bind(this)); - this._richMimeTypeRenderers.set('image/png', this.renderPNG.bind(this)); - this._richMimeTypeRenderers.set('image/jpeg', this.renderJPEG.bind(this)); - this._richMimeTypeRenderers.set('text/plain', this.renderPlainText.bind(this)); - this._richMimeTypeRenderers.set('text/x-javascript', this.renderCode.bind(this)); - this._richMimeTypeRenderers.set('application/x.notebook.error-traceback', this._renderErrorTraceback.bind(this)); + super(); } - render(output: IDisplayOutputViewModel, container: HTMLElement, preferredMimeType: string | undefined, notebookUri: URI): IRenderOutput { - if (!output.model.data) { - const contentNode = document.createElement('p'); - contentNode.innerText = `No data could be found for output.`; - container.appendChild(contentNode); - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) { - const contentNode = document.createElement('p'); - const mimeTypes = []; - for (const property in output.model.data) { - mimeTypes.push(property); - } - - const mimeTypesMessage = mimeTypes.join(', '); - - if (preferredMimeType) { - contentNode.innerText = `No renderer could be found for MIME type: ${preferredMimeType}`; - } else { - contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`; - } - - container.appendChild(contentNode); - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - const renderer = this._richMimeTypeRenderers.get(preferredMimeType); - return renderer!(output, notebookUri, container); - } - - renderJSON(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['application/json']; - const str = JSON.stringify(data, null, '\t'); + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const str = items.map(item => JSON.stringify(item.value, null, '\t')).join(''); const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { ...getOutputSimpleEditorOptions(), @@ -105,15 +69,57 @@ class RichRenderer implements IOutputTransformContribution { width }); - container.style.height = `${height + 16}px`; + container.style.height = `${height + 8}px`; - return { type: RenderOutputType.None, hasDynamicHeight: true }; + return { type: RenderOutputType.Mainframe, hasDynamicHeight: true }; + } +} + +class JavaScriptRendererContrib extends Disposable implements IOutputRendererContribution { + getMimetypes() { + return ['application/javascript']; } - renderCode(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['text/x-javascript']; - const str = (isArray(data) ? data.join('') : data) as string; + constructor( + public notebookEditor: ICommonNotebookEditor, + ) { + super(); + } + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + let scriptVal = ''; + items.forEach(item => { + const data = item.value; + const str = isArray(data) ? data.join('') : data; + scriptVal += ``; + + }); + return { + type: RenderOutputType.Html, + source: output, + htmlContent: scriptVal, + hasDynamicHeight: false + }; + } +} + +class CodeRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['text/x-javascript']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + @IModelService private readonly modelService: IModelService, + @IModeService private readonly modeService: IModeService, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const str = items.map(item => getStringValue(item.value)).join(''); const editor = this.instantiationService.createInstance(CodeEditorWidget, container, { ...getOutputSimpleEditorOptions(), dimension: { @@ -138,99 +144,227 @@ class RichRenderer implements IOutputTransformContribution { width }); - container.style.height = `${height + 16}px`; + container.style.height = `${height + 8}px`; - return { type: RenderOutputType.None, hasDynamicHeight: true }; - } - - renderJavaScript(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['application/javascript']; - const str = isArray(data) ? data.join('') : data; - const scriptVal = ``; - return { - type: RenderOutputType.Html, - source: output, - htmlContent: scriptVal, - hasDynamicHeight: false - }; - } - - renderHTML(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['text/html']; - const str = (isArray(data) ? data.join('') : data) as string; - return { - type: RenderOutputType.Html, - source: output, - htmlContent: str, - hasDynamicHeight: false - }; - } - - renderSVG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['image/svg+xml']; - const str = (isArray(data) ? data.join('') : data) as string; - return { - type: RenderOutputType.Html, - source: output, - htmlContent: str, - hasDynamicHeight: false - }; - } - - renderMarkdown(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['text/markdown']; - const str = (isArray(data) ? data.join('') : data) as string; - const mdOutput = document.createElement('div'); - const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); - mdOutput.appendChild(mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }, undefined, { gfm: true }).element); - container.appendChild(mdOutput); - - return { type: RenderOutputType.None, hasDynamicHeight: true }; - } - - renderPNG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const image = document.createElement('img'); - image.src = `data:image/png;base64,${output.model.data['image/png']}`; - const display = document.createElement('div'); - display.classList.add('display'); - display.appendChild(image); - container.appendChild(display); - return { type: RenderOutputType.None, hasDynamicHeight: true }; - } - - renderJPEG(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const image = document.createElement('img'); - image.src = `data:image/jpeg;base64,${output.model.data['image/jpeg']}`; - const display = document.createElement('div'); - display.classList.add('display'); - display.appendChild(image); - container.appendChild(display); - return { type: RenderOutputType.None, hasDynamicHeight: true }; - } - - renderPlainText(output: IDisplayOutputViewModel, notebookUri: URI, container: HTMLElement): IRenderOutput { - const data = output.model.data['text/plain']; - const contentNode = DOM.$('.output-plaintext'); - truncatedArrayOfString(contentNode, isArray(data) ? data : [data], this.openerService, this.textFileService, this.themeService); - container.appendChild(contentNode); - - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - private _renderErrorTraceback(outputViewModel: IDisplayOutputViewModel, _notebookUri: URI, container: HTMLElement): IRenderOutput { - const output = outputViewModel.model.data['application/x.notebook.error-traceback'] as any; - const transform = new ErrorTransform(this.notebookEditor, this.themeService); - transform.render(output, container); - transform.dispose(); - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - dispose(): void { + return { type: RenderOutputType.Mainframe, hasDynamicHeight: true }; } } -NotebookRegistry.registerOutputTransform('notebook.output.rich', CellOutputKind.Rich, RichRenderer); +class StreamRendererContrib extends Disposable implements IOutputRendererContribution { + getMimetypes() { + return ['application/x.notebook.stream']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IOpenerService private readonly openerService: IOpenerService, + @IThemeService private readonly themeService: IThemeService, + @ITextFileService private readonly textFileService: ITextFileService + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + items.forEach(item => { + const text = getStringValue(item.value); + const contentNode = DOM.$('span.output-stream'); + truncatedArrayOfString(contentNode, [text], this.openerService, this.textFileService, this.themeService); + container.appendChild(contentNode); + }); + + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false }; + } +} + +class ErrorRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['application/x.notebook.error-traceback']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IThemeService private readonly themeService: IThemeService + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + items.forEach(item => { + const data = item.value; + + ErrorTransform.render(data, container, this.themeService); + }); + + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false }; + } +} + +class PlainTextRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['text/plain']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IOpenerService private readonly openerService: IOpenerService, + @IThemeService private readonly themeService: IThemeService, + @ITextFileService private readonly textFileService: ITextFileService + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const str = items.map(item => getStringValue(item.value)); + const contentNode = DOM.$('.output-plaintext'); + truncatedArrayOfString(contentNode, str, this.openerService, this.textFileService, this.themeService); + container.appendChild(contentNode); + + return { type: RenderOutputType.Mainframe, hasDynamicHeight: false, supportAppend: true }; + } +} + +class HTMLRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['text/html']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const data = items.map(item => getStringValue(item.value)).join(''); + + const str = (isArray(data) ? data.join('') : data) as string; + return { + type: RenderOutputType.Html, + source: output, + htmlContent: str, + hasDynamicHeight: false + }; + } +} + +class SVGRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['image/svg+xml']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + const str = items.map(item => getStringValue(item.value)).join(''); + return { + type: RenderOutputType.Html, + source: output, + htmlContent: str, + hasDynamicHeight: false + }; + } +} + +class MdRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['text/markdown']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + @IInstantiationService private readonly instantiationService: IInstantiationService, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI): IRenderOutput { + items.forEach(item => { + const data = item.value; + const str = (isArray(data) ? data.join('') : data) as string; + const mdOutput = document.createElement('div'); + const mdRenderer = this.instantiationService.createInstance(MarkdownRenderer, { baseUrl: dirname(notebookUri) }); + mdOutput.appendChild(mdRenderer.render({ value: str, isTrusted: true, supportThemeIcons: true }, undefined, { gfm: true }).element); + container.appendChild(mdOutput); + }); + + return { type: RenderOutputType.Mainframe, hasDynamicHeight: true }; + } +} + +class PNGRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['image/png']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + items.forEach(item => { + const image = document.createElement('img'); + const imagedata = item.value; + image.src = `data:image/png;base64,${imagedata}`; + const display = document.createElement('div'); + display.classList.add('display'); + display.appendChild(image); + container.appendChild(display); + }); + return { type: RenderOutputType.Mainframe, hasDynamicHeight: true }; + } +} + +class JPEGRendererContrib extends Disposable implements IOutputRendererContribution { + + getMimetypes() { + return ['image/jpeg']; + } + + constructor( + public notebookEditor: ICommonNotebookEditor, + ) { + super(); + } + + render(output: ICellOutputViewModel, items: IOutputItemDto[], container: HTMLElement, notebookUri: URI | undefined): IRenderOutput { + items.forEach(item => { + const image = document.createElement('img'); + const imagedata = item.value; + image.src = `data:image/jpeg;base64,${imagedata}`; + const display = document.createElement('div'); + display.classList.add('display'); + display.appendChild(image); + container.appendChild(display); + }); + + return { type: RenderOutputType.Mainframe, hasDynamicHeight: true }; + } +} + +NotebookRegistry.registerOutputTransform('json', JSONRendererContrib); +NotebookRegistry.registerOutputTransform('javascript', JavaScriptRendererContrib); +NotebookRegistry.registerOutputTransform('html', HTMLRendererContrib); +NotebookRegistry.registerOutputTransform('svg', SVGRendererContrib); +NotebookRegistry.registerOutputTransform('markdown', MdRendererContrib); +NotebookRegistry.registerOutputTransform('png', PNGRendererContrib); +NotebookRegistry.registerOutputTransform('jpeg', JPEGRendererContrib); +NotebookRegistry.registerOutputTransform('plain', PlainTextRendererContrib); +NotebookRegistry.registerOutputTransform('code', CodeRendererContrib); +NotebookRegistry.registerOutputTransform('error-trace', ErrorRendererContrib); +NotebookRegistry.registerOutputTransform('stream-text', StreamRendererContrib); export function getOutputSimpleEditorOptions(): IEditorOptions { return { diff --git a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts b/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts deleted file mode 100644 index 17276e2c6b7..00000000000 --- a/src/vs/workbench/contrib/notebook/browser/view/output/transforms/streamTransform.ts +++ /dev/null @@ -1,36 +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 DOM from 'vs/base/browser/dom'; -import { CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; -import { NotebookRegistry } from 'vs/workbench/contrib/notebook/browser/notebookRegistry'; -import { ICommonNotebookEditor, IOutputTransformContribution, IRenderOutput, IStreamOutputViewModel, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { truncatedArrayOfString } from 'vs/workbench/contrib/notebook/browser/view/output/transforms/textHelper'; -import { IOpenerService } from 'vs/platform/opener/common/opener'; -import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; - -class StreamRenderer implements IOutputTransformContribution { - constructor( - editor: ICommonNotebookEditor, - @IOpenerService readonly openerService: IOpenerService, - @ITextFileService readonly textFileService: ITextFileService, - @IThemeService readonly themeService: IThemeService - ) { - } - - render(viewModel: IStreamOutputViewModel, container: HTMLElement): IRenderOutput { - const output = viewModel.model; - const contentNode = DOM.$('span.output-stream'); - truncatedArrayOfString(contentNode, [output.text], this.openerService, this.textFileService, this.themeService); - container.appendChild(contentNode); - return { type: RenderOutputType.None, hasDynamicHeight: false }; - } - - dispose(): void { - } -} - -NotebookRegistry.registerOutputTransform('notebook.output.stream', CellOutputKind.Text, StreamRenderer); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 02041b5035c..bc6c5860802 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -17,10 +17,10 @@ import { IFileDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IFileService } from 'vs/platform/files/common/files'; import { IOpenerService, matchesScheme } from 'vs/platform/opener/common/opener'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { CellEditState, ICellOutputViewModel, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads'; import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping'; -import { CellEditState, ICommonCellInfo, ICommonNotebookEditor, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IGenericCellViewModel, IInsetRenderOutput, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; -import { CellOutputKind, IDisplayOutput, INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IWebviewService, WebviewContentPurpose, WebviewElement } from 'vs/workbench/contrib/webview/browser/webview'; import { asWebviewUri } from 'vs/workbench/contrib/webview/common/webviewUri'; @@ -116,11 +116,28 @@ export interface IClearMessage { type: 'clear'; } +export interface IOutputRequestMetadata { + /** + * Additional attributes of a cell metadata. + */ + custom?: { [key: string]: unknown }; +} + +export interface IOutputRequestDto { + /** + * { mime_type: value } + */ + data: { [key: string]: unknown; } + + metadata?: IOutputRequestMetadata; + outputId: string; +} + export interface ICreationRequestMessage { type: 'html'; content: | { type: RenderOutputType.Html; htmlContent: string } - | { type: RenderOutputType.Extension; output: IDisplayOutput; mimeType: string }; + | { type: RenderOutputType.Extension; output: IOutputRequestDto; mimeType: string }; cellId: string; outputId: string; top: number; @@ -596,7 +613,7 @@ export class BackLayerWebView extends Disposable { }); } - private resolveOutputId(id: string): { cellInfo: T, output: IDisplayOutputViewModel } | undefined { + private resolveOutputId(id: string): { cellInfo: T, output: ICellOutputViewModel } | undefined { const output = this.reversedInsetMapping.get(id); if (!output) { return; @@ -859,7 +876,7 @@ var requirejs = (function() { return webview; } - shouldUpdateInset(cell: IGenericCellViewModel, output: IDisplayOutputViewModel, cellTop: number) { + shouldUpdateInset(cell: IGenericCellViewModel, output: ICellOutputViewModel, cellTop: number) { if (this._disposed) { return; } @@ -1030,6 +1047,10 @@ var requirejs = (function() { if (content.type === RenderOutputType.Extension) { const output = content.source.model; renderer = content.renderer; + let data: { [key: string]: unknown } = {}; + let metadata: { [key: string]: unknown } = {}; + data[content.mimeType] = output.outputs.find(op => op.mime === content.mimeType)?.value || undefined; + metadata[content.mimeType] = output.outputs.find(op => op.mime === content.mimeType)?.metadata || undefined; message = { ...messageBase, outputId: output.outputId, @@ -1039,9 +1060,9 @@ var requirejs = (function() { type: RenderOutputType.Extension, mimeType: content.mimeType, output: { - outputKind: CellOutputKind.Rich, - metadata: output.metadata, - data: output.data, + metadata: metadata, + data: data, + outputId: output.outputId }, }, }; @@ -1062,7 +1083,7 @@ var requirejs = (function() { this.reversedInsetMapping.set(message.outputId, content.source); } - removeInset(output: IDisplayOutputViewModel) { + removeInset(output: ICellOutputViewModel) { if (this._disposed) { return; } @@ -1085,7 +1106,7 @@ var requirejs = (function() { this.reversedInsetMapping.delete(id); } - hideInset(output: IDisplayOutputViewModel) { + hideInset(output: ICellOutputViewModel) { if (this._disposed) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts index 66e7e0511d8..f10882298bb 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/cellOutput.ts @@ -10,10 +10,10 @@ import { KeyCode } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; -import { CodeCellRenderTemplate, ICellOutputViewModel, IDisplayOutputViewModel, IInsetRenderOutput, INotebookEditor, IRenderOutput, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { CodeCellRenderTemplate, ICellOutputViewModel, IInsetRenderOutput, INotebookEditor, IRenderOutput, outputHasDynamicHeight, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellWidgets'; import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel'; -import { BUILTIN_RENDERER_ID, CellUri, CellOutputKind, NotebookCellOutputsSplice, IOrderedMimeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { BUILTIN_RENDERER_ID, CellUri, NotebookCellOutputsSplice, IOrderedMimeType } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; @@ -55,6 +55,10 @@ export class CellOutputElement extends Disposable { readonly output: ICellOutputViewModel ) { super(); + + this._register(this.output.model.onDidChangeData(() => { + this.updateOutputRendering(); + })); } detach() { @@ -67,6 +71,22 @@ export class CellOutputElement extends Disposable { } } + updateOutputRendering() { + // user chooses another mimetype + const index = this.viewCell.outputsViewModels.indexOf(this.output); + const nextElement = this.domNode.nextElementSibling; + this.resizeListener.clear(); + const element = this.domNode; + if (element) { + element.parentElement?.removeChild(element); + this.notebookEditor.removeInset(this.output); + } + + // this.output.pickedMimeType = pick; + this.render(index, nextElement as HTMLElement); + this.relayoutCell(); + } + render(index: number, beforeElement?: HTMLElement) { if (this.viewCell.metadata.outputCollapsed || !this.notebookEditor.hasModel()) { @@ -75,60 +95,36 @@ export class CellOutputElement extends Disposable { const notebookTextModel = this.notebookEditor.viewModel.notebookDocument; - let outputItemDiv; let renderResult: IRenderOutput | undefined = undefined; - if (this.output.isDisplayOutput()) { - outputItemDiv = document.createElement('div'); - const [mimeTypes, pick] = this.output.resolveMimeTypes(notebookTextModel); - if (mimeTypes.length > 1) { - this.attachMimetypeSwitcher(outputItemDiv, notebookTextModel, mimeTypes); - } + // Reuse output item div + this.useDedicatedDOM = !(!beforeElement && this.output.supportAppend() && this.previousDivSupportAppend()); + this.domNode = this.useDedicatedDOM ? DOM.$('.output-inner-container') : this.outputContainer.lastChild as HTMLElement; - if (mimeTypes.length !== 0) { - const pickedMimeTypeRenderer = mimeTypes[pick]; - - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); - - - if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { - const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); - renderResult = renderer - ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } - : this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); - } else { - renderResult = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); - } - - this.output.pickedMimeType = pick; - } - } else if (this.output.isStreamOutput()) { - let innerContainer: HTMLElement; - if (!beforeElement && this.isPreviousDivStreamOutput() && this.output.isStreamOutput()) { - this.useDedicatedDOM = false; - // the previous output and this one are both stream output - outputItemDiv = this.outputContainer.lastChild as HTMLElement; - innerContainer = outputItemDiv.lastChild && (outputItemDiv.lastChild).classList.contains('output-inner-container') ? outputItemDiv.lastChild as HTMLElement : document.createElement('div'); - } else { - outputItemDiv = document.createElement('div'); - innerContainer = DOM.$('.output-inner-container'); - } - - outputItemDiv.classList.add('stream-output'); - DOM.append(outputItemDiv, innerContainer); - - renderResult = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),); - } else { - // for text and error, there is no mimetype - outputItemDiv = document.createElement('div'); - const innerContainer = DOM.$('.output-inner-container'); - DOM.append(outputItemDiv, innerContainer); - - renderResult = this.notebookEditor.getOutputRenderer().render(this.output, innerContainer, undefined, this.getNotebookUri(),); + if (this.output.supportAppend()) { + this.domNode.classList.add('support-append'); + } + + const [mimeTypes, pick] = this.output.resolveMimeTypes(notebookTextModel); + if (mimeTypes.length > 1) { + this.attachMimetypeSwitcher(this.domNode, notebookTextModel, mimeTypes); + } + + if (mimeTypes.length !== 0) { + const pickedMimeTypeRenderer = mimeTypes[pick]; + + if (pickedMimeTypeRenderer.rendererId !== BUILTIN_RENDERER_ID) { + const renderer = this.notebookService.getRendererInfo(pickedMimeTypeRenderer.rendererId); + renderResult = renderer + ? { type: RenderOutputType.Extension, renderer, source: this.output, mimeType: pickedMimeTypeRenderer.mimeType } + : this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); + } else { + renderResult = this.notebookEditor.getOutputRenderer().render(this.output, this.domNode, pickedMimeTypeRenderer.mimeType, this.getNotebookUri(),); + } + + this.output.pickedMimeType = pick; } - this.domNode = outputItemDiv; this.renderResult = renderResult; if (!renderResult) { @@ -137,27 +133,28 @@ export class CellOutputElement extends Disposable { } if (beforeElement) { - this.outputContainer.insertBefore(outputItemDiv, beforeElement); + this.outputContainer.insertBefore(this.domNode, beforeElement); } else if (this.useDedicatedDOM) { - this.outputContainer.appendChild(outputItemDiv); + this.outputContainer.appendChild(this.domNode); } - if (renderResult.type !== RenderOutputType.None) { + if (renderResult.type !== RenderOutputType.Mainframe) { this.notebookEditor.createInset(this.viewCell, renderResult, this.viewCell.getOutputOffset(index)); + this.domNode.classList.add('background'); } else { - outputItemDiv.classList.add('foreground', 'output-element'); - outputItemDiv.style.position = 'absolute'; + this.domNode.classList.add('foreground', 'output-element'); + this.domNode.style.position = 'absolute'; } if (outputHasDynamicHeight(renderResult)) { - const clientHeight = outputItemDiv.clientHeight; + const clientHeight = this.domNode.clientHeight; const dimension = { width: this.viewCell.layoutInfo.editorWidth, height: clientHeight }; - const elementSizeObserver = getResizesObserver(outputItemDiv, dimension, () => { + const elementSizeObserver = getResizesObserver(this.domNode, dimension, () => { if (this.outputContainer && document.body.contains(this.outputContainer)) { - const height = Math.ceil(elementSizeObserver.getHeight()); + const height = Math.ceil(elementSizeObserver.getHeight()) + 8; if (clientHeight === height) { return; @@ -175,19 +172,19 @@ export class CellOutputElement extends Disposable { elementSizeObserver.startObserving(); this.resizeListener.add(elementSizeObserver); this.viewCell.updateOutputHeight(index, clientHeight); - } else if (renderResult.type === RenderOutputType.None) { // no-op if it's a webview + } else if (renderResult.type === RenderOutputType.Mainframe) { // no-op if it's a webview if (this.useDedicatedDOM) { - const clientHeight = Math.ceil(outputItemDiv.clientHeight); + const clientHeight = Math.ceil(this.domNode.clientHeight); this.viewCell.updateOutputHeight(index, clientHeight); const top = this.viewCell.getOutputOffsetInContainer(index); - outputItemDiv.style.top = `${top}px`; + this.domNode.style.top = `${top}px`; } } } - private isPreviousDivStreamOutput() { - return this.outputContainer.lastChild && (this.outputContainer.lastChild).classList.contains('stream-output'); + private previousDivSupportAppend() { + return this.outputContainer.lastChild && (this.outputContainer.lastChild).classList.contains('support-append'); } private async attachMimetypeSwitcher(outputItemDiv: HTMLElement, notebookTextModel: NotebookTextModel, mimeTypes: readonly IOrderedMimeType[]) { @@ -201,7 +198,7 @@ export class CellOutputElement extends Disposable { if (e.leftButton) { e.preventDefault(); e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(notebookTextModel, this.output as IDisplayOutputViewModel); + await this.pickActiveMimeTypeRenderer(notebookTextModel, this.output); } })); @@ -210,12 +207,12 @@ export class CellOutputElement extends Disposable { if ((event.equals(KeyCode.Enter) || event.equals(KeyCode.Space))) { e.preventDefault(); e.stopPropagation(); - await this.pickActiveMimeTypeRenderer(notebookTextModel, this.output as IDisplayOutputViewModel); + await this.pickActiveMimeTypeRenderer(notebookTextModel, this.output); } }))); } - private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: IDisplayOutputViewModel) { + private async pickActiveMimeTypeRenderer(notebookTextModel: NotebookTextModel, viewModel: ICellOutputViewModel) { const [mimeTypes, currIndex] = viewModel.resolveMimeTypes(notebookTextModel); const items = mimeTypes.filter(mimeType => mimeType.isTrusted).map((mimeType, index): IMimeTypeRenderer => ({ @@ -367,7 +364,7 @@ export class CellOutputContainer extends Disposable { const renderedOutput = this.outputEntries.get(currOutput); if (renderedOutput && renderedOutput.renderResult) { - if (renderedOutput.renderResult.type !== RenderOutputType.None) { + if (renderedOutput.renderResult.type !== RenderOutputType.Mainframe) { this.notebookEditor.createInset(this.viewCell, renderedOutput.renderResult as IInsetRenderOutput, this.viewCell.getOutputOffset(index)); } else { this.viewCell.updateOutputHeight(index, renderedOutput.domClientHeight); @@ -383,14 +380,14 @@ export class CellOutputContainer extends Disposable { viewUpdateHideOuputs(): void { for (const e of this.outputEntries.keys()) { - this.notebookEditor.hideInset(e as IDisplayOutputViewModel); + this.notebookEditor.hideInset(e); } } onCellWidthChange(): void { this.viewCell.outputsViewModels.forEach((o, i) => { const renderedOutput = this.outputEntries.get(o); - if (renderedOutput && renderedOutput.renderResult && renderedOutput.renderResult.type === RenderOutputType.None && !renderedOutput.renderResult.hasDynamicHeight) { + if (renderedOutput && renderedOutput.renderResult && renderedOutput.renderResult.type === RenderOutputType.Mainframe && !renderedOutput.renderResult.hasDynamicHeight) { this.viewCell.updateOutputHeight(i, renderedOutput.domClientHeight); } }); @@ -401,30 +398,18 @@ export class CellOutputContainer extends Disposable { if (!this.notebookEditor.viewModel!.metadata.trusted) { // not trusted const secureOutput = outputs.filter(output => { - switch (output.model.outputKind) { - case CellOutputKind.Text: - return true; - case CellOutputKind.Error: - return true; - case CellOutputKind.Rich: - { - const mimeTypes = []; - for (const property in output.model.data) { - mimeTypes.push(property); - } + const mimeTypes = output.model.outputs.map(op => op.mime); - if (mimeTypes.indexOf('text/plain') >= 0 - || mimeTypes.indexOf('text/markdown') >= 0 - || mimeTypes.indexOf('application/json') >= 0 - || mimeTypes.includes('image/png')) { - return true; - } - - return false; - } - default: - return false; + if (mimeTypes.indexOf('application/x.notebook.stream') >= 0 + || mimeTypes.indexOf('application/x.notebook.error-traceback') >= 0 + || mimeTypes.indexOf('text/plain') >= 0 + || mimeTypes.indexOf('text/markdown') >= 0 + || mimeTypes.indexOf('application/json') >= 0 + || mimeTypes.includes('image/png')) { + return true; } + + return false; }); return secureOutput; @@ -460,9 +445,7 @@ export class CellOutputContainer extends Disposable { removedKeys.push(key); // remove element from DOM value.detach(); - if (key.isDisplayOutput()) { - this.notebookEditor.removeInset(key); - } + this.notebookEditor.removeInset(key); } }); @@ -525,25 +508,7 @@ export class CellOutputContainer extends Disposable { callback: (content) => { if (content === 'command:workbench.action.openLargeOutput') { const content = JSON.stringify(this.viewCell.outputsViewModels.map(output => { - switch (output.model.outputKind) { - case CellOutputKind.Text: - return { - outputKind: 'text', - text: output.model.text - }; - case CellOutputKind.Error: - return { - outputKind: 'error', - ename: output.model.ename, - evalue: output.model.evalue, - traceback: output.model.traceback - }; - case CellOutputKind.Rich: - return { - data: output.model.data, - metadata: output.model.metadata - }; - } + return output.toRawJSON(); })); const edits = format(content, undefined, {}); const metadataSource = applyEdits(content, edits); diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts index 641e043f08b..07c9350af1c 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEdit.ts @@ -5,7 +5,7 @@ import { Range } from 'vs/editor/common/core/range'; import { Selection } from 'vs/editor/common/core/selection'; -import { CellKind, IProcessedOutput, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, IOutputDto, NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { IResourceUndoRedoElement, UndoRedoElementType } from 'vs/platform/undoRedo/common/undoRedo'; import { URI } from 'vs/base/common/uri'; import { BaseCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/baseCellViewModel'; @@ -16,7 +16,7 @@ import { ITextCellEditingDelegate } from 'vs/workbench/contrib/notebook/common/m export interface IViewCellEditingDelegate extends ITextCellEditingDelegate { createCellViewModel?(cell: NotebookCellTextModel): BaseCellViewModel; - createCell?(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IProcessedOutput[]): BaseCellViewModel; + createCell?(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[]): BaseCellViewModel; } export class JoinCellEdit implements IResourceUndoRedoElement { diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts index ece96c36c2c..5a4db12a75b 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellOutputViewModel.ts @@ -4,16 +4,16 @@ *--------------------------------------------------------------------------------------------*/ import { Disposable } from 'vs/base/common/lifecycle'; -import { ICellOutputViewModel, IDisplayOutputViewModel, IErrorOutputViewModel, IGenericCellViewModel, IStreamOutputViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellOutputViewModel, IGenericCellViewModel } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellOutputKind, IOrderedMimeType, IProcessedOutput, ITransformedDisplayOutputDto, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICellOutput, IOrderedMimeType, RENDERER_NOT_AVAILABLE } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; let handle = 0; export class CellOutputViewModel extends Disposable implements ICellOutputViewModel { outputHandle = handle++; - get model() { - return this._outputData; + get model(): ICellOutput { + return this._outputRawData; } private _pickedMimeType: number = -1; @@ -27,26 +27,19 @@ export class CellOutputViewModel extends Disposable implements ICellOutputViewMo constructor( readonly cellViewModel: IGenericCellViewModel, - private readonly _outputData: IProcessedOutput, + private readonly _outputRawData: ICellOutput, private readonly _notebookService: INotebookService ) { super(); } - isStreamOutput(): this is IStreamOutputViewModel { - return this._outputData.outputKind === CellOutputKind.Text; - } - - isErrorOutput(): this is IErrorOutputViewModel { - return this._outputData.outputKind === CellOutputKind.Error; - } - - isDisplayOutput(): this is IDisplayOutputViewModel { - return this._outputData.outputKind === CellOutputKind.Rich; + supportAppend() { + // if there is any mime type other than `application/x.notebook.stream`, then it's not mergeable. + return !this._outputRawData.outputs.find(op => op.mime !== 'application/x.notebook.stream'); } resolveMimeTypes(textModel: NotebookTextModel): [readonly IOrderedMimeType[], number] { - const mimeTypes = this._notebookService.getMimeTypeInfo(textModel, this.model as ITransformedDisplayOutputDto); + const mimeTypes = this._notebookService.getMimeTypeInfo(textModel, this.model); if (this._pickedMimeType === -1) { // there is at least one mimetype which is safe and can be rendered by the core this._pickedMimeType = Math.max(mimeTypes.findIndex(mimeType => mimeType.rendererId !== RENDERER_NOT_AVAILABLE && mimeType.isTrusted), 0); @@ -54,4 +47,11 @@ export class CellOutputViewModel extends Disposable implements ICellOutputViewMo return [mimeTypes, this._pickedMimeType]; } + + toRawJSON() { + return { + outputs: this._outputRawData.outputs, + // TODO@rebronix, no id, right? + }; + } } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts index 22e154b98ff..dc191b89763 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel.ts @@ -23,7 +23,7 @@ import { NotebookEventDispatcher, NotebookMetadataChangedEvent } from 'vs/workbe import { CellFoldingState, EditorFoldingStateDelegate } from 'vs/workbench/contrib/notebook/browser/contrib/fold/foldingModel'; import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IProcessedOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, NotebookCellMetadata, INotebookSearchOptions, ICellRange, NotebookCellsChangeType, ICell, NotebookCellTextModelSplice, CellEditType, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { FoldingRegions } from 'vs/editor/contrib/folding/foldingRanges'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { MarkdownRenderer } from 'vs/editor/browser/core/markdownRenderer'; @@ -645,7 +645,7 @@ export class NotebookViewModel extends Disposable implements EditorFoldingStateD return result; } - createCell(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IProcessedOutput[], synchronous: boolean, pushUndoStop: boolean = true, previouslyFocused: ICellViewModel[] = []): CellViewModel { + createCell(index: number, source: string, language: string, type: CellKind, metadata: NotebookCellMetadata | undefined, outputs: IOutputDto[], synchronous: boolean, pushUndoStop: boolean = true, previouslyFocused: ICellViewModel[] = []): CellViewModel { const beforeSelections = previouslyFocused.map(e => e.handle); this._notebook.applyEdits(this._notebook.versionId, [ { diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts new file mode 100644 index 00000000000..0172d6a1f2d --- /dev/null +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel.ts @@ -0,0 +1,58 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Emitter } from 'vs/base/common/event'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ICellOutput, IOutputDto, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; + +let _handle = 0; +export class NotebookCellOutputTextModel extends Disposable implements ICellOutput { + handle = _handle++; + private _onDidChangeData = new Emitter(); + onDidChangeData = this._onDidChangeData.event; + + get outputs() { + return this._rawOutput.outputs || []; + } + // get metadata(): NotebookCellOutputMetadata | undefined { + // return this._rawOutput.metadata; + // } + get outputId(): string { + return this._rawOutput.outputId; + } + + constructor( + readonly _rawOutput: IOutputDto + ) { + super(); + } + + replaceData(items: IOutputItemDto[]) { + this._rawOutput.outputs = items; + this._onDidChangeData.fire(); + } + + appendData(items: IOutputItemDto[]) { + this._rawOutput.outputs.push(...items); + // for (const property in data) { + // if ((property === 'text/plain' || property === 'application/x.notebook.stream') && this._data[property] !== undefined) { + // const original = (isArray(this._data[property]) ? this._data[property] : [this._data[property]]) as string[]; + // const more = (isArray(data[property]) ? data[property] : [data[property]]) as string[]; + // this._data[property] = [...original, ...more]; + // } + // } + + this._onDidChangeData.fire(); + } + + toJSON() { + return { + // data: this._data, + // metadata: this._rawOutput.metadata, + outputs: this._rawOutput.outputs, + outputId: this._rawOutput.outputId + }; + } +} diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts index 061b5023066..ab1142f62d5 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookCellTextModel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { Emitter, Event } from 'vs/base/common/event'; -import { ICell, IProcessedOutput, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata, NotebookDocumentMetadata, TransientOptions } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { ICell, NotebookCellOutputsSplice, CellKind, NotebookCellMetadata, NotebookDocumentMetadata, TransientOptions, IOutputDto, ICellOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; import { URI } from 'vs/base/common/uri'; import * as model from 'vs/editor/common/model'; @@ -13,6 +13,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { hash } from 'vs/base/common/hash'; import { PieceTreeTextBuffer } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBuffer'; +import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; export class NotebookCellTextModel extends Disposable implements ICell { private _onDidChangeOutputs = new Emitter(); @@ -27,9 +28,9 @@ export class NotebookCellTextModel extends Disposable implements ICell { private _onDidChangeLanguage = new Emitter(); onDidChangeLanguage: Event = this._onDidChangeLanguage.event; - private _outputs: IProcessedOutput[]; + private _outputs: NotebookCellOutputTextModel[]; - get outputs(): IProcessedOutput[] { + get outputs(): ICellOutput[] { return this._outputs; } @@ -86,13 +87,13 @@ export class NotebookCellTextModel extends Disposable implements ICell { private _source: string, private _language: string, public cellKind: CellKind, - outputs: IProcessedOutput[], + outputs: IOutputDto[], metadata: NotebookCellMetadata | undefined, public readonly transientOptions: TransientOptions, private readonly _modelService: ITextModelService ) { super(); - this._outputs = outputs; + this._outputs = outputs.map(op => new NotebookCellOutputTextModel(op)); this._metadata = metadata || {}; } diff --git a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts index e688833d892..38a90af56c6 100644 --- a/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts +++ b/src/vs/workbench/contrib/notebook/common/model/notebookTextModel.ts @@ -7,7 +7,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; -import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent, IProcessedOutput, CellOutputKind } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { INotebookTextModel, NotebookCellOutputsSplice, NotebookDocumentMetadata, NotebookCellMetadata, ICellEditOperation, CellEditType, CellUri, notebookDocumentMetadataDefaults, diff, NotebookCellsChangeType, ICellDto2, TransientOptions, NotebookTextModelChangedEvent, NotebookRawContentEvent, IOutputDto, ICellOutput, IOutputItemDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { ITextSnapshot } from 'vs/editor/common/model'; import { IUndoRedoService, UndoRedoElementType, IUndoRedoElement, IResourceUndoRedoElement, UndoRedoGroup, IWorkspaceUndoRedoElement } from 'vs/platform/undoRedo/common/undoRedo'; import { MoveCellEdit, SpliceCellsEdit, CellMetadataEdit } from 'vs/workbench/contrib/notebook/common/model/cellEdit'; @@ -15,6 +15,7 @@ import { ITextModelService } from 'vs/editor/common/services/resolverService'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ISequence, LcsDiff } from 'vs/base/common/diff/diff'; import { hash } from 'vs/base/common/hash'; +import { NotebookCellOutputTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellOutputTextModel'; export class NotebookTextModelSnapshot implements ITextSnapshot { @@ -340,19 +341,23 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._assertIndex(edit.index); const cell = this._cells[edit.index]; if (edit.append) { - this._spliceNotebookCellOutputs(cell.handle, [[cell.outputs.length, 0, edit.outputs]], computeUndoRedo); + this._spliceNotebookCellOutputs(cell.handle, [[cell.outputs.length, 0, edit.outputs.map(op => new NotebookCellOutputTextModel(op))]], computeUndoRedo); } else { - this._spliceNotebookCellOutputs2(cell.handle, edit.outputs, computeUndoRedo); + this._spliceNotebookCellOutputs2(cell.handle, edit.outputs.map(op => new NotebookCellOutputTextModel(op)), computeUndoRedo); } break; - case CellEditType.OutputsSplice: + case CellEditType.OutputItems: { - //TODO@jrieken,@rebornix no event, no undo stop (?) this._assertIndex(edit.index); const cell = this._cells[edit.index]; - this._spliceNotebookCellOutputs(cell.handle, edit.splices, computeUndoRedo); - break; + if (edit.append) { + this._appendNotebookCellOutputItems(cell.handle, edit.outputId, edit.items); + } else { + this._replaceNotebookCellOutputItems(cell.handle, edit.outputId, edit.items); + } } + break; + case CellEditType.Metadata: this._assertIndex(edit.index); this._changeCellMetadata(this._cells[edit.index].handle, edit.metadata, computeUndoRedo); @@ -649,7 +654,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel this._eventEmitter.emit({ kind: NotebookCellsChangeType.ChangeLanguage, index: this._cells.indexOf(cell), language: languageId, transient: false }, true); } - private _spliceNotebookCellOutputs2(cellHandle: number, outputs: IProcessedOutput[], computeUndoRedo: boolean): void { + private _spliceNotebookCellOutputs2(cellHandle: number, outputs: ICellOutput[], computeUndoRedo: boolean): void { const cell = this._mapping.get(cellHandle); if (!cell) { return; @@ -675,6 +680,38 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } } + private _appendNotebookCellOutputItems(cellHandle: number, outputId: string, items: IOutputItemDto[]) { + const cell = this._mapping.get(cellHandle); + if (!cell) { + return; + } + + const outputIndex = cell.outputs.findIndex(output => output.outputId === outputId); + + if (outputIndex < 0) { + return; + } + + const output = cell.outputs[outputIndex]; + output.appendData(items); + } + + private _replaceNotebookCellOutputItems(cellHandle: number, outputId: string, items: IOutputItemDto[]) { + const cell = this._mapping.get(cellHandle); + if (!cell) { + return; + } + + const outputIndex = cell.outputs.findIndex(output => output.outputId === outputId); + + if (outputIndex < 0) { + return; + } + + const output = cell.outputs[outputIndex]; + output.replaceData(items); + } + private _moveCellToIdx(index: number, length: number, newIdx: number, synchronous: boolean, pushedToUndoStack: boolean, beforeSelections: number[] | undefined, endSelections: number[] | undefined): boolean { if (pushedToUndoStack) { this._operationManager.pushEditOperation(new MoveCellEdit(this.uri, index, length, newIdx, { @@ -702,19 +739,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel } class OutputSequence implements ISequence { - constructor(readonly outputs: IProcessedOutput[]) { + constructor(readonly outputs: IOutputDto[]) { } getElements(): Int32Array | number[] | string[] { return this.outputs.map(output => { - switch (output.outputKind) { - case CellOutputKind.Rich: - return hash([output.outputKind, output.metadata, output.data]); - case CellOutputKind.Error: - return hash([output.outputKind, output.ename, output.evalue, output.traceback]); - case CellOutputKind.Text: - return hash([output.outputKind, output.text]); - } + return hash(output.outputs); }); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 887839d4944..b3800141c57 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -7,7 +7,6 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { IDiffResult, ISequence } from 'vs/base/common/diff/diff'; import { Event } from 'vs/base/common/event'; import * as glob from 'vs/base/common/glob'; -import * as UUID from 'vs/base/common/uuid'; import { Schemas } from 'vs/base/common/network'; import { basename } from 'vs/base/common/path'; import { isWindows } from 'vs/base/common/platform'; @@ -30,12 +29,6 @@ export enum CellKind { Code = 2 } -export enum CellOutputKind { - Text = 1, - Error = 2, - Rich = 3 -} - export const NOTEBOOK_DISPLAY_ORDER = [ 'application/json', 'application/javascript', @@ -147,27 +140,6 @@ export interface INotebookMarkdownRendererInfo { readonly extensionIsBuiltin: boolean; } -export interface IStreamOutput { - outputKind: CellOutputKind.Text; - text: string; -} - -export interface IErrorOutput { - outputKind: CellOutputKind.Error; - /** - * Exception Name - */ - ename: string; - /** - * Exception Value - */ - evalue: string; - /** - * Exception call stacks - */ - traceback: string[]; -} - export interface NotebookCellOutputMetadata { /** * Additional attributes of a cell metadata. @@ -175,101 +147,50 @@ export interface NotebookCellOutputMetadata { custom?: { [key: string]: unknown }; } -export interface IDisplayOutput { - outputKind: CellOutputKind.Rich; - /** - * { mime_type: value } - */ - data: { [key: string]: unknown; } - - metadata?: NotebookCellOutputMetadata; -} - -export enum MimeTypeRendererResolver { - Core, - Active, - Lazy -} - export interface IOrderedMimeType { mimeType: string; rendererId: string; isTrusted: boolean; } -export interface ITransformedDisplayOutputDto extends IDisplayOutput { +export interface IOutputItemDto { + readonly mime: string; + readonly value: unknown; + readonly metadata?: Record; +} + +export interface IOutputDto { + outputs: IOutputItemDto[]; + /** + * { mime_type: value } + */ + // data: { [key: string]: unknown; } + + // metadata?: NotebookCellOutputMetadata; outputId: string; } -export function isTransformedDisplayOutput(thing: unknown): thing is ITransformedDisplayOutputDto { - return (thing as ITransformedDisplayOutputDto).outputKind === CellOutputKind.Rich && !!(thing as ITransformedDisplayOutputDto).outputId; -} - - -export const addIdToOutput = (output: IRawOutput, id = UUID.generateUuid()): IProcessedOutput => output.outputKind === CellOutputKind.Rich - ? ({ ...output, outputId: id }) : output; - - -export type IProcessedOutput = ITransformedDisplayOutputDto | IStreamOutput | IErrorOutput; - -export type IRawOutput = IDisplayOutput | IStreamOutput | IErrorOutput; - -export interface IOutputRenderRequestOutputInfo { - index: number; +export interface ICellOutput { + outputs: IOutputItemDto[]; + // metadata?: NotebookCellOutsputMetadata; outputId: string; - handlerId: string; - mimeType: string; - output?: IRawOutput; + onDidChangeData: Event; + replaceData(items: IOutputItemDto[]): void; + appendData(items: IOutputItemDto[]): void; } -export interface IOutputRenderRequestCellInfo { - key: T; - outputs: IOutputRenderRequestOutputInfo[]; -} - -export interface IOutputRenderRequest { - items: IOutputRenderRequestCellInfo[]; -} - -export interface IOutputRenderResponseOutputInfo { - index: number; - outputId: string; - mimeType: string; - handlerId: string; - transformedOutput: string; -} - -export interface IOutputRenderResponseCellInfo { - key: T; - outputs: IOutputRenderResponseOutputInfo[]; -} - - -export interface IOutputRenderResponse { - items: IOutputRenderResponseCellInfo[]; -} - - export interface ICell { readonly uri: URI; handle: number; language: string; cellKind: CellKind; - outputs: IProcessedOutput[]; + outputs: ICellOutput[]; metadata?: NotebookCellMetadata; onDidChangeOutputs?: Event; onDidChangeLanguage: Event; onDidChangeMetadata: Event; } -export interface LanguageInfo { - file_extension: string; -} - -export interface IMetadata { - language_info: LanguageInfo; -} - export interface INotebookTextModel { readonly viewType: string; metadata: NotebookDocumentMetadata @@ -291,7 +212,7 @@ export type NotebookCellTextModelSplice = [ export type NotebookCellOutputsSplice = [ start: number /* start */, deleteCount: number /* delete count */, - newOutputs: IProcessedOutput[] + newOutputs: ICellOutput[] ]; export interface IMainCellDto { @@ -301,7 +222,7 @@ export interface IMainCellDto { eol: string; language: string; cellKind: CellKind; - outputs: IProcessedOutput[]; + outputs: IOutputDto[]; metadata?: NotebookCellMetadata; } @@ -350,7 +271,7 @@ export interface NotebookCellsModelMoveEvent { export interface NotebookOutputChangedEvent { readonly kind: NotebookCellsChangeType.Output; readonly index: number; - readonly outputs: IProcessedOutput[]; + readonly outputs: IOutputDto[]; } export interface NotebookCellsChangeLanguageEvent { @@ -398,14 +319,15 @@ export const enum CellEditType { OutputsSplice = 6, Move = 7, Unknown = 8, - CellContent = 9 + CellContent = 9, + OutputItems = 10 } export interface ICellDto2 { source: string; language: string; cellKind: CellKind; - outputs: IProcessedOutput[]; + outputs: IOutputDto[]; metadata?: NotebookCellMetadata; } @@ -419,10 +341,18 @@ export interface ICellReplaceEdit { export interface ICellOutputEdit { editType: CellEditType.Output; index: number; - outputs: IProcessedOutput[]; + outputs: IOutputDto[]; append?: boolean } +export interface ICellOutputItemEdit { + editType: CellEditType.OutputItems; + index: number; + outputId: string; + items: IOutputItemDto[]; + append?: boolean; +} + export interface ICellMetadataEdit { editType: CellEditType.Metadata; index: number; @@ -441,12 +371,6 @@ export interface IDocumentMetadataEdit { metadata: NotebookDocumentMetadata; } -export interface ICellOutputsSpliceEdit { - editType: CellEditType.OutputsSplice; - index: number; - splices: NotebookCellOutputsSplice[]; -} - export interface ICellMoveEdit { editType: CellEditType.Move; index: number; @@ -454,12 +378,7 @@ export interface ICellMoveEdit { newIdx: number; } -export type ICellEditOperation = ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellLanguageEdit | IDocumentMetadataEdit | ICellOutputsSpliceEdit | ICellMoveEdit; - -export interface INotebookEditData { - documentVersionId: number; - cellEdits: ICellEditOperation[]; -} +export type ICellEditOperation = ICellReplaceEdit | ICellOutputEdit | ICellMetadataEdit | ICellLanguageEdit | IDocumentMetadataEdit | ICellMoveEdit | ICellOutputItemEdit; export interface NotebookDataDto { readonly cells: ICellDto2[]; diff --git a/src/vs/workbench/contrib/notebook/common/notebookService.ts b/src/vs/workbench/contrib/notebook/common/notebookService.ts index a298c8f5497..a347ac720b9 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookService.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookService.ts @@ -10,7 +10,7 @@ import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.pr import { Event } from 'vs/base/common/event'; import { INotebookTextModel, INotebookRendererInfo, - IEditor, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, ITransformedDisplayOutputDto, INotebookMarkdownRendererInfo + IEditor, INotebookKernelProvider, INotebookKernelInfo2, TransientMetadata, NotebookDataDto, TransientOptions, INotebookDecorationRenderOptions, INotebookExclusiveDocumentFilter, IOrderedMimeType, IOutputDto, INotebookMarkdownRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -49,7 +49,7 @@ export interface INotebookService { onDidChangeNotebookActiveKernel: Event<{ uri: URI, providerHandle: number | undefined, kernelFriendlyId: string | undefined; }>; registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): IDisposable; - getMimeTypeInfo(textModel: NotebookTextModel, output: ITransformedDisplayOutputDto): readonly IOrderedMimeType[]; + getMimeTypeInfo(textModel: NotebookTextModel, output: IOutputDto): readonly IOrderedMimeType[]; registerNotebookKernelProvider(provider: INotebookKernelProvider): IDisposable; getContributedNotebookKernels(viewType: string, resource: URI, token: CancellationToken): Promise; diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts index 66d7be20dc1..1e7ca334718 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookSimpleWorker.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IRequestHandler } from 'vs/base/common/worker/simpleWorker'; import * as model from 'vs/editor/common/model'; import { PieceTreeTextBufferBuilder } from 'vs/editor/common/model/pieceTreeTextBuffer/pieceTreeTextBufferBuilder'; -import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IProcessedOutput, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, ICellDto2, IMainCellDto, INotebookDiffResult, IOutputDto, NotebookCellMetadata, NotebookCellsChangedEventDto, NotebookCellsChangeType, NotebookCellsSplice2, NotebookDataDto, NotebookDocumentMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Range } from 'vs/editor/common/core/range'; import { EditorWorkerHost } from 'vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl'; @@ -45,7 +45,7 @@ class MirrorCell { private _source: string | string[], public language: string, public cellKind: CellKind, - public outputs: IProcessedOutput[], + public outputs: IOutputDto[], public metadata?: NotebookCellMetadata ) { } diff --git a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts index a93f198dea9..5d1f47606ec 100644 --- a/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/services/notebookWorkerServiceImpl.ts @@ -104,7 +104,7 @@ export class NotebookEditorModelManager extends Disposable { eol: cell.textBuffer.getEOL(), language: cell.language, cellKind: cell.cellKind, - outputs: cell.outputs, + outputs: cell.outputs.map(op => ({ outputId: op.outputId, outputs: op.outputs })), metadata: cell.metadata })), languages: model.languages, diff --git a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts index c1c13513e9b..867e0bcb193 100644 --- a/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts +++ b/src/vs/workbench/contrib/notebook/test/notebookTextModel.test.ts @@ -4,12 +4,11 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { CellKind, CellEditType, CellOutputKind, NotebookTextModelChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellEditType, NotebookTextModelChangedEvent } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { withTestNotebook, TestCell, setupInstantiationService } from 'vs/workbench/contrib/notebook/test/testNotebookEditor'; import { IBulkEditService } from 'vs/editor/browser/services/bulkEditService'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { assertType } from 'vs/base/common/types'; suite('NotebookTextModel', () => { const instantiationService = setupInstantiationService(); @@ -197,15 +196,13 @@ suite('NotebookTextModel', () => { index: 0, editType: CellEditType.Output, outputs: [{ - outputKind: CellOutputKind.Rich, outputId: 'someId', - data: { 'text/markdown': '_Hello_' } + outputs: [{ mime: 'text/markdown', value: '_Hello_' }] }] }], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 1); assert.strictEqual(textModel.cells[0].outputs.length, 1); - assert.strictEqual(textModel.cells[0].outputs[0].outputKind, CellOutputKind.Rich); // append textModel.applyEdits(textModel.versionId, [{ @@ -213,17 +210,14 @@ suite('NotebookTextModel', () => { editType: CellEditType.Output, append: true, outputs: [{ - outputKind: CellOutputKind.Rich, outputId: 'someId2', - data: { 'text/markdown': '_Hello2_' } + outputs: [{ mime: 'text/markdown', value: '_Hello2_' }] }] }], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 1); assert.strictEqual(textModel.cells[0].outputs.length, 2); let [first, second] = textModel.cells[0].outputs; - assertType(first.outputKind === CellOutputKind.Rich); - assertType(second.outputKind === CellOutputKind.Rich); assert.strictEqual(first.outputId, 'someId'); assert.strictEqual(second.outputId, 'someId2'); @@ -232,16 +226,14 @@ suite('NotebookTextModel', () => { index: 0, editType: CellEditType.Output, outputs: [{ - outputKind: CellOutputKind.Rich, outputId: 'someId3', - data: { 'text/plain': 'Last, replaced output' } + outputs: [{ mime: 'text/plain', value: 'Last, replaced output' }] }] }], true, undefined, () => undefined, undefined); assert.strictEqual(textModel.cells.length, 1); assert.strictEqual(textModel.cells[0].outputs.length, 1); [first] = textModel.cells[0].outputs; - assertType(first.outputKind === CellOutputKind.Rich); assert.strictEqual(first.outputId, 'someId3'); } ); diff --git a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts index 0b1a257bbee..6f098280a87 100644 --- a/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts +++ b/src/vs/workbench/contrib/notebook/test/testNotebookEditor.ts @@ -12,13 +12,13 @@ import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; import { Range } from 'vs/editor/common/core/range'; import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo'; import { EditorModel } from 'vs/workbench/common/editor'; -import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, INotebookDeltaDecoration, INotebookEditorCreationOptions, NotebookEditorOptions, ICellOutputViewModel, IInsetRenderOutput, IDisplayOutputViewModel, ICommonCellInfo, IGenericCellViewModel, INotebookCellOutputLayoutInfo, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; +import { ICellViewModel, INotebookEditor, INotebookEditorContribution, INotebookEditorMouseEvent, NotebookLayoutInfo, INotebookDeltaDecoration, INotebookEditorCreationOptions, NotebookEditorOptions, ICellOutputViewModel, IInsetRenderOutput, ICommonCellInfo, IGenericCellViewModel, INotebookCellOutputLayoutInfo, CellEditState } from 'vs/workbench/contrib/notebook/browser/notebookBrowser'; import { OutputRenderer } from 'vs/workbench/contrib/notebook/browser/view/output/outputRenderer'; import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/viewModel/eventDispatcher'; import { CellViewModel, IModelDecorationsChangeAccessor, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel'; import { NotebookCellTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookCellTextModel'; import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel'; -import { CellKind, CellUri, INotebookEditorModel, IProcessedOutput, NotebookCellMetadata, ICellRange, INotebookKernelInfo2, notebookDocumentMetadataDefaults } from 'vs/workbench/contrib/notebook/common/notebookCommon'; +import { CellKind, CellUri, INotebookEditorModel, NotebookCellMetadata, ICellRange, INotebookKernelInfo2, notebookDocumentMetadataDefaults, IOutputDto } from 'vs/workbench/contrib/notebook/common/notebookCommon'; import { Webview } from 'vs/workbench/contrib/webview/browser/webview'; import { ICompositeCodeEditor, IEditor } from 'vs/editor/common/editorCommon'; import { NotImplementedError } from 'vs/base/common/errors'; @@ -44,7 +44,7 @@ export class TestCell extends NotebookCellTextModel { public source: string, language: string, cellKind: CellKind, - outputs: IProcessedOutput[], + outputs: IOutputDto[], modelService: ITextModelService ) { super(CellUri.generate(URI.parse('test:///fake/notebook'), handle), handle, source, language, cellKind, outputs, undefined, { transientMetadata: {}, transientOutputs: false }, modelService); @@ -84,7 +84,7 @@ export class TestNotebookEditor implements INotebookEditor { getCellByInfo(cellInfo: ICommonCellInfo): ICellViewModel { throw new Error('Method not implemented.'); } - updateOutputHeight(cellInfo: ICommonCellInfo, output: IDisplayOutputViewModel, height: number, isInit: boolean): void { + updateOutputHeight(cellInfo: ICommonCellInfo, output: ICellOutputViewModel, height: number, isInit: boolean): void { throw new Error('Method not implemented.'); } setMarkdownCellEditState(cellId: string, editState: CellEditState): void { @@ -445,7 +445,7 @@ export function setupInstantiationService() { return instantiationService; } -export function withTestNotebook(instantiationService: TestInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string, string, CellKind, IProcessedOutput[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { +export function withTestNotebook(instantiationService: TestInstantiationService, blukEditService: IBulkEditService, undoRedoService: IUndoRedoService, cells: [string, string, CellKind, IOutputDto[], NotebookCellMetadata][], callback: (editor: TestNotebookEditor, viewModel: NotebookViewModel, textModel: NotebookTextModel) => void) { const textModelService = instantiationService.get(ITextModelService); const modeService = instantiationService.get(IModeService); diff --git a/src/vs/workbench/contrib/remote/browser/tunnelView.ts b/src/vs/workbench/contrib/remote/browser/tunnelView.ts index f2808ab0899..ed625f3c294 100644 --- a/src/vs/workbench/contrib/remote/browser/tunnelView.ts +++ b/src/vs/workbench/contrib/remote/browser/tunnelView.ts @@ -87,6 +87,7 @@ export class TunnelViewModel extends Disposable implements ITunnelViewModel { this._register(this.model.onCandidatesChanged(() => this._onForwardedPortsChanged.fire())); this._input = { label: nls.localize('remote.tunnelsView.add', "Forward a Port..."), + wideLabel: nls.localize('remote.tunnelsView.add', "Forward a Port..."), tunnelType: TunnelType.Add, remoteHost: 'localhost', remotePort: 0, @@ -265,8 +266,9 @@ class TunnelTreeRenderer extends Disposable implements ITreeRenderer { if (runSource === TaskRunSource.User) { this.getWorkspaceTasks().then(workspaceTasks => { - RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, this.workspaceTrustService, workspaceTasks); + RunAutomaticTasks.promptForPermission(this, this.storageService, this.notificationService, this.workspaceTrustService, this.openerService, workspaceTasks); }); } return value; diff --git a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts index 743b1c75374..7c5b14327d3 100644 --- a/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts +++ b/src/vs/workbench/contrib/tasks/browser/runAutomaticTasks.ts @@ -4,17 +4,21 @@ *--------------------------------------------------------------------------------------------*/ import * as nls from 'vs/nls'; +import * as resources from 'vs/base/common/resources'; import { Disposable } from 'vs/base/common/lifecycle'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { ITaskService, WorkspaceFolderTaskResult } from 'vs/workbench/contrib/tasks/common/taskService'; import { forEach } from 'vs/base/common/collections'; -import { RunOnOptions, Task, TaskRunSource, TASKS_CATEGORY } from 'vs/workbench/contrib/tasks/common/tasks'; +import { RunOnOptions, Task, TaskRunSource, TaskSource, TaskSourceKind, TASKS_CATEGORY, WorkspaceFileTaskSource, WorkspaceTaskSource } from 'vs/workbench/contrib/tasks/common/tasks'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IQuickPickItem, IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { Action2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IWorkspaceTrustService, WorkspaceTrustState } from 'vs/platform/workspace/common/workspaceTrust'; +import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { URI } from 'vs/base/common/uri'; const ARE_AUTOMATIC_TASKS_ALLOWED_IN_WORKSPACE = 'tasks.run.allowAutomatic'; @@ -55,9 +59,24 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut }); } - private static findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map): { tasks: Array>, taskNames: Array } { + private static getTaskSource(source: TaskSource): URI | undefined { + const taskKind = TaskSourceKind.toConfigurationTarget(source.kind); + switch (taskKind) { + case ConfigurationTarget.WORKSPACE_FOLDER: { + return resources.joinPath((source).config.workspaceFolder!.uri, (source).config.file); + } + case ConfigurationTarget.WORKSPACE: { + return (source).config.workspace?.configuration ?? undefined; + } + } + return undefined; + } + + private static findAutoTasks(taskService: ITaskService, workspaceTaskResult: Map): { tasks: Array>, taskNames: Array, locations: Map } { const tasks = new Array>(); const taskNames = new Array(); + const locations = new Map(); + if (workspaceTaskResult) { workspaceTaskResult.forEach(resultElement => { if (resultElement.set) { @@ -65,6 +84,10 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut if (task.runOptions.runOn === RunOnOptions.folderOpen) { tasks.push(task); taskNames.push(task._label); + const location = RunAutomaticTasks.getTaskSource(task._source); + if (location) { + locations.set(location.fsPath, location); + } } }); } @@ -79,16 +102,20 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } else { taskNames.push(configedTask.value.configures.task); } + const location = RunAutomaticTasks.getTaskSource(configedTask.value._source); + if (location) { + locations.set(location.fsPath, location); + } } }); } }); } - return { tasks, taskNames }; + return { tasks, taskNames, locations }; } public static async promptForPermission(taskService: ITaskService, storageService: IStorageService, notificationService: INotificationService, workspaceTrustService: IWorkspaceTrustService, - workspaceTaskResult: Map) { + openerService: IOpenerService, workspaceTaskResult: Map) { const isWorkspaceTrusted = await workspaceTrustService.requireWorkspaceTrust({ immediate: false }) === WorkspaceTrustState.Trusted; if (!isWorkspaceTrusted) { return; @@ -99,10 +126,10 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut return; } - let { tasks, taskNames } = RunAutomaticTasks.findAutoTasks(taskService, workspaceTaskResult); + let { tasks, taskNames, locations } = RunAutomaticTasks.findAutoTasks(taskService, workspaceTaskResult); if (taskNames.length > 0) { // We have automatic tasks, prompt to allow. - this.showPrompt(notificationService, storageService, taskService, taskNames).then(allow => { + this.showPrompt(notificationService, storageService, taskService, openerService, taskNames, locations).then(allow => { if (allow) { RunAutomaticTasks.runTasks(taskService, tasks); } @@ -111,9 +138,13 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } private static showPrompt(notificationService: INotificationService, storageService: IStorageService, taskService: ITaskService, - taskNames: Array): Promise { + openerService: IOpenerService, taskNames: Array, locations: Map): Promise { return new Promise(resolve => { - notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic', "This folder has tasks ({0}) defined in \'tasks.json\' that run automatically when you open this folder. Do you allow automatic tasks to run when you open this folder?", taskNames.join(', ')), + notificationService.prompt(Severity.Info, nls.localize('tasks.run.allowAutomatic', + "This workspace has tasks ({0}) defined ({1}) that run automatically when you open this workspace. Do you allow automatic tasks to run when you open this workspace?", + taskNames.join(', '), + Array.from(locations.keys()).join(', ') + ), [{ label: nls.localize('allow', "Allow and run"), run: () => { @@ -129,9 +160,11 @@ export class RunAutomaticTasks extends Disposable implements IWorkbenchContribut } }, { - label: nls.localize('openTasks', "Open tasks.json"), - run: () => { - taskService.openConfig(undefined); + label: locations.size === 1 ? nls.localize('openTask', "Open file") : nls.localize('openTasks', "Open files"), + run: async () => { + for (const location of locations) { + await openerService.open(location[1]); + } resolve(false); } }] diff --git a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts index 36398dc8ef1..f2120d64b2e 100644 --- a/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts +++ b/src/vs/workbench/contrib/userDataSync/electron-sandbox/userDataSync.contribution.ts @@ -7,7 +7,7 @@ import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWo import { IUserDataSyncUtilService, SyncStatus, UserDataSyncError, UserDataSyncErrorCode, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { UserDataSycnUtilServiceChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; import { localize } from 'vs/nls'; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts index a10ee16014b..7ab98787bb1 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewElement.ts @@ -13,7 +13,7 @@ import { FileAccess, Schemas } from 'vs/base/common/network'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; diff --git a/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts index e44124e7a22..ebd9ea4cde7 100644 --- a/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts +++ b/src/vs/workbench/contrib/webview/electron-browser/webviewIgnoreMenuShortcutsManager.ts @@ -9,7 +9,7 @@ import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifec import { isMacintosh } from 'vs/base/common/platform'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewMessageChannels } from 'vs/workbench/contrib/webview/browser/baseWebviewElement'; diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts index d6c80d16fe2..672018e2ce4 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/iframeWebviewElement.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IFileService } from 'vs/platform/files/common/files'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { INotificationService } from 'vs/platform/notification/common/notification'; diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts b/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts index 1f46cf1bddb..0e3daf4b30d 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/resourceLoading.ts @@ -13,7 +13,7 @@ import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import * as modes from 'vs/editor/common/modes'; import { IFileService } from 'vs/platform/files/common/files'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ILogService } from 'vs/platform/log/common/log'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; diff --git a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts index 7a4d3d20f51..ba3258b9545 100644 --- a/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts +++ b/src/vs/workbench/contrib/webview/electron-sandbox/windowIgnoreMenuShortcutsManager.ts @@ -6,7 +6,7 @@ import { isMacintosh } from 'vs/base/common/platform'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index 4f323918702..a6b22ab4def 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -28,7 +28,7 @@ import { IWorkbenchConfigurationService } from 'vs/workbench/services/configurat import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; import { registerWindowDriver } from 'vs/platform/driver/electron-browser/driver'; -import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; @@ -52,6 +52,7 @@ import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uri import { KeyboardLayoutService } from 'vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout'; import { IKeyboardLayoutService } from 'vs/platform/keyboardLayout/common/keyboardLayout'; import { LoggerService } from 'vs/workbench/services/log/electron-sandbox/loggerService'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; class DesktopMain extends Disposable { diff --git a/src/vs/workbench/electron-sandbox/desktop.main.ts b/src/vs/workbench/electron-sandbox/desktop.main.ts index fa0cbdcea23..d72468c610b 100644 --- a/src/vs/workbench/electron-sandbox/desktop.main.ts +++ b/src/vs/workbench/electron-sandbox/desktop.main.ts @@ -19,7 +19,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { Disposable } from 'vs/base/common/lifecycle'; -import { IMainProcessService, ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { RemoteAgentService } from 'vs/workbench/services/remote/electron-sandbox/remoteAgentServiceImpl'; @@ -37,6 +37,7 @@ import { INativeWorkbenchConfiguration, INativeWorkbenchEnvironmentService } fro import { RemoteAuthorityResolverService } from 'vs/platform/remote/electron-sandbox/remoteAuthorityResolverService'; import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity'; import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService'; +import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; class DesktopMain extends Disposable { diff --git a/src/vs/workbench/services/diagnostics/electron-sandbox/diagnosticsService.ts b/src/vs/workbench/services/diagnostics/electron-sandbox/diagnosticsService.ts index 75eae982425..45581940b51 100644 --- a/src/vs/workbench/services/diagnostics/electron-sandbox/diagnosticsService.ts +++ b/src/vs/workbench/services/diagnostics/electron-sandbox/diagnosticsService.ts @@ -3,21 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { registerSharedProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { IDiagnosticsService } from 'vs/platform/diagnostics/common/diagnostics'; -// @ts-ignore: interface is implemented via proxy -export class DiagnosticsService implements IDiagnosticsService { - - declare readonly _serviceBrand: undefined; - - constructor( - @ISharedProcessService sharedProcessService: ISharedProcessService - ) { - return ProxyChannel.toService(sharedProcessService.getChannel('diagnostics')); - } -} - -registerSingleton(IDiagnosticsService, DiagnosticsService, true); +registerSharedProcessRemoteService(IDiagnosticsService, 'diagnostics', { supportsDelayedInstantiation: true }); diff --git a/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts b/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts index 198bf51ee01..046649b6b63 100644 --- a/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts +++ b/src/vs/workbench/services/encryption/electron-sandbox/encryptionService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IEncryptionService } from 'vs/workbench/services/encryption/common/encryptionService'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts index ecf194b44a4..3b6c4f7c127 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionManagementServerService.ts @@ -9,7 +9,7 @@ import { IExtensionManagementServer, IExtensionManagementServerService } from 'v import { ExtensionManagementChannelClient } from 'vs/platform/extensionManagement/common/extensionManagementIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { NativeRemoteExtensionManagementService } from 'vs/workbench/services/extensionManagement/electron-sandbox/remoteExtensionManagementService'; import { ILabelService } from 'vs/platform/label/common/label'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts index 65b8aed23e8..df25bdb6ef8 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionTipsService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionTipsService, IExecutableBasedExtensionTip, IWorkspaceTips, IConfigBasedExtensionTip } from 'vs/platform/extensionManagement/common/extensionManagement'; import { URI } from 'vs/base/common/uri'; diff --git a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts index 00610fa6eb5..0bf4ab120dd 100644 --- a/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts +++ b/src/vs/workbench/services/extensionManagement/electron-sandbox/extensionUrlTrustService.ts @@ -6,7 +6,7 @@ import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; class ExtensionUrlTrustService { diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index de309efcbd1..8a6dc3619e9 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IIssueService } from 'vs/platform/issue/electron-sandbox/issue'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts b/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts index a20f74953ca..0994fd280bd 100644 --- a/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts +++ b/src/vs/workbench/services/keybinding/electron-sandbox/nativeKeyboardLayout.ts @@ -13,7 +13,7 @@ import { MacLinuxFallbackKeyboardMapper } from 'vs/workbench/services/keybinding import { MacLinuxKeyboardMapper } from 'vs/workbench/services/keybinding/common/macLinuxKeyboardMapper'; import { DispatchConfig } from 'vs/platform/keyboardLayout/common/dispatchConfig'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IKeyboardLayoutMainService } from 'vs/platform/keyboardLayout/common/keyboardLayoutMainService'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; diff --git a/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts b/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts index 7b980dd9b70..7c59c49ee4b 100644 --- a/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts +++ b/src/vs/workbench/services/localizations/electron-sandbox/localizationsService.ts @@ -5,7 +5,7 @@ import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; // @ts-ignore: interface is implemented via proxy diff --git a/src/vs/workbench/services/log/electron-sandbox/logService.ts b/src/vs/workbench/services/log/electron-sandbox/logService.ts index 39d348eeb85..d0fba0e728c 100644 --- a/src/vs/workbench/services/log/electron-sandbox/logService.ts +++ b/src/vs/workbench/services/log/electron-sandbox/logService.ts @@ -8,7 +8,7 @@ import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/enviro import { LogLevelChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { LoggerService } from 'vs/workbench/services/log/electron-sandbox/loggerService'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; export class NativeLogService extends LogService { diff --git a/src/vs/workbench/services/log/electron-sandbox/loggerService.ts b/src/vs/workbench/services/log/electron-sandbox/loggerService.ts index eea18d19747..50795cdf780 100644 --- a/src/vs/workbench/services/log/electron-sandbox/loggerService.ts +++ b/src/vs/workbench/services/log/electron-sandbox/loggerService.ts @@ -6,7 +6,7 @@ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { LogLevel, ILoggerService, ILogger, AbstractMessageLogger, ILoggerOptions } from 'vs/platform/log/common/log'; import { URI } from 'vs/base/common/uri'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; export class LoggerService implements ILoggerService { diff --git a/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts b/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts index 55d28ef7062..a4a49297e75 100644 --- a/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts +++ b/src/vs/workbench/services/menubar/electron-sandbox/menubarService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IMenubarService } from 'vs/platform/menubar/electron-sandbox/menubar'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 24cac4ab5c0..89c8acfcae0 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -48,6 +48,7 @@ export interface ITunnelItem { wideDescription?: string; readonly icon?: ThemeIcon; readonly label: string; + readonly wideLabel: string; } export interface Tunnel { diff --git a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts index 3f3293f1610..736e0b9df78 100644 --- a/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts +++ b/src/vs/workbench/services/telemetry/electron-sandbox/telemetryService.ts @@ -9,7 +9,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { Disposable } from 'vs/base/common/lifecycle'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IProductService } from 'vs/platform/product/common/productService'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { TelemetryAppenderClient } from 'vs/platform/telemetry/common/telemetryIpc'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { resolveWorkbenchCommonProperties } from 'vs/workbench/services/telemetry/electron-sandbox/workbenchCommonProperties'; diff --git a/src/vs/workbench/services/update/electron-sandbox/updateService.ts b/src/vs/workbench/services/update/electron-sandbox/updateService.ts index b3b4d686ad9..7961b927b12 100644 --- a/src/vs/workbench/services/update/electron-sandbox/updateService.ts +++ b/src/vs/workbench/services/update/electron-sandbox/updateService.ts @@ -3,59 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event, Emitter } from 'vs/base/common/event'; -import { IUpdateService, State } from 'vs/platform/update/common/update'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { IUpdateService } from 'vs/platform/update/common/update'; +import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { UpdateChannelClient } from 'vs/platform/update/common/updateIpc'; -export class NativeUpdateService implements IUpdateService { - - declare readonly _serviceBrand: undefined; - - private readonly _onStateChange = new Emitter(); - readonly onStateChange: Event = this._onStateChange.event; - - private _state: State = State.Uninitialized; - get state(): State { return this._state; } - - private channel: IChannel; - - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('update'); - - // always set this._state as the state changes - this.onStateChange(state => this._state = state); - - this.channel.call('_getInitialState').then(state => { - // fire initial state - this._onStateChange.fire(state); - - // fire subsequent states as they come in from remote - - this.channel.listen('onStateChange')(state => this._onStateChange.fire(state)); - }); - } - - checkForUpdates(context: any): Promise { - return this.channel.call('checkForUpdates', context); - } - - downloadUpdate(): Promise { - return this.channel.call('downloadUpdate'); - } - - applyUpdate(): Promise { - return this.channel.call('applyUpdate'); - } - - quitAndInstall(): Promise { - return this.channel.call('quitAndInstall'); - } - - isLatestVersion(): Promise { - return this.channel.call('isLatestVersion'); - } -} - -registerSingleton(IUpdateService, NativeUpdateService); +registerMainProcessRemoteService(IUpdateService, 'update', { channelClientCtor: UpdateChannelClient }); diff --git a/src/vs/workbench/services/url/electron-sandbox/urlService.ts b/src/vs/workbench/services/url/electron-sandbox/urlService.ts index 6e6b0e1a030..30f57f77363 100644 --- a/src/vs/workbench/services/url/electron-sandbox/urlService.ts +++ b/src/vs/workbench/services/url/electron-sandbox/urlService.ts @@ -5,7 +5,7 @@ import { IURLService, IURLHandler, IOpenURLOptions } from 'vs/platform/url/common/url'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { URLHandlerChannel } from 'vs/platform/url/common/urlIpc'; import { IOpenerService, IOpener, matchesScheme } from 'vs/platform/opener/common/opener'; import { IProductService } from 'vs/platform/product/common/productService'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts index 1bc002b9411..bdcc59cf765 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataAutoSyncService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IUserDataAutoSyncService, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts index 14eb88405fc..4bfff33d1f3 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncAccountService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Disposable } from 'vs/base/common/lifecycle'; import { Event, Emitter } from 'vs/base/common/event'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts index c4cbc8c1f31..6a12f780a9a 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncMachinesService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { Disposable } from 'vs/base/common/lifecycle'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts index 64c6b35b9b3..8e4493669f3 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, ISyncResourceHandle, ISyncTask, IManualSyncTask, IUserDataManifest, ISyncResourcePreview, IResourcePreview } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { Disposable } from 'vs/base/common/lifecycle'; import { Emitter, Event } from 'vs/base/common/event'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; diff --git a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts index 95f15dd236c..c42315a7454 100644 --- a/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts +++ b/src/vs/workbench/services/userDataSync/electron-sandbox/userDataSyncStoreManagementService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IUserDataSyncStoreManagementService, UserDataSyncStoreType, IUserDataSyncStore } from 'vs/platform/userDataSync/common/userDataSync'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { AbstractUserDataSyncStoreManagementService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts index 7ae6139c8f8..48fbb4e5a1c 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspacesService.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; -import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; +import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; diff --git a/src/vs/workbench/test/common/notifications.test.ts b/src/vs/workbench/test/common/notifications.test.ts index 11655e5ad5f..3a4a7bd235e 100644 --- a/src/vs/workbench/test/common/notifications.test.ts +++ b/src/vs/workbench/test/common/notifications.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { NotificationsModel, NotificationViewItem, INotificationChangeEvent, NotificationChangeType, NotificationViewItemContentChangeKind, IStatusMessageChangeEvent, StatusMessageChangeType } from 'vs/workbench/common/notifications'; import { Action } from 'vs/base/common/actions'; import { INotification, Severity, NotificationsFilter } from 'vs/platform/notification/common/notification'; -import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; +import { createErrorWithActions } from 'vs/base/common/errors'; suite('Notifications', () => { diff --git a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts index 335025cb37b..f2c26043db4 100644 --- a/src/vs/workbench/test/electron-browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/electron-browser/workbenchTestServices.ts @@ -5,7 +5,7 @@ import { workbenchInstantiationService as browserWorkbenchInstantiationService, ITestInstantiationService, TestLifecycleService, TestFilesConfigurationService, TestFileService, TestFileDialogService, TestPathService, TestEncodingOracle, TestProductService } from 'vs/workbench/test/browser/workbenchTestServices'; import { Event } from 'vs/base/common/event'; -import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService'; import { NativeTextFileService, } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { INativeHostService } from 'vs/platform/native/electron-sandbox/native'; diff --git a/src/vs/workbench/workbench.sandbox.main.ts b/src/vs/workbench/workbench.sandbox.main.ts index fd7193e1089..5ff656776f3 100644 --- a/src/vs/workbench/workbench.sandbox.main.ts +++ b/src/vs/workbench/workbench.sandbox.main.ts @@ -66,7 +66,8 @@ import { TimerService } from 'vs/workbench/services/timer/electron-sandbox/timer import { IUserDataInitializationService, UserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit'; import { IUserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync'; import { UserDataAutoSyncEnablementService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; -import { ISharedProcessService, SharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; +import { ISharedProcessService } from 'vs/platform/ipc/electron-sandbox/services'; +import { SharedProcessService } from 'vs/platform/ipc/electron-sandbox/sharedProcessService'; registerSingleton(ITimerService, TimerService); registerSingleton(IUserDataInitializationService, UserDataInitializationService);