CodeMirror 6: Proper way to listen for changes

What is the intended way to listen for changes to editor state?

I need to sync the document contents with some other state, and I noticed that while I can listen for input events on the editor DOM, the state.doc doesn’t hold the correct value until at least a microtask after the event fires. So my code to listen for changes looks like this:

    this._editorView.dom.addEventListener('input', async (e) => {
      e.stopPropagation();
      await 0;
      this._value = this._editorView.state.doc.toString();
      this.dispatchEvent(new InputEvent('input'));
    });

Is this the right way?

Absolutely not. Not only is it a giant hack, it’ll also fail to notice programmatic changes. Depending on what you are trying to do, it may make sense to provide a dispatch function when creating your editor view, or register a view plugin that starts the action you want when its update method is called.

Ah, sorry, I thought dispatch was more meant for customizing how transactions were applied. I was looking through the docs for a more notification-only API point.

Is this._editorView.state.doc.toString() an ok way to get the document contents, or is there something better?

That’s the way. Though for giant documents it’s going to do quite a lot of string concatenation, so depending on your use case you might not want to constantly do it when you can avoid it.

1 Like

I read your conversation and decided to use a ViewPlugin. When reading the documentation I struggled a bit to figure out that a plugin can be added as an extension.
I came up with the following solution to watch the editor:

initEditor() {
	const compThis = this //save this context
	const editor = new EditorView({
        state: EditorState.create({
          doc: 'Loading the real content',
          extensions: [
            ViewPlugin.fromClass(class {
              constructor(view) {}

              update(update) {
                if (update.docChanged)
                  compThis.onDocumentChanged() //escort the update event up one level. This function saves the contents and interact with the other UI.
              }
            }),
            lineNumbers(),
            highlightSpecialChars(),
            history(),
            foldGutter(),
            multipleSelections(),
            defaultHighlighter,
            bracketMatching(),
            closeBrackets(),
            autocomplete(),
            rectangularSelection(),
            highlightActiveLine(),
            highlightSelectionMatches(),
            keymap([
              ...defaultKeymap,
              ...searchKeymap,
              ...historyKeymap,
              ...foldKeymap,
              ...commentKeymap,
              ...gotoLineKeymap,
              ...autocompleteKeymap,
              ...lintKeymap
            ])
          ]
        })
	this.$refs.editor.append(this.editorView.dom)
	//other init stuff
}

I doubt that this is used in the way its intended to be used? Is there a better way to do this? How can I pass information down to my plugin?

See EditorView.updateListener for a shorthand way to do this.

Thanks for your response. The documentation is very minimal. Could you provide a small example?

You provide EditorView.updateListener.of(update => ...) as an extension, and your function gets called with a view update object every time the view is updated.

1 Like