Skip to content

Commit

Permalink
Fix #157689. Trigger outputs to re-render outputs when missing render…
Browse files Browse the repository at this point in the history
…ers are installed. (#157692)
  • Loading branch information
rebornix authored and joyceerhl committed Aug 10, 2022
1 parent 25d076a commit d4170a7
Show file tree
Hide file tree
Showing 9 changed files with 176 additions and 44 deletions.
2 changes: 2 additions & 0 deletions src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ export interface ICellOutputViewModel extends IDisposable {
resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number];
pickedMimeType: IOrderedMimeType | undefined;
hasMultiMimeType(): boolean;
readonly onDidResetRenderer: Event<void>;
resetRenderer(): void;
toRawJSON(): any;
}

Expand Down
30 changes: 27 additions & 3 deletions src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser
import { NotebookOverviewRuler } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler';
import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar';
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
import { CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { CellKind, INotebookSearchOptions, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import { NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
Expand Down Expand Up @@ -366,6 +366,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer);
this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));

this._register(_notebookService.onDidChangeOutputRenderers(() => {
this._updateOutputRenderers();
}));

this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this));

this._register(notebookKernelService.onDidChangeSelectedNotebooks(e => {
Expand Down Expand Up @@ -1064,6 +1068,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
}));
}

private _updateOutputRenderers() {
if (!this.viewModel || !this._webview) {
return;
}

this._webview.updateOutputRenderers();
this.viewModel.viewCells.forEach(cell => {
cell.outputsViewModels.forEach(output => {
if (output.pickedMimeType?.rendererId === RENDERER_NOT_AVAILABLE) {
output.resetRenderer();
}
});
});
}

getDomNode() {
return this._overlayContainer;
}
Expand Down Expand Up @@ -2680,8 +2699,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD

const cellTop = this._list.getAbsoluteTopOfElement(cell) + top;

// const cellTop = this._list.getAbsoluteTopOfElement(cell);
if (!this._webview.insetMapping.has(output.source)) {
const existingOutput = this._webview.insetMapping.get(output.source);
if (!existingOutput
|| (!existingOutput.renderer && output.type === RenderOutputType.Extension)
|| (existingOutput.renderer
&& output.type === RenderOutputType.Extension
&& existingOutput.renderer.id !== output.renderer.id)
) {
await this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset);
} else {
const outputIndex = cell.outputsViewModels.indexOf(output.source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,8 @@ export class NotebookService extends Disposable implements INotebookService {
return this._notebookProviderInfoStore;
}
private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore);
private readonly _onDidChangeOutputRenderers = this._register(new Emitter<void>());
readonly onDidChangeOutputRenderers = this._onDidChangeOutputRenderers.event;
private readonly _models = new ResourceMap<ModelData>();

private readonly _onWillAddNotebookDocument = this._register(new Emitter<NotebookTextModel>());
Expand Down Expand Up @@ -480,6 +482,8 @@ export class NotebookService extends Disposable implements INotebookService {
}));
}
}

this._onDidChangeOutputRenderers.fire();
});

const updateOrder = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,11 @@ export class CellOutputElement extends Disposable {
this.contextKeyService = parentContextKeyService;

this._register(this.output.model.onDidChangeData(() => {
this.updateOutputData();
this.rerender();
}));

this._register(this.output.onDidResetRenderer(() => {
this.rerender();
}));
}

Expand Down Expand Up @@ -120,7 +124,7 @@ export class CellOutputElement extends Disposable {
}
}

updateOutputData() {
rerender() {
if (
this.notebookEditor.hasModel() &&
this.innerContainer &&
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w
import { asWebviewUri, webviewGenericCspSource } from 'vs/workbench/common/webview';
import { CellEditState, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
Expand All @@ -45,7 +45,7 @@ import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/work
import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, RendererMetadata, ToWebviewMessage } from './webviewMessages';

export interface ICachedInset<K extends ICommonCellInfo> {
outputId: string;
Expand Down Expand Up @@ -900,16 +900,7 @@ var requirejs = (function() {
}

private _createInset(webviewService: IWebviewService, content: string) {
const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri);
const notebookDir = this.getNotebookBaseUri();

this.localResourceRootsCache = [
...this.notebookService.getNotebookProviderResourceRoots(),
...this.notebookService.getRenderers().map(x => dirname(x.entrypoint)),
...workspaceFolders,
notebookDir,
...this.getBuiltinLocalResourceRoots(),
];
this.localResourceRootsCache = this._getResourceRootsCache();
const webview = webviewService.createWebviewElement({
id: this.id,
options: {
Expand All @@ -929,6 +920,18 @@ var requirejs = (function() {
return webview;
}

private _getResourceRootsCache() {
const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri);
const notebookDir = this.getNotebookBaseUri();
return [
...this.notebookService.getNotebookProviderResourceRoots(),
...this.notebookService.getRenderers().map(x => dirname(x.entrypoint)),
...workspaceFolders,
notebookDir,
...this.getBuiltinLocalResourceRoots()
];
}

private initializeWebViewState() {
this._preloadsCache.clear();
if (this._currentKernel) {
Expand Down Expand Up @@ -1168,25 +1171,37 @@ var requirejs = (function() {
await p;
}

/**
* Validate if cached inset is out of date and require a rerender
* Note that it doesn't account for output content change.
*/
private _cachedInsetEqual(cachedInset: ICachedInset<T>, content: IInsetRenderOutput) {
if (content.type === RenderOutputType.Extension) {
// Use a new renderer
return cachedInset.renderer?.id === content.renderer.id;
} else {
// The new renderer is the default HTML renderer
return cachedInset.cachedCreation.type === 'html';
}
}

async createOutput(cellInfo: T, content: IInsetRenderOutput, cellTop: number, offset: number) {
if (this._disposed) {
return;
}

if (this.insetMapping.has(content.source)) {
const outputCache = this.insetMapping.get(content.source);
const cachedInset = this.insetMapping.get(content.source);

if (outputCache) {
this.hiddenInsetMapping.delete(content.source);
this._sendMessageToWebview({
type: 'showOutput',
cellId: outputCache.cellInfo.cellId,
outputId: outputCache.outputId,
cellTop: cellTop,
outputOffset: offset
});
return;
}
if (cachedInset && this._cachedInsetEqual(cachedInset, content)) {
this.hiddenInsetMapping.delete(content.source);
this._sendMessageToWebview({
type: 'showOutput',
cellId: cachedInset.cellInfo.cellId,
outputId: cachedInset.outputId,
cellTop: cellTop,
outputOffset: offset
});
return;
}

const messageBase = {
Expand Down Expand Up @@ -1407,6 +1422,25 @@ var requirejs = (function() {

}

updateOutputRenderers() {
if (!this.webview) {
return;
}

const renderersData = this.getRendererData();
this.localResourceRootsCache = this._getResourceRootsCache();
const mixedResourceRoots = [
...(this.localResourceRootsCache || []),
...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []),
];

this.webview.localResourcesRoot = mixedResourceRoots;
this._sendMessageToWebview({
type: 'updateRenderers',
rendererData: renderersData
});
}

async updateKernelPreloads(kernel: INotebookKernel | undefined) {
if (this._disposed || kernel === this._currentKernel) {
return;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,20 @@ export interface IUpdateControllerPreloadsMessage {
readonly resources: readonly IControllerPreload[];
}

export interface RendererMetadata {
readonly id: string;
readonly entrypoint: string;
readonly mimeTypes: readonly string[];
readonly extends: string | undefined;
readonly messaging: boolean;
readonly isBuiltin: boolean;
}

export interface IUpdateRenderersMessage {
readonly type: 'updateRenderers';
readonly rendererData: readonly RendererMetadata[];
}

export interface IUpdateDecorationsMessage {
readonly type: 'decorations';
readonly cellId: string;
Expand Down Expand Up @@ -450,6 +464,7 @@ export type ToWebviewMessage = IClearMessage |
IHideOutputMessage |
IShowOutputMessage |
IUpdateControllerPreloadsMessage |
IUpdateRenderersMessage |
IUpdateDecorationsMessage |
ICustomKernelMessage |
ICustomRendererMessage |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import type { Event } from 'vs/base/common/event';
import type { IDisposable } from 'vs/base/common/lifecycle';
import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages';
import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
import type * as rendererApi from 'vscode-notebook-renderer';

// !! IMPORTANT !! ----------------------------------------------------------------------------------
Expand Down Expand Up @@ -68,7 +68,7 @@ interface PreloadContext {
readonly nonce: string;
readonly style: PreloadStyles;
readonly options: PreloadOptions;
readonly rendererData: readonly RendererMetadata[];
readonly rendererData: readonly webviewMessages.RendererMetadata[];
readonly isWorkspaceTrusted: boolean;
readonly lineLimit: number;
}
Expand Down Expand Up @@ -1160,6 +1160,11 @@ async function webviewPreloads(ctx: PreloadContext) {
}
break;
}
case 'updateRenderers': {
const { rendererData } = event.data;
renderers.updateRendererData(rendererData);
break;
}
case 'focus-output':
focusFirstFocusableInCell(event.data.cellId);
break;
Expand Down Expand Up @@ -1242,7 +1247,7 @@ async function webviewPreloads(ctx: PreloadContext) {

class Renderer {
constructor(
public readonly data: RendererMetadata,
public readonly data: webviewMessages.RendererMetadata,
private readonly loadExtension: (id: string) => Promise<void>,
) { }

Expand Down Expand Up @@ -1403,6 +1408,50 @@ async function webviewPreloads(ctx: PreloadContext) {
return this._renderers.get(id);
}

private rendererEqual(a: webviewMessages.RendererMetadata, b: webviewMessages.RendererMetadata) {
if (a.entrypoint !== b.entrypoint || a.id !== b.id || a.extends !== b.extends || a.messaging !== b.messaging) {
return false;
}

if (a.mimeTypes.length !== b.mimeTypes.length) {
return false;
}

for (let i = 0; i < a.mimeTypes.length; i++) {
if (a.mimeTypes[i] !== b.mimeTypes[i]) {
return false;
}
}

return true;
}

public updateRendererData(rendererData: readonly webviewMessages.RendererMetadata[]) {
const oldKeys = new Set(this._renderers.keys());
const newKeys = new Set(rendererData.map(d => d.id));

for (const renderer of rendererData) {
const existing = this._renderers.get(renderer.id);
if (existing && this.rendererEqual(existing.data, renderer)) {
continue;
}

this._renderers.set(renderer.id, new Renderer(renderer, async (extensionId) => {
const ext = this._renderers.get(extensionId);
if (!ext) {
throw new Error(`Could not find extending renderer: ${extensionId}`);
}
await ext.load();
}));
}

for (const key of oldKeys) {
if (!newKeys.has(key)) {
this._renderers.delete(key);
}
}
}

public async load(id: string) {
const renderer = this._renderers.get(id);
if (!renderer) {
Expand Down Expand Up @@ -2205,16 +2254,7 @@ async function webviewPreloads(ctx: PreloadContext) {
}();
}

export interface RendererMetadata {
readonly id: string;
readonly entrypoint: string;
readonly mimeTypes: readonly string[];
readonly extends: string | undefined;
readonly messaging: boolean;
readonly isBuiltin: boolean;
}

export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly RendererMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly webviewMessages.RendererMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
const ctx: PreloadContext = {
style: styleValues,
options,
Expand Down

0 comments on commit d4170a7

Please sign in to comment.