🚀 CM6 Gutter & Diff Styling Cheat Sheet

┌────────────────────────────────────────┐
│ 🟢 + │ 1  │  const code = "foo";       │ 💡 Decoration.line (Added)
│ 🔵 ~ │ 2  │  let bar = "baz";          │ 💡 Decoration.line (Modified)
│ 🔴 ─ │ 3  │  // Deleted line indicator │ 💡 Decoration.widget (Border)
└────────────────────────────────────────┘
  ▲      ▲     ▲
  │      │     └─ Editor View
  │      └─────── Line Numbers Gutter
  └────────────── Custom Diff Gutter

1. Custom Gutters & Markers

import { gutter, GutterMarker } from "@codemirror/view";
 
// 🎨 Define Marker Visuals
class DiffMarker extends GutterMarker {
  constructor(private type: "add" | "mod" | "del") { super(); }
  toDOM() {
    const dom = document.createElement("div");
    dom.className = `cm-diff-gutter-${this.type}`;
    dom.textContent = this.type === "add" ? "+" : this.type === "mod" ? "~" : "-";
    return dom;
  }
}
 
// 🧱 Define Gutter Extension
const diffGutter = gutter({
  lineMarker(view, line) {
    // Retrieve status from State Field (see Step 2)
    const diffState = view.state.field(diffStateField);
    const status = diffState.getLineStatus(line.from); 
    return status ? new DiffMarker(status) : null;
  },
  initialSpacer: () => new DiffMarker("add")
});

2. Diff Tracking State Field

import { StateField, StateEffect } from "@codemirror/state";
 
const updateDiffs = StateEffect.define<DiffMap>();
 
const diffStateField = StateField.define<DiffMap>({
  create() { return new DiffMap(); },
  update(value, tr) {
    // ⚡ Shift marker coordinates during active typing
    value = value.map(tr.changes); 
    
    // 🔄 Update state on transaction
    for (let effect of tr.effects) {
      if (effect.is(updateDiffs)) return effect.value;
    }
    return value;
  }
});

3. Line Background & Border Decorations

import { Decoration, DecorationSet, ViewPlugin, ViewUpdate } from "@codemirror/view";
 
// 🟢/🔵 Highlights
const addDeco = Decoration.line({ attributes: { class: "cm-diff-added" } });
const modDeco = Decoration.line({ attributes: { class: "cm-diff-modified" } });
 
// 🔴 Underline/Border for deletions
const delDeco = Decoration.widget({
  widget: new class extends WidgetType {
    toDOM() {
      const el = document.createElement("div");
      el.className = "cm-diff-deleted-line";
      return el;
    }
  }(),
  side: -1 // Render between lines
});

4. Combined Setup & Theme

import { EditorView } from "@codemirror/view";
 
const diffTheme = EditorView.theme({
  ".cm-diff-gutter-add": { color: "#2ea44f", fontWeight: "bold" },
  ".cm-diff-gutter-mod": { color: "#0969da", fontWeight: "bold" },
  ".cm-diff-added": { backgroundColor: "rgba(46, 164, 79, 0.15)" },
  ".cm-diff-modified": { backgroundColor: "rgba(9, 105, 218, 0.15)" },
  ".cm-diff-deleted-line": { borderBottom: "2px solid #cf222e", width: "100%" }
});
 
// 🔌 Load into extensions array
const extensions = [
  diffStateField,
  diffGutter,
  diffTheme
];