diff --git a/extensions/notebook-renderers/package.json b/extensions/notebook-renderers/package.json
index 9b85015d4bdcb5195a1203f8b3cbe910c06cd84e..14a74bb485bf4ddbb7dd3dd642f1865e331f8137 100644
--- a/extensions/notebook-renderers/package.json
+++ b/extensions/notebook-renderers/package.json
@@ -22,6 +22,19 @@
"*"
],
"contributes": {
+ "notebookOutputRenderer": [
+ {
+ "viewType": "nteract",
+ "displayName": "nteract renderer for notebook",
+ "mimeTypes": [
+ "text/latex",
+ "text/markdown",
+ "application/json",
+ "application/vnd.plotly.v1+json",
+ "application/vnd.vega.v5+json"
+ ]
+ }
+ ]
},
"scripts": {
"compile": "tsc -p ./",
diff --git a/extensions/notebook-renderers/src/extension.ts b/extensions/notebook-renderers/src/extension.ts
index 008d3ac930cec0118c2f715a0c146871d64dd980..d2625e4fbe228b5edaed99eb33a1579af1ec7c79 100644
--- a/extensions/notebook-renderers/src/extension.ts
+++ b/extensions/notebook-renderers/src/extension.ts
@@ -10,6 +10,7 @@ export function activate(context: vscode.ExtensionContext) {
console.log(context.extensionPath);
context.subscriptions.push(vscode.window.registerNotebookOutputRenderer(
+ 'nteract',
{
type: 'display_data',
subTypes: [
diff --git a/extensions/notebook-test/package.json b/extensions/notebook-test/package.json
index d2756f899d4b28dca521c471c9edffe52dbcb9ca..79b9bb8ef471efe0b3a0376a1a3f25b21a533c75 100644
--- a/extensions/notebook-test/package.json
+++ b/extensions/notebook-test/package.json
@@ -49,6 +49,19 @@
}
]
}
+ ],
+ "notebookOutputRenderer": [
+ {
+ "viewType": "kerneltest",
+ "displayName": "kernel test renderer for notebook",
+ "mimeTypes": [
+ "text/latex",
+ "text/markdown",
+ "application/json",
+ "application/vnd.plotly.v1+json",
+ "application/vnd.vega.v5+json"
+ ]
+ }
]
},
"scripts": {
diff --git a/extensions/notebook-test/src/extension.ts b/extensions/notebook-test/src/extension.ts
index 24295f9aa00f39071b118e324b6d1813afa3fe21..9044d32c17a8892a281f895e493f75ee172da53e 100644
--- a/extensions/notebook-test/src/extension.ts
+++ b/extensions/notebook-test/src/extension.ts
@@ -14,6 +14,25 @@ export function activate(context: vscode.ExtensionContext) {
context.subscriptions.push(vscode.window.registerNotebookProvider('jupyter', new NotebookProvider(context.extensionPath, true)));
context.subscriptions.push(vscode.window.registerNotebookProvider('jupytertest', new NotebookProvider(context.extensionPath, false)));
+ context.subscriptions.push(vscode.window.registerNotebookOutputRenderer(
+ 'kerneltest',
+ {
+ type: 'display_data',
+ subTypes: [
+ 'text/latex',
+ 'text/markdown',
+ 'application/json',
+ 'application/vnd.plotly.v1+json',
+ 'application/vnd.vega.v5+json'
+ ]
+ },
+ {
+ render: () => {
+ return '
kernel test renderer
';
+ }
+ }
+ ));
+
vscode.commands.registerCommand('notebook.saveToMarkdown', () => {
if (vscode.window.activeNotebookDocument) {
let document = vscode.window.activeNotebookDocument;
diff --git a/src/vs/vscode.proposed.d.ts b/src/vs/vscode.proposed.d.ts
index 71a203abd86d33dcb045e2dd838e650d2349db6e..df4ac3e5f57f947875660d7343aa749fee7ac170 100644
--- a/src/vs/vscode.proposed.d.ts
+++ b/src/vs/vscode.proposed.d.ts
@@ -1443,7 +1443,7 @@ declare module 'vscode' {
provider: NotebookProvider
): Disposable;
- export function registerNotebookOutputRenderer(outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable;
+ export function registerNotebookOutputRenderer(type: string, outputSelector: NotebookOutputSelector, renderer: NotebookOutputRenderer): Disposable;
export let activeNotebookDocument: NotebookDocument | undefined;
}
diff --git a/src/vs/workbench/api/browser/mainThreadNotebook.ts b/src/vs/workbench/api/browser/mainThreadNotebook.ts
index 9affac7817e775a44f3936ddbb0beb1f8ee739d6..a2aac4fd6687d17ddc5d880b1d5d9fea8cbd0c48 100644
--- a/src/vs/workbench/api/browser/mainThreadNotebook.ts
+++ b/src/vs/workbench/api/browser/mainThreadNotebook.ts
@@ -228,8 +228,8 @@ export class MainThreadNotebooks extends Disposable implements MainThreadNoteboo
});
}
- async $registerNotebookRenderer(extension: NotebookExtensionDescription, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise {
- this._notebookService.registerNotebookRenderer(handle, extension, selectors, preloads.map(uri => URI.revive(uri)));
+ async $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise {
+ this._notebookService.registerNotebookRenderer(handle, extension, type, selectors, preloads.map(uri => URI.revive(uri)));
}
async $unregisterNotebookRenderer(handle: number): Promise {
diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts
index 455185f6f7dd7a55ed64f9ce5fdd4470e45ecbd3..dfd2ec920ea46a28172d47955f670ac85ddb7fee 100644
--- a/src/vs/workbench/api/common/extHost.api.impl.ts
+++ b/src/vs/workbench/api/common/extHost.api.impl.ts
@@ -583,8 +583,8 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
registerNotebookProvider: (viewType: string, provider: vscode.NotebookProvider) => {
return extHostNotebook.registerNotebookProvider(extension, viewType, provider);
},
- registerNotebookOutputRenderer: (outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
- return extHostNotebook.registerNotebookOutputRenderer(extension, outputFilter, renderer);
+ registerNotebookOutputRenderer: (type: string, outputFilter: vscode.NotebookOutputSelector, renderer: vscode.NotebookOutputRenderer) => {
+ return extHostNotebook.registerNotebookOutputRenderer(type, extension, outputFilter, renderer);
},
get activeNotebookDocument(): vscode.NotebookDocument | undefined {
return extHostNotebook.activeNotebookDocument;
diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts
index 98f6e41add2f834a63df066e0f50761a9110e4fa..326badb63c2c4d90ec00bf1fa798a5912f13c196 100644
--- a/src/vs/workbench/api/common/extHost.protocol.ts
+++ b/src/vs/workbench/api/common/extHost.protocol.ts
@@ -654,7 +654,7 @@ export type NotebookCellOutputsSplice = [
export interface MainThreadNotebookShape extends IDisposable {
$registerNotebookProvider(extension: NotebookExtensionDescription, viewType: string): Promise;
$unregisterNotebookProvider(viewType: string): Promise;
- $registerNotebookRenderer(extension: NotebookExtensionDescription, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise;
+ $registerNotebookRenderer(extension: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, handle: number, preloads: UriComponents[]): Promise;
$unregisterNotebookRenderer(handle: number): Promise;
$createNotebookDocument(handle: number, viewType: string, resource: UriComponents): Promise;
$updateNotebookLanguages(viewType: string, resource: UriComponents, languages: string[]): Promise;
diff --git a/src/vs/workbench/api/common/extHostNotebook.ts b/src/vs/workbench/api/common/extHostNotebook.ts
index 1c7e49fce6052fbcb3e682016e1224af80acf785..570091742d96f98462c9f471c98f47180921afc6 100644
--- a/src/vs/workbench/api/common/extHostNotebook.ts
+++ b/src/vs/workbench/api/common/extHostNotebook.ts
@@ -239,20 +239,23 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
outputs = outputs.map(output => {
let richestMimeType: string | undefined = undefined;
- if (this.renderingHandler.outputDisplayOrder?.userOrder || this._parsedDisplayOrder.length > 0) {
+ if (this.renderingHandler.outputDisplayOrder?.defaultOrder || this.renderingHandler.outputDisplayOrder?.userOrder || this._parsedDisplayOrder.length > 0) {
richestMimeType = this.findRichestMimeType(output);
}
+ output.pickedMimeType = richestMimeType;
+
let transformedOutput: vscode.CellOutput | undefined = undefined;
if (richestMimeType) {
let handler = this.renderingHandler.findBestMatchedRenderer(richestMimeType);
- if (handler) {
- renderers.add(handler.handle);
- transformedOutput = handler?.render(this, cell, output);
+ if (handler.length) {
+ renderers.add(handler[0].handle);
+ transformedOutput = handler[0].render(this, cell, output);
- output = transformedOutput;
- output.pickedMimeType = richestMimeType;
+ output.pickedRenderer = handler[0].handle;
+ // output.transformedOutput = transformedOutput;
+ output.transformedOutput = { richestMimeType: transformedOutput };
}
}
@@ -289,20 +292,23 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
outputs = outputs.map(output => {
let richestMimeType: string | undefined = undefined;
- if (this.renderingHandler.outputDisplayOrder?.userOrder || this._parsedDisplayOrder.length > 0) {
+ if (this.renderingHandler.outputDisplayOrder?.defaultOrder || this.renderingHandler.outputDisplayOrder?.userOrder || this._parsedDisplayOrder.length > 0) {
richestMimeType = this.findRichestMimeType(output);
}
+ (output).pickedMimeType = richestMimeType;
let transformedOutput: vscode.CellOutput | undefined = undefined;
if (richestMimeType) {
let handler = this.renderingHandler.findBestMatchedRenderer(richestMimeType);
- if (handler) {
- renderers.add(handler.handle);
- transformedOutput = handler?.render(this, cell, output);
+ if (handler.length) {
+ let pickedHandler = handler[0];
+ renderers.add(pickedHandler.handle);
+
+ transformedOutput = pickedHandler.render(this, cell, output);
+ (output).pickedRenderer = pickedHandler.handle;
+ (output).transformedOutput = { richestMimeType: transformedOutput };
- output = transformedOutput;
- (output).pickedMimeType = richestMimeType;
}
}
@@ -383,6 +389,16 @@ export class ExtHostNotebookDocument implements vscode.NotebookDocument {
order++;
}
+
+ let defaultOrder = coreDisplayOrder.defaultOrder;
+
+ for (let i = 0; i < defaultOrder.length; i++) {
+ if (defaultOrder[i](mimeType)) {
+ return order;
+ }
+
+ order++;
+ }
}
return order;
@@ -486,8 +502,9 @@ export class ExtHostNotebookOutputRenderer {
readonly handle = ExtHostNotebookOutputRenderer._handlePool++;
constructor(
- private filter: vscode.NotebookOutputSelector,
- private renderer: vscode.NotebookOutputRenderer
+ public type: string,
+ public filter: vscode.NotebookOutputSelector,
+ public renderer: vscode.NotebookOutputRenderer
) {
}
@@ -517,7 +534,7 @@ export class ExtHostNotebookOutputRenderer {
export interface ExtHostNotebookOutputRenderingHandler {
outputDisplayOrder: ExtHostOutputDisplayOrder | undefined;
- findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer | undefined;
+ findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[];
}
export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostNotebookOutputRenderingHandler {
@@ -545,27 +562,29 @@ export class ExtHostNotebookController implements ExtHostNotebookShape, ExtHostN
}
registerNotebookOutputRenderer(
+ type: string,
extension: IExtensionDescription,
filter: vscode.NotebookOutputSelector,
renderer: vscode.NotebookOutputRenderer
): vscode.Disposable {
- let extHostRenderer = new ExtHostNotebookOutputRenderer(filter, renderer);
+ let extHostRenderer = new ExtHostNotebookOutputRenderer(type, filter, renderer);
this._notebookOutputRenderers.set(extHostRenderer.handle, extHostRenderer);
- this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, filter, extHostRenderer.handle, renderer.preloads || []);
+ this._proxy.$registerNotebookRenderer({ id: extension.identifier, location: extension.extensionLocation }, type, filter, extHostRenderer.handle, renderer.preloads || []);
return new VSCodeDisposable(() => {
this._notebookOutputRenderers.delete(extHostRenderer.handle);
this._proxy.$unregisterNotebookRenderer(extHostRenderer.handle);
});
}
- findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer | undefined {
+ findBestMatchedRenderer(mimeType: string): ExtHostNotebookOutputRenderer[] {
+ let matches: ExtHostNotebookOutputRenderer[] = [];
for (let renderer of this._notebookOutputRenderers) {
if (renderer[1].matches(mimeType)) {
- return renderer[1];
+ matches.push(renderer[1]);
}
}
- return;
+ return matches;
}
registerNotebookProvider(
diff --git a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts
index de2efbee51df1bde1e254a623343e8d12aa297ce..7dbcd235058c91cf1c9102cb240215d1436609dd 100644
--- a/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts
+++ b/src/vs/workbench/contrib/notebook/browser/extensionPoint.ts
@@ -6,7 +6,6 @@
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
-import { languagesExtPoint } from 'vs/workbench/services/mode/common/workbenchModeService';
import { NotebookSelector } from 'vs/workbench/contrib/notebook/common/notebookProvider';
namespace NotebookEditorContribution {
@@ -15,15 +14,28 @@ namespace NotebookEditorContribution {
export const selector = 'selector';
}
-
interface INotebookEditorContribution {
readonly [NotebookEditorContribution.viewType]: string;
readonly [NotebookEditorContribution.displayName]: string;
readonly [NotebookEditorContribution.selector]?: readonly NotebookSelector[];
}
-const notebookContribution: IJSONSchema = {
- description: nls.localize('contributes.notebook', 'Contributes notebook.'),
+namespace NotebookRendererContribution {
+ export const viewType = 'viewType';
+ export const displayName = 'displayName';
+ export const mimeTypes = 'mimeTypes';
+}
+
+interface INotebookRendererContribution {
+ readonly [NotebookRendererContribution.viewType]: string;
+ readonly [NotebookRendererContribution.displayName]: string;
+ readonly [NotebookRendererContribution.mimeTypes]?: readonly string[];
+}
+
+
+
+const notebookProviderContribution: IJSONSchema = {
+ description: nls.localize('contributes.notebook.provider', 'Contributes notebook document provider.'),
type: 'array',
defaultSnippets: [{ body: [{ viewType: '', displayName: '' }] }],
items: {
@@ -36,25 +48,25 @@ const notebookContribution: IJSONSchema = {
properties: {
[NotebookEditorContribution.viewType]: {
type: 'string',
- description: nls.localize('contributes.notebook.viewType', 'Unique identifier of the notebook.'),
+ description: nls.localize('contributes.notebook.provider.viewType', 'Unique identifier of the notebook.'),
},
[NotebookEditorContribution.displayName]: {
type: 'string',
- description: nls.localize('contributes.notebook.displayName', 'Human readable name of the notebook.'),
+ description: nls.localize('contributes.notebook.provider.displayName', 'Human readable name of the notebook.'),
},
[NotebookEditorContribution.selector]: {
type: 'array',
- description: nls.localize('contributes.notebook.selector', 'Set of globs that the notebook is for.'),
+ description: nls.localize('contributes.notebook.provider.selector', 'Set of globs that the notebook is for.'),
items: {
type: 'object',
properties: {
filenamePattern: {
type: 'string',
- description: nls.localize('contributes.notebook.selector.filenamePattern', 'Glob that the notebook is enabled for.'),
+ description: nls.localize('contributes.notebook.provider.selector.filenamePattern', 'Glob that the notebook is enabled for.'),
},
excludeFileNamePattern: {
type: 'string',
- description: nls.localize('contributes.notebook.selector.excludeFileNamePattern', 'Glob that the notebook is disabled for.')
+ description: nls.localize('contributes.notebook.selector.provider.excludeFileNamePattern', 'Glob that the notebook is disabled for.')
}
}
}
@@ -63,8 +75,45 @@ const notebookContribution: IJSONSchema = {
}
};
-export const notebookExtensionPoint = ExtensionsRegistry.registerExtensionPoint({
- extensionPoint: 'notebookProvider',
- deps: [languagesExtPoint],
- jsonSchema: notebookContribution
-});
+const notebookRendererContribution: IJSONSchema = {
+ description: nls.localize('contributes.notebook.renderer', 'Contributes notebook output renderer provider.'),
+ type: 'array',
+ defaultSnippets: [{ body: [{ viewType: '', displayName: '', mimeTypes: [''] }] }],
+ items: {
+ type: 'object',
+ required: [
+ NotebookRendererContribution.viewType,
+ NotebookRendererContribution.displayName,
+ NotebookRendererContribution.mimeTypes,
+ ],
+ properties: {
+ [NotebookRendererContribution.viewType]: {
+ type: 'string',
+ description: nls.localize('contributes.notebook.renderer.viewType', 'Unique identifier of the notebook output renderer.'),
+ },
+ [NotebookRendererContribution.displayName]: {
+ type: 'string',
+ description: nls.localize('contributes.notebook.renderer.displayName', 'Human readable name of the notebook output renderer.'),
+ },
+ [NotebookRendererContribution.mimeTypes]: {
+ type: 'array',
+ description: nls.localize('contributes.notebook.selector', 'Set of globs that the notebook is for.'),
+ items: {
+ type: 'string'
+ }
+ }
+ }
+ }
+};
+
+export const notebookProviderExtensionPoint = ExtensionsRegistry.registerExtensionPoint(
+ {
+ extensionPoint: 'notebookProvider',
+ jsonSchema: notebookProviderContribution
+ });
+
+export const notebookRendererExtensionPoint = ExtensionsRegistry.registerExtensionPoint(
+ {
+ extensionPoint: 'notebookOutputRenderer',
+ jsonSchema: notebookRendererContribution
+ });
diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.css b/src/vs/workbench/contrib/notebook/browser/notebook.css
index ba2b6f5a4e684133c1fb8c0b4816a971c9c8461c..32914a8953066ee24c824aeb610396fb4392dbe8 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebook.css
+++ b/src/vs/workbench/contrib/notebook/browser/notebook.css
@@ -37,6 +37,7 @@
padding-right: 8px;
user-select: text;
transform: translate3d(0px, 0px, 0px);
+ cursor: auto;
}
.monaco-workbench .part.editor > .content .notebook-editor .output p {
@@ -45,6 +46,15 @@
margin: 0px;
}
+.monaco-workbench .part.editor > .content .notebook-editor .output .multi-mimetype-output {
+ position: absolute;
+ top: 4px;
+ left: -24px;
+ width: 16px;
+ height: 16px;
+ cursor: pointer;
+}
+
.monaco-workbench .part.editor > .content .notebook-editor .output .error_message {
color: red;
}
diff --git a/src/vs/workbench/contrib/notebook/browser/notebookService.ts b/src/vs/workbench/contrib/notebook/browser/notebookService.ts
index 1c6330d71b6caeba1bf5e68d50b5a717ee986526..50fe556f28eaa5b2dbdbeabd0b60f795c600712f 100644
--- a/src/vs/workbench/contrib/notebook/browser/notebookService.ts
+++ b/src/vs/workbench/contrib/notebook/browser/notebookService.ts
@@ -6,12 +6,13 @@
import { Disposable, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { URI } from 'vs/base/common/uri';
-import { notebookExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
+import { notebookProviderExtensionPoint, notebookRendererExtensionPoint } from 'vs/workbench/contrib/notebook/browser/extensionPoint';
import { NotebookProviderInfo } from 'vs/workbench/contrib/notebook/common/notebookProvider';
import { NotebookExtensionDescription } from 'vs/workbench/api/common/extHost.protocol';
import { Emitter, Event } from 'vs/base/common/event';
import { INotebook, ICell, INotebookMimeTypeSelector } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
+import { NotebookOutputRendererInfo } from 'vs/workbench/contrib/notebook/common/notebookOutputRenderer';
function MODEL_ID(resource: URI): string {
return resource.toString();
@@ -35,7 +36,7 @@ export interface INotebookService {
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }>;
registerNotebookController(viewType: string, extensionData: NotebookExtensionDescription, controller: IMainNotebookController): void;
unregisterNotebookProvider(viewType: string): void;
- registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
+ registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]): void;
unregisterNotebookRenderer(handle: number): void;
getRendererPreloads(handle: number): URI[];
resolveNotebook(viewType: string, uri: URI): Promise;
@@ -50,7 +51,7 @@ export interface INotebookService {
updateActiveNotebookDocument(viewType: string, resource: URI): void;
}
-export class NotebookInfoStore {
+export class NotebookProviderInfoStore {
private readonly contributedEditors = new Map();
clear() {
@@ -75,6 +76,31 @@ export class NotebookInfoStore {
}
}
+export class NotebookOutputRendererInfoStore {
+ private readonly contributedRenderers = new Map();
+
+ clear() {
+ this.contributedRenderers.clear();
+ }
+
+ get(viewType: string): NotebookOutputRendererInfo | undefined {
+ return this.contributedRenderers.get(viewType);
+ }
+
+ add(info: NotebookOutputRendererInfo): void {
+ if (this.contributedRenderers.has(info.id)) {
+ console.log(`Custom notebook output renderer with id '${info.id}' already registered`);
+ return;
+ }
+ this.contributedRenderers.set(info.id, info);
+ }
+
+ getContributedRenderer(mimeType: string): readonly NotebookOutputRendererInfo[] {
+ return Array.from(this.contributedRenderers.values()).filter(customEditor =>
+ customEditor.matches(mimeType));
+ }
+}
+
class ModelData implements IDisposable {
private readonly _modelEventListeners = new DisposableStore();
@@ -94,8 +120,9 @@ class ModelData implements IDisposable {
export class NotebookService extends Disposable implements INotebookService {
_serviceBrand: undefined;
private readonly _notebookProviders = new Map();
- private readonly _notebookRenderers = new Map();
- notebookProviderInfoStore: NotebookInfoStore = new NotebookInfoStore();
+ private readonly _notebookRenderers = new Map();
+ notebookProviderInfoStore: NotebookProviderInfoStore = new NotebookProviderInfoStore();
+ notebookRenderersInfoStore: NotebookOutputRendererInfoStore = new NotebookOutputRendererInfoStore();
private readonly _models: { [modelId: string]: ModelData; };
private _onDidChangeActiveEditor = new Emitter<{ viewType: string, uri: URI }>();
onDidChangeActiveEditor: Event<{ viewType: string, uri: URI }> = this._onDidChangeActiveEditor.event;
@@ -107,7 +134,7 @@ export class NotebookService extends Disposable implements INotebookService {
super();
this._models = {};
- notebookExtensionPoint.setHandler((extensions) => {
+ notebookProviderExtensionPoint.setHandler((extensions) => {
this.notebookProviderInfoStore.clear();
for (const extension of extensions) {
@@ -122,6 +149,21 @@ export class NotebookService extends Disposable implements INotebookService {
// console.log(this._notebookProviderInfoStore);
});
+ notebookRendererExtensionPoint.setHandler((renderers) => {
+ this.notebookRenderersInfoStore.clear();
+
+ for (const extension of renderers) {
+ for (const notebookContribution of extension.value) {
+ this.notebookRenderersInfoStore.add(new NotebookOutputRendererInfo({
+ id: notebookContribution.viewType,
+ displayName: notebookContribution.displayName,
+ mimeTypes: notebookContribution.mimeTypes || []
+ }));
+ }
+ }
+
+ // console.log(this.notebookRenderersInfoStore);
+ });
}
async canResolve(viewType: string): Promise {
@@ -151,8 +193,8 @@ export class NotebookService extends Disposable implements INotebookService {
this._notebookProviders.delete(viewType);
}
- registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, selectors: INotebookMimeTypeSelector, preloads: URI[]) {
- this._notebookRenderers.set(handle, { extensionData, selectors, preloads });
+ registerNotebookRenderer(handle: number, extensionData: NotebookExtensionDescription, type: string, selectors: INotebookMimeTypeSelector, preloads: URI[]) {
+ this._notebookRenderers.set(handle, { extensionData, type, selectors, preloads });
}
unregisterNotebookRenderer(handle: number) {
@@ -234,6 +276,10 @@ export class NotebookService extends Disposable implements INotebookService {
return this.notebookProviderInfoStore.getContributedNotebook(resource);
}
+ getContributedNotebookOutputRenderers(mimeType: string): readonly NotebookOutputRendererInfo[] {
+ return this.notebookRenderersInfoStore.getContributedRenderer(mimeType);
+ }
+
getNotebookProviderResourceRoots(): URI[] {
let ret: URI[] = [];
this._notebookProviders.forEach(val => {
diff --git a/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts b/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts
index 9c06ee6c8636e7f06156c800ba6aff7090fda949..4da7d769ed71a2cf6a31f2f62a79665e3e299cb2 100644
--- a/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/output/outputRenderer.ts
@@ -45,11 +45,11 @@ export class OutputRenderer {
};
}
- render(output: IOutput, container: HTMLElement): IRenderOutput {
+ render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput {
let transform = this._mimeTypeMapping[output.output_type];
if (transform) {
- return transform.render(output, container);
+ return transform.render(output, container, preferredMimeType);
} else {
return this.renderNoop(output, container);
}
diff --git a/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts b/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts
index 24d1a2f2b1e621e7d54240d2904e37f6a6025faa..b41350c5f63e555acd1f1d0c748d199c4ccecd8e 100644
--- a/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts
+++ b/src/vs/workbench/contrib/notebook/browser/output/transforms/richTransform.ts
@@ -18,6 +18,7 @@ import { URI } from 'vs/base/common/uri';
class RichRenderer implements IOutputTransformContribution {
private _mdRenderer: marked.Renderer = new marked.Renderer({ gfm: true });;
+ private _richMimeTypeRenderers = new Map IRenderOutput>();
constructor(
public notebookEditor: INotebookEditor,
@@ -25,119 +26,158 @@ class RichRenderer implements IOutputTransformContribution {
@IModelService private readonly modelService: IModelService,
@IModeService private readonly modeService: IModeService
) {
+ 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.renderJavaScript.bind(this));
+ this._richMimeTypeRenderers.set('text/plain', this.renderPlainText.bind(this));
}
- render(output: any, container: HTMLElement): IRenderOutput {
- let hasDynamicHeight = false;
-
- if (output.data) {
- if (output.data['application/json']) {
- let data = output.data['application/json'];
- let str = JSON.stringify(data, null, '\t');
-
- const editor = this.instantiationService.createInstance(CodeEditorWidget, container, {
- ...getJSONSimpleEditorOptions(),
- dimension: {
- width: 0,
- height: 0
- }
- }, {
- isSimpleWidget: true
- });
-
- let mode = this.modeService.create('json');
- let resource = URI.parse(`notebook-output-${Date.now()}.json`);
- const textModel = this.modelService.createModel(str, mode, resource, false);
- editor.setModel(textModel);
-
- let width = this.notebookEditor.getListDimension()!.width;
- let fontInfo = this.notebookEditor.getFontInfo();
- let height = Math.min(textModel.getLineCount(), 16) * (fontInfo?.lineHeight || 18);
-
- editor.layout({
- height,
- width
- });
-
- container.style.height = `${height + 16}px`;
-
- return {
- hasDynamicHeight: true
- };
- } else if (output.data['application/javascript']) {
- let data = output.data['application/javascript'];
- let str = isArray(data) ? data.join('') : data;
- let scriptVal = ``;
- hasDynamicHeight = false;
- return {
- shadowContent: scriptVal,
- hasDynamicHeight
- };
- } else if (output.data['text/html']) {
- let data = output.data['text/html'];
- let str = isArray(data) ? data.join('') : data;
- hasDynamicHeight = false;
- return {
- shadowContent: str,
- hasDynamicHeight
- };
- } else if (output.data['image/svg+xml']) {
- let data = output.data['image/svg+xml'];
- let str = isArray(data) ? data.join('') : data;
- hasDynamicHeight = false;
- return {
- shadowContent: str,
- hasDynamicHeight
- };
- } else if (output.data['text/markdown']) {
- let data = output.data['text/markdown'];
- const str = isArray(data) ? data.join('') : data;
- const mdOutput = document.createElement('div');
- mdOutput.innerHTML = marked(str, { renderer: this._mdRenderer });
- container.appendChild(mdOutput);
- hasDynamicHeight = true;
- } else if (output.data['image/png']) {
- const image = document.createElement('img');
- image.src = `data:image/png;base64,${output.data['image/png']}`;
- const display = document.createElement('div');
- DOM.addClasses(display, 'display');
- display.appendChild(image);
- container.appendChild(display);
- hasDynamicHeight = true;
- } else if (output.data['image/jpeg']) {
- const image = document.createElement('img');
- image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`;
- const display = document.createElement('div');
- DOM.addClasses(display, 'display');
- display.appendChild(image);
- container.appendChild(display);
- hasDynamicHeight = true;
- } else if (output.data['text/plain']) {
- let data = output.data['text/plain'];
- let str = isArray(data) ? data.join('') : data;
- const contentNode = document.createElement('p');
- contentNode.innerText = str;
- container.appendChild(contentNode);
- } else {
- const contentNode = document.createElement('p');
- let mimeTypes = [];
- for (const property in output.data) {
- mimeTypes.push(property);
- }
-
- let mimeTypesMessage = mimeTypes.join(', ');
-
- contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`;
- container.appendChild(contentNode);
- }
- } else {
+ render(output: any, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput {
+ if (!output.data) {
const contentNode = document.createElement('p');
contentNode.innerText = `No data could be found for output.`;
container.appendChild(contentNode);
+
+ return {
+ hasDynamicHeight: false
+ };
}
+ if (!preferredMimeType || !this._richMimeTypeRenderers.has(preferredMimeType)) {
+ const contentNode = document.createElement('p');
+ let mimeTypes = [];
+ for (const property in output.data) {
+ mimeTypes.push(property);
+ }
+
+ let mimeTypesMessage = mimeTypes.join(', ');
+
+ contentNode.innerText = `No renderer could be found for output. It has the following MIME types: ${mimeTypesMessage}`;
+ container.appendChild(contentNode);
+
+ return {
+ hasDynamicHeight: false
+ };
+ }
+
+ let renderer = this._richMimeTypeRenderers.get(preferredMimeType);
+ return renderer!(output, container);
+ }
+
+ renderJSON(output: any, container: HTMLElement) {
+ let data = output.data['application/json'];
+ let str = JSON.stringify(data, null, '\t');
+
+ const editor = this.instantiationService.createInstance(CodeEditorWidget, container, {
+ ...getJSONSimpleEditorOptions(),
+ dimension: {
+ width: 0,
+ height: 0
+ }
+ }, {
+ isSimpleWidget: true
+ });
+
+ let mode = this.modeService.create('json');
+ let resource = URI.parse(`notebook-output-${Date.now()}.json`);
+ const textModel = this.modelService.createModel(str, mode, resource, false);
+ editor.setModel(textModel);
+
+ let width = this.notebookEditor.getListDimension()!.width;
+ let fontInfo = this.notebookEditor.getFontInfo();
+ let height = Math.min(textModel.getLineCount(), 16) * (fontInfo?.lineHeight || 18);
+
+ editor.layout({
+ height,
+ width
+ });
+
+ container.style.height = `${height + 16}px`;
+
+ return {
+ hasDynamicHeight: true
+ };
+ }
+
+ renderJavaScript(output: any, container: HTMLElement) {
+ let data = output.data['application/javascript'];
+ let str = isArray(data) ? data.join('') : data;
+ let scriptVal = ``;
+ return {
+ shadowContent: scriptVal,
+ hasDynamicHeight: false
+ };
+ }
+
+ renderHTML(output: any, container: HTMLElement) {
+ let data = output.data['text/html'];
+ let str = isArray(data) ? data.join('') : data;
+ return {
+ shadowContent: str,
+ hasDynamicHeight: false
+ };
+
+ }
+
+ renderSVG(output: any, container: HTMLElement) {
+ let data = output.data['image/svg+xml'];
+ let str = isArray(data) ? data.join('') : data;
+ return {
+ shadowContent: str,
+ hasDynamicHeight: false
+ };
+ }
+
+ renderMarkdown(output: any, container: HTMLElement) {
+ let data = output.data['text/markdown'];
+ const str = isArray(data) ? data.join('') : data;
+ const mdOutput = document.createElement('div');
+ mdOutput.innerHTML = marked(str, { renderer: this._mdRenderer });
+ container.appendChild(mdOutput);
+
+ return {
+ hasDynamicHeight: true
+ };
+ }
+
+ renderPNG(output: any, container: HTMLElement) {
+ const image = document.createElement('img');
+ image.src = `data:image/png;base64,${output.data['image/png']}`;
+ const display = document.createElement('div');
+ DOM.addClasses(display, 'display');
+ display.appendChild(image);
+ container.appendChild(display);
+ return {
+ hasDynamicHeight: true
+ };
+
+ }
+
+ renderJPEG(output: any, container: HTMLElement) {
+ const image = document.createElement('img');
+ image.src = `data:image/jpeg;base64,${output.data['image/jpeg']}`;
+ const display = document.createElement('div');
+ DOM.addClasses(display, 'display');
+ display.appendChild(image);
+ container.appendChild(display);
+ return {
+ hasDynamicHeight: true
+ };
+ }
+
+ renderPlainText(output: any, container: HTMLElement) {
+ let data = output.data['text/plain'];
+ let str = isArray(data) ? data.join('') : data;
+ const contentNode = document.createElement('p');
+ contentNode.innerText = str;
+ container.appendChild(contentNode);
+
return {
- hasDynamicHeight
+ hasDynamicHeight: false
};
}
diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts
index 153bfd353cca727a6465791219dd8285002b2b7f..11eaa0c0aa049942524e4a44a1823aca65c36e31 100644
--- a/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts
+++ b/src/vs/workbench/contrib/notebook/browser/renderers/cellRenderer.ts
@@ -331,7 +331,7 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
this.showContextMenu(listIndex, element, e.posx, top + height);
}));
- elementDisposable?.add(new CodeCell(this.notebookEditor, element, templateData));
+ elementDisposable?.add(this.instantiationService.createInstance(CodeCell, this.notebookEditor, element, templateData));
this.renderedEditors.set(element, templateData.editor);
}
diff --git a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts
index 5031ff3882dccf1f16c648f16ca048b04f6aacd3..0d4234c2f764380090d66b37ec2ed4f32fd09ffb 100644
--- a/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts
+++ b/src/vs/workbench/contrib/notebook/browser/renderers/codeCell.ts
@@ -3,21 +3,25 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
+import * as nls from 'vs/nls';
+import * as DOM from 'vs/base/browser/dom';
+import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/renderers/cellViewModel';
import { getResizesObserver } from 'vs/workbench/contrib/notebook/browser/renderers/sizeObserver';
-import { CELL_MARGIN, IOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
+import { CELL_MARGIN, IOutput, IDisplayOutput } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellRenderTemplate, INotebookEditor } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { raceCancellation } from 'vs/base/common/async';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
+import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
export class CodeCell extends Disposable {
- private outputResizeListeners = new Map();
+ private outputResizeListeners = new Map();
private outputElements = new Map();
constructor(
private notebookEditor: INotebookEditor,
private viewCell: CellViewModel,
private templateData: CellRenderTemplate,
+ @IQuickInputService private readonly quickInputService: IQuickInputService
) {
super();
@@ -65,6 +69,10 @@ export class CodeCell extends Disposable {
viewCell.editorHeight = realContentHeight;
}
+
+ if (this.notebookEditor.getActiveCell() === this.viewCell) {
+ templateData.editor?.focus();
+ }
}
});
@@ -187,8 +195,44 @@ export class CodeCell extends Disposable {
}
renderOutput(currOutput: IOutput, index: number, beforeElement?: HTMLElement) {
+ if (!this.outputResizeListeners.has(currOutput)) {
+ this.outputResizeListeners.set(currOutput, new DisposableStore());
+ }
+
let outputItemDiv = document.createElement('div');
- let result = this.notebookEditor.getOutputRenderer().render(currOutput, outputItemDiv);
+ let transformedOutput: IOutput;
+ let transformedMimeType: string;
+ if (currOutput.pickedMimeType) {
+ if (currOutput.transformedOutput && currOutput.transformedOutput![currOutput.pickedMimeType]) {
+ // currently, transformed output is always text/html
+ transformedMimeType = 'text/html';
+ transformedOutput = currOutput.transformedOutput![currOutput.pickedMimeType];
+ } else {
+ // otherwise
+ transformedMimeType = currOutput.pickedMimeType;
+ transformedOutput = currOutput;
+ }
+ } else {
+ transformedOutput = currOutput;
+ }
+
+ if (currOutput.output_type === 'display_data' || currOutput.output_type === 'execute_result') {
+ let mimeTypes = Object.keys((currOutput! as IDisplayOutput).data);
+
+ if (mimeTypes.length > 1) {
+ outputItemDiv.style.position = 'relative';
+ const mimeTypePicker = DOM.$('.multi-mimetype-output');
+ DOM.addClasses(mimeTypePicker, 'codicon', 'codicon-list-selection');
+ outputItemDiv.appendChild(mimeTypePicker);
+ this.outputResizeListeners.get(currOutput)!.add(DOM.addStandardDisposableListener(mimeTypePicker, 'mousedown', async e => {
+ e.preventDefault();
+ e.stopPropagation();
+ await this.pickActiveMimeType(currOutput, mimeTypes);
+ }));
+ }
+ }
+
+ let result = this.notebookEditor.getOutputRenderer().render(transformedOutput!, outputItemDiv, currOutput.pickedMimeType);
if (!result) {
this.viewCell.updateOutputHeight(index, 0);
@@ -239,7 +283,7 @@ export class CodeCell extends Disposable {
}
});
elementSizeObserver.startObserving();
- this.outputResizeListeners.set(currOutput, elementSizeObserver);
+ this.outputResizeListeners.get(currOutput)!.add(elementSizeObserver);
this.viewCell.updateOutputHeight(index, clientHeight);
} else {
if (result.shadowContent) {
@@ -254,6 +298,64 @@ export class CodeCell extends Disposable {
}
}
+ async pickActiveMimeType(output: IOutput, mimeTypes: string[]) {
+ const sorted = mimeTypes.sort((a, b) => {
+ if (a === output.pickedMimeType) {
+ return -1;
+ } else {
+ return 0;
+ }
+ });
+
+ const items = sorted.map((mimeType): IQuickPickItem => ({
+ label: mimeType,
+ id: mimeType,
+ description: mimeType === output.pickedMimeType
+ ? nls.localize('curruentActiveMimeType', "Currently Active")
+ : undefined,
+ // buttons: resourceExt ? [{
+ // iconClass: 'codicon-settings-gear',
+ // tooltip: nls.localize('promptOpenWith.setDefaultTooltip', "Set as default editor for '{0}' files", resourceExt)
+ // }] : undefined
+ }));
+
+ const picker = this.quickInputService.createQuickPick();
+ picker.items = items;
+ picker.placeholder = nls.localize('promptChooseMimeType.placeHolder', "Select output mimetype to render for current output");
+
+ const pick = await new Promise(resolve => {
+ picker.onDidAccept(() => {
+ resolve(picker.selectedItems.length === 1 ? picker.selectedItems[0].id : undefined);
+ picker.dispose();
+ });
+ picker.show();
+ });
+
+ if (!pick) {
+ return;
+ }
+
+ if (pick !== output.pickedMimeType) {
+ // user chooses another mimetype
+ let index = this.viewCell.outputs.indexOf(output);
+ let nextElement = index + 1 < this.viewCell.outputs.length ? this.outputElements.get(this.viewCell.outputs[index + 1]) : undefined;
+ this.outputResizeListeners.get(output)?.clear();
+ let element = this.outputElements.get(output);
+ if (element) {
+ this.templateData?.outputContainer?.removeChild(element);
+ this.notebookEditor.removeInset(output);
+ }
+
+ output.pickedMimeType = pick;
+
+ this.renderOutput(output, index, nextElement);
+
+ let editorHeight = this.viewCell.editorHeight;
+ let totalOutputHeight = this.viewCell.getOutputTotalHeight();
+ this.notebookEditor.layoutNotebookCell(this.viewCell, editorHeight + 32 + totalOutputHeight);
+ }
+ }
+
dispose() {
this.outputResizeListeners.forEach((value) => {
value.dispose();
diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
index c913dfe1c6465604d71613c7396c94115351e8fa..4ecb503969dcecd33029d41651c9304a657dc83d 100644
--- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
+++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts
@@ -69,11 +69,11 @@ export interface IErrorOutput {
* @internal
*/
export interface IDisplayOutput {
- output_type: 'display_data';
+ output_type: 'display_data' | 'execute_result';
/**
* { mime_type: value }
*/
- data: { string: string };
+ data: { [key: string]: any; }
}
/**
@@ -82,7 +82,8 @@ export interface IDisplayOutput {
export interface IGenericOutput {
output_type: string;
pickedMimeType?: string;
- // transformedOutput?: IGenericOutput;
+ pickedRenderer?: number;
+ transformedOutput?: { [key: string]: IDisplayOutput };
}
/**
@@ -146,7 +147,7 @@ export interface IOutputTransformContribution {
*/
dispose(): void;
- render(output: IOutput, container: HTMLElement): IRenderOutput;
+ render(output: IOutput, container: HTMLElement, preferredMimeType: string | undefined): IRenderOutput;
}
diff --git a/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..796de2f51da9df8c7011b8fc400f4ec012e8c6f1
--- /dev/null
+++ b/src/vs/workbench/contrib/notebook/common/notebookOutputRenderer.ts
@@ -0,0 +1,30 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the MIT License. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as glob from 'vs/base/common/glob';
+
+export class NotebookOutputRendererInfo {
+
+ readonly id: string;
+ readonly displayName: string;
+ readonly mimeTypes: readonly string[];
+ readonly mimeTypeGlobs: glob.ParsedPattern[];
+
+ constructor(descriptor: {
+ readonly id: string;
+ readonly displayName: string;
+ readonly mimeTypes: readonly string[];
+ }) {
+ this.id = descriptor.id;
+ this.displayName = descriptor.displayName;
+ this.mimeTypes = descriptor.mimeTypes;
+ this.mimeTypeGlobs = this.mimeTypes.map(pattern => glob.parse(pattern));
+ }
+
+ matches(mimeType: string) {
+ let matched = this.mimeTypeGlobs.find(pattern => pattern(mimeType));
+ return matched;
+ }
+}