Synchronising CodeMirror 6 and ProseMirror

I’m building a multi-view editor based on CodeMirror 6 (which is amazing and thanks for releasing it). I have several local CodeMirror views that are synchronised following the Split View example, although on the CodeMirror side I’ve rewritten the example’s syncDispatch function to aggregate an array of Transactions into a TransactionSpec[] of changes, because I read in the code that the single-transaction version of dispatchTransactions is deprecated:

const syncDispatch = (trs: readonly Transaction[], view: EditorView,...) => {}

I’m now trying to synchronise the master CodeMirror instance with a ProseMirror view, following the Collab example.

I create the ProseMirror view:

// create prosemirror view, and bind to element
const view: PMEditorView = new PMEditorView( element as HTMLElement,{
    state: PMEditorState.create({...}),
    dispatchTransaction: transaction => customDispatch({type: "transaction", transaction}, view)
});

and set up its dispatch function:

const customDispatch = (action: any, local_view: PMEditorView) => {
    const source = action.transaction.changes ? 'cm' : 'pm';
    switch (source) {
        case 'cm' :
            const changes: TransactionSpec[] = action.transaction.changes;
            // @todo merge in CodeMirror transaction
            break;
        case 'pm' :
            // merge in ProseMirror transaction to update local view
            local_view.updateState(local_view.state.apply(action.transaction));
            // @todo create and dispatch CodeMirror transaction
            break;
    }
};

Could you offer any advice on how to translate between the CodeMirror and ProseMirror transactions please?

I understand that CodeMirror 6 is newer than ProseMirror, so I wonder if the former is indicative of the latter’s future direction. If so, might future versions of ProseMirror become more structure-compatible with CodeMirror 6? Mine is not an urgent requirement, so I could wait if this activity is prohibitively complex now, but will get easier at some point in the future?

1 Like

Not really. The way you map between rich text and plain text documents is going to involve some kind of translation, I guess, depending on the text format and ProseMirror schema used. You say nothing about how you’re doing that, but I suspect it involves some kind of parsing. Mapping changes in such a context, and especially translating text changes to structured ProseMirror steps is likely going to be non-trivial. It may be easier to compute a diff of some kind and work from that.

Thanks for getting back to me.

The fundamental document (in CodeMirror) is markdown, so the mapping is likely non-trivial as you say, though it is fundamentally the same content. I thought maybe about treating every update as a whole single line, so when processing the transaction I would:

  • Fetch affected line as text (markdown) from CodeMirror doc state
  • Re-parse into ProseMirror using defaultMarkdownParser.parse(content)
  • Then maybe use replaceWith to update a single line in ProseMirror?

An alternative approach might be to write a CodeMirror 6 view that looked and behaved like ProseMirror. Is that a better route to follow here?

By any chance did you solve this ? I am having similar use case where I have some content in prosemirror, when I open codeView ( a seperate popup/modal) which displays the RAW HTML content inside codemirror

I need to keep the cursor in the same line as prosemirror while opening the code view, is this possible ?

From what I understand & tried, when we do

selection.$pos - > // returns `8` when it is on 2nd line

But on code view, the <html> is beautified & displayed in a single line, the 8 on PM view → to line 1 on codemirror does not sync up.