unable to decorate codeblocks/fenced code

I wanted to change the background color of lines used for code blocks, so i written a code for that like below.

import { syntaxTree } from "@codemirror/language";
import { RangeSetBuilder, StateField } from "@codemirror/state";
import { Decoration, DecorationSet } from "@codemirror/view";
import { EditorView } from "codemirror";

const codeBlockMarkder = Decoration.line({ class: "cm-codeblock" });
const CodeBlockField = StateField.define<DecorationSet>({
  create() {
    return Decoration.none;
  },
  update(decorations, tr) {
    const builder = new RangeSetBuilder<Decoration>();
    decorations = decorations.map(tr.changes);
    syntaxTree(tr.state).iterate({
      enter(node) {
        if (node.type.is("FencedCode")) {
          builder.add(node.from, node.to, codeBlockMarkder);
        }
      },
    });
    return builder.finish();
  },
  provide(field) {
    return EditorView.decorations.from(field);
  },
});

export default CodeBlockField;

and imported the statefild and used it as extenstion

const state = EditorState.create({
    doc: noteBody,
    extensions: [
      basicSetup,
      language.of(
        markdown({ base: markdownLanguage, extensions: [MarkStylingExtention] })
      ),
      lightTheme,
      onBlur,
      CodeBlockField,
      syntaxHighlighting(markdownHighlightStyle),          // styled heading tags
      syntaxHighlighting(modedDefaultHightLightStyle),  // removed underlines from heading tags
    ],
  });

now if i type in the editor first 2 back ticks appears but if added one more back tick the the whole line disappears if try to complete the code block editor view is completely empty and the line is empty if i inspect elements.

I am new to codemirror so i dont really understand lot of things.

Line decorations shouldn’t be given a range like that. They should be single-position decorations at the start of the relevant line. If you use the .range method on them, they’ll warn you about this, but RangeSetBuilder.add doesn’t have the capability to do decoration-type-specific checks like that because it doesn’t know about specific types of range values.

Thanks @marijn for the reply.

I tried another way, I don’t know if its alright to perform things like this but please take a look and suggest any changes or if possible provide me a link to similar example

import {
  MatchDecorator,
  Decoration,
  DecorationSet,
  ViewPlugin,
  EditorView,
  ViewUpdate,
} from "@codemirror/view";

const codeBlockDecoration = Decoration.line({
  class: "cm-codeblock",
});

export const codeBlockTheme = EditorView.baseTheme({
  '.cm-codeblock': {
    backgroundColor: "#aaa"
  }
})

const codeBlockMatcher = new MatchDecorator({
  regexp: /```(.+[\n\r]){0,}```/g,
  decorate(add, from, to) {
    add(from, to, codeBlockDecoration);
  },
});

const CodeBlockPlugin = ViewPlugin.fromClass(
  class {
    codeblocks: DecorationSet;
    constructor(view: EditorView) {
      this.codeblocks = codeBlockMatcher.createDeco(view);
    }
    update(update: ViewUpdate) {
      this.codeblocks = codeBlockMatcher.updateDeco(update, this.codeblocks);
    }
  },
  {
    decorations: (instance) => instance.codeblocks,
  }
);

export default CodeBlockPlugin;

okay, I finally realised what you were saying. Apologies for the misinterpretation.
I want to decorate the whole code block or fencedCode (style its backgroundColor prop).
please suggest possible ways to achieve this.
Thanks

You’d have to iterate over the lines touched by a given code block and add a decoration at the start of each.

Finally some visible results after struggling

import { syntaxTree } from "@codemirror/language";
import { RangeSetBuilder, StateField } from "@codemirror/state";
import { Decoration, DecorationSet, EditorView } from "@codemirror/view";

const codeBlockMarker = Decoration.line({ class: "cm-codeblock" });
export const codeBlockTheme = EditorView.baseTheme({
  ".cm-line.cm-codeblock": {
    backgroundColor: "#838383",
    color: "#fff",
  },
  ".cm-line.cm-activeLine.cm-codeblock": {
    backgroundColor: "#939393",
    color: "#fff",
  },
});
const CodeBlockField = StateField.define<DecorationSet>({
  create() {
    return Decoration.none;
  },
  update(decorations, tr) {
    const builder = new RangeSetBuilder<Decoration>();
    decorations = decorations.map(tr.changes);
    syntaxTree(tr.state).iterate({
      enter(node) {
        if (node.type.is("FencedCode")) {
          const firstLine = tr.state.doc.lineAt(node.from).number;
          const lastLine = tr.state.doc.lineAt(node.to).number;
          console.log(lastLine,node.to);
          for (let i = firstLine; i <= lastLine; i++) {
            builder.add(
              tr.state.doc.line(i).from,
              tr.state.doc.line(i).from,
              codeBlockMarker
            );
          }
        }
      },
    });
    return builder.finish();
  },
  provide(field) {
    return EditorView.decorations.from(field);
  },
});

export default CodeBlockField;

please just let me know if it is the correct way of doing it.
It partially solved my problem though so it’s kind of acceptable. but in the end, I might have to find a way to wrap this entire code block in a span or something while keeping it editable.
Anyways thanks for help man. just let me know if it is an efficient way of doing this.


Faced a weird issue while testing it. if I click outside of the editor, sometimes the decoration changes
(.cm-codeblock disappears from the lines) until I click back on editor

The issue was caused by the editor being re-rendered by state management to store the state locally.
To adapt that thing I changed the code a bit. I still don’t know if it is the best practice. but still posting the crude solutions here for anyone in need.

const CodeBlockField = StateField.define<DecorationSet>({
  create(state) {
    const builder = new RangeSetBuilder<Decoration>();
    syntaxTree(state).iterate({
      enter(node) {
        if (node.type.is("FencedCode")) {
          const firstLine = state.doc.lineAt(node.from).number;
          const lastLine = state.doc.lineAt(node.to).number;
          for (let i = firstLine; i <= lastLine; i++) {
            builder.add(
              state.doc.line(i).from,
              state.doc.line(i).from,
              codeBlockMarker
            );
          }
        }
      },
    });
    return builder.finish();
  },
  update(decorations, tr) {
    const builder = new RangeSetBuilder<Decoration>();
    decorations = decorations.map(tr.changes);
    syntaxTree(tr.state).iterate({
      enter(node) {
        if (node.type.is("FencedCode")) {
          const firstLine = tr.state.doc.lineAt(node.from).number;
          const lastLine = tr.state.doc.lineAt(node.to).number;
          for (let i = firstLine; i <= lastLine; i++) {
            builder.add(
              tr.state.doc.line(i).from,
              tr.state.doc.line(i).from,
              codeBlockMarker
            );
          }
        }
      },
    });
    return builder.finish();
  },
  provide(field) {
    return EditorView.decorations.from(field);
  },
});