🚀 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
];