動的な設定変更
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() : [],
),
});
}
以下のボタンを使って行番号のオン/オフを切り替えてみてください:
例: テーマの切り替え
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 テーマをランタイムで切り替えてみてください:
例: 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 全体の入れ替え用であり、きめ細かな状態更新には向いていません。