Editor state data structures for the CodeMirror code editor
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
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
});