Updates not synchronised with requestMeasure and ViewPlugin

I want to create a soft-wrap plugin, and for the most part I have gotten it working thanks to this thread: Custom dom inline styles - v6 - discuss.CodeMirror

But the problem is that requestMeasure runs in a different cycle rather than ViewPlugin and sometimes it require a click to get the updated values to render. What is the best way to get value from requestMeasure and run it inside of ViewPlugin. requestMeasures write() method stats that it shouldn’t do any sort of DOM layout changes so thats out of the picture.

Another way to look at this problem which I haven’t looked into is measuring text width outside of CM, and then updating the CM instance, but it won’t take into account any sorts of widgets.

Maybe there is a way to get value out of requestMeasure and into ViewPlugin without them running in different cycles, at-least it seems like it should be common use-case, but I couldn’t find anything.

Thanks,
Dex Devlon.

I think you may be misunderstanding the docs there? write should not read the DOM layout, but it can manipulate the DOM.

Hmm, I thought this wasn’t the case because I wasn’t able to get it working before, but it turns out that I was having some weird behaviour when I was updating the ViewPlugin with condition of docChanged, viewportChanged, and geometryChanged. If I updated it every case without any conditions on update it works as intended, but other wise for some reason my changes get overwritten.

Heres a minimum example which where I add 140px left padding to the first line. Notice when you click the doc it resets the padding:

import { EditorView, ViewPlugin, ViewUpdate } from "@codemirror/view";

export class SoftWrapPlugin {
  constructor(view: EditorView) {
    this.buildDeco(view);
  }

  update(update: ViewUpdate) {
    if (update.docChanged || update.viewportChanged || update.geometryChanged) {
      // <-  this is causing issues. If you remove all condition it works as intended.
      this.buildDeco(update.view);
    }
  }

  buildDeco(view: EditorView) {
    view.requestMeasure({
      read: () => {
        return view.domAtPos(0).node; // Return cm-line element for the first line
      },
      write: (cmlineNode) => {
        (cmlineNode as HTMLElement).style.paddingLeft = "140px"; // Add a left padding of 140px to the element
      }
    });
  }
}

export let softWrap = ViewPlugin.define((view) => new SoftWrapPlugin(view));

The behaviour is even worst when using custom auto completion for some reason, where it’s a lot more apparent and clear there. Heres a gif:

Untitled1

As of now updating without any conditions works, but it may not be the best case for optimization. What may be going wrong here? Is there a better way to do this.

Oh, right, you can write to the DOM outside of the editor, but not to its content, since that’ll immediately trigger the editor’s mutation observer which will fix your changes again. You could try to do whatever it is you are trying to do here with decorations (preferably computed directly whenever the editor is updated, to avoid introducing additional update cycles).

That would make sense. For what I am trying to do it would be preferable to get values from requestMeasure, but because it runs in a different loop, as in it has delay I can’t really use it directly inside the same update cycle.

Is there a way to directly get values from requestMeasure? As of now the only way it seems is to get values from read, but it may not happen in the same update cycle so I would need to do it in the next update or even later.

What are you measuring in requestMeasure?

Sorry I misread you, I am measuing the coords of two positions inside the view, to find the vertical difference between them to find the exact pixels to be padded/wrapped.

Heres the code inside of requestMeasure, here line.from is the position of the start of the line, text.length is the length of the white space, or whatever(could also contain markdown lists *):

size_in_px = (view.coordsAtPos(line.from + text.length, -1) as Rect).right - (view.coordsAtPos(line.from, 1) as Rect).left;

I then later on add padding and text indentation of the exact size, thats about it.

Oh, I see. That’s definitely annoying, since it wouldn’t be possible to fit in a single update. You could just multiply the space count by defaultCharacterWidth, which will usually work, and doesn’t require measuring, but when there are different fonts involved it may not. Possibly a system that starts with that and, in a timeout, measures whether it is correct and dispatches a transaction that fixes things up, would work, but it’d be a bit finicky to set up.

1 Like

Yeah I guess it’s not possible, though it seems like a very useful feature to have, maybe a future update where one could request measure within ViewPlugin or StateField and not have to worry about cycles not synchronising. But thanks a lot.

For anyone other struggling with this specific issue of soft wrapping, try to find the char width using the font externally.