How to achieve visible tabs in CodeMirror 6?

I found this example: CodeMirror: Visible tabs demo but I think it features CodeMirror 5. How to code it myself with CodeMirror 6?

I want to do “visible spaces”, and I think I can edit “visible tabs” to spaces.

There’s something like that here in the Chrome devtools source.

@marijn Okay, I managed to create this ViewPlugin based on their example:

const matchDecorator = new MatchDecorator({
  regexp: /^ +/gm,
  decoration: match => Decoration.mark({
    attributes: {class: 'cm-highlightedSpaces', 'data-display': '·'.repeat(match[0].length)},
  boundary: /\S/,

const showAllWhitespace = ViewPlugin.define(
  view => ({
    decorations: matchDecorator.createDeco(view),
    update(viewUpdate) {
      this.decorations = matchDecorator.updateDeco(viewUpdate, this.decorations);
    decorations: view => view.decorations,

But I’m using markdown language, and I only want to run these decorations, when in CodeBlock element. Where should add the check?

@marijn I implemented the same thing as before, but without MatchDecorator, but by iterating of syntaxTree() myself:

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

const cachedDecorations = new Map();

function whitespaceDecoration(spaces) {
  const cached = cachedDecorations.get(spaces);
  if (cached) {
    return cached;
  const decoration = Decoration.mark({
    attributes: {class: 'cm-visibleSpaces', 'data-spaces': '·'.repeat(spaces.length)},
  cachedDecorations.set(spaces, decoration);
  return decoration;

function decorations({view}) {
  const builder = new RangeSetBuilder();

  for (const {from, to} of view.visibleRanges) {
      from, to,
      enter: syntaxNode => {
        if ( === "FencedCode") {
          const content = view.state.doc.sliceString(syntaxNode.from,;

          const myRe = /^ +/gm;
          let myArray;

          while ((myArray = myRe.exec(content)) !== null) {
            const offset = myArray.index;
            const length = myArray[0].length;

              syntaxNode.from + offset,
              syntaxNode.from + offset + length,
  return builder.finish();

export const visibleSpaces = ViewPlugin.define(view => ({view}), {decorations});

Which of these approaches would be better?

  1. The first approach uses MatchDecorator, isn’t that inefficient? I would image it probably does a global match.
  2. The second approach only iterates .visibleRanges, but has to parse the tree with syntaxTree().

Which would be better?