How to update multiple inline decorations on node change

Hey, currently I try to implement automatic code highlighting with highlight.js for code blocks. It’s already working but it’s not performant for large documents because it’s getting re-rendered on every change of the editor.


I can’t find any example for decorations in a similar use case. So basically here is what I’ve done already:

  • I create a Plugin() for adding my decorations
  • I search in the doc for all code_block nodes
  • I create an array of inline decorations for all code highlights of all code blocks and return them with a DecorationSet

At the moment I found only two ways to get this working:

  1. Re-render all decorations on every change
  2. Render all decorations only on init

But what I want is to render all decorations for code_block nodes on init and re-render them individually whenever a code_block node changes.

I have no clue how to solve this because nodes do not have any identifiers so if I try to cache all the decorations I do not know which decorations belongs to which nodes. :weary:Any idea?

1 Like

What you do is track the position of the element you are interested in, and on each transaction, map to its new position (possibly tracking both the start and end so that it’s easier to determine whether any of the step maps in the transaction touch it).

1 Like

I believe this can work to check what nodes need their decorations updated. I would appreciate if someone knows a better way.

function checkTextBlocks(tr) {
    if (tr.mapping) {
        let positions = []
        tr.mapping.maps.forEach(m => {
            positions = =>
            if (m.ranges.length) {
                positions.push(m.ranges[0], m.ranges[0] + m.ranges[2])

        tr.doc.descendants((node, pos) => {
            if (node.isTextblock) {
                for (let i = 0, l = positions.length; i < l; i += 2) {
                    let range = {
                        from: positions[i],
                        to: positions[i + 1]

                    // check if range starts or end in node or if range wraps node
                    if ((pos > range.from && > pos) || (range.from > pos && range.from < pos + node.nodeSize)) {
              'update this node: ', node)
                return false
            return node.isBlock
1 Like