Skip to content

Commit 51f8dff

Browse files
authored
Feat: better lexical for code (#397)
* fix: lexical cell output * jupyter: cursor * fix: jupyter input and output move * lint: lexical * fix: output adpater * lexical: optional kernel * fix: output reload * fix: output rendering on load * bump: jupyter-react * feat: picker * feat: disabled prop for component picker plugin * jupyter: onsessionconnection * react: onsession * wip * fix: flood * fix: mutations * fix: load * wip * lint * lexical: initcode
1 parent 79692fc commit 51f8dff

22 files changed

+720
-261
lines changed

.eslintrc.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ module.exports = {
1313
ecmaFeatures: {
1414
jsx: true,
1515
},
16+
// Explicitly set tsconfigRootDir and project to resolve multiple candidate TSConfigRootDirs error
17+
tsconfigRootDir: __dirname,
18+
project: './tsconfig.eslint.json',
1619
},
1720
settings: {
1821
react: {

packages/lexical/src/convert/NbformatToLexical.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export const nbformatToLexical = (
1515
editor: LexicalEditor,
1616
) => {
1717
editor.update(() => {
18-
notebook.cells.map(cell => {
18+
notebook.cells.map((cell, index) => {
1919
let code = '';
2020
if (typeof cell.source === 'object') {
2121
code = (cell.source as string[]).join('\n');
@@ -35,7 +35,10 @@ export const nbformatToLexical = (
3535
// autoStart: false,
3636
});
3737
}
38-
editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);
38+
// Only add paragraph between cells, not after the last cell
39+
if (index < notebook.cells.length - 1) {
40+
editor.dispatchCommand(INSERT_PARAGRAPH_COMMAND, undefined);
41+
}
3942
});
4043
});
4144
};

packages/lexical/src/editor/Editor.tsx

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,55 +29,60 @@ import { CodeNode } from '@lexical/code';
2929
import { INotebookContent } from '@jupyterlab/nbformat';
3030
import { useJupyter } from '@datalayer/jupyter-react';
3131
import {
32-
JupyterInputOutputPlugin,
32+
CounterNode,
3333
EquationNode,
34-
HorizontalRulePlugin,
35-
ListMaxIndentLevelPlugin,
36-
AutoLinkPlugin,
37-
ComponentPickerMenuPlugin,
38-
EquationsPlugin,
39-
ImagesPlugin,
40-
YouTubePlugin,
4134
ImageNode,
42-
YouTubeNode,
4335
JupyterInputHighlightNode,
4436
JupyterInputNode,
4537
JupyterOutputNode,
4638
// JupyterCellNode,
47-
// JupyterCellPlugin,
48-
CodeActionMenuPlugin,
39+
YouTubeNode,
40+
} from '../nodes';
41+
import {
4942
AutoEmbedPlugin,
50-
NbformatContentPlugin,
51-
TableOfContentsPlugin,
52-
MarkdownPlugin,
43+
AutoLinkPlugin,
44+
CodeActionMenuPlugin,
5345
CommentPlugin,
46+
ComponentPickerMenuPlugin,
47+
DraggableBlockPlugin,
48+
EquationsPlugin,
5449
FloatingTextFormatToolbarPlugin,
50+
HorizontalRulePlugin,
51+
ImagesPlugin,
52+
// JupyterCellPlugin,
53+
JupyterInputOutputPlugin,
54+
ListMaxIndentLevelPlugin,
55+
MarkdownPlugin,
56+
NbformatContentPlugin,
57+
TableOfContentsPlugin,
58+
YouTubePlugin,
5559
} from './..';
5660
import { commentTheme } from '../themes';
57-
import { useLexical } from '../context/LexicalContext';
61+
import { useLexical } from '../context';
5862
import { TreeViewPlugin, ToolbarPlugin } from '../plugins';
59-
import DraggableBlockPlugin from '../plugins/DraggableBlockPlugin';
63+
import { OnSessionConnection } from '@datalayer/jupyter-react';
6064

6165
import './../../style/index.css';
62-
import { CounterNode } from '../nodes/CounterNode';
6366

6467
type Props = {
6568
notebook?: INotebookContent;
69+
onSessionConnection?: OnSessionConnection;
6670
};
6771

6872
function Placeholder() {
6973
return <div className="editor-placeholder">Code and analyse data.</div>;
7074
}
7175

7276
const initialConfig = {
73-
namespace: 'Jupyter Lexical example',
77+
namespace: 'Jupyter Lexical Example',
7478
theme: commentTheme,
7579
onError(error: Error) {
7680
throw error;
7781
},
7882
nodes: [
7983
AutoLinkNode,
8084
CodeNode,
85+
CounterNode,
8186
EquationNode,
8287
HashtagNode,
8388
HeadingNode,
@@ -96,7 +101,6 @@ const initialConfig = {
96101
TableNode,
97102
TableRowNode,
98103
YouTubeNode,
99-
CounterNode,
100104
],
101105
};
102106

@@ -111,7 +115,7 @@ const EditorContextPlugin = () => {
111115
};
112116

113117
export function Editor(props: Props) {
114-
const { notebook } = props;
118+
const { notebook, onSessionConnection } = props;
115119
const { defaultKernel } = useJupyter();
116120
const [floatingAnchorElem, setFloatingAnchorElem] =
117121
useState<HTMLDivElement | null>(null);
@@ -154,10 +158,11 @@ export function Editor(props: Props) {
154158
<ListMaxIndentLevelPlugin maxDepth={7} />
155159
<MarkdownPlugin />
156160
{/* <JupyterCellPlugin /> */}
157-
{defaultKernel && (
158-
<JupyterInputOutputPlugin kernel={defaultKernel} />
159-
)}
160-
<ComponentPickerMenuPlugin />
161+
<JupyterInputOutputPlugin
162+
kernel={defaultKernel}
163+
onSessionConnection={onSessionConnection}
164+
/>
165+
<ComponentPickerMenuPlugin kernel={defaultKernel} />
161166
<EquationsPlugin />
162167
<ImagesPlugin />
163168
<HorizontalRulePlugin />

packages/lexical/src/examples/AppNbformat.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,12 @@ const Tabs = () => {
9797
</UnderlineNav>
9898
{tab === 'editor' && (
9999
<Box>
100-
<Editor notebook={nbformat} />
100+
<Editor
101+
notebook={nbformat}
102+
onSessionConnection={session => {
103+
console.log('Session changed:', session);
104+
}}
105+
/>
101106
<Button
102107
onClick={(e: React.MouseEvent) => {
103108
e.preventDefault();

packages/lexical/src/examples/AppSimple.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,12 @@ const LexicalEditor = () => {
1919
return (
2020
<Box className="center">
2121
<Box>
22-
<Editor notebook={NBFORMAT_MODEL} />
22+
<Editor
23+
notebook={NBFORMAT_MODEL}
24+
onSessionConnection={session => {
25+
console.log('Session changed:', session);
26+
}}
27+
/>
2328
<Button
2429
onClick={(e: React.MouseEvent) => {
2530
e.preventDefault();

packages/lexical/src/examples/content/Example.lexical.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@
3232
"type": "jupyter-input-highlight",
3333
"version": 1,
3434
"highlightType": "string"
35+
},
36+
{
37+
"detail": 0,
38+
"format": 0,
39+
"mode": "normal",
40+
"style": "",
41+
"text": ")",
42+
"type": "jupyter-input-highlight",
43+
"version": 1,
44+
"highlightType": "punctuation"
3545
}
3646
],
3747
"direction": "ltr",

packages/lexical/src/nodes/JupyterOutputNode.tsx

Lines changed: 44 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ import {
2121
INPUT_UUID_TO_OUTPUT_UUID,
2222
OUTPUT_UUID_TO_OUTPUT_KEY,
2323
} from '../plugins/JupyterInputOutputPlugin';
24-
import { Output, OutputAdapter, newUuid } from '@datalayer/jupyter-react';
24+
import {
25+
Output,
26+
OutputAdapter,
27+
newUuid,
28+
Kernel,
29+
} from '@datalayer/jupyter-react';
2530

2631
export type SerializedJupyterOutputNode = Spread<
2732
{
@@ -43,6 +48,7 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
4348
__jupyterInputNodeUuid: string;
4449
__jupyterOutputNodeUuid: string;
4550
__executeTrigger: number;
51+
__renderTrigger: number;
4652

4753
/** @override */
4854
static getType() {
@@ -68,7 +74,7 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
6874
): JupyterOutputNode {
6975
return $createJupyterOutputNode(
7076
serializedNode.source,
71-
new OutputAdapter(newUuid(), undefined, []),
77+
new OutputAdapter(newUuid(), undefined, serializedNode.outputs),
7278
serializedNode.outputs,
7379
false,
7480
serializedNode.jupyterInputNodeUuid,
@@ -93,10 +99,11 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
9399
this.__outputs = outputs;
94100
this.__outputAdapter = outputAdapter;
95101
this.__executeTrigger = 0;
102+
this.__renderTrigger = 0;
96103
this.__autoRun = autoRun;
97104
OUTPUT_UUID_TO_CODE_UUID.set(
98105
this.__jupyterOutputNodeUuid,
99-
jupyterInputNodeUuid,
106+
this.__jupyterInputNodeUuid,
100107
);
101108
INPUT_UUID_TO_OUTPUT_KEY.set(this.__jupyterInputNodeUuid, this.__key);
102109
INPUT_UUID_TO_OUTPUT_UUID.set(
@@ -194,7 +201,7 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
194201
outputs={this.__outputs}
195202
adapter={this.__outputAdapter}
196203
id={this.__jupyterOutputNodeUuid}
197-
executeTrigger={this.getExecuteTrigger()}
204+
executeTrigger={this.getExecuteTrigger() + this.__renderTrigger}
198205
autoRun={this.__autoRun}
199206
/>
200207
);
@@ -222,9 +229,41 @@ export class JupyterOutputNode extends DecoratorNode<JSX.Element> {
222229
}
223230

224231
public executeCode(code: string) {
232+
console.warn(
233+
'🎯 JupyterOutputNode.executeCode called with:',
234+
code.slice(0, 50),
235+
);
236+
console.warn(
237+
'🔧 OutputAdapter kernel available:',
238+
!!this.__outputAdapter.kernel,
239+
);
240+
225241
this.setJupyterInput(code);
242+
243+
if (!this.__outputAdapter.kernel) {
244+
console.error('❌ OutputAdapter has no kernel - cannot execute!');
245+
return;
246+
}
247+
248+
console.warn('✅ Calling OutputAdapter.execute()');
226249
this.__outputAdapter.execute(code);
227-
this.setExecuteTrigger(this.getExecuteTrigger() + 1);
250+
console.warn('✅ OutputAdapter.execute() completed');
251+
// this.setExecuteTrigger(this.getExecuteTrigger() + 1);
252+
}
253+
254+
public updateKernel(kernel: Kernel | undefined) {
255+
console.warn('🔄 JupyterOutputNode.updateKernel called with:', !!kernel);
256+
console.warn('🔧 Previous kernel:', !!this.__outputAdapter.kernel);
257+
258+
const self = this.getWritable();
259+
self.__outputAdapter.kernel = kernel;
260+
// Don't increment renderTrigger immediately - let execution complete first
261+
// The renderTrigger will be incremented after execution if needed
262+
263+
console.warn(
264+
'✅ Kernel updated, new kernel:',
265+
!!self.__outputAdapter.kernel,
266+
);
228267
}
229268
}
230269

packages/lexical/src/nodes/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* MIT License
55
*/
66

7+
export * from './CounterNode';
78
export * from './EquationNode';
89
export * from './ImageNode';
910
export * from './JupyterInputHighlightNode';

0 commit comments

Comments
 (0)