zudo-codemirror

Type to search...

to open search from anywhere

動的な設定変更

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

CodeMirror 6 で Compartment を使って実行時に Extension を動的に入れ替える方法: 機能のトグル、テーマの切り替え、ランタイム再設定。

Compartment

CodeMirror 6 では、Extension は通常 EditorState を作成する時点で固定されます。Compartment を使うと、エディタが既に実行中でも Extension を変更できます。Extension を Compartment でラップし、後からエフェクトをディスパッチして内容を置き換えます。

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

const myCompartment = new Compartment();

Compartment の作成

Compartment は of() メソッドを使って初期の Extension 値をラップします。状態を作成する際に、Compartment の of() の結果を Extension 配列の一部として渡します。

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

const lineNumberCompartment = new Compartment();

const state = EditorState.create({
  doc: "Hello",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
  ],
});

Compartment の再設定

実行時に Compartment 内の Extension を変更するには、reconfigure エフェクトを含むトランザクションをディスパッチします。

// Remove the extension (pass an empty array)
view.dispatch({
  effects: lineNumberCompartment.reconfigure([]),
});

// Re-add the extension
view.dispatch({
  effects: lineNumberCompartment.reconfigure(lineNumbers()),
});

空の配列 [] を渡すと、実質的に Extension が無効化されます。新しい Extension を渡すと、以前のものが置き換えられます。

例: 行番号のトグル

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

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

const state = EditorState.create({
  doc: "Toggle line numbers on and off.",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
  ],
});

const view = new EditorView({ state, parent: document.getElementById("editor") });

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

以下のボタンを使って行番号のオン/オフを切り替えてみてください:

Toggle Line Numbers

例: テーマの切り替え

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

const themeCompartment = new Compartment();

const lightTheme = EditorView.theme({
  "&": { backgroundColor: "#ffffff", color: "#333333" },
  ".cm-gutters": { backgroundColor: "#f0f0f0" },
});

const darkTheme = EditorView.theme({
  "&": { backgroundColor: "#1e1e1e", color: "#d4d4d4" },
  ".cm-gutters": { backgroundColor: "#252526" },
}, { dark: true });

const state = EditorState.create({
  doc: "Switch between light and dark.",
  extensions: [
    themeCompartment.of(lightTheme),
  ],
});

const view = new EditorView({ state, parent: document.getElementById("editor") });

function setTheme(dark: boolean) {
  view.dispatch({
    effects: themeCompartment.reconfigure(dark ? darkTheme : lightTheme),
  });
}

「Toggle Theme」ボタンをクリックして、Compartment を使ってライトテーマと oneDark テーマをランタイムで切り替えてみてください:

Toggle Theme with Compartment

例: Vim モードのトグル

@replit/codemirror-vim パッケージは Vim のキーバインディングを提供します。Compartment でラップすることで、ユーザーが実行時に Vim モードを有効/無効にできます。

import { Compartment } from "@codemirror/state";
import { vim } from "@replit/codemirror-vim";

const vimCompartment = new Compartment();
let vimEnabled = false;

// Initial setup: vim off
const extensions = [
  vimCompartment.of([]),
];

function toggleVim() {
  vimEnabled = !vimEnabled;
  view.dispatch({
    effects: vimCompartment.reconfigure(
      vimEnabled ? vim() : [],
    ),
  });
}

⚠️ Warning

Vim モードをトグルする場合、Vim Extension は Extension 配列の先頭付近に配置し、他のキーマップよりも先にキーイベントをインターセプトできるようにしてください。

例: 行の折り返しのトグル

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

const wrapCompartment = new Compartment();

// Initial setup with wrapping enabled
const extensions = [
  wrapCompartment.of(EditorView.lineWrapping),
];

// Toggle wrapping off
view.dispatch({
  effects: wrapCompartment.reconfigure([]),
});

// Toggle wrapping on
view.dispatch({
  effects: wrapCompartment.reconfigure(EditorView.lineWrapping),
});

複数の Compartment

必要な数だけ Compartment を使用できます。各 Compartment は独立して自身の Extension を管理します。

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

const lineNumberCompartment = new Compartment();
const wrapCompartment = new Compartment();
const themeCompartment = new Compartment();

const state = EditorState.create({
  doc: "",
  extensions: [
    lineNumberCompartment.of(lineNumbers()),
    wrapCompartment.of(EditorView.lineWrapping),
    themeCompartment.of(myTheme),
  ],
});

1 回のディスパッチでエフェクトの配列を渡すことで、複数の Compartment を同時に再設定できます。

view.dispatch({
  effects: [
    themeCompartment.reconfigure(darkTheme),
    wrapCompartment.reconfigure([]),
  ],
});

現在の Compartment の値を確認する

compartment.get() を使って、Compartment が現在保持している Extension を確認できます。

const currentExtension = lineNumberCompartment.get(view.state);

これは現在の Extension の値を返します。トグル前の現在の状態を判定するのに便利です。

Compartment を使うべき場面

Compartment は以下のようなケースに適したツールです。

  • 実行時に機能を有効/無効にする(行番号、折り返し、Vim モード)
  • テーマやハイライトスタイルを切り替える
  • ユーザーがファイルを切り替えたときに言語サポートを変更する
  • ユーザー設定に基づいて設定を更新する

トランザクションごとに変化する状態(ワードカウントなど)には、代わりに StateField を使用してください。Compartment は Extension 全体の入れ替え用であり、きめ細かな状態更新には向いていません。

Revision History