zudo-codemirror

Type to search...

to open search from anywhere

Quick Reference

CreatedMar 29, 2026UpdatedMar 29, 2026Takeshi Takatsudo

Common CodeMirror 6 patterns - imports, editor setup, content manipulation, and event handling.

Common Import Patterns

// Meta-package (includes basicSetup, EditorView, EditorState)
import { EditorView, basicSetup } from "codemirror";

// Core state
import { EditorState, StateField, StateEffect, Facet } from "@codemirror/state";

// Core view
import { EditorView, keymap, lineNumbers, Decoration, ViewPlugin } from "@codemirror/view";

// Commands and keymaps
import { defaultKeymap, historyKeymap, history, undo, redo } from "@codemirror/commands";

// Language support
import { javascript } from "@codemirror/lang-javascript";
import { markdown } from "@codemirror/lang-markdown";
import { html } from "@codemirror/lang-html";
import { css } from "@codemirror/lang-css";
import { json } from "@codemirror/lang-json";
import { python } from "@codemirror/lang-python";

// Search
import { search, searchKeymap, openSearchPanel } from "@codemirror/search";

// Autocomplete
import { autocompletion, completionKeymap } from "@codemirror/autocomplete";

// Linting
import { linter, lintKeymap } from "@codemirror/lint";

// Vim mode
import { vim } from "@replit/codemirror-vim";

Minimal Editor Setup

import { EditorView, basicSetup } from "codemirror";
import { javascript } from "@codemirror/lang-javascript";

const view = new EditorView({
  doc: "// your code here\n",
  extensions: [basicSetup, javascript()],
  parent: document.getElementById("editor"),
});

Setup Without basicSetup

If you want full control over which features are included:

import { EditorView, keymap, lineNumbers, highlightActiveLine } from "@codemirror/view";
import { EditorState } from "@codemirror/state";
import { defaultKeymap, historyKeymap, history } from "@codemirror/commands";
import { javascript } from "@codemirror/lang-javascript";
import { syntaxHighlighting, defaultHighlightStyle } from "@codemirror/language";

const view = new EditorView({
  state: EditorState.create({
    doc: "// your code here\n",
    extensions: [
      lineNumbers(),
      highlightActiveLine(),
      history(),
      syntaxHighlighting(defaultHighlightStyle),
      keymap.of([...defaultKeymap, ...historyKeymap]),
      javascript(),
    ],
  }),
  parent: document.getElementById("editor"),
});

Get Editor Content

// Get the full document as a string
const content = view.state.doc.toString();

// Get a specific line (1-indexed)
const line = view.state.doc.line(1);
console.log(line.text); // line content
console.log(line.from); // start position
console.log(line.to);   // end position

// Get the number of lines
const lineCount = view.state.doc.lines;

// Get text in a range
const slice = view.state.sliceDoc(0, 100);

Set Editor Content

// Replace the entire document
view.dispatch({
  changes: {
    from: 0,
    to: view.state.doc.length,
    insert: "new content here",
  },
});

// Insert text at a position
view.dispatch({
  changes: { from: 10, insert: "inserted text" },
});

// Delete a range
view.dispatch({
  changes: { from: 5, to: 15 },
});

// Replace a range
view.dispatch({
  changes: { from: 5, to: 15, insert: "replacement" },
});

// Multiple changes in one transaction
view.dispatch({
  changes: [
    { from: 0, to: 5, insert: "AAA" },
    { from: 20, to: 25, insert: "BBB" },
  ],
});

⚠️ Warning

When dispatching multiple changes in a single transaction, positions in the changes array refer to positions in the original document (before any of the changes are applied). CodeMirror maps positions automatically.

Replace the Entire Editor State

To completely reset the editor with a new state (new document, new extensions, new everything):

view.setState(
  EditorState.create({
    doc: "brand new document",
    extensions: [basicSetup, javascript()],
  })
);

Listen for Changes

Using updateListener

const view = new EditorView({
  doc: "",
  extensions: [
    basicSetup,
    EditorView.updateListener.of((update) => {
      if (update.docChanged) {
        console.log("Document changed:", update.state.doc.toString());
      }
    }),
  ],
  parent: document.getElementById("editor"),
});

Using a ViewPlugin

For more complex logic that needs access to the view:

import { ViewPlugin, ViewUpdate } from "@codemirror/view";

const changeTracker = ViewPlugin.fromClass(
  class {
    update(update: ViewUpdate) {
      if (update.docChanged) {
        // React to document changes
        console.log("New length:", update.state.doc.length);
      }
      if (update.selectionSet) {
        // React to selection changes
        const { from, to } = update.state.selection.main;
        console.log("Selection:", from, to);
      }
    }
  }
);

Focus the Editor

// Focus the editor
view.focus();

// Check if the editor has focus
const hasFocus = view.hasFocus;

Selection

// Get the current selection
const { from, to } = view.state.selection.main;

// Get the selected text
const selectedText = view.state.sliceDoc(from, to);

// Set the cursor position (collapsed selection)
view.dispatch({
  selection: { anchor: 0 },
});

// Set a selection range
view.dispatch({
  selection: { anchor: 0, head: 10 },
});

// Set cursor position and scroll it into view
view.dispatch({
  selection: { anchor: 0 },
  scrollIntoView: true,
});

Add and Remove Extensions

CodeMirror does not have an addExtension / removeExtension API. Instead, you use compartments to dynamically reconfigure parts of the editor.

Using Compartments

import { Compartment } from "@codemirror/state";

// Create a compartment for the language
const languageConf = new Compartment();

const view = new EditorView({
  doc: "",
  extensions: [
    basicSetup,
    languageConf.of(javascript()),
  ],
  parent: document.getElementById("editor"),
});

// Later: switch the language to Python
import { python } from "@codemirror/lang-python";

view.dispatch({
  effects: languageConf.reconfigure(python()),
});

// Remove the extension by reconfiguring with an empty array
view.dispatch({
  effects: languageConf.reconfigure([]),
});

Toggle an Extension On/Off

import { Compartment } from "@codemirror/state";
import { lineNumbers } from "@codemirror/view";

const lineNumberConf = new Compartment();
let lineNumbersEnabled = true;

const view = new EditorView({
  doc: "",
  extensions: [
    basicSetup,
    lineNumberConf.of(lineNumbers()),
  ],
  parent: document.getElementById("editor"),
});

function toggleLineNumbers() {
  lineNumbersEnabled = !lineNumbersEnabled;
  view.dispatch({
    effects: lineNumberConf.reconfigure(
      lineNumbersEnabled ? lineNumbers() : []
    ),
  });
}

Dispatch Transactions

Transactions are the only way to update the editor state. Every call to view.dispatch() creates and applies a transaction.

// Simple document change
view.dispatch({
  changes: { from: 0, insert: "Hello " },
});

// Change with selection update
view.dispatch({
  changes: { from: 0, insert: "Hello " },
  selection: { anchor: 6 },
});

// Add an annotation to a transaction
import { Annotation } from "@codemirror/state";

const externalChange = Annotation.define<boolean>();

view.dispatch({
  changes: { from: 0, to: view.state.doc.length, insert: "reset" },
  annotations: externalChange.of(true),
});

// Multiple sequential changes (applied as separate transactions)
view.dispatch({ changes: { from: 0, insert: "A" } });
view.dispatch({ changes: { from: 0, insert: "B" } });

Read-Only Mode

import { EditorState } from "@codemirror/state";
import { Compartment } from "@codemirror/state";

const readOnlyConf = new Compartment();

const view = new EditorView({
  doc: "read-only content",
  extensions: [
    basicSetup,
    readOnlyConf.of(EditorState.readOnly.of(true)),
  ],
  parent: document.getElementById("editor"),
});

// Toggle read-only
view.dispatch({
  effects: readOnlyConf.reconfigure(
    EditorState.readOnly.of(false)
  ),
});

Destroy the Editor

view.destroy();

This removes the editor from the DOM and cleans up event listeners. The EditorView instance should not be used after calling destroy().

Theming

import { EditorView } from "@codemirror/view";

const myTheme = EditorView.theme({
  "&": {
    fontSize: "14px",
    backgroundColor: "#1e1e1e",
  },
  ".cm-content": {
    fontFamily: "'Fira Code', monospace",
    color: "#d4d4d4",
  },
  ".cm-gutters": {
    backgroundColor: "#252525",
    color: "#858585",
    border: "none",
  },
  ".cm-activeLine": {
    backgroundColor: "#2a2a2a",
  },
  "&.cm-focused .cm-cursor": {
    borderLeftColor: "#ffffff",
  },
});

const view = new EditorView({
  doc: "",
  extensions: [basicSetup, myTheme],
  parent: document.getElementById("editor"),
});

Theme selectors use & to refer to the editor’s outer element (.cm-editor). All CSS properties use their standard camelCase names.

DOM Events

const view = new EditorView({
  doc: "",
  extensions: [
    basicSetup,
    EditorView.domEventHandlers({
      keydown(event, view) {
        if (event.key === "Escape") {
          console.log("Escape pressed");
          // Return true to prevent further handling
          return false;
        }
      },
      blur(event, view) {
        console.log("Editor lost focus");
      },
    }),
  ],
  parent: document.getElementById("editor"),
});

Revision History