Partial CodeMirror Selection with External ProseMirror Selection

Right now, the CodeMirror example only supports full text selection – meaning all the text with the block is either selected or not – when the selection begins externally from the ProseMirror document.

Is there a way to begin the selection externally and select in CodeMirror a character-by-character basis as if the selection began internally? The ideal behavior would be something like the Slate Prism plugin:

I saw previously a discussion to integrate the ProseMirror tooltip on top of CodeMirror, and that seems to work for the tooltip but not selection.

This seems possible: the CodeMirror example currently supports a one-way binding from CodeMirror selection to ProseMirror selection, which involves a conversion and a setSelection.

forwardSelection() {
  if (!this.cm.hasFocus()) return
  let state     = this.view.state
  let selection = this.asProseMirrorSelection(state.doc)
  if (!selection.eq(state.selection))
    this.view.dispatch(state.tr.setSelection(selection))
},
asProseMirrorSelection(doc) {
  let offset = this.getPos() + 1
  let anchor = this.cm.indexFromPos(this.cm.getCursor("anchor")) + offset
  let head   = this.cm.indexFromPos(this.cm.getCursor("head")  ) + offset
  return TextSelection.create(doc, anchor, head)
}

To create a mutual binding, we would have to allow for a binding from ProseMirror selection to CodeMirror selection. For that, there’s a CodeMirror setSelection method. However, I’m not sure what event to trigger here which allows the CodeMirror block to respond to an external ProseMirror selection.

Not really. The browser doesn’t allow its selection to start in editable content and end in uneditable content (or another separate editable element). It might be possible to fake the entire selection in cases like this, if you throw enough code at it, but that’s out of scope for core ProseMirror.

I think that’s already implemented by the node view’s setSelection method (when ProseMirror syncs it selection, it’ll end up calling that).

Oof that’s sad to hear; I wouldn’t have known about browser selection behavior otherwise. The main motivation of patching the CodeMirror and ProseMirror selection together was to allow a user to see another user’s selection within the CodeMirror block with collaborative editing. Given the difficulty, that may explain why AtlasKit and Tiptap went with a simpler CodeBlock implementation with syntax highlighting via highlightjs/prismjs.

I’m curious though, on what makes CodeMirror a separate editable element, as opposed to a CodeBlock Node with a mix of non-editable elements (for stuff like line numbers) and editable elements?

Edit: The AtlasKit CodeBlock has a non-editable gutter for line-numbers, and keeps all of its editable content in a single html element – only doing syntax highlighting when it’s in read mode. TipTap only has an editable elements; syntax highlighting is dynamic and occurs during editing. It seems like you can combine these two approaches to have non-editable elements + syntax highlighting; but going back to the question, that seems close to what CodeMirror is composed of.

2 Likes