Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

notebook output optimization #14234

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { NotebookCommands } from './notebook-actions-contribution';
import { changeCellType } from './cell-operations';
import { EditorLanguageQuickPickService } from '@theia/editor/lib/browser/editor-language-quick-pick-service';
import { NotebookService } from '../service/notebook-service';
import { NOTEBOOK_EDITOR_ID_PREFIX } from '../notebook-editor-widget';

export namespace NotebookCellCommands {
/** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
Expand Down Expand Up @@ -371,7 +372,9 @@ export class NotebookCellActionContribution implements MenuContribution, Command
}], true)
));
commands.registerCommand(NotebookCellCommands.CHANGE_OUTPUT_PRESENTATION_COMMAND, this.editableCellCommandHandler(
(_, __, output) => output?.requestOutputPresentationUpdate()
(notebook, cell, output) => {
this.notebookEditorWidgetService.getNotebookEditor(NOTEBOOK_EDITOR_ID_PREFIX + notebook.uri.toString())?.requestOuputPresentationChange(cell.handle, output);
}
));

const insertCommand = (type: CellKind, index: number | 'above' | 'below', focusContainer: boolean): CommandHandler => this.editableCellCommandHandler(() =>
Expand Down
39 changes: 32 additions & 7 deletions packages/notebook/src/browser/notebook-editor-widget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import * as React from '@theia/core/shared/react';
import { CommandRegistry, MenuModelRegistry, URI } from '@theia/core';
import { ReactWidget, Navigatable, SaveableSource, Message, DelegatingSaveable, lock, unlock, animationFrame } from '@theia/core/lib/browser';
import { ReactNode } from '@theia/core/shared/react';
import { CellKind } from '../common';
import { CellKind, NotebookCellsChangeType } from '../common';
import { CellRenderer as CellRenderer, NotebookCellListView } from './view/notebook-cell-list-view';
import { NotebookCodeCellRenderer } from './view/notebook-code-cell-view';
import { NotebookMarkdownCellRenderer } from './view/notebook-markdown-cell-view';
Expand All @@ -35,6 +35,8 @@ import { NotebookViewportService } from './view/notebook-viewport-service';
import { NotebookCellCommands } from './contributions/notebook-cell-actions-contribution';
import { NotebookFindWidget } from './view/notebook-find-widget';
import debounce = require('lodash/debounce');
import { CellOutputWebview, CellOutputWebviewFactory } from './renderers/cell-output-webview';
import { NotebookCellOutputModel } from './view-model/notebook-cell-output-model';
const PerfectScrollbar = require('react-perfect-scrollbar');

export const NotebookEditorWidgetContainerFactory = Symbol('NotebookEditorWidgetContainerFactory');
Expand All @@ -44,6 +46,9 @@ export function createNotebookEditorWidgetContainer(parent: interfaces.Container

child.bind(NotebookEditorProps).toConstantValue(props);

const cellOutputWebviewFactory: CellOutputWebviewFactory = parent.get(CellOutputWebviewFactory);
child.bind(CellOutputWebview).toConstantValue(cellOutputWebviewFactory());

child.bind(NotebookContextManager).toSelf().inSingletonScope();
child.bind(NotebookMainToolbarRenderer).toSelf().inSingletonScope();
child.bind(NotebookCellToolbarFactory).toSelf().inSingletonScope();
Expand Down Expand Up @@ -104,6 +109,9 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
@inject(NotebookViewportService)
protected readonly viewportService: NotebookViewportService;

@inject(CellOutputWebview)
protected readonly cellOutputWebview: CellOutputWebview;

protected readonly onDidChangeModelEmitter = new Emitter<void>();
readonly onDidChangeModel = this.onDidChangeModelEmitter.event;

Expand Down Expand Up @@ -173,11 +181,18 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
this.commandRegistry.executeCommand(NotebookCellCommands.EDIT_COMMAND.id, model, model.cells[0]);
model.setSelectedCell(model.cells[0]);
}
model.onDidChangeContent(changeEvents => {
const cellEvent = changeEvents.filter(event => event.kind === NotebookCellsChangeType.Move || event.kind === NotebookCellsChangeType.ModelChange);
if (cellEvent.length > 0) {
this.cellOutputWebview.cellsChanged(cellEvent);
}
});
});
}

protected async waitForData(): Promise<NotebookModel> {
this._model = await this.props.notebookData;
this.cellOutputWebview.init(this._model, this);
this.saveable.delegate = this._model;
this.toDispose.push(this._model);
this.toDispose.push(this._model.onDidChangeContent(() => {
Expand Down Expand Up @@ -261,12 +276,15 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
<PerfectScrollbar className='theia-notebook-scroll-container'
ref={this.scrollBarRef}
onScrollY={(e: HTMLDivElement) => this.viewportService.onScroll(e)}>
<NotebookCellListView renderers={this.renderers}
notebookModel={this._model}
notebookContext={this.notebookContextManager}
toolbarRenderer={this.cellToolbarFactory}
commandRegistry={this.commandRegistry}
menuRegistry={this.menuRegistry} />
<div className='theia-notebook-scroll-area'>
{this.cellOutputWebview.render()}
<NotebookCellListView renderers={this.renderers}
notebookModel={this._model}
notebookContext={this.notebookContextManager}
toolbarRenderer={this.cellToolbarFactory}
commandRegistry={this.commandRegistry}
menuRegistry={this.menuRegistry} />
</div>
</PerfectScrollbar>
</div>
</div>;
Expand All @@ -282,6 +300,12 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
this.notebookEditorService.removeNotebookEditor(this);
}

requestOuputPresentationChange(cellHandle: number, output?: NotebookCellOutputModel): void {
if (output) {
this.cellOutputWebview.requestOutputPresentationUpdate(cellHandle, output);
}
}

postKernelMessage(message: unknown): void {
this.onDidPostKernelMessageEmitter.fire(message);
}
Expand All @@ -307,6 +331,7 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
}

override dispose(): void {
this.cellOutputWebview.dispose();
this.notebookContextManager.dispose();
this.onDidChangeModelEmitter.dispose();
this.onDidPostKernelMessageEmitter.dispose();
Expand Down
24 changes: 21 additions & 3 deletions packages/notebook/src/browser/renderers/cell-output-webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,20 +14,38 @@
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Disposable } from '@theia/core';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { Disposable, Event } from '@theia/core';
import { NotebookModel } from '../view-model/notebook-model';
import { NotebookEditorWidget } from '../notebook-editor-widget';
import { NotebookContentChangedEvent } from '../notebook-types';
import { NotebookCellOutputModel } from '../view-model/notebook-cell-output-model';
import { NotebookCellModel } from '../view-model/notebook-cell-model';

export const CellOutputWebviewFactory = Symbol('outputWebviewFactory');
export const CellOutputWebview = Symbol('outputWebview');

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

export interface OutputRenderEvent {
cellHandle: number;
outputId: string;
outputHeight: number;
}

export interface CellOutputWebview extends Disposable {

readonly id: string;

init(notebook: NotebookModel, editor: NotebookEditorWidget): void;

render(): React.ReactNode;

setCellHeight(cell: NotebookCellModel, height: number): void;
cellsChanged(cellEvent: NotebookContentChangedEvent[]): void;
onDidRenderOutput: Event<OutputRenderEvent>

requestOutputPresentationUpdate(cellHandle: number, output: NotebookCellOutputModel): void;

attachWebview(): void;
isAttached(): boolean
}
44 changes: 35 additions & 9 deletions packages/notebook/src/browser/style/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,24 @@
}

.theia-notebook-cell-list {
position: absolute;
top: 0;
width: 100%;
overflow-y: auto;
list-style: none;
padding-left: 0px;
background-color: var(--theia-notebook-editorBackground);
z-index: 0;
pointer-events: none;
}


.theia-notebook-cell-output-webview {
padding: 5px 0px;
margin: 0px 15px 0px 50px;
width: calc(100% - 60px);
position: absolute;
z-index: 0;
}

.theia-notebook-cell {
Expand Down Expand Up @@ -69,7 +83,8 @@
/* Rendered Markdown Content */

.theia-notebook-markdown-content {
padding: 8px 16px 8px 36px;
pointer-events: all;
padding: 8px 16px 8px 0px;
font-size: var(--theia-notebook-markdown-size);
}

Expand All @@ -87,9 +102,13 @@
padding-bottom: 0;
}

.theia-notebook-markdown-sidebar {
width: 35px;
}

/* Markdown cell edit mode */
.theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
margin-left: 36px;
pointer-events: all;
margin-right: var(--theia-notebook-cell-editor-margin-right);
outline: 1px solid var(--theia-notebook-cellBorderColor);
}
Expand All @@ -108,6 +127,7 @@
}

.theia-notebook-cell-editor-container {
pointer-events: all;
width: calc(100% - 46px);
flex: 1;
outline: 1px solid var(--theia-notebook-cellBorderColor);
Expand Down Expand Up @@ -149,6 +169,7 @@
}

.theia-notebook-cell-toolbar {
pointer-events: all;
border: 1px solid var(--theia-notebook-cellToolbarSeparator);
display: flex;
position: absolute;
Expand All @@ -161,11 +182,15 @@
display: flex;
flex-direction: column;
padding: 2px;
background-color: var(--theia-editor-background);
flex-grow: 1;
}

.theia-notebook-cell-sidebar {
pointer-events: all;
display: flex;
}

.theia-notebook-cell-sidebar-actions {
display: flex;
flex-direction: column;
}
Expand Down Expand Up @@ -194,6 +219,7 @@
}

.theia-notebook-cell-divider {
pointer-events: all;
height: 25px;
width: 100%;
}
Expand Down Expand Up @@ -303,19 +329,19 @@
margin: 1px 0 0 4px;
}

.theia-notebook-cell-output-webview {
padding: 5px 0px;
margin: 0px 15px 0px 9px;
width: 100%;
}

.theia-notebook-cell-drop-indicator {
height: 2px;
background-color: var(--theia-notebook-focusedCellBorder);
width: 100%;
}

.theia-notebook-collapsed-output-container {
width: 0;
overflow: visible;
}

.theia-notebook-collapsed-output {
text-wrap: nowrap;
padding: 4px 8px;
color: var(--theia-foreground);
margin-left: 30px;
Expand Down
13 changes: 13 additions & 0 deletions packages/notebook/src/browser/view-model/notebook-cell-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,9 @@ export class NotebookCellModel implements NotebookCell, Disposable {
protected onDidRequestCenterEditorEmitter = new Emitter<void>();
readonly onDidRequestCenterEditor = this.onDidRequestCenterEditorEmitter.event;

protected onDidCellHeightChangeEmitter = new Emitter<number>();
readonly onDidCellHeightChange = this.onDidCellHeightChangeEmitter.event;

@inject(NotebookCellModelProps)
protected readonly props: NotebookCellModelProps;

Expand Down Expand Up @@ -251,6 +254,16 @@ export class NotebookCellModel implements NotebookCell, Disposable {
}
}

protected _cellheight: number = 0;
get cellHeight(): number {
return this._cellheight;
}

set cellHeight(height: number) {
this.onDidCellHeightChangeEmitter.fire(height);
this._cellheight = height;
}

@postConstruct()
protected init(): void {
this._outputs = this.props.outputs.map(op => new NotebookCellOutputModel(op));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,6 @@ export class NotebookCellOutputModel implements Disposable {
private didChangeDataEmitter = new Emitter<void>();
readonly onDidChangeData = this.didChangeDataEmitter.event;

private requestOutputPresentationChangeEmitter = new Emitter<void>();
readonly onRequestOutputPresentationChange = this.requestOutputPresentationChangeEmitter.event;

get outputId(): string {
return this.rawOutput.outputId;
}
Expand Down Expand Up @@ -54,11 +51,6 @@ export class NotebookCellOutputModel implements Disposable {

dispose(): void {
this.didChangeDataEmitter.dispose();
this.requestOutputPresentationChangeEmitter.dispose();
}

requestOutputPresentationUpdate(): void {
this.requestOutputPresentationChangeEmitter.fire();
}

getData(): CellOutput {
Expand Down
6 changes: 5 additions & 1 deletion packages/notebook/src/browser/view-model/notebook-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -509,10 +509,14 @@ export class NotebookModel implements Saveable, Disposable {
return true;
}

protected getCellIndexByHandle(handle: number): number {
getCellIndexByHandle(handle: number): number {
return this.cells.findIndex(c => c.handle === handle);
}

getCellByHandle(handle: number): NotebookCellModel | undefined {
return this.cells.find(c => c.handle === handle);
}

protected isCellMetadataChanged(a: NotebookCellMetadata, b: NotebookCellMetadata): boolean {
const keys = new Set<keyof NotebookCellMetadata>([...Object.keys(a || {}), ...Object.keys(b || {})]);
for (const key of keys) {
Expand Down
Loading
Loading