extend/overlay mode

In CM5 I could overlay or extend a mode by either using the overlayMode function or wrapping it. Is this something that’s possible with CM6? My use-case was adding some custom stuff to the GFM mode. I saw that there’s a lang-markdown package and also a stream parser (which is what I use in CM5) but I’m not exactly sure if I can use them together or if there’s a preferred approach. Sorry If I’ve missed this in the documentation.

Thanks!

Overlay-style highlighting is now done with a view plugin that maintains a set of decorations for visible content. Extending or wrapping a mode isn’t really possible anymore. Which feature, specifically, do you want to add? The new Markdown parser was written with the intention of, at some point, allowing extension through user code, but I haven’t nailed down the API for that yet.

My use case is adding some non-standard markdown syntax like @ style mention, :: highlighting :: ,#hashtags and so on which was pretty straightforward by using Codemirror.defineMode and the stream tokenizer and then using overlayMode to combine with the normal GFM mode.

If these aren’t going to affect the Markdown parsing itself, and don’t need to appear in the syntax tree, a crude view plugin like below should work. If you want to actually integrate these into the Markdown parser, you might need a lot more patience (or could consider paying me to implement that feature).

let plugin = ViewPlugin.fromClass(class {
  decorations: DecorationSet
  constructor(view: EditorView) {
    this.decorations = this.mkDeco(view)
  }
  update(update: ViewUpdate) {
    if (update.viewportChanged || update.docChanged) this.decorations = this.mkDeco(update.view)
  }
  mkDeco(view: EditorView) {
    let b = new RangeSetBuilder<Decoration>()
    let highlight = /(@\w+)|(::.*?::)|(#\w+)/g
    for (let {from, to} of view.visibleRanges) {
      let range = view.state.sliceDoc(from, to), m
      while (m = highlight.exec(range))
        b.add(from + m.index, from + m.index + m[0].length, Decoration.mark({
          class: m[1] ? "at-mention" : m[2] ? "highlight" : "hashtag"
        }))
    }
    return b.finish()
  }
}, {
  decorations: v => v.decorations
})

Thanks a lot! I’ll test it out.

Given how verbose my previous answer was, I’ve added a helper class to @codemirror/view 0.17.4 that makes this easier…

import {MatchDecorator, ViewPlugin, Decoration} from "@codemirror/view"

let mentionDeco = Decoration.mark({class: "mention"})
let tagDeco = Decoration.mark({class: "hashtag"})
let highlightDeco = Decoration.mark({class: "highlight"})
let decorator = new MatchDecorator({
  regexp: /(@\w+)|(::.*?::)|(#\w+)/g,
  decoration: m => m[1] ? mentionDeco : m[2] ? highlightDeco : tagDeco
})

let plugin = ViewPlugin.define(view => ({
  decorations: decorator.createDeco(view),
  update(u) { this.decorations = decorator.updateDeco(u, this.decorations) }
}), {
  decorations: v => v.decorations
})
1 Like