Billy
Billy

Reputation: 905

How to highligh specific text in the monaco editor

I am highlighting the text in the textarea using the following approach in angular.

https://stackblitz.com/edit/angular-textarea-highlight?file=src%2Fapp%2Fapp.component.ts

How can we achieve the same in monaco editor. Is there a way I can use Monaco editor to do this?

My Approach:

 const acceptedList = ['do', 'have'];
    //  let editor1 = monaco.editor.create( ... 
    let model = editor1.getModel();
    for (let i = 0; i < model.getLineCount(); i++) {
        let line = model.getLineContent(i);
        // here highlight the line if contains from acceptedList
    }

Trying to loop through the lines and highlight if it matches our condition. Is it the right approach? how can we highlight the line in monaco editor?

Upvotes: 6

Views: 7893

Answers (3)

Pier-Luc Gendreau
Pier-Luc Gendreau

Reputation: 13814

createDecorationsCollection is the way to go but there's an important bit of information in the method's comments:

These decorations will be automatically cleared when the editor's model changes.

Here's how I implemented it:

editor.onDidChangeModelContent(() => {
  editor.createDecorationsCollection([
    {
      range: new monaco.Range(3, 1, 3, 1),
      options: {
        isWholeLine: true,
        glyphMarginClassName: "myClassName",
      },
    },
  ]);
});

When you're done with it, don't forget to clean up. This will vary depending on your framework but here's the typical way to do it with React:

useEffect(() => {
  const handler = editor.onDidChangeModelContent(() => {
    // ...
  });

  return () => {
    editor.dispose();
  };
}, [])

Upvotes: 0

Halfist
Halfist

Reputation: 540

@hbarnett91's answer really helped me a lot, but unfortunately it didn't work, because createDecorationsCollection() doesn't exist anymore.

My setup:

So, here's how I solved it (with types).

import { editor } from 'monaco-editor';

const acceptedList = ['do', 'have'];
const editorModel: editor.ITextModel = (window as any).monaco.editor.getModels()[0];

acceptedList.forEach((item: string): void => {
  const matches: editor.FindMatch[] = editorModel.findMatches(item, false, false, false, null, false);
  matches.forEach((match: editor.FindMatch): void => {
    editorModel.deltaDecorations([], [
      {
        range: match.range,
        options: {
          isWholeLine: false,
          inlineClassName: 'highlight'
        }
      }
    ]);
  });
});

And CSS of course.

.highlight {
  background: #ffff00;
}

Apart from replacing createDecorationsCollection() with deltaDecorations(), I also had to use getModels()[0] instead of getModel(), since the latter was throwing ERROR TypeError: Cannot read properties of undefined (reading 'toString').

And here's how it looks.

Monaco Editor with highlighted search results

Update

Here's how to use getModel() properly, and avoid (window as any).monaco by sticking the logic to exact editor instance.

<ngx-monaco-editor
  [options]="editorOptions"
  [model]="model"
  [(ngModel)]="value"
  (ngModelChange)="onValueChange()"
  (onInit)="onInit($event)"
></ngx-monaco-editor>
import { editor } from 'monaco-editor';

onInit(editor: editor.ICodeEditor): void {
  const acceptedList = ['do', 'have'];
  const editorModel: editor.ITextModel = editor.getModel();

  acceptedList.forEach((item: string): void => {
    const matches: editor.FindMatch[] = editorModel.findMatches(item, false, false, false, null, false);
    if (matches.length > 0) {
      matches.forEach((match: editor.FindMatch): void => {
        editorModel.deltaDecorations([], [
          {
            range: match.range,
            options: {
              isWholeLine: false,
              inlineClassName: 'highlight'
            }
          }
        ]);
      });
    }
  });
}

Upvotes: 4

hbarnett91
hbarnett91

Reputation: 63

If I'm understanding correctly, you'll want to use the findMatches and createDecorationsCollection APIs. Instead of looping through each, individual line of the editor, you might do something like this:

acceptedList.forEach(item => {
  var matches = editor1.getModel().findMatches(item);
  matches.forEach(match => {
    editor1.createDecorationsCollection([
      {
        range: match.range,
        options: {
          isWholeLine: false,
          inlineClassName: "someClassName"
        }
      },
    ]);
  });
})

Then in your stylesheet you'd have something like:

.someClassName {
  background: #FFFF00;
}

Each match has a property range which is a collection of integers like (startLineNumber: number, startColumn: number, endLineNumber: number, endColumn: number). This ought to apply the class someClassName to every span of text within the range of every match returned from the editor.

I just got done implementing something like this in an bespoke editor and had to do lots of digging through the Monaco Editor docs. If you need clarification on how to remove the decorations after they've been applied, feel free to ask, but it's a whole different can of worms.

EDIT: Rereading your question, it sounds like you may want to highlight the entire line with the matching word? I haven't used this option, but in the decorations collection you might try setting the option isWholeLine to true? I'm not sure if this would work, but it might be a step in the right direction if that's what you're going for. Otherwise, this solution should work like the example you shared.

Upvotes: 1

Related Questions