How to use CodeMirror in JupyterLab Extension

Hello everyone,

I’m trying to play around and use the native Jupyterlab CodeEditor in my extension.
However I struggle with what I believe a change in the API with the 4.0 and can’t find examples to help me.

For example this extension uses the CodeEditor but for JLAB v3. My problem is with the value. It looks like we could set the value of the text in the code editor with model.value but it seems to not be the case now.

Here is the code I have for now:

export const CodeTextarea: React.FC<CodeTextareaProps> = ({
  field,
  value,
  handleChange,
  advanced,
}) => {
  const editorRef = useRef<HTMLDivElement | null>(null);
  const [editor, setEditor] = useState<CodeEditor.IEditor | null>(null);

  useEffect(() => {
    if (editorRef.current) {
      const languages = new EditorLanguageRegistry();
      const factory = new CodeMirrorEditorFactory({
        extensions: new EditorExtensionRegistry(),
        languages
      });

      const model = new CodeEditor.Model({
        mimeType: 'text/x-python',
      });

      const editor = factory.newInlineEditor({
        host: editorRef.current,
        model: model,
        config: {
          value: value,
          mode: 'text/x-python',
          lineNumbers: true
        }
      });

      setEditor(editor);

      return () => {
        editor.dispose();
      };
    }
  }, [value]);

  useEffect(() => {
    if (editor) {
      editor.model.value.changed.connect((sender, args) => {
        handleChange(editor.model.value.text, field.id);
      });
    }
  }, [editor, handleChange, field.id]);

  return (
    <div ref={editorRef} style={{ height: '300px', width: '100%' }} />
  );
};

Errors are with:

      editor.model.value.changed.connect((sender, args) => {
        handleChange(editor.model.value.text, field.id);
      });
    }

because:

Property ‘value’ does not exist on type ‘IModel’.ts(2339)

Of course, I checked the reference API, but don’t necessarily understand the documentation.

Any pointers on what I’m missing would be really appreciated, thanks in advance :pray:

Thibaut

Maybe something along the lines of editor.model.sharedModel.getSource()?

Related to Document how to set text on CodeEditor in JupyterLab 4.0 · Issue #13427 · jupyterlab/jupyterlab · GitHub

1 Like

As for how to get there from API reference, the link you posted has sharedModel:

which links to ISharedText:

which has a source attribute and getSource() method. It seems to me that these two are redundant as source getter just calls getSource()

1 Like

Thanks @krassowski, that was it. Thanks for the references, I actually saw it in the doc but didn’t understand it was value’s replacement.

Now, there is still something I didn’t get:

    if (editorRef.current && !editorInstanceRef.current) {
      const model = new CodeEditor.Model();
      const options: CodeEditor.IOptions = {
        host: editorRef.current,
        model,
        config: {
          lineHeight: 4,
        },
      };

      const codeEditor = new CodeMirrorEditor(options);
      editorInstanceRef.current = codeEditor;

      // Handle changes from the editor
      codeEditor.model.sharedModel.changed.connect(() => {
        const newValue = codeEditor.model.sharedModel.getSource();
        handleChange(newValue, field.id);
      });

I thought that typing text in the CodeEditor would trigger codeEditor.model.sharedModel.changed so that I could capture the value. However, it seems that is not the case. the sharedModel is the value that is saved, but how to get the value that is typed in the CodeEditor?

Thanks you,
Thibaut

Your CodeMirrorEditor is missing ybinding so the model updates will indeed not get propagated correctly, for an example see:

But if you do not want to setup the shared model for some reason you could possibly use the direct access to codeEditor.doc().toString()?

2 Likes

Thanks a lot @krassowski. Binding the model to the editor was the key indeed :slight_smile:

Haven’t tried the direct access because I needed to be able to detect changes and found the observe method (which might be possible with direct access idk but only found the solution with the sharedModel):

      sharedModel.ysource.observe(() => {
        const newValue = sharedModel.ysource.toString();
        if (newValue !== value) {
          handleChange(newValue, field.id);
        }
      });