CodeMirror State provides immutable data structures for representing editor state in the CodeMirror code editor. It implements the core state management system including document content, selections, and extensions through a functional, transaction-based architecture.
npm install @codemirror/stateimport {
EditorState,
EditorSelection,
Transaction,
Text,
ChangeSet,
StateField,
Facet
} from "@codemirror/state";For CommonJS:
const {
EditorState,
EditorSelection,
Transaction,
Text,
ChangeSet,
StateField,
Facet
} = require("@codemirror/state");import { EditorState, EditorSelection, Text } from "@codemirror/state";
// Create an initial editor state
const state = EditorState.create({
doc: "Hello, world!\nThis is CodeMirror.",
selection: EditorSelection.single(0)
});
// Create a transaction to modify the state
const transaction = state.update({
changes: { from: 0, to: 5, insert: "Hi" },
selection: EditorSelection.single(2)
});
// Apply the transaction to get a new state
const newState = transaction.state;
console.log(newState.doc.toString()); // "Hi, world!\nThis is CodeMirror."
console.log(newState.selection.main.head); // 2CodeMirror State is built around several key architectural principles:
Core editor state functionality for creating and managing immutable editor states with document content, selections, and configuration.
class EditorState {
static create(config?: EditorStateConfig): EditorState;
update(...specs: readonly TransactionSpec[]): Transaction;
readonly doc: Text;
readonly selection: EditorSelection;
}
interface EditorStateConfig {
doc?: string | Text;
selection?: EditorSelection | {anchor: number, head?: number};
extensions?: Extension;
}Immutable text document data structure with efficient operations for large documents and line-based access.
abstract class Text {
static of(text: string[]): Text;
readonly length: number;
readonly lines: number;
lineAt(pos: number): Line;
line(n: number): Line;
sliceString(from: number, to?: number, lineSep?: string): string;
}
interface Line {
readonly from: number;
readonly to: number;
readonly number: number;
readonly text: string;
}Selection system supporting single and multiple selection ranges with cursor positioning and range operations.
class EditorSelection {
static single(pos: number): EditorSelection;
static cursor(pos: number, assoc?: number, bidiLevel?: number, goalColumn?: number): EditorSelection;
static range(anchor: number, head?: number, goalColumn?: number, bidiLevel?: number): EditorSelection;
static create(ranges: readonly SelectionRange[], mainIndex?: number): EditorSelection;
readonly ranges: readonly SelectionRange[];
readonly main: SelectionRange;
map(change: ChangeDesc, assoc?: number): EditorSelection;
}
class SelectionRange {
readonly from: number;
readonly to: number;
readonly anchor: number;
readonly head: number;
readonly empty: boolean;
}Transaction-based state updates that bundle document changes, selection updates, and side effects into atomic operations.
class Transaction {
readonly startState: EditorState;
readonly state: EditorState;
readonly changes: ChangeSet;
readonly selection: EditorSelection;
readonly effects: readonly StateEffect<any>[];
readonly annotations: readonly Annotation<any>[];
readonly docChanged: boolean;
readonly reconfigured: boolean;
}
interface TransactionSpec {
changes?: ChangeSpec;
selection?: EditorSelection | {anchor: number, head?: number};
effects?: StateEffect<any> | readonly StateEffect<any>[];
annotations?: Annotation<any> | readonly Annotation<any>[];
scrollIntoView?: boolean;
filter?: boolean;
}Document change system with immutable change sets, position mapping, and change composition for collaborative editing.
class ChangeSet extends ChangeDesc {
static of(changes: ChangeSpec, length: number, lineSep?: string): ChangeSet;
static empty(length: number): ChangeSet;
apply(doc: Text): Text;
map(other: ChangeDesc, before?: boolean): ChangeSet;
compose(other: ChangeSet): ChangeSet;
invert(doc: Text): ChangeSet;
}
class ChangeDesc {
readonly length: number;
readonly newLength: number;
readonly empty: boolean;
mapPos(pos: number, assoc?: number, mode?: MapMode): number | null;
touchesRange(from: number, to?: number): boolean | "cover";
}
enum MapMode {
Simple,
TrackDel,
TrackBefore,
TrackAfter
}Flexible extension architecture using facets and state fields for configurable editor behavior and plugin development.
class Facet<Input, Output = readonly Input[]> {
static define<Input, Output = readonly Input[]>(config?: FacetConfig<Input, Output>): Facet<Input, Output>;
of(value: Input): Extension;
compute(deps: readonly Slot<any>[], get: (state: EditorState) => Input): Extension;
from<T>(field: StateField<T>, get?: (value: T) => Input): Extension;
}
class StateField<Value> {
static define<Value>(config: StateFieldSpec<Value>): StateField<Value>;
init(create: (state: EditorState) => Value): Extension;
}
class Compartment {
of(ext: Extension): Extension;
reconfigure(content: Extension): StateEffect<unknown>;
get(state: EditorState): Extension | undefined;
}Efficient data structures for managing non-overlapping ranges with associated values, used for decorations and markers.
class RangeSet<T extends RangeValue> {
static of<T extends RangeValue>(ranges: readonly Range<T>[] | Range<T>, sort?: boolean): RangeSet<T>;
static empty: RangeSet<any>;
update(updateSpec: RangeSetUpdateSpec<T>): RangeSet<T>;
map(changes: ChangeDesc): RangeSet<T>;
between(from: number, to: number, f: (from: number, to: number, value: T) => void | false): void;
iter(from?: number): RangeCursor<T>;
}
abstract class RangeValue {
range(from: number, to?: number): Range<this>;
startSide: number;
endSide: number;
point: boolean;
mapMode: MapMode;
}
class Range<T extends RangeValue> {
readonly from: number;
readonly to: number;
readonly value: T;
}Unicode-aware character processing utilities for handling grapheme clusters, code points, and text categorization.
function findClusterBreak(str: string, pos: number, forward?: boolean, includeExtending?: boolean): number;
function codePointAt(str: string, pos: number): number;
function fromCodePoint(code: number): string;
function codePointSize(code: number): 1 | 2;
function countColumn(string: string, tabSize: number, to?: number): number;
function findColumn(string: string, col: number, tabSize: number, strict?: boolean): number;
enum CharCategory {
Word,
Space,
Other
}Utility functions for combining and merging configuration objects.
function combineConfig<Config extends object>(
configs: readonly Partial<Config>[],
defaults: Partial<Config>,
combine?: {[P in keyof Config]?: (first: Config[P], second: Config[P]) => Config[P]}
): Config;Usage Example:
import { combineConfig } from "@codemirror/state";
interface MyConfig {
theme: string;
tabSize: number;
readOnly: boolean;
}
const configs = [
{ theme: "dark", tabSize: 2 },
{ theme: "light" }, // Conflict with theme
{ readOnly: true }
];
const defaults = { tabSize: 4, readOnly: false };
// This would throw due to theme conflict
try {
const merged = combineConfig(configs, defaults);
} catch (e) {
console.log("Config conflict detected");
}
// Resolve conflicts with combine functions
const resolved = combineConfig(configs, defaults, {
theme: (first, second) => second || first // Last value wins
});