Thank you very much! I figured that out shortly after writing this question, but it’s good to hear the confirmation from you that the way I found is also the preferred way.
For anyone who’s curious and wants to know how to do this properly:
After starting this topic, I cloned the codemirror-view repo and searched for the error message, finding the following lines of code:
if (this.disallowBlockEffectsFor[index] && deco instanceof PointDecoration) {
if (deco.block)
throw new RangeError("Block decorations may not be specified via plugins")
if (to > this.doc.lineAt(this.pos).to)
throw new RangeError("Decorations that replace line breaks may not be specified via plugins")
}
This indicated to me that apparently there was a flag set to disallow any type of block widgets to be drawn. This also means that apparently one doesn’t have to explicitly set the block
-flag on the widgets, since Codemirror is smart enough to figure out whether the widget spans multiple lines. (Please correct me if I’m wrong)
Then, I returned to the documentation where I found this specific sentence:
There are two ways to provide them—directly, or though a function that will be called with a view instance to produce a set of decorations. Decorations that signficantly change the vertical layout of the editor, for example by replacing line breaks or inserting block widgets, must be provided directly, since indirect decorations are only retrieved after the viewport has been computed.
This was very unclear to me, but in effect it means the following: To define something “directly” means to define them as a StateField
, whereas defining them “through a function” equals defining them as a ViewPlugin
. The error in my thinking was that both ways of defining decorations actually have a function that gets called and returns the decorations. In the case of a ViewPlugin
it’s decorations: view => view.decorations
, whereas in the case of a StateField
it’s provide: f => EditorView.decorations.from(f)
.
In short, depending on your use case the methods are as following:
Inline-only widgets
If you want to render widgets that are guaranteed to be only inline (i.e. Markdown links), then you should define a ViewPlugin
. Those get called after the viewport has been created and are therefore much cheaper. Also, you can make use of the view.visibleRanges
-property which enables you to only render those widgets for any actually visible ranges (i.e. must be within the viewport and additionally not be folded).
The example for inline-only-widgets is the Boolean Toggle Widgets example.
Inline and block widgets
If you, on the other hand, have widgets that may also be block-level and not just inline you have to, as Marijn said, call StateField.define()
. You can – more or less – provide the same logic to compute the widgets, but since StateField
s are computed prior to rendering, you can also provide block-level widgets.
The example for the inline and block widgets is the Underline command example. (The trick is to understand that you can also underline pieces of text that span line breaks, even if you don’t underline full lines. To me, for the past weeks, it always seemed to me inline-only just as well.)