How to highlight lines?

Hi!
I am working on a plugin for Obsidian, which uses Codemirror6. This is my first plugin, so please be nice :slight_smile: I wanted to add line numbers to codeblocks, which I succcessfully could finish after struggling for weeks. Now I want to highlight specific lines, or line ranges in codeblock. I can’t figure this one out. I don’t know what I am doing, and there are probably easier methods, but I couldn’t find any simple examples :frowning: Could someone please help me. Here is my current code:

export const lineNumberField = StateField.define<DecorationSet>({
  create(state): DecorationSet {
    return Decoration.none;
  },
  update(oldState: DecorationSet, transaction: Transaction): DecorationSet {
    const builder = new RangeSetBuilder<Decoration>();
    let lineNumber = 1;    
    syntaxTree(transaction.state).iterate({
      enter(node) {
        if (node.type.id === 3 ) {
          // opening ```
        }
        if (node.type.id === 5 ) {
          // code lines
          builder.add(
            node.from,
            node.from,
            Decoration.widget({
              widget: new LineNumberWidget(lineNumber),
            })
          );
          const line = transaction.state.doc.lineAt(lineNumber);
          const start = line.from;
          const end = line.to;
            builder.add(
              start,
              end,
              Decoration.line({
                attributes : {
                class: "highlighted"}
                })
            );
          lineNumber++;
        }
         if (node.type.id === 6 ) {
            // closing ```
            lineNumber = 1;
          }
      },
    });
    return builder.finish();
  },
  provide(field: StateField<DecorationSet>): Extension {
    return EditorView.decorations.from(field);
  },
});

class LineNumberWidget extends WidgetType {
  constructor(private lineNumber: number) {
    super();
  }

  toDOM(view: EditorView): HTMLElement {
    const span = document.createElement("span");
    span.classList.add("gutter");
    span.innerText = `${this.lineNumber}`;
    return span;
  }
}

export default class LineNumberingPlugin extends Plugin  {
  async onload() {
    this.registerEditorExtension(lineNumberField);
    console.log("Plugin is registered");
  }
}

Line decorations should have both their start and end at the start of the line that’s being decorated. Maybe that’s what’s going wrong?

This is interesting. If I disable the the Decoration.widget which adds the linenumbering, then I realized that the Decoration.line is working. My CSS was just not correct. This highlights the second line in every codeblcok:

 if (lineNumber === 2){            
          const line = transaction.state.doc.lineAt(2);
          const start = line.from;
          const end = line.to;
            builder.add(
              node.from,
              node.from,
              Decoration.line({
                attributes : {
                class: "highlighted"}
                })
            );
          }

However, if I enable both decorator at once then I get:

app.js:1 Uncaught (in promise) Error: Ranges must be added sorted by `from` position and `startSide`

Apparently, I can’t add two decorators to the same line at the same time. This causes the problem. If I add two decorators with the same node.from positions, then none of them works, and the exception occurs. How could I add two decorators (maybe it can be solved with one as well), to display line numbers and highlight lines?

Yes you can, if you add them in the opposite order.

Thank you! And now it works. I was going crazy over this. You saved my life.
Btw, am I doing this right? I mean I am using statefields now, but is this ok, or should I use Viewplugin or something else instead? I couldn’t figure out what type of extension should I use for my purposes.

This works, though it may be reasonable to only re-run your decoration logic when the document actually changes, and use the previous decoration set instead. It would be more efficient, for very large documents, to only compute the decorations inside the viewport in a view plugin, but depending on your use case you may not need that optimization.

May I ask why it works normally if you do this?