zudo-codemirror

Type to search...

to open search from anywhere

EditorState

作成2026年3月29日更新2026年3月29日Takeshi Takatsudo

CodeMirror 6 のイミュータブルな EditorState の作成と操作: ドキュメント、選択範囲、Extension、StateField。

EditorState

EditorState は CodeMirror 6 の中心にあるイミュータブルなデータ構造です。ドキュメントの内容、選択範囲、すべての Extension が管理する状態を保持します。EditorState を直接変更することはありません。代わりに、新しい状態オブジェクトを生成する Transaction を作成します。

@codemirror/state から import します:

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

EditorState の作成

EditorState.create() を使用して初期状態を作成します。docselectionextensions フィールドを持つオプションの設定オブジェクトを受け取ります。

let state = EditorState.create({
  doc: "Hello World",
  extensions: [
    // your extensions here
  ],
});

すべてのフィールドはオプションです。doc を省略すると、空のドキュメントで状態が開始されます。extensions を省略すると、デフォルト以外の設定された動作を持たない状態になります。

// Minimal state with no document and no extensions
let empty = EditorState.create();
console.log(empty.doc.toString()); // ""

初期選択範囲を指定することもできます:

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

let state = EditorState.create({
  doc: "Hello World",
  selection: EditorSelection.cursor(5), // cursor after "Hello"
});

doc フィールド

state.doc はプレーンな文字列ではなく Text オブジェクトです。Text 型はドキュメントを行のツリーとして表現しており、大きなドキュメントでも効率的に処理できます。

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 は 1 ベースの行番号を使用します。state.doc.line(1) は最初の行を返します。一方、文字位置はドキュメントの先頭からの 0 ベースのオフセットです。

行のイテレーション

iterLines()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);
}

ドキュメントのスライス

sliceString() を使用して部分文字列を抽出します:

let text = state.doc.sliceString(9, 17);
console.log(text); // "line two"

selection フィールド

state.selectionEditorSelection オブジェクトです。1 つ以上の選択範囲を含みます。各範囲には anchorhead(選択範囲の移動端)があります。

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)

カーソルは fromto が同じ選択範囲です。空でない選択範囲は fromto が異なります:

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

複数選択

CodeMirror 6 は複数選択をサポートしています。selection.ranges 配列がすべての範囲を保持し、selection.main がプライマリの範囲です:

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

複数選択には EditorState.allowMultipleSelections Facet を有効にする必要があります。有効にしないと、メインの選択範囲のみが保持されます。

StateField の読み取り

StateField は Extension が管理するカスタムの状態です。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

状態の Extension に含まれていない StateField を読み取ろうとすると、エラーがスローされます。第 2 引数に false を渡すと、代わりに undefined が返されます:

let value = state.field(someField, false);
if (value !== undefined) {
  // field is available
}

Facet の読み取り

state.facet() を使用して設定された Facet の値を読み取ります:

console.log(state.facet(EditorState.tabSize)); // default: 4
console.log(state.facet(EditorState.readOnly)); // default: false

設定された Extension

EditorState.create() に渡した Extension は状態に組み込まれます。Compartment を使用しない限り、作成後に Extension を追加・削除することはできません(Extension を参照)。

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

既存の状態から新しい状態を作成する

EditorState はイミュータブルですが、Transaction を通じて新しい状態を導出できます:

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"

state.toJSON()EditorState.fromJSON() を使用して、既存の状態から特定の設定を継承する新しい状態を EditorState.create() で構築することもできますが、これはあまり一般的ではありません。

完全な例

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

Revision History