How to retain higher-order highlight style with Decoration.replace()

Sometimes, nodes are nested within other nodes. CodeMirror handles this by assigning all styles/classes that apply (to the node itself and all parent nodes) to DOM elements so that the DOM is more or less flat. Usually this works finde, but when adding widgets that replace parts of the text, the styles/classes are not automatically assigned to the inserted HTML (as expected). It is easy enough to make sure the widget itself is styled properly, but I can’t find an easy way to add styles/classes from parent nodes. I thought about passing a HighlightStyle to my plugin (the one handling the replacement) and then compute the styles that should apply manually, but that seems rather involved.

Is there any better way of doing this?

This is what a simple replacing widget might look like:

class LinkWidget extends WidgetType {
    private text: string;

    constructor(text) { super(); this.text = text;}

    toDOM() {
        let wrap = document.createElement("span");
        wrap.classList.add("cm-link")
        wrap.innerText = this.text;
        return wrap
    }
}

Best,
Marc

It doesn’t do that anymore since 0.12.0. Decorations with lower precedence now produce parent nodes wrapping the nodes created by decorations with higher precedence.

Hi marijn,

thanks for your reply. Using the standard markdown parser (using a CSS class-based highlighter), the line

> A single-line quote with **bold text**.

produces the following DOM:

<div class="cm-line">
     <span class="cm-quote cm-mark">></span>
     <span class="cm-quote"> A single-line quote with </span>
     <span class="cm-quote cm-strong cm-mark">**</span>
     <span class="cm-quote cm-strong">bold text</span>
     <span class="cm-quote cm-strong cm-mark">**</span>
     <span class="cm-quote">.</span>
</div>

This really is fine, but let’s say I want to replace the **bold text** with a widget. That’s quite easy to do, but since the quote is not a DOM parent to the bold text, it doesn’t automatically inherit its styles. My plan now is to somehow figure out which styles/classed should apply and then manually assign them.

Im on version 0.18.3 (2021-05-19) from npm btw.

Best,
Marc

You’re talking about mirroring the syntax tree in the DOM tree? Right, that’s not something the editor does. Also wouldn’t really work across line boundaries anyway. So indeed, you’ll have to find some other way to do this.

I see. Would you advice to do this in the Decoration’s toDOM() method or to hook into the actual process where Codemirror creates the DOM?

I’m still really not sure what you’re trying to accomplish, so it’s hard to give specific advice.

Yeah, I might have been a little unclear. Fo the sake of simplicity, let’s assume I want to style markdown quote with the class cm-quote - easy! Now, there is some strongly emphasised text within that quote - like this: > Quote with some **bold text**. Since I style the “bold text” with the class cm-strong, the DOM element for the “bold text” has now two classes, cm-strong and cm-quote - great!

However, let’s also assume I want to replace every occurrence of **some bold text** with some different text. It doesn’t really matter what that replacement is, but I use a widget whose toDDM() method returns a span with class cm-widget. In this case, I want that span’s style to match that of any parent nodes (replicating what CM would do without the widget). For example, when my widget is within a markdown quote, I want the span to be assigned to the classes cm-widget as well as cm-quote.

I thought about trying to infer the widget’s position in the syntax tree within the toDOM() method and then adding all the appropriate classes. However, I have not found an elegant way to do so (I would also need to somehow access the active highlighter) and it might be smarter to do this at some other place?

Ah, that makes it clearer. I think the best way to find the bold text would be to iterate through the syntax tree—and that should also give you information about the text’s parent nodes, making it relatively straightforward to add classes for those.

Is there an elegant way to obtain tag information from a syntax tree’s node? I know that they are stored in node.type.props, but this is marked as internal (so typescript complains when accessing is) and the accessor method prop() needs ruleNodeProp which is not exported from codemirror/highlight.

No, but why do you need the highlight tags? Doesn’t working directly with node types work well enough for this use case?

I guess it makes sense for consistency, no? Using the same HighlightStyle as for the actual styling?

You can get the generated class names out of a HighlightStyle with match, but yeah, mapping AST nodes to tags using the style rules in the tree is a lot more involved and not publicly available. I still feel like hard-coding those associations for the few nodes that are relevant should be fine in this case.

Isn’t that what’s HighlightStyle.get() for? Actually it seems to work pretty well, aside from the hackery involved in actually getting the the style tags :).

Right, that works per state, where HighlightStyle.match works per highlight style.