Custom dom inline styles

I have just migrated from 0.18 to 0.19 and I noticed that we are no longer able to set inline styles on DOM elements. After some digging I believe it’s related to this commit:

Use case

I have a small piece of code that adds a inline style of a negative text-indent with a positive padding-left to soft-wrap indented lists like this:
image

Since the 0.19 update, it seems that the inline style gets wiped as soon as it’s added, and the DOM is reverted back looking like this:
image

Method

What I’m doing right now, which previously worked in 0.18, is to use view.requestMeasure in an updateListener.

In the read phase, I read all the lines from view.viewportLines for whether they’re a list with indentation, and if so, compute the pixel value using coordsAtPos. Then in the write phase, I’m adding both style rules.

Question

What would be the recommended way in 0.19? The goal is to add varying levels of text-indent (and matching padding-left to each line individually, depending on the text.

Indeed, messing with the content DOM directly isn’t supported (and wasn’t supported before 0.19 either). You’ll have to go through decorations if you want to change the way the code is rendered.

Hmm while building this, I ran into a few things that I don’t quite understand (and really weird bugs):

Since the process would require measuring, I’ve had to put it in a view.requestMeasure, but it seems that decorations plugins has to build the list of decorations during the update cycle, because otherwise the entire editor breaks down, lines starts to go missing, and clicks don’t go to the right spots anymore.

I’m doing something similar to this:

update(update) {
	update.view.requestMeasure({
		read: (view) => {
			let builder = new RangeSetBuilder<Decoration>();
			view.viewportLines(line => {
				// compute this value from view.coordsAtPos
				let x = 4;
				builder.add(line.from, line.to, Decoration.line({style: `text-indent: ${x}px`}));
			});
			this.decorations = builder.finish();
		},
		write() {
		}
	});
}

I’m not sure what else I can try here; previously on 0.18, while it was not officially supported, the requestMeasure trick worked pretty well.

Actually my bad, it turns out the problem was that Decoration.line requires setting the RangeSet to be line.from, line.from rather than line.from, line.to.

In that regard, is there a way for a view plugin to force a refresh (for its decorations) after a requestMeasure cycle?

Decorations provided by plugins should only really change on update calls, not at other times. Are you sure you need to measure this on every update, rather than computing it from defaultCharacterWidth and updating it when the editor geometry changes?

Decorations provided by plugins should only really change on update calls, not at other times.

Ah I see. I do have a few other use cases for plugins to update at other times. For example, one such use case is to provide a widget that shows up only after a timeout where the user has put the cursor at the trigger spot, to avoid unnecessarily annoying popups that flash by when the cursor is being moved.

Are you sure you need to measure this on every update, rather than computing it from defaultCharacterWidth and updating it when the editor geometry changes?

Computing it from the defaultCharacterWidth only works for monospaced and fixed sized text - in my case, we’re using regular font, but also the syntax highlighter’s styles will also change font-size (for example, a markdown heading).

What I ended up doing is to use a cache to store the sizes for line prefixes, and use defaultCharacterWidth as an approximation before the value is measured. The cached values are only flushed out when major style changes are applied (like swapping out the app’s theme).

Here is an implementation of soft wrapping that circumvents view.requestMeasure and uses defaultCharacterWidth, might help: https://gist.github.com/dralletje/058fe51415fe7dbac4709a65c615b52e

Thank you! I’ve managed to do it via a view extension rather than a state field in a similar way to yours, except that I’m using reqestMeasure also to get the real pixel values.

You happen to have some code that I can use as inspiration? I’m running into some problems with the defaultCharacterWidth method

Here you go. This plugin is always 1 step behind for any text that wasn’t measured, for which the defaultCharacterWidth will be used as approximation. The next rendering cycle will fix it, whenever it’s triggered. In my testing, just clicking on the text makes it work.

You can tweak the regex to match more things if needed (like for markdown, also matching the - part of a list).