Skip to content

Commit 3f9c6af

Browse files
committed
wip
1 parent a422532 commit 3f9c6af

File tree

10 files changed

+317
-45
lines changed

10 files changed

+317
-45
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1816,7 +1816,8 @@
18161816
"entrypoint": "./dist/webviews/webview-side/dataframeRenderer/dataframeRenderer.js",
18171817
"mimeTypes": [
18181818
"application/vnd.deepnote.dataframe.v3+json"
1819-
]
1819+
],
1820+
"requiresMessaging": "optional"
18201821
}
18211822
],
18221823
"viewsContainers": {
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT License.
3+
4+
import {
5+
CancellationToken,
6+
NotebookCell,
7+
NotebookCellExecution,
8+
NotebookCellOutput,
9+
NotebookCellOutputItem,
10+
NotebookController
11+
} from 'vscode';
12+
13+
import { KernelController } from '../kernelController';
14+
15+
/**
16+
* DeepnoteController extends KernelController to intercept cell execution
17+
* and prepend initialization code to each cell execution.
18+
*/
19+
export class DeepnoteController extends KernelController {
20+
constructor(controller: NotebookController) {
21+
super(controller);
22+
}
23+
24+
public override createNotebookCellExecution(cell: NotebookCell): NotebookCellExecution {
25+
const execution = super.createNotebookCellExecution(cell);
26+
return new DeepnoteNotebookCellExecution(execution, cell);
27+
}
28+
}
29+
30+
/**
31+
* Wrapper around NotebookCellExecution that prepends initialization code.
32+
* This is implemented by delegating all calls to the underlying execution object.
33+
*
34+
* Note: This wrapper currently just delegates to the underlying execution.
35+
* The actual code interception will be implemented at the execution layer.
36+
*/
37+
class DeepnoteNotebookCellExecution implements NotebookCellExecution {
38+
constructor(
39+
private readonly execution: NotebookCellExecution,
40+
public readonly cell: NotebookCell
41+
) {
42+
// Prepend code will be: print("Hello world")
43+
// This will be implemented at the CellExecution layer
44+
}
45+
46+
get executionOrder(): number | undefined {
47+
return this.execution.executionOrder;
48+
}
49+
50+
set executionOrder(value: number | undefined) {
51+
this.execution.executionOrder = value;
52+
}
53+
54+
get token(): CancellationToken {
55+
return this.execution.token;
56+
}
57+
58+
public start(startTime?: number): void {
59+
this.execution.start(startTime);
60+
}
61+
62+
public end(success: boolean | undefined, endTime?: number): void {
63+
this.execution.end(success, endTime);
64+
}
65+
66+
public clearOutput(cell?: NotebookCell): Thenable<void> {
67+
return this.execution.clearOutput(cell);
68+
}
69+
70+
public replaceOutput(out: NotebookCellOutput | readonly NotebookCellOutput[], cell?: NotebookCell): Thenable<void> {
71+
return this.execution.replaceOutput(out, cell);
72+
}
73+
74+
public appendOutput(out: NotebookCellOutput | readonly NotebookCellOutput[], cell?: NotebookCell): Thenable<void> {
75+
return this.execution.appendOutput(out, cell);
76+
}
77+
78+
public replaceOutputItems(
79+
items: NotebookCellOutputItem | readonly NotebookCellOutputItem[],
80+
output: NotebookCellOutput
81+
): Thenable<void> {
82+
return this.execution.replaceOutputItems(items, output);
83+
}
84+
85+
public appendOutputItems(
86+
items: NotebookCellOutputItem | readonly NotebookCellOutputItem[],
87+
output: NotebookCellOutput
88+
): Thenable<void> {
89+
return this.execution.appendOutputItems(items, output);
90+
}
91+
}

src/kernels/execution/cellExecution.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,10 @@ export class CellExecution implements ICellExecution, IDisposable {
406406
return this.completedSuccessfully();
407407
}
408408

409+
// Prepend initialization code
410+
const prependCode = 'print("Hello world")';
411+
code = prependCode + '\n' + code;
412+
409413
// Generate metadata from our cell (some kernels expect this.)
410414
// eslint-disable-next-line @typescript-eslint/no-explicit-any
411415
const metadata: any = {

src/notebooks/controllers/vscodeNotebookController.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ import { initializeInteractiveOrNotebookTelemetryBasedOnUserAction } from '../..
7171
import { NotebookCellLanguageService } from '../languages/cellLanguageService';
7272
import { IDataScienceErrorHandler } from '../../kernels/errors/types';
7373
import { ITrustedKernelPaths } from '../../kernels/raw/finder/types';
74-
import { KernelController } from '../../kernels/kernelController';
74+
import { DeepnoteController } from '../../kernels/deepnote/deepnoteController';
7575
import { RemoteKernelReconnectBusyIndicator } from './remoteKernelReconnectBusyIndicator';
7676
import { LastCellExecutionTracker } from '../../kernels/execution/lastCellExecutionTracker';
7777
import type { IAnyMessageArgs } from '@jupyterlab/services/lib/kernel/kernel';
@@ -548,7 +548,7 @@ export class VSCodeNotebookController implements Disposable, IVSCodeNotebookCont
548548
// Creating these execution objects marks the cell as queued for execution (vscode will update cell UI).
549549
type CellExec = { cell: NotebookCell; exec: NotebookCellExecution };
550550
const cellExecs: CellExec[] = (this.cellQueue.get(doc) || []).map((cell) => {
551-
const exec = this.createCellExecutionIfNecessary(cell, new KernelController(this.controller));
551+
const exec = this.createCellExecutionIfNecessary(cell, new DeepnoteController(this.controller));
552552
return { cell, exec };
553553
});
554554
this.cellQueue.delete(doc);
@@ -561,7 +561,7 @@ export class VSCodeNotebookController implements Disposable, IVSCodeNotebookCont
561561

562562
// Connect to a matching kernel if possible (but user may pick a different one)
563563
let currentContext: 'start' | 'execution' = 'start';
564-
let controller: IKernelController = new KernelController(this.controller);
564+
let controller: IKernelController = new DeepnoteController(this.controller);
565565
const lastCellExecutionTracker = this.serviceContainer.get<LastCellExecutionTracker>(LastCellExecutionTracker);
566566
let kernel: IKernel | undefined;
567567
try {
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import { injectable } from 'inversify';
2+
import {
3+
env,
4+
NotebookEdit,
5+
NotebookEditor,
6+
NotebookRendererMessaging,
7+
notebooks,
8+
workspace,
9+
WorkspaceEdit
10+
} from 'vscode';
11+
12+
import { IExtensionSyncActivationService } from '../../../platform/activation/types';
13+
import { IDisposable } from '../../../platform/common/types';
14+
import { dispose } from '../../../platform/common/utils/lifecycle';
15+
import { logger } from '../../../platform/logging';
16+
17+
type SelectPageSizeCommand = {
18+
command: 'selectPageSize';
19+
cellIndex: number;
20+
size: number;
21+
};
22+
23+
type GoToPageCommand = {
24+
command: 'goToPage';
25+
cellIndex: number;
26+
page: number;
27+
};
28+
29+
type CopyTableDataCommand = {
30+
command: 'copyTableData';
31+
data: string;
32+
};
33+
34+
type ExportDataframeCommand = {
35+
command: 'exportDataframe';
36+
cellIndex: number;
37+
};
38+
39+
type DataframeCommand = SelectPageSizeCommand | GoToPageCommand | CopyTableDataCommand | ExportDataframeCommand;
40+
41+
@injectable()
42+
export class DataframeRendererComms implements IExtensionSyncActivationService {
43+
private readonly disposables: IDisposable[] = [];
44+
45+
public dispose() {
46+
dispose(this.disposables);
47+
}
48+
49+
activate() {
50+
const comms = notebooks.createRendererMessaging('deepnote-dataframe-renderer');
51+
comms.onDidReceiveMessage(this.onDidReceiveMessage.bind(this, comms), this, this.disposables);
52+
}
53+
54+
private onDidReceiveMessage(
55+
_comms: NotebookRendererMessaging,
56+
{ editor, message }: { editor: NotebookEditor; message: DataframeCommand }
57+
) {
58+
logger.info('DataframeRendererComms received message', message);
59+
60+
if (!message || typeof message !== 'object') {
61+
return;
62+
}
63+
64+
switch (message.command) {
65+
case 'selectPageSize':
66+
this.handleSelectPageSize(editor, message);
67+
break;
68+
case 'goToPage':
69+
this.handleGoToPage(editor, message);
70+
break;
71+
case 'copyTableData':
72+
this.handleCopyTableData(message);
73+
break;
74+
case 'exportDataframe':
75+
this.handleExportDataframe(editor, message);
76+
break;
77+
}
78+
}
79+
80+
private async handleSelectPageSize(editor: NotebookEditor, message: SelectPageSizeCommand) {
81+
const cell = editor.notebook.cellAt(message.cellIndex);
82+
logger.info(
83+
`[DataframeRenderer] selectPageSize called for cell ${
84+
message.cellIndex
85+
} (${cell?.document.uri.toString()}), size=${message.size}`
86+
);
87+
88+
// Store page size in cell metadata
89+
if (cell) {
90+
const edit = new WorkspaceEdit();
91+
const notebookEdit = NotebookEdit.updateCellMetadata(message.cellIndex, {
92+
...cell.metadata,
93+
dataframePageSize: message.size
94+
});
95+
edit.set(editor.notebook.uri, [notebookEdit]);
96+
await workspace.applyEdit(edit);
97+
}
98+
}
99+
100+
private handleGoToPage(editor: NotebookEditor, message: GoToPageCommand) {
101+
const cell = editor.notebook.cellAt(message.cellIndex);
102+
logger.info(
103+
`[DataframeRenderer] goToPage called for cell ${
104+
message.cellIndex
105+
} (${cell?.document.uri.toString()}), page=${message.page}`
106+
);
107+
// Could store current page in cell metadata if needed
108+
}
109+
110+
private async handleCopyTableData(message: CopyTableDataCommand) {
111+
logger.info(`[DataframeRenderer] copyTableData called, data length=${message.data.length} characters`);
112+
await env.clipboard.writeText(message.data);
113+
}
114+
115+
private handleExportDataframe(editor: NotebookEditor, message: ExportDataframeCommand) {
116+
const cell = editor.notebook.cellAt(message.cellIndex);
117+
logger.info(
118+
`[DataframeRenderer] exportDataframe called for cell ${
119+
message.cellIndex
120+
} (${cell?.document.uri.toString()})`
121+
);
122+
// TODO: Implement dataframe export functionality
123+
}
124+
}

src/webviews/extension-side/serviceRegistry.node.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
IJupyterVariableDataProvider,
1818
IJupyterVariableDataProviderFactory
1919
} from './dataviewer/types';
20+
import { DataframeRendererComms } from './dataframe/rendererComms';
2021
import { IPyWidgetRendererComms } from './ipywidgets/rendererComms';
2122
import { PlotViewer } from './plotting/plotViewer.node';
2223
import { PlotViewerProvider } from './plotting/plotViewerProvider';
@@ -65,6 +66,10 @@ export function registerTypes(serviceManager: IServiceManager) {
6566
IExtensionSyncActivationService,
6667
IPyWidgetRendererComms
6768
);
69+
serviceManager.addSingleton<IExtensionSyncActivationService>(
70+
IExtensionSyncActivationService,
71+
DataframeRendererComms
72+
);
6873
serviceManager.addSingleton<IVariableViewProvider>(IVariableViewProvider, VariableViewProvider);
6974
serviceManager.add<IJupyterVariableDataProvider>(IJupyterVariableDataProvider, JupyterVariableDataProvider);
7075
serviceManager.addSingleton<IJupyterVariableDataProviderFactory>(

src/webviews/extension-side/serviceRegistry.web.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import { PlotSaveHandler } from './plotView/plotSaveHandler';
2727
import { PlotViewHandler } from './plotView/plotViewHandler';
2828
import { RendererCommunication } from './plotView/rendererCommunication';
2929
import { IPlotSaveHandler } from './plotView/types';
30+
import { DataframeRendererComms } from './dataframe/rendererComms';
3031
import { IPyWidgetRendererComms } from './ipywidgets/rendererComms';
3132
import { DataViewerDelegator } from './dataviewer/dataViewerDelegator';
3233

@@ -65,6 +66,10 @@ export function registerTypes(serviceManager: IServiceManager) {
6566
IExtensionSyncActivationService,
6667
IPyWidgetRendererComms
6768
);
69+
serviceManager.addSingleton<IExtensionSyncActivationService>(
70+
IExtensionSyncActivationService,
71+
DataframeRendererComms
72+
);
6873
serviceManager.addSingleton<IVariableViewProvider>(IVariableViewProvider, VariableViewProvider);
6974
serviceManager.add<IJupyterVariableDataProvider>(IJupyterVariableDataProvider, JupyterVariableDataProvider);
7075
serviceManager.addSingleton<IJupyterVariableDataProviderFactory>(
Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,70 @@
1-
import * as React from 'react';
1+
import React, { memo, useState } from 'react';
2+
import { RendererContext } from 'vscode-notebook-renderer';
23

34
interface DataframeRendererProps {
4-
data: any;
5+
context: RendererContext<unknown>;
6+
data: {
7+
column_count: number;
8+
columns: {
9+
dtype: string;
10+
name: string;
11+
stats: any;
12+
}[];
13+
preview_row_count: number;
14+
row_count: number;
15+
rows: {
16+
_deepnote_index_column: number;
17+
[key: string]: any;
18+
}[];
19+
type: string;
20+
};
521
}
622

7-
export const DataframeRenderer: React.FC<DataframeRendererProps> = ({ data }) => {
8-
console.log('DataframeRenderer', data);
23+
export const DataframeRenderer = memo(function DataframeRenderer({ context, data }: DataframeRendererProps) {
24+
const [resultsPerPage, setResultsPerPage] = useState(10);
925

10-
return <div className="dataframe-container">This is the Deepnote dataframe renderer</div>;
11-
};
26+
const filteredColumns = data.columns.filter((column) => !column.name.startsWith('_deepnote_'));
27+
const numberOfRows = Math.min(data.row_count, data.preview_row_count);
28+
const numberOfColumns = filteredColumns.length;
29+
30+
const updateCellMetadata = (metadata: Record<string, any>) => {
31+
if (context.postMessage) {
32+
context.postMessage({
33+
command: 'updateCellMetadata',
34+
cellIndex: 0, // or get the actual cell index
35+
metadata: metadata
36+
});
37+
}
38+
};
39+
40+
return (
41+
<div className="dataframe-container">
42+
<button onClick={() => updateCellMetadata({ customField: 'value' })}>Update Metadata</button>
43+
<div className="dataframe-content">
44+
{filteredColumns.map((column) => {
45+
const rows = data.rows.map((row) => row[column.name]);
46+
47+
return (
48+
<div key={column.name} className="dataframe-column">
49+
<div className="dataframe-header">{column.name}</div>
50+
<div className="dataframe-cells">
51+
{rows.map((value, index) => (
52+
<div key={index} className="dataframe-cell">
53+
{value ? value.toString() : 'None'}
54+
</div>
55+
))}
56+
</div>
57+
</div>
58+
);
59+
})}
60+
</div>
61+
<div className="dataframe-footer">
62+
<div>
63+
{numberOfRows} rows, {numberOfColumns} columns
64+
</div>
65+
<div></div>
66+
<div></div>
67+
</div>
68+
</div>
69+
);
70+
});

0 commit comments

Comments
 (0)