Skip to content

Commit 06f2a63

Browse files
authored
Fix microsoft#157689. Trigger outputs to re-render outputs when missing renderers are installed. (microsoft#157692)
1 parent a5abda7 commit 06f2a63

File tree

9 files changed

+176
-44
lines changed

9 files changed

+176
-44
lines changed

src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,8 @@ export interface ICellOutputViewModel extends IDisposable {
100100
resolveMimeTypes(textModel: NotebookTextModel, kernelProvides: readonly string[] | undefined): [readonly IOrderedMimeType[], number];
101101
pickedMimeType: IOrderedMimeType | undefined;
102102
hasMultiMimeType(): boolean;
103+
readonly onDidResetRenderer: Event<void>;
104+
resetRenderer(): void;
103105
toRawJSON(): any;
104106
}
105107

src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser
7171
import { NotebookOverviewRuler } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler';
7272
import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar';
7373
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
74-
import { CellKind, INotebookSearchOptions, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
74+
import { CellKind, INotebookSearchOptions, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
7575
import { NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
7676
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
7777
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
@@ -366,6 +366,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
366366
this.scopedContextKeyService = contextKeyService.createScoped(this._overlayContainer);
367367
this.instantiationService = instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
368368

369+
this._register(_notebookService.onDidChangeOutputRenderers(() => {
370+
this._updateOutputRenderers();
371+
}));
372+
369373
this._register(this.instantiationService.createInstance(NotebookEditorContextKeys, this));
370374

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

1071+
private _updateOutputRenderers() {
1072+
if (!this.viewModel || !this._webview) {
1073+
return;
1074+
}
1075+
1076+
this._webview.updateOutputRenderers();
1077+
this.viewModel.viewCells.forEach(cell => {
1078+
cell.outputsViewModels.forEach(output => {
1079+
if (output.pickedMimeType?.rendererId === RENDERER_NOT_AVAILABLE) {
1080+
output.resetRenderer();
1081+
}
1082+
});
1083+
});
1084+
}
1085+
10671086
getDomNode() {
10681087
return this._overlayContainer;
10691088
}
@@ -2680,8 +2699,13 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
26802699

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

2683-
// const cellTop = this._list.getAbsoluteTopOfElement(cell);
2684-
if (!this._webview.insetMapping.has(output.source)) {
2702+
const existingOutput = this._webview.insetMapping.get(output.source);
2703+
if (!existingOutput
2704+
|| (!existingOutput.renderer && output.type === RenderOutputType.Extension)
2705+
|| (existingOutput.renderer
2706+
&& output.type === RenderOutputType.Extension
2707+
&& existingOutput.renderer.id !== output.renderer.id)
2708+
) {
26852709
await this._webview.createOutput({ cellId: cell.id, cellHandle: cell.handle, cellUri: cell.uri }, output, cellTop, offset);
26862710
} else {
26872711
const outputIndex = cell.outputsViewModels.indexOf(output.source);

src/vs/workbench/contrib/notebook/browser/notebookServiceImpl.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,8 @@ export class NotebookService extends Disposable implements INotebookService {
416416
return this._notebookProviderInfoStore;
417417
}
418418
private readonly _notebookRenderersInfoStore = this._instantiationService.createInstance(NotebookOutputRendererInfoStore);
419+
private readonly _onDidChangeOutputRenderers = this._register(new Emitter<void>());
420+
readonly onDidChangeOutputRenderers = this._onDidChangeOutputRenderers.event;
419421
private readonly _models = new ResourceMap<ModelData>();
420422

421423
private readonly _onWillAddNotebookDocument = this._register(new Emitter<NotebookTextModel>());
@@ -480,6 +482,8 @@ export class NotebookService extends Disposable implements INotebookService {
480482
}));
481483
}
482484
}
485+
486+
this._onDidChangeOutputRenderers.fire();
483487
});
484488

485489
const updateOrder = () => {

src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,11 @@ export class CellOutputElement extends Disposable {
8787
this.contextKeyService = parentContextKeyService;
8888

8989
this._register(this.output.model.onDidChangeData(() => {
90-
this.updateOutputData();
90+
this.rerender();
91+
}));
92+
93+
this._register(this.output.onDidResetRenderer(() => {
94+
this.rerender();
9195
}));
9296
}
9397

@@ -120,7 +124,7 @@ export class CellOutputElement extends Disposable {
120124
}
121125
}
122126

123-
updateOutputData() {
127+
rerender() {
124128
if (
125129
this.notebookEditor.hasModel() &&
126130
this.innerContainer &&

src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts

Lines changed: 59 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/w
3434
import { asWebviewUri, webviewGenericCspSource } from 'vs/workbench/common/webview';
3535
import { CellEditState, ICellOutputViewModel, ICellViewModel, ICommonCellInfo, IDisplayOutputLayoutUpdateRequest, IDisplayOutputViewModel, IFocusNotebookCellOptions, IGenericCellViewModel, IInsetRenderOutput, INotebookEditorCreationOptions, INotebookWebviewMessage, RenderOutputType } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
3636
import { NOTEBOOK_WEBVIEW_BOUNDARY } from 'vs/workbench/contrib/notebook/browser/view/notebookCellList';
37-
import { preloadsScriptStr, RendererMetadata } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
37+
import { preloadsScriptStr } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads';
3838
import { transformWebviewThemeVars } from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewThemeMapping';
3939
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
4040
import { CellUri, INotebookRendererInfo, NotebookSetting, RendererMessagingSpec } from 'vs/workbench/contrib/notebook/common/notebookCommon';
@@ -45,7 +45,7 @@ import { IWebviewElement, IWebviewService, WebviewContentPurpose } from 'vs/work
4545
import { WebviewWindowDragMonitor } from 'vs/workbench/contrib/webview/browser/webviewWindowDragMonitor';
4646
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
4747
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
48-
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, ToWebviewMessage } from './webviewMessages';
48+
import { FromWebviewMessage, IAckOutputHeight, IClickedDataUrlMessage, ICodeBlockHighlightRequest, IContentWidgetTopRequest, IControllerPreload, ICreationContent, ICreationRequestMessage, IFindMatch, IMarkupCellInitialization, RendererMetadata, ToWebviewMessage } from './webviewMessages';
4949

5050
export interface ICachedInset<K extends ICommonCellInfo> {
5151
outputId: string;
@@ -900,16 +900,7 @@ var requirejs = (function() {
900900
}
901901

902902
private _createInset(webviewService: IWebviewService, content: string) {
903-
const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri);
904-
const notebookDir = this.getNotebookBaseUri();
905-
906-
this.localResourceRootsCache = [
907-
...this.notebookService.getNotebookProviderResourceRoots(),
908-
...this.notebookService.getRenderers().map(x => dirname(x.entrypoint)),
909-
...workspaceFolders,
910-
notebookDir,
911-
...this.getBuiltinLocalResourceRoots(),
912-
];
903+
this.localResourceRootsCache = this._getResourceRootsCache();
913904
const webview = webviewService.createWebviewElement({
914905
id: this.id,
915906
options: {
@@ -929,6 +920,18 @@ var requirejs = (function() {
929920
return webview;
930921
}
931922

923+
private _getResourceRootsCache() {
924+
const workspaceFolders = this.contextService.getWorkspace().folders.map(x => x.uri);
925+
const notebookDir = this.getNotebookBaseUri();
926+
return [
927+
...this.notebookService.getNotebookProviderResourceRoots(),
928+
...this.notebookService.getRenderers().map(x => dirname(x.entrypoint)),
929+
...workspaceFolders,
930+
notebookDir,
931+
...this.getBuiltinLocalResourceRoots()
932+
];
933+
}
934+
932935
private initializeWebViewState() {
933936
this._preloadsCache.clear();
934937
if (this._currentKernel) {
@@ -1168,25 +1171,37 @@ var requirejs = (function() {
11681171
await p;
11691172
}
11701173

1174+
/**
1175+
* Validate if cached inset is out of date and require a rerender
1176+
* Note that it doesn't account for output content change.
1177+
*/
1178+
private _cachedInsetEqual(cachedInset: ICachedInset<T>, content: IInsetRenderOutput) {
1179+
if (content.type === RenderOutputType.Extension) {
1180+
// Use a new renderer
1181+
return cachedInset.renderer?.id === content.renderer.id;
1182+
} else {
1183+
// The new renderer is the default HTML renderer
1184+
return cachedInset.cachedCreation.type === 'html';
1185+
}
1186+
}
1187+
11711188
async createOutput(cellInfo: T, content: IInsetRenderOutput, cellTop: number, offset: number) {
11721189
if (this._disposed) {
11731190
return;
11741191
}
11751192

1176-
if (this.insetMapping.has(content.source)) {
1177-
const outputCache = this.insetMapping.get(content.source);
1193+
const cachedInset = this.insetMapping.get(content.source);
11781194

1179-
if (outputCache) {
1180-
this.hiddenInsetMapping.delete(content.source);
1181-
this._sendMessageToWebview({
1182-
type: 'showOutput',
1183-
cellId: outputCache.cellInfo.cellId,
1184-
outputId: outputCache.outputId,
1185-
cellTop: cellTop,
1186-
outputOffset: offset
1187-
});
1188-
return;
1189-
}
1195+
if (cachedInset && this._cachedInsetEqual(cachedInset, content)) {
1196+
this.hiddenInsetMapping.delete(content.source);
1197+
this._sendMessageToWebview({
1198+
type: 'showOutput',
1199+
cellId: cachedInset.cellInfo.cellId,
1200+
outputId: cachedInset.outputId,
1201+
cellTop: cellTop,
1202+
outputOffset: offset
1203+
});
1204+
return;
11901205
}
11911206

11921207
const messageBase = {
@@ -1407,6 +1422,25 @@ var requirejs = (function() {
14071422

14081423
}
14091424

1425+
updateOutputRenderers() {
1426+
if (!this.webview) {
1427+
return;
1428+
}
1429+
1430+
const renderersData = this.getRendererData();
1431+
this.localResourceRootsCache = this._getResourceRootsCache();
1432+
const mixedResourceRoots = [
1433+
...(this.localResourceRootsCache || []),
1434+
...(this._currentKernel ? [this._currentKernel.localResourceRoot] : []),
1435+
];
1436+
1437+
this.webview.localResourcesRoot = mixedResourceRoots;
1438+
this._sendMessageToWebview({
1439+
type: 'updateRenderers',
1440+
rendererData: renderersData
1441+
});
1442+
}
1443+
14101444
async updateKernelPreloads(kernel: INotebookKernel | undefined) {
14111445
if (this._disposed || kernel === this._currentKernel) {
14121446
return;

src/vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,20 @@ export interface IUpdateControllerPreloadsMessage {
272272
readonly resources: readonly IControllerPreload[];
273273
}
274274

275+
export interface RendererMetadata {
276+
readonly id: string;
277+
readonly entrypoint: string;
278+
readonly mimeTypes: readonly string[];
279+
readonly extends: string | undefined;
280+
readonly messaging: boolean;
281+
readonly isBuiltin: boolean;
282+
}
283+
284+
export interface IUpdateRenderersMessage {
285+
readonly type: 'updateRenderers';
286+
readonly rendererData: readonly RendererMetadata[];
287+
}
288+
275289
export interface IUpdateDecorationsMessage {
276290
readonly type: 'decorations';
277291
readonly cellId: string;
@@ -450,6 +464,7 @@ export type ToWebviewMessage = IClearMessage |
450464
IHideOutputMessage |
451465
IShowOutputMessage |
452466
IUpdateControllerPreloadsMessage |
467+
IUpdateRenderersMessage |
453468
IUpdateDecorationsMessage |
454469
ICustomKernelMessage |
455470
ICustomRendererMessage |

src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts

Lines changed: 53 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import type { Event } from 'vs/base/common/event';
77
import type { IDisposable } from 'vs/base/common/lifecycle';
88
import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages';
9-
import { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
9+
import type { NotebookCellMetadata } from 'vs/workbench/contrib/notebook/common/notebookCommon';
1010
import type * as rendererApi from 'vscode-notebook-renderer';
1111

1212
// !! IMPORTANT !! ----------------------------------------------------------------------------------
@@ -68,7 +68,7 @@ interface PreloadContext {
6868
readonly nonce: string;
6969
readonly style: PreloadStyles;
7070
readonly options: PreloadOptions;
71-
readonly rendererData: readonly RendererMetadata[];
71+
readonly rendererData: readonly webviewMessages.RendererMetadata[];
7272
readonly isWorkspaceTrusted: boolean;
7373
readonly lineLimit: number;
7474
}
@@ -1160,6 +1160,11 @@ async function webviewPreloads(ctx: PreloadContext) {
11601160
}
11611161
break;
11621162
}
1163+
case 'updateRenderers': {
1164+
const { rendererData } = event.data;
1165+
renderers.updateRendererData(rendererData);
1166+
break;
1167+
}
11631168
case 'focus-output':
11641169
focusFirstFocusableInCell(event.data.cellId);
11651170
break;
@@ -1242,7 +1247,7 @@ async function webviewPreloads(ctx: PreloadContext) {
12421247

12431248
class Renderer {
12441249
constructor(
1245-
public readonly data: RendererMetadata,
1250+
public readonly data: webviewMessages.RendererMetadata,
12461251
private readonly loadExtension: (id: string) => Promise<void>,
12471252
) { }
12481253

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

1411+
private rendererEqual(a: webviewMessages.RendererMetadata, b: webviewMessages.RendererMetadata) {
1412+
if (a.entrypoint !== b.entrypoint || a.id !== b.id || a.extends !== b.extends || a.messaging !== b.messaging) {
1413+
return false;
1414+
}
1415+
1416+
if (a.mimeTypes.length !== b.mimeTypes.length) {
1417+
return false;
1418+
}
1419+
1420+
for (let i = 0; i < a.mimeTypes.length; i++) {
1421+
if (a.mimeTypes[i] !== b.mimeTypes[i]) {
1422+
return false;
1423+
}
1424+
}
1425+
1426+
return true;
1427+
}
1428+
1429+
public updateRendererData(rendererData: readonly webviewMessages.RendererMetadata[]) {
1430+
const oldKeys = new Set(this._renderers.keys());
1431+
const newKeys = new Set(rendererData.map(d => d.id));
1432+
1433+
for (const renderer of rendererData) {
1434+
const existing = this._renderers.get(renderer.id);
1435+
if (existing && this.rendererEqual(existing.data, renderer)) {
1436+
continue;
1437+
}
1438+
1439+
this._renderers.set(renderer.id, new Renderer(renderer, async (extensionId) => {
1440+
const ext = this._renderers.get(extensionId);
1441+
if (!ext) {
1442+
throw new Error(`Could not find extending renderer: ${extensionId}`);
1443+
}
1444+
await ext.load();
1445+
}));
1446+
}
1447+
1448+
for (const key of oldKeys) {
1449+
if (!newKeys.has(key)) {
1450+
this._renderers.delete(key);
1451+
}
1452+
}
1453+
}
1454+
14061455
public async load(id: string) {
14071456
const renderer = this._renderers.get(id);
14081457
if (!renderer) {
@@ -2205,16 +2254,7 @@ async function webviewPreloads(ctx: PreloadContext) {
22052254
}();
22062255
}
22072256

2208-
export interface RendererMetadata {
2209-
readonly id: string;
2210-
readonly entrypoint: string;
2211-
readonly mimeTypes: readonly string[];
2212-
readonly extends: string | undefined;
2213-
readonly messaging: boolean;
2214-
readonly isBuiltin: boolean;
2215-
}
2216-
2217-
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly RendererMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
2257+
export function preloadsScriptStr(styleValues: PreloadStyles, options: PreloadOptions, renderers: readonly webviewMessages.RendererMetadata[], isWorkspaceTrusted: boolean, lineLimit: number, nonce: string) {
22182258
const ctx: PreloadContext = {
22192259
style: styleValues,
22202260
options,

0 commit comments

Comments
 (0)