EditorState
Creating and working with CodeMirror 6's immutable EditorState: document, selection, extensions, and state fields.
EditorState
EditorState is the immutable data structure at the center of CodeMirror 6. It holds the document content, selection, and all extension-managed state. You never mutate an EditorState directly — instead, you create transactions that produce new state objects.
Import it from @codemirror/state:
import { EditorState } from "@codemirror/state";
Creating EditorState
Use EditorState.create() to create an initial state. It accepts an optional configuration object with doc, selection, and extensions fields.
let state = EditorState.create({
doc: "Hello World",
extensions: [
// your extensions here
],
});
All fields are optional. If you omit doc, the state starts with an empty document. If you omit extensions, the state has no configured behavior beyond its defaults.
// Minimal state with no document and no extensions
let empty = EditorState.create();
console.log(empty.doc.toString()); // ""
You can also provide an initial selection:
import { EditorSelection } from "@codemirror/state";
let state = EditorState.create({
doc: "Hello World",
selection: EditorSelection.cursor(5), // cursor after "Hello"
});
The doc Field
state.doc is a Text object, not a plain string. The Text type represents the document as a tree of lines, which makes it efficient for large documents.
let state = EditorState.create({ doc: "line one\nline two\nline three" });
// Total character length
console.log(state.doc.length); // 28
// Number of lines
console.log(state.doc.lines); // 3
// Get a specific line (1-based indexing)
let line = state.doc.line(2);
console.log(line.text); // "line two"
console.log(line.from); // 9
console.log(line.to); // 17
// Convert to string
console.log(state.doc.toString());
// Get the line at a character position
let lineAtPos = state.doc.lineAt(10);
console.log(lineAtPos.number); // 2
📝 Note
Text uses 1-based line numbering. state.doc.line(1) returns the first line. Character positions, however, are 0-based offsets from the start of the document.
Iterating Over Lines
You can iterate over a range of the document using iterLines() or iterRange():
// Iterate over all lines
for (let iter = state.doc.iterLines(); !iter.done; iter.next()) {
console.log(iter.value);
}
// Iterate character-by-character over a range
for (let iter = state.doc.iterRange(0, 20); !iter.done; iter.next()) {
console.log(iter.value);
}
Slicing the Document
Use sliceString() to extract a substring:
let text = state.doc.sliceString(9, 17);
console.log(text); // "line two"
The selection Field
state.selection is an EditorSelection object. It contains one or more selection ranges. Each range has an anchor and a head (the moving end of the selection).
let state = EditorState.create({ doc: "Hello World" });
// Default: cursor at position 0
let sel = state.selection;
console.log(sel.main.from); // 0
console.log(sel.main.to); // 0
console.log(sel.main.empty); // true (cursor, no selected text)
A cursor is a selection range where from and to are the same. A non-empty selection has different from and to values:
import { EditorSelection } from "@codemirror/state";
let state = EditorState.create({
doc: "Hello World",
selection: EditorSelection.single(0, 5), // "Hello" selected
});
console.log(state.selection.main.from); // 0
console.log(state.selection.main.to); // 5
console.log(state.selection.main.empty); // false
Multiple Selections
CodeMirror 6 supports multiple selections. The selection.ranges array holds all ranges, and selection.main is the primary one:
let state = EditorState.create({
doc: "aaa bbb ccc",
selection: EditorSelection.create([
EditorSelection.range(0, 3),
EditorSelection.range(4, 7),
EditorSelection.range(8, 11),
]),
});
console.log(state.selection.ranges.length); // 3
console.log(state.selection.main.from); // 0
📝 Note
Multiple selections require the EditorState.allowMultipleSelections facet to be enabled. Without it, only the main selection is kept.
Reading State Fields
State fields are custom pieces of state managed by extensions. You read them with state.field():
import { StateField } from "@codemirror/state";
let changeCount = StateField.define({
create() {
return 0;
},
update(value, tr) {
return tr.docChanged ? value + 1 : value;
},
});
let state = EditorState.create({ extensions: [changeCount] });
console.log(state.field(changeCount)); // 0
state = state.update({ changes: { from: 0, insert: "x" } }).state;
console.log(state.field(changeCount)); // 1
If you try to read a state field that was not included in the state’s extensions, it throws an error. You can pass false as a second argument to return undefined instead:
let value = state.field(someField, false);
if (value !== undefined) {
// field is available
}
Reading Facets
Use state.facet() to read configured facet values:
console.log(state.facet(EditorState.tabSize)); // default: 4
console.log(state.facet(EditorState.readOnly)); // default: false
Configured Extensions
The extensions you pass to EditorState.create() are baked into the state. You cannot add or remove extensions after creation without using compartments (see Extensions).
import { keymap, lineNumbers } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
let state = EditorState.create({
doc: "Hello",
extensions: [
EditorState.tabSize.of(2),
EditorState.readOnly.of(true),
lineNumbers(),
keymap.of(defaultKeymap),
],
});
console.log(state.facet(EditorState.tabSize)); // 2
console.log(state.facet(EditorState.readOnly)); // true
Creating a New State from an Existing One
While EditorState is immutable, you can derive a new state through transactions:
let state1 = EditorState.create({ doc: "Hello" });
let tr = state1.update({ changes: { from: 5, insert: " World" } });
let state2 = tr.state;
console.log(state1.doc.toString()); // "Hello"
console.log(state2.doc.toString()); // "Hello World"
You can also use EditorState.create() to build a fresh state that inherits certain configuration from an existing one via state.toJSON() and EditorState.fromJSON(), though this is less common.
Full Example
import { EditorState, EditorSelection, StateField } from "@codemirror/state";
import { keymap, lineNumbers } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";
// Define a custom state field
let docLength = StateField.define({
create(state) {
return state.doc.length;
},
update(_value, tr) {
return tr.state.doc.length;
},
});
// Create the state
let state = EditorState.create({
doc: "function hello() {\n return 'world';\n}",
selection: EditorSelection.cursor(0),
extensions: [
EditorState.tabSize.of(2),
lineNumbers(),
keymap.of(defaultKeymap),
docLength,
],
});
// Inspect the state
console.log(state.doc.lines); // 3
console.log(state.doc.line(1).text); // "function hello() {"
console.log(state.selection.main.head); // 0
console.log(state.facet(EditorState.tabSize)); // 2
console.log(state.field(docLength)); // 38