Skip to content

Commit

Permalink
Support truncated notebook output commands (#13555)
Browse files Browse the repository at this point in the history
* made notebook truncated output commands working (open as texteditor, as scrollable element, open settings)

Signed-off-by: Jonah Iden <[email protected]>

* review changes

Signed-off-by: Jonah Iden <[email protected]>

* fixed type

Co-authored-by: Mark Sujew <[email protected]>

---------

Signed-off-by: Jonah Iden <[email protected]>
Co-authored-by: Mark Sujew <[email protected]>
  • Loading branch information
jonah-iden and msujew authored Apr 4, 2024
1 parent 35a340b commit a77ba54
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 8 deletions.
2 changes: 1 addition & 1 deletion packages/core/src/browser/command-open-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class CommandOpenHandler implements OpenHandler {
try {
args = JSON.parse(uri.query);
} catch {
// ignore error
args = uri.query;
}
}
if (!Array.isArray(args)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// *****************************************************************************
// Copyright (C) 2024 TypeFox and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// http://www.eclipse.org/legal/epl-2.0.
//
// This Source Code may also be made available under the following Secondary
// Licenses when the conditions for such availability set forth in the Eclipse
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
// with the GNU Classpath Exception which is available at
// https://www.gnu.org/software/classpath/license.html.
//
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
// *****************************************************************************

import { Command, CommandContribution, CommandRegistry } from '@theia/core';
import { inject, injectable } from '@theia/core/shared/inversify';
import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
import { CellOutput, CellUri } from '../../common';
import { NotebookCellModel } from '../view-model/notebook-cell-model';
import { EditorManager } from '@theia/editor/lib/browser';

export namespace NotebookOutputCommands {
export const ENABLE_SCROLLING = Command.toDefaultLocalizedCommand({
id: 'cellOutput.enableScrolling',
});

export const OPEN_LARGE_OUTPUT = Command.toDefaultLocalizedCommand({
id: 'workbench.action.openLargeOutput',
label: 'Open Large Output'
});
}

@injectable()
export class NotebookOutputActionContribution implements CommandContribution {

@inject(NotebookEditorWidgetService)
protected readonly notebookEditorService: NotebookEditorWidgetService;

@inject(EditorManager)
protected readonly editorManager: EditorManager;

registerCommands(commands: CommandRegistry): void {
commands.registerCommand(NotebookOutputCommands.ENABLE_SCROLLING, {
execute: outputId => {
const [cell, output] = this.findOutputAndCell(outputId) ?? [];
if (cell && output?.metadata) {
output.metadata['scrollable'] = true;
cell.restartOutputRenderer(output.outputId);
}
}
});

commands.registerCommand(NotebookOutputCommands.OPEN_LARGE_OUTPUT, {
execute: outputId => {
const [cell, output] = this.findOutputAndCell(outputId) ?? [];
if (cell && output) {
this.editorManager.open(CellUri.generateCellOutputUri(CellUri.parse(cell.uri)!.notebook, output.outputId));
}
}
});
}

protected findOutputAndCell(output: string): [NotebookCellModel, CellOutput] | undefined {
const model = this.notebookEditorService.focusedEditor?.model;
if (!model) {
return undefined;
}

const outputId = output.slice(0, output.lastIndexOf('-'));

for (const cell of model.cells) {
for (const outputModel of cell.outputs) {
if (outputModel.outputId === outputId) {
return [cell, outputModel];
}
}
}
}

}
40 changes: 39 additions & 1 deletion packages/notebook/src/browser/notebook-cell-resource-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export class NotebookCellResourceResolver implements ResourceResolver {
protected readonly notebookService: NotebookService;

async resolve(uri: URI): Promise<Resource> {
if (uri.scheme !== CellUri.scheme) {
if (uri.scheme !== CellUri.cellUriScheme) {
throw new Error(`Cannot resolve cell uri with scheme '${uri.scheme}'`);
}

Expand All @@ -90,3 +90,41 @@ export class NotebookCellResourceResolver implements ResourceResolver {
}

}

@injectable()
export class NotebookOutputResourceResolver implements ResourceResolver {

@inject(NotebookService)
protected readonly notebookService: NotebookService;

async resolve(uri: URI): Promise<Resource> {
if (uri.scheme !== CellUri.outputUriScheme) {
throw new Error(`Cannot resolve output uri with scheme '${uri.scheme}'`);
}

const parsedUri = CellUri.parseCellOutputUri(uri);
if (!parsedUri) {
throw new Error(`Cannot parse uri '${uri.toString()}'`);
}

const notebookModel = this.notebookService.getNotebookEditorModel(parsedUri.notebook);

if (!notebookModel) {
throw new Error(`No notebook found for uri '${parsedUri.notebook}'`);
}

const ouputModel = notebookModel.cells.flatMap(cell => cell.outputs).find(output => output.outputId === parsedUri.outputId);

if (!ouputModel) {
throw new Error(`No output found with id '${parsedUri.outputId}' in '${parsedUri.notebook}'`);
}

return {
uri: uri,
dispose: () => { },
readContents: async () => ouputModel.outputs[0].data.toString(),
readOnly: true,
};
}

}
8 changes: 7 additions & 1 deletion packages/notebook/src/browser/notebook-frontend-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import { NotebookTypeRegistry } from './notebook-type-registry';
import { NotebookRendererRegistry } from './notebook-renderer-registry';
import { NotebookService } from './service/notebook-service';
import { NotebookEditorWidgetFactory } from './notebook-editor-widget-factory';
import { NotebookCellResourceResolver } from './notebook-cell-resource-resolver';
import { NotebookCellResourceResolver, NotebookOutputResourceResolver } from './notebook-cell-resource-resolver';
import { NotebookModelResolverService } from './service/notebook-model-resolver-service';
import { NotebookCellActionContribution } from './contributions/notebook-cell-actions-contribution';
import { NotebookCellToolbarFactory } from './view/notebook-cell-toolbar-factory';
Expand All @@ -41,6 +41,7 @@ import { NotebookEditorWidgetService } from './service/notebook-editor-widget-se
import { NotebookRendererMessagingService } from './service/notebook-renderer-messaging-service';
import { NotebookColorContribution } from './contributions/notebook-color-contribution';
import { NotebookMonacoTextModelService } from './service/notebook-monaco-text-model-service';
import { NotebookOutputActionContribution } from './contributions/notebook-output-action-contribution';

export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotebookColorContribution).toSelf().inSingletonScope();
Expand All @@ -67,6 +68,8 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(NotebookCellResourceResolver).toSelf().inSingletonScope();
bind(ResourceResolver).toService(NotebookCellResourceResolver);
bind(NotebookModelResolverService).toSelf().inSingletonScope();
bind(NotebookOutputResourceResolver).toSelf().inSingletonScope();
bind(ResourceResolver).toService(NotebookOutputResourceResolver);

bind(NotebookCellActionContribution).toSelf().inSingletonScope();
bind(MenuContribution).toService(NotebookCellActionContribution);
Expand All @@ -78,6 +81,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(MenuContribution).toService(NotebookActionsContribution);
bind(KeybindingContribution).toService(NotebookActionsContribution);

bind(NotebookOutputActionContribution).toSelf().inSingletonScope();
bind(CommandContribution).toService(NotebookOutputActionContribution);

bind(NotebookEditorWidgetContainerFactory).toFactory(ctx => (props: NotebookEditorProps) =>
createNotebookEditorWidgetContainer(ctx.container, props).get(NotebookEditorWidget)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,6 +298,13 @@ export class NotebookCellModel implements NotebookCell, Disposable {
});
return ref.object;
}

restartOutputRenderer(outputId: string): void {
const output = this.outputs.find(out => out.outputId === outputId);
if (output) {
this.onDidChangeOutputItemsEmitter.fire(output);
}
}
}

function computeRunStartTimeAdjustment(oldMetadata: NotebookCellInternalMetadata, newMetadata: NotebookCellInternalMetadata): number | undefined {
Expand Down
33 changes: 29 additions & 4 deletions packages/notebook/src/common/notebook-common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,8 @@ export function isTextStreamMime(mimeType: string): boolean {

export namespace CellUri {

export const scheme = 'vscode-notebook-cell';
export const cellUriScheme = 'vscode-notebook-cell';
export const outputUriScheme = 'vscode-notebook-cell-output';

const _lengths = ['W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f'];
const _padRegexp = new RegExp(`^[${_lengths.join('')}]+`);
Expand All @@ -273,11 +274,11 @@ export namespace CellUri {
const p = s.length < _lengths.length ? _lengths[s.length - 1] : 'z';

const fragment = `${p}${s}s${Buffer.from(BinaryBuffer.fromString(notebook.scheme).buffer).toString('base64')} `;
return notebook.withScheme(scheme).withFragment(fragment);
return notebook.withScheme(cellUriScheme).withFragment(fragment);
}

export function parse(cell: URI): { notebook: URI; handle: number } | undefined {
if (cell.scheme !== scheme) {
if (cell.scheme !== cellUriScheme) {
return undefined;
}

Expand All @@ -298,6 +299,30 @@ export namespace CellUri {
};
}

export function generateCellOutputUri(notebook: URI, outputId?: string): URI {
return notebook
.withScheme(outputUriScheme)
.withQuery(`op${outputId ?? ''},${notebook.scheme !== 'file' ? notebook.scheme : ''}`);
};

export function parseCellOutputUri(uri: URI): { notebook: URI; outputId?: string } | undefined {
if (uri.scheme !== outputUriScheme) {
return;
}

const match = /^op([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})?\,(.*)$/i.exec(uri.query);
if (!match) {
return undefined;
}

const outputId = match[1] || undefined;
const scheme = match[2];
return {
outputId,
notebook: uri.withScheme(scheme || 'file').withoutQuery()
};
}

export function generateCellPropertyUri(notebook: URI, handle: number, cellScheme: string): URI {
return CellUri.generate(notebook, handle).withScheme(cellScheme);
}
Expand All @@ -307,6 +332,6 @@ export namespace CellUri {
return undefined;
}

return CellUri.parse(uri.withScheme(scheme));
return CellUri.parse(uri.withScheme(cellUriScheme));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,20 @@ export class CellOutputWebviewImpl implements CellOutputWebview, Disposable {
}

this.webviewWidget = await this.widgetManager.getOrCreateWidget(WebviewWidget.FACTORY_ID, { id: this.id });
this.webviewWidget.setContentOptions({ allowScripts: true });
this.webviewWidget.setContentOptions({
allowScripts: true,
// eslint-disable-next-line max-len
// list taken from https://github.com/microsoft/vscode/blob/a27099233b956dddc2536d4a0d714ab36266d897/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts#L762-L774
enableCommandUris: [
'github-issues.authNow',
'workbench.extensions.search',
'workbench.action.openSettings',
'_notebook.selectKernel',
'jupyter.viewOutput',
'workbench.action.openLargeOutput',
'cellOutput.enableScrolling',
]
});
this.webviewWidget.setHTML(await this.createWebviewContent());

this.webviewWidget.onMessage((message: FromWebviewMessage) => {
Expand Down

0 comments on commit a77ba54

Please sign in to comment.