Codemirror 6 highlighting specific substring

I’m building a code editor using codemirror v6, using Angular.

I wanted to search for a given substring in the text, and highlight it with a yellow background.

I’ve been searching in a lot of places and found multplie ideas that could help me achieve what I want. For instance, How to search for and highlight a substring in Codemirror 6? and Codemirror v6: Marked Text. All of these solutions use something similar (through the use of StateEffect and view dispatch). I’m trying to implement it, but I keep getting an error wit the message ‘Argument of type ‘Range []’ is not assignable to parameter of type ‘null’.’ whenever I try view.dispatch(…)

My code is as follows (I’ve ommitted some parts in orther to simplify it):

  initCodeMirror() {

    let state = EditorState.create({/* state of code editor */});

    let view = new EditorView({
      state,
      parent: element
    });


    const addMarks = StateEffect.define(), filterMarks = StateEffect.define()

    const markField = StateField.define({
      create() { 
        return Decoration.none 
      },
      update(value, tr) {
        value = value.map(tr.changes)

        for (let effect of tr.effects) {
          if (effect.is(addMarks)) value = value.update({add: effect.value, sort: true})
          else if (effect.is(filterMarks)) value = value.update({filter: effect.value})
        }

        return value
      },

      provide: f => EditorView.decorations.from(f)
    })


    const bgMark = Decoration.mark({
      attributes: {style: "backgroundColor: yellow"}
    })


    highlight(view);
    function highlight(view: EditorView) {
      const doc = view.state.doc;
      const lines = doc.toString().split('\n');

      let i = 0;
      for (const line of lines) {
        if (line.includes("# COMPLETE THIS:")) {
          view.dispatch({ 
            effects: addMarks.of([bgMark.range(doc.line(i).from, doc.line(i).to)])
          })
        }
        i++;
      }
    }

  }

The implementation is almost identical to the Codemirror v6: Marked Text. The only difference is that I first search for the range on where the substring is inside the line (my substring is “# COMPLETE THIS:” ), and only then I execute the dispatch(). Unfortunately, I don’t really understand what I’m doing wrong. I’ve been with this issue almost two days now, and the documentation online is a little bit confusing, and there are few examples with this … I’ve already asked in other forums, but unfortunately didn’t get an answer. Can anyone help me?

Thank you so much in advanced

It sounds like you are getting a TypeScript type error when compiling the code? That is probably because your StateEffect definitions are missing a type parameter (defaulting to hold only null). You can do StateEffect.define<Range<Decoration>[]> or something similar to give them the proper type.

Hello,

Thank you so much for the quick answer.

I’ve tried changing the stateEffect ‘addMarks’ to

const addMarks = StateEffect.define<Range<Decoration>[]>;

Unfortunately, it gives an error of “Type ‘Range’ is not generic.” Am I changing it where it is supposed to? Or am I doing something wrong?

Thanks again

Looking at another code from here: How to search for and highlight a substring in Codemirror 6? and the solution you gave me I was able to put it up and running!

My error was just an import I had missing (Range) and I forgot to add the parentheses after de define. Thank you very much for everything!

This is my final version if anyone needs it:

import { EditorView } from "codemirror";
import { EditorState, StateField, StateEffect, Range } from "@codemirror/state";
import { SearchCursor } from "@codemirror/search";
import { Decoration } from "@codemirror/view";

initCodeMirror() {

    const highlight_effect = StateEffect.define<Range<Decoration>[]>();

    const highlight_extension = StateField.define({
      create() { return Decoration.none },
      update(value, transaction) {
        value = value.map(transaction.changes)

        for (let effect of transaction.effects) {
          if (effect.is(highlight_effect)) value = value.update({add: effect.value, sort: true})
        }

        return value
      },
      provide: f => EditorView.decorations.from(f)
    });

    let state = EditorState.create({/* ... */ extensions: [highlight_extension]});

    let view = new EditorView({
      state,
      parent: element
    });


    let cursor = new SearchCursor(view.state.doc, "# COMPLETE THIS");

    cursor.next();

    const highlight_decoration = Decoration.mark({
      attributes: {style: "background-color: yellow"}
    });

    view.dispatch({
      effects: highlight_effect.of([highlight_decoration.range(cursor.value.from, cursor.value.to)])
    });

  }

Thank you once again!

2 Likes