Skip to content

Commit

Permalink
Let notebook renderers throw error to fallback to next mime type (mic…
Browse files Browse the repository at this point in the history
  • Loading branch information
mjbvz authored Jan 18, 2023
1 parent dcb358a commit d8568bb
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,11 @@ export class BackLayerWebView<T extends ICommonCellInfo> extends Themable {
'notebook-cell-renderer-not-found-error': nls.localize({
key: 'notebook.error.rendererNotFound',
comment: ['$0 is a placeholder for the mime type']
}, "No renderer found for '$0' a"),
}, "No renderer found for '$0'"),
'notebook-cell-renderer-fallbacks-exhausted': nls.localize({
key: 'notebook.error.rendererFallbacksExhausted',
comment: ['$0 is a placeholder for the mime type']
}, "Could not render content for '$0'"),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -714,8 +714,13 @@ async function webviewPreloads(ctx: PreloadContext) {
}
};

interface ExtendedOutputItem {
readonly _allOutputItems: ReadonlyArray<{ readonly mime: string; getItem(): Promise<rendererApi.OutputItem | undefined> }>;
interface AdditionalOutputItemInfo {
readonly mime: string;
getItem(): Promise<rendererApi.OutputItem | undefined>;
}

interface ExtendedOutputItem extends rendererApi.OutputItem {
readonly _allOutputItems: ReadonlyArray<AdditionalOutputItemInfo>;
}

let hasWarnedAboutAllOutputItemsProposal = false;
Expand All @@ -726,15 +731,15 @@ async function webviewPreloads(ctx: PreloadContext) {
metadata: unknown,
valueBytes: Uint8Array,
allOutputItemData: ReadonlyArray<{ readonly mime: string }>
): rendererApi.OutputItem & ExtendedOutputItem {
): ExtendedOutputItem {

function create(
id: string,
mime: string,
metadata: unknown,
valueBytes: Uint8Array,
): rendererApi.OutputItem & ExtendedOutputItem {
return Object.freeze<rendererApi.OutputItem & ExtendedOutputItem>({
): ExtendedOutputItem {
return Object.freeze<ExtendedOutputItem>({
id,
mime,
metadata,
Expand Down Expand Up @@ -1268,6 +1273,8 @@ async function webviewPreloads(ctx: PreloadContext) {
}
});

const renderFallbackErrorName = 'vscode.fallbackToNextRenderer';

class Renderer {

private _onMessageEvent = createEmitter();
Expand Down Expand Up @@ -1305,11 +1312,16 @@ async function webviewPreloads(ctx: PreloadContext) {
this.postDebugMessage('Rendered output item', { id: item.id, duration: `${performance.now() - renderStart}ms` });

} catch (e) {
if (!signal.aborted) {
showRenderError(`Error rendering output item using '${this.data.id}'`, element, e instanceof Error ? [e] : []);
if (signal.aborted) {
return;
}

this.postDebugMessage('Rendering output item failed', { id: item.id, error: e + '' });
if (e instanceof Error && e.name === renderFallbackErrorName) {
throw e;
}

showRenderError(`Error rendering output item using '${this.data.id}'`, element, e instanceof Error ? [e] : []);
this.postDebugMessage('Rendering output item failed', { id: item.id, error: e + '' });
}
}

Expand Down Expand Up @@ -1560,7 +1572,63 @@ async function webviewPreloads(ctx: PreloadContext) {
this._renderers.get(rendererId)?.disposeOutputItem(outputId);
}

public async render(info: rendererApi.OutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise<void> {
public async render(item: ExtendedOutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise<void> {
const primaryRenderer = this.findRenderer(preferredRendererId, item);
if (!primaryRenderer) {
const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime);
this.showRenderError(item, element, errorMessage);
return;
}

// Try primary renderer first
if (!(await this._doRender(item, element, primaryRenderer, signal)).continue) {
return;
}

// Primary renderer failed in an expected way. Fallback to render the next mime types
for (const additionalItemData of item._allOutputItems) {
if (additionalItemData.mime === item.mime) {
continue;
}

const additionalItem = await additionalItemData.getItem();
if (signal.aborted) {
return;
}

if (additionalItem) {
const renderer = this.findRenderer(undefined, additionalItem);
if (renderer) {
if (!(await this._doRender(additionalItem, element, renderer, signal)).continue) {
return; // We rendered successfully
}
}
}
}

// All renderers have failed and there is nothing left to fallback to
const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime);
this.showRenderError(item, element, errorMessage);
}

private async _doRender(item: rendererApi.OutputItem, element: HTMLElement, renderer: Renderer, signal: AbortSignal): Promise<{ continue: boolean }> {
try {
await renderer.renderOutputItem(item, element, signal);
return { continue: false }; // We rendered successfully
} catch (e) {
if (signal.aborted) {
return { continue: false };
}

if (e instanceof Error && e.name === renderFallbackErrorName) {
return { continue: true };
} else {
throw e; // Bail and let callers handle unknown errors
}
}
}

private findRenderer(preferredRendererId: string | undefined, info: rendererApi.OutputItem) {
let renderer: Renderer | undefined;

if (typeof preferredRendererId === 'string') {
Expand All @@ -1578,26 +1646,24 @@ async function webviewPreloads(ctx: PreloadContext) {
renderer = renderers[0];
}
}
return renderer;
}

if (renderer) {
await renderer.renderOutputItem(info, element, signal);
} else {
const errorContainer = document.createElement('div');
private showRenderError(info: rendererApi.OutputItem, element: HTMLElement, errorMessage: string) {
const errorContainer = document.createElement('div');

const error = document.createElement('div');
error.className = 'no-renderer-error';
const errorText = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => info.mime);
error.innerText = errorText;
const error = document.createElement('div');
error.className = 'no-renderer-error';
error.innerText = errorMessage;

const cellText = document.createElement('div');
cellText.innerText = info.text();
const cellText = document.createElement('div');
cellText.innerText = info.text();

errorContainer.appendChild(error);
errorContainer.appendChild(cellText);
errorContainer.appendChild(error);
errorContainer.appendChild(cellText);

element.innerText = '';
element.appendChild(errorContainer);
}
element.innerText = '';
element.appendChild(errorContainer);
}
}();

Expand Down Expand Up @@ -1819,7 +1885,7 @@ async function webviewPreloads(ctx: PreloadContext) {
public readonly id: string;
public readonly element: HTMLElement;

private readonly outputItem: rendererApi.OutputItem;
private readonly outputItem: ExtendedOutputItem;

/// Internal field that holds text content
private _content: { readonly value: string; readonly version: number; readonly metadata: NotebookCellMetadata };
Expand All @@ -1840,7 +1906,7 @@ async function webviewPreloads(ctx: PreloadContext) {
});

let cachedData: { readonly version: number; readonly value: Uint8Array } | undefined;
this.outputItem = Object.freeze<rendererApi.OutputItem>({
this.outputItem = Object.freeze<ExtendedOutputItem>({
id,
mime,

Expand Down Expand Up @@ -1868,7 +1934,12 @@ async function webviewPreloads(ctx: PreloadContext) {

blob(): Blob {
return new Blob([this.data()], { type: this.mime });
}
},

_allOutputItems: [{
mime,
getItem: async () => this.outputItem,
}]
});

const root = document.getElementById('container')!;
Expand Down

0 comments on commit d8568bb

Please sign in to comment.