line background layer

Hello! I am trying to implement a line background layer as mentioned in Line background color and selection layering . This is my code:

import { RectangleMarker } from '@codemirror/view';
import { layer } from '@codemirror/view';

export const backlayer = layer({
  update: (update) => update.docChanged,
  markers: (view) => {
    const offsetTop = 14; // how to not hard code these numbers?
    const offsetLeft = 4;
    return view.viewportLineBlocks.map((block) => {
      const { left } = view.coordsAtPos(block.to);
      return new RectangleMarker(
        'cm-backlayer', 
        offsetLeft, // left
        block.top + offsetTop, // top
        left, // width
        block.height // height
      );
    });
  },
});

This works perfectly but when I turn on lineWrapping, the rectangles won’t fit for wrapped lines:

Here, line 5 would have needed two rectangles… the reason why this happens is that view.viewportLineBlocks only returns one LineBlock per line, even if a line spans multiple real lines (like line 5). Obviously, a single rectangle won’t work to wrap 2 lines of different length…

Is there another way to get the line blocks for each individual “real” line? Or maybe there’s a completely different way to solve this problem?

I have also tried to use RectangleMarker.forRange:

RectangleMarker.forRange(
      view,
      'cm-backlayer',
      SelectionRange.fromJSON({
        from: 0,
        to: view.state.doc.length,
        anchor: 0,
        head: view.state.doc.length,
        empty: false,
        assoc: 0,
        bidiLevel: null,
      }),
    )

The problem here is that I will just get a giant rectangle that is also behind empty areas.

Thanks for reading, I hope someone can help me with this

You need to exclude the trailing newline of the last line, this will also get rid of the narrow background on empty lines.

Thanks for the answer! It looks like the newlines are not part of the LineBlock items returned by view.viewportLineBlocks. I’ve verified that by looking at view.state.doc.toString().slice(block.from, block.to), which gives an empty string for empty lines (without line break).
I am also not sure how that would solve the problem that a wrapped line would need two LineBlock items. Maybe I also misunderstand something here