Skip to content

Commit

Permalink
Improve notebook kernel/renderer messaging (#13401)
Browse files Browse the repository at this point in the history
  • Loading branch information
jonah-iden authored Feb 21, 2024
1 parent 6e8031b commit 4a275b2
Show file tree
Hide file tree
Showing 19 changed files with 362 additions and 69 deletions.
37 changes: 37 additions & 0 deletions packages/notebook/src/browser/notebook-editor-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ export function createNotebookEditorWidgetContainer(parent: interfaces.Container

const NotebookEditorProps = Symbol('NotebookEditorProps');

interface RenderMessage {
rendererId: string;
message: unknown;
}

export interface NotebookEditorProps {
uri: URI,
readonly notebookType: string,
Expand Down Expand Up @@ -87,6 +92,18 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
protected readonly onDidChangeReadOnlyEmitter = new Emitter<boolean | MarkdownString>();
readonly onDidChangeReadOnly = this.onDidChangeReadOnlyEmitter.event;

protected readonly onPostKernelMessageEmitter = new Emitter<unknown>();
readonly onPostKernelMessage = this.onPostKernelMessageEmitter.event;

protected readonly onDidPostKernelMessageEmitter = new Emitter<unknown>();
readonly onDidPostKernelMessage = this.onDidPostKernelMessageEmitter.event;

protected readonly onPostRendererMessageEmitter = new Emitter<RenderMessage>();
readonly onPostRendererMessage = this.onPostRendererMessageEmitter.event;

protected readonly onDidReceiveKernelMessageEmitter = new Emitter<unknown>();
readonly onDidRecieveKernelMessage = this.onDidReceiveKernelMessageEmitter.event;

protected readonly renderers = new Map<CellKind, CellRenderer>();
protected _model?: NotebookModel;
protected _ready: Deferred<NotebookModel> = new Deferred();
Expand Down Expand Up @@ -190,4 +207,24 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
super.onAfterDetach(msg);
this.notebookEditorService.removeNotebookEditor(this);
}

postKernelMessage(message: unknown): void {
this.onDidPostKernelMessageEmitter.fire(message);
}

postRendererMessage(rendererId: string, message: unknown): void {
this.onPostRendererMessageEmitter.fire({ rendererId, message });
}

recieveKernelMessage(message: unknown): void {
this.onDidReceiveKernelMessageEmitter.fire(message);
}

override dispose(): void {
this.onDidChangeModelEmitter.dispose();
this.onDidPostKernelMessageEmitter.dispose();
this.onDidReceiveKernelMessageEmitter.dispose();
this.onPostRendererMessageEmitter.dispose();
super.dispose();
}
}
19 changes: 19 additions & 0 deletions packages/notebook/src/browser/notebook-renderer-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export interface NotebookRendererInfo {
readonly requiresMessaging: boolean;
}

export interface NotebookPreloadInfo {
readonly type: string;
readonly entrypoint: string;
}

@injectable()
export class NotebookRendererRegistry {

Expand All @@ -39,6 +44,12 @@ export class NotebookRendererRegistry {
return this._notebookRenderers;
}

private readonly _staticNotebookPreloads: NotebookPreloadInfo[] = [];

get staticNotebookPreloads(): readonly NotebookPreloadInfo[] {
return this._staticNotebookPreloads;
}

registerNotebookRenderer(type: NotebookRendererDescriptor, basePath: string): Disposable {
let entrypoint;
if (typeof type.entrypoint === 'string') {
Expand All @@ -62,5 +73,13 @@ export class NotebookRendererRegistry {
this._notebookRenderers.splice(this._notebookRenderers.findIndex(renderer => renderer.id === type.id), 1);
});
}

registerStaticNotebookPreload(type: string, entrypoint: string, basePath: string): Disposable {
const staticPreload = { type, entrypoint: new Path(basePath).join(entrypoint).toString() };
this._staticNotebookPreloads.push(staticPreload);
return Disposable.create(() => {
this._staticNotebookPreloads.splice(this._staticNotebookPreloads.indexOf(staticPreload), 1);
});
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

import { Disposable } from '@theia/core';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { NotebookModel } from '../view-model/notebook-model';

export const CellOutputWebviewFactory = Symbol('outputWebviewFactory');

export type CellOutputWebviewFactory = (cell: NotebookCellModel) => Promise<CellOutputWebview>;
export type CellOutputWebviewFactory = (cell: NotebookCellModel, notebook: NotebookModel) => Promise<CellOutputWebview>;

export interface CellOutputWebview extends Disposable {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ export interface NotebookKernel {
// ID of the extension providing this kernel
readonly extensionId: string;

readonly localResourceRoot: URI;
readonly preloadUris: URI[];
readonly preloadProvides: string[];

readonly handle: number;
label: string;
description?: string;
detail?: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@
*--------------------------------------------------------------------------------------------*/

import { Emitter } from '@theia/core';
import { injectable } from '@theia/core/shared/inversify';
import { inject, injectable } from '@theia/core/shared/inversify';
import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
import { NotebookEditorWidgetService } from './notebook-editor-widget-service';

interface RendererMessage {
editorId: string;
Expand Down Expand Up @@ -50,6 +51,9 @@ export class NotebookRendererMessagingService implements Disposable {
private readonly willActivateRendererEmitter = new Emitter<string>();
readonly onWillActivateRenderer = this.willActivateRendererEmitter.event;

@inject(NotebookEditorWidgetService)
private readonly editorWidgetService: NotebookEditorWidgetService;

private readonly activations = new Map<string /* rendererId */, undefined | RendererMessage[]>();
private readonly scopedMessaging = new Map<string /* editorId */, RendererMessaging>();

Expand Down Expand Up @@ -86,6 +90,10 @@ export class NotebookRendererMessagingService implements Disposable {

const messaging: RendererMessaging = {
postMessage: (rendererId, message) => this.postMessage(editorId, rendererId, message),
receiveMessage: async (rendererId, message) => {
this.editorWidgetService.getNotebookEditor(editorId)?.postRendererMessage(rendererId, message);
return true;
},
dispose: () => this.scopedMessaging.delete(editorId),
};

Expand Down
11 changes: 6 additions & 5 deletions packages/notebook/src/browser/view/notebook-code-cell-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ export class NotebookCodeCellRenderer implements CellRenderer {
</div>
</div>
<div className='theia-notebook-cell-with-sidebar'>
<NotebookCodeCellOutputs cell={cell} outputWebviewFactory={this.cellOutputWebviewFactory}
<NotebookCodeCellOutputs cell={cell} notebook={notebookModel} outputWebviewFactory={this.cellOutputWebviewFactory}
renderSidebar={() =>
this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, notebookModel, cell, cell.outputs[0])} />
</div>
Expand Down Expand Up @@ -166,6 +166,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat

interface NotebookCellOutputProps {
cell: NotebookCellModel;
notebook: NotebookModel;
outputWebviewFactory: CellOutputWebviewFactory;
renderSidebar: () => React.ReactNode;
}
Expand All @@ -182,14 +183,14 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
}

override async componentDidMount(): Promise<void> {
const { cell, outputWebviewFactory } = this.props;
const { cell, notebook, outputWebviewFactory } = this.props;
this.toDispose.push(cell.onDidChangeOutputs(async () => {
if (!this.outputsWebviewPromise && cell.outputs.length > 0) {
this.outputsWebviewPromise = outputWebviewFactory(cell).then(webview => {
this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
this.outputsWebview = webview;
this.forceUpdate();
return webview;
});
});
this.forceUpdate();
} else if (this.outputsWebviewPromise && cell.outputs.length === 0 && cell.internalMetadata.runEndTime) {
(await this.outputsWebviewPromise).dispose();
Expand All @@ -199,7 +200,7 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
}
}));
if (cell.outputs.length > 0) {
this.outputsWebviewPromise = outputWebviewFactory(cell).then(webview => {
this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
this.outputsWebview = webview;
this.forceUpdate();
return webview;
Expand Down
4 changes: 2 additions & 2 deletions packages/plugin-ext/src/common/plugin-api-rpc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2288,7 +2288,7 @@ export const MAIN_RPC_CONTEXT = {
NOTEBOOKS_EXT: createProxyIdentifier<NotebooksExt>('NotebooksExt'),
NOTEBOOK_DOCUMENTS_EXT: createProxyIdentifier<NotebookDocumentsExt>('NotebookDocumentsExt'),
NOTEBOOK_EDITORS_EXT: createProxyIdentifier<NotebookEditorsExt>('NotebookEditorsExt'),
NOTEBOOK_RENDERERS_EXT: createProxyIdentifier<NotebookRenderersExt>('NotebooksExt'),
NOTEBOOK_RENDERERS_EXT: createProxyIdentifier<NotebookRenderersExt>('NotebooksRenderersExt'),
NOTEBOOK_KERNELS_EXT: createProxyIdentifier<NotebookKernelsExt>('NotebookKernelsExt'),
TERMINAL_EXT: createProxyIdentifier<TerminalServiceExt>('TerminalServiceExt'),
OUTPUT_CHANNEL_REGISTRY_EXT: createProxyIdentifier<OutputChannelRegistryExt>('OutputChannelRegistryExt'),
Expand Down Expand Up @@ -2488,7 +2488,7 @@ export interface NotebookKernelDto {
id: string;
notebookType: string;
extensionId: string;
// extensionLocation: UriComponents;
extensionLocation: UriComponents;
label: string;
detail?: string;
description?: string;
Expand Down
13 changes: 12 additions & 1 deletion packages/plugin-ext/src/common/plugin-protocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ export interface PluginPackageContribution {
terminal?: PluginPackageTerminal;
notebooks?: PluginPackageNotebook[];
notebookRenderer?: PluginNotebookRendererContribution[];
notebookPreload?: PluginPackageNotebookPreload[];
}

export interface PluginPackageNotebook {
Expand All @@ -120,6 +121,11 @@ export interface PluginNotebookRendererContribution {
readonly requiresMessaging?: 'always' | 'optional' | 'never'
}

export interface PluginPackageNotebookPreload {
type: string;
entrypoint: string;
}

export interface PluginPackageAuthenticationProvider {
id: string;
label: string;
Expand Down Expand Up @@ -610,8 +616,8 @@ export interface PluginContribution {
terminalProfiles?: TerminalProfile[];
notebooks?: NotebookContribution[];
notebookRenderer?: NotebookRendererContribution[];
notebookPreload?: notebookPreloadContribution[];
}

export interface NotebookContribution {
type: string;
displayName: string;
Expand All @@ -627,6 +633,11 @@ export interface NotebookRendererContribution {
readonly requiresMessaging?: 'always' | 'optional' | 'never'
}

export interface notebookPreloadContribution {
type: string;
entrypoint: string;
}

export interface AuthenticationProviderInformation {
id: string;
label: string;
Expand Down
10 changes: 8 additions & 2 deletions packages/plugin-ext/src/hosted/node/scanners/scanner-theia.ts
Original file line number Diff line number Diff line change
Expand Up @@ -341,13 +341,19 @@ export class TheiaPluginScanner extends AbstractPluginScanner {
try {
contributions.notebooks = rawPlugin.contributes.notebooks;
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'notebooks'.`, rawPlugin.contributes.authentication, err);
console.error(`Could not read '${rawPlugin.name}' contribution 'notebooks'.`, rawPlugin.contributes.notebooks, err);
}

try {
contributions.notebookRenderer = rawPlugin.contributes.notebookRenderer;
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'notebooks'.`, rawPlugin.contributes.authentication, err);
console.error(`Could not read '${rawPlugin.name}' contribution 'notebook-renderer'.`, rawPlugin.contributes.notebookRenderer, err);
}

try {
contributions.notebookPreload = rawPlugin.contributes.notebookPreload;
} catch (err) {
console.error(`Could not read '${rawPlugin.name}' contribution 'notebooks-preload'.`, rawPlugin.contributes.notebookPreload, err);
}

try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ import { UriComponents } from '@theia/core/lib/common/uri';
import { LanguageService } from '@theia/core/lib/browser/language-service';
import { CellExecuteUpdateDto, CellExecutionCompleteDto, MAIN_RPC_CONTEXT, NotebookKernelDto, NotebookKernelsExt, NotebookKernelsMain } from '../../../common';
import { RPCProtocol } from '../../../common/rpc-protocol';
import { CellExecution, NotebookExecutionStateService, NotebookKernelChangeEvent, NotebookKernelService, NotebookService } from '@theia/notebook/lib/browser';
import {
CellExecution, NotebookEditorWidgetService, NotebookExecutionStateService,
NotebookKernelChangeEvent, NotebookKernelService, NotebookService
} from '@theia/notebook/lib/browser';
import { combinedDisposable } from '@theia/monaco-editor-core/esm/vs/base/common/lifecycle';
import { interfaces } from '@theia/core/shared/inversify';
import { NotebookKernelSourceAction } from '@theia/notebook/lib/common';
Expand Down Expand Up @@ -54,7 +57,7 @@ abstract class NotebookKernel {
return this.preloads.map(p => p.provides).flat();
}

constructor(data: NotebookKernelDto, private languageService: LanguageService) {
constructor(public readonly handle: number, data: NotebookKernelDto, private languageService: LanguageService) {
this.id = data.id;
this.viewType = data.notebookType;
this.extensionId = data.extensionId;
Expand All @@ -65,6 +68,7 @@ abstract class NotebookKernel {
this.detail = data.detail;
this.supportedLanguages = (data.supportedLanguages && data.supportedLanguages.length > 0) ? data.supportedLanguages : languageService.languages.map(lang => lang.id);
this.implementsExecutionOrder = data.supportsExecutionOrder ?? false;
this.localResourceRoot = URI.fromComponents(data.extensionLocation);
this.preloads = data.preloads?.map(u => ({ uri: URI.fromComponents(u.uri), provides: u.provides })) ?? [];
}

Expand Down Expand Up @@ -125,6 +129,7 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain {
private notebookService: NotebookService;
private languageService: LanguageService;
private notebookExecutionStateService: NotebookExecutionStateService;
private notebookEditorWidgetService: NotebookEditorWidgetService;

private readonly executions = new Map<number, CellExecution>();

Expand All @@ -138,10 +143,46 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain {
this.notebookExecutionStateService = container.get(NotebookExecutionStateService);
this.notebookService = container.get(NotebookService);
this.languageService = container.get(LanguageService);
this.notebookEditorWidgetService = container.get(NotebookEditorWidgetService);

this.notebookEditorWidgetService.onDidAddNotebookEditor(editor => {
editor.onDidRecieveKernelMessage(async message => {
const kernel = this.notebookKernelService.getSelectedOrSuggestedKernel(editor.model!);
if (kernel) {
this.proxy.$acceptKernelMessageFromRenderer(kernel.handle, editor.id, message);
}
});
});
}

$postMessage(handle: number, editorId: string | undefined, message: unknown): Promise<boolean> {
throw new Error('Method not implemented.');
async $postMessage(handle: number, editorId: string | undefined, message: unknown): Promise<boolean> {
const tuple = this.kernels.get(handle);
if (!tuple) {
throw new Error('kernel already disposed');
}
const [kernel] = tuple;
let didSend = false;
for (const editor of this.notebookEditorWidgetService.getNotebookEditors()) {
if (!editor.model) {
continue;
}
if (this.notebookKernelService.getMatchingKernel(editor.model).selected !== kernel) {
// different kernel
continue;
}
if (editorId === undefined) {
// all editors
editor.postKernelMessage(message);
didSend = true;
} else if (editor.id === editorId) {
// selected editors
editor.postKernelMessage(message);
didSend = true;
break;
}
}
return didSend;

}

async $addKernel(handle: number, data: NotebookKernelDto): Promise<void> {
Expand All @@ -153,7 +194,7 @@ export class NotebookKernelsMainImpl implements NotebookKernelsMain {
async cancelNotebookCellExecution(uri: URI, handles: number[]): Promise<void> {
await that.proxy.$cancelCells(handle, uri.toComponents(), handles);
}
}(data, this.languageService);
}(handle, data, this.languageService);

const listener = this.notebookKernelService.onDidChangeSelectedKernel(e => {
if (e.oldKernel === kernel.id) {
Expand Down
Loading

0 comments on commit 4a275b2

Please sign in to comment.