zudo-codemirror

Type to search...

to open search from anywhere

EditorView

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

CodeMirror 6 の EditorView の操作: DOM レンダリング、Transaction の dispatch、テーマ、プラグイン、View のライフサイクル。

EditorView

EditorViewEditorState を DOM に接続する命令型シェルです。エディタをレンダリングし、ユーザー入力を処理し、状態の更新を調整します。EditorState が純粋に関数型であるのに対し、EditorView は副作用を管理します: DOM の操作、スクロール、フォーカス、イベント処理。

@codemirror/view から import します:

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

EditorView の作成

parent 要素と、オプションで事前に構築した EditorState を渡します:

import { EditorState } from "@codemirror/state";
import { EditorView, keymap } from "@codemirror/view";
import { defaultKeymap } from "@codemirror/commands";

let state = EditorState.create({
  doc: "Hello World",
  extensions: [keymap.of(defaultKeymap)],
});

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

別途状態を作成せずに、すべてを EditorView のコンストラクタに直接渡すこともできます:

let view = new EditorView({
  doc: "Hello World",
  extensions: [keymap.of(defaultKeymap)],
  parent: document.getElementById("editor"),
});

このショートハンドを使用すると、View が内部的に EditorState を作成します。

以下は EditorView で作成されたライブエディタです。入力してエディタの動作を確認してください:

Creating an EditorView

Transaction の dispatch

view.dispatch() は状態変更を適用する方法です。1 つ以上の Transaction spec を受け取ります:

// Insert text at position 0
view.dispatch({
  changes: { from: 0, insert: "// " },
});

// Replace a range
view.dispatch({
  changes: { from: 0, to: 5, insert: "Goodbye" },
});

// Move the cursor
view.dispatch({
  selection: { anchor: 10 },
});

dispatch() を呼び出すたびに、Transaction が作成され、新しい状態が生成され、DOM の影響を受けた部分が再レンダリングされます。

単一の dispatch で複数の変更を組み合わせることができます:

view.dispatch({
  changes: [
    { from: 0, insert: "// " },
    { from: 20, insert: "\n" },
  ],
});

📝 Note

changes 配列内の位置は、同じ配列内の先の変更が適用された後のドキュメントではなく、元のドキュメントを参照します。CodeMirror が内部的にオフセットの調整を処理します。

状態へのアクセス

現在の状態は常に view.state を通じて利用可能です:

console.log(view.state.doc.toString());
console.log(view.state.selection.main.head);

Transaction を dispatch した後、view.state は新しい状態を反映します。

ViewPlugin と StateField の比較

CodeMirror は、時間の経過に伴う状態を管理するための 2 種類の Extension を提供しています:

  • StateFieldEditorState 内に存在します。純粋関数的で、Transaction を受け取って新しい値を計算します。DOM にはアクセスできません。
  • ViewPluginEditorView 上に存在します。DOM にアクセスし、ビューポートの変更に応答し、副作用を実行できます。View が更新されるたびに update オブジェクトを受け取ります。

データがドキュメントの内容や Transaction から純粋に導出される場合は StateField を使用します。DOM アクセスやビューポートの変更への対応が必要な場合は ViewPlugin を使用します。

import { ViewPlugin, ViewUpdate } from "@codemirror/view";

let myPlugin = ViewPlugin.fromClass(
  class {
    constructor(view) {
      // Runs once when the view is created
      console.log("Editor mounted with", view.state.doc.length, "chars");
    }

    update(update) {
      if (update.docChanged) {
        console.log("Document changed");
      }
      if (update.viewportChanged) {
        console.log("Viewport changed");
      }
    }

    destroy() {
      console.log("Plugin destroyed");
    }
  }
);

ViewPlugin は Extension として追加します:

let view = new EditorView({
  extensions: [myPlugin],
  parent: document.body,
});

DOM イベントハンドラ

EditorView.domEventHandlers() を使用して、エディタ上の DOM イベントのハンドラを登録します:

let handlers = EditorView.domEventHandlers({
  click(event, view) {
    console.log("Clicked at", event.clientX, event.clientY);
    // Return true to indicate the event was handled
    return false;
  },
  keydown(event, view) {
    if (event.key === "Escape") {
      console.log("Escape pressed");
      return true;
    }
    return false;
  },
});

let view = new EditorView({
  extensions: [handlers],
  parent: document.body,
});

ハンドラから true を返すと、イベントのそれ以上の処理が防止されます。

テーマシステム

CodeMirror 6 はスタイリングに CSS-in-JS アプローチを使用しています。テーマは EditorView.theme()EditorView.baseTheme() で定義します。

EditorView.theme()

ベーステーマをオーバーライドする高い詳細度のセレクタを持つテーマ Extension を作成します:

let myTheme = EditorView.theme({
  "&": {
    fontSize: "14px",
    border: "1px solid #ddd",
  },
  ".cm-content": {
    fontFamily: "'JetBrains Mono', monospace",
  },
  "&.cm-focused .cm-cursor": {
    borderLeftColor: "#528bff",
  },
  ".cm-gutters": {
    backgroundColor: "#f5f5f5",
    borderRight: "1px solid #ddd",
  },
});

let view = new EditorView({
  extensions: [myTheme],
  parent: document.body,
});

& セレクタはエディタの外側の要素(cm-editor クラスを持つ要素)を参照します。

EditorView.baseTheme()

より低い詳細度のベーステーマを作成します。ベーステーマは、より高い優先度のテーマがオーバーライドできるデフォルトを提供します:

let baseStyles = EditorView.baseTheme({
  ".cm-tooltip": {
    border: "1px solid #ccc",
    padding: "4px",
  },
});

Extension の作者は通常、ユーザーが theme() でスタイルをオーバーライドできるように baseTheme() を使用します。

ダークモード

theme() メソッドはテーマのバリアントを指定するための第 2 引数を受け取ります:

let darkTheme = EditorView.theme(
  {
    "&": {
      backgroundColor: "#1e1e1e",
      color: "#d4d4d4",
    },
  },
  { dark: true }
);

{ dark: true } が設定されている場合、エディタはルート要素に cm-dark クラスを追加し、ベーステーマがダークモード固有のスタイルを提供できるようにします。

コンテンツ属性

EditorView.contentAttributes を使用して、エディタのコンテンツ要素に HTML 属性を追加します:

let attrs = EditorView.contentAttributes.of({
  "aria-label": "Code editor",
  spellcheck: "false",
  autocorrect: "off",
});

これは Facet なので、複数の Extension が属性を提供でき、それらがマージされます。

行の折り返し

デフォルトでは、CodeMirror は長い行に対して水平スクロールを使用します。EditorView.lineWrapping で行の折り返しを有効にします:

let view = new EditorView({
  extensions: [EditorView.lineWrapping],
  parent: document.body,
});

ビューのスクロール

指定した位置が見えるようにエディタをスクロールするには、scrollIntoView を含む Transaction を dispatch します:

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

view.dispatch({
  effects: EditorView.scrollIntoView(100),
});

範囲を渡すこともできます:

view.dispatch({
  effects: EditorView.scrollIntoView(100, { y: "center" }),
});

フォーカス管理

エディタのフォーカスを確認・制御します:

// Check if the editor has focus
console.log(view.hasFocus);

// Programmatically focus the editor
view.focus();

view.dispatch() を使用して選択範囲を設定した場合、エディタは自動的にフォーカスを取得しません。プログラムによる選択変更後にエディタにフォーカスが必要な場合は、view.focus() を呼び出してください。

Update リスナー

ViewPlugin を作成せずに状態の更新に反応するには、EditorView.updateListener を使用します:

let listener = EditorView.updateListener.of((update) => {
  if (update.docChanged) {
    console.log("New content:", update.state.doc.toString());
  }
});

これは外部の状態をエディタと同期するための便利な方法です。

View の破棄

ページからエディタを削除する際は、destroy() を呼び出してクリーンアップします:

view.destroy();

これにより、エディタの DOM が削除され、イベントリスナーがデタッチされ、すべての ViewPlugin の destroy() が呼び出されます。destroy() を呼び出した後は、View を使用しないでください。

React や Vue などのフレームワークで作業している場合は、コンポーネントのクリーンアップフェーズ(例: useEffect のクリーンアップや onUnmounted)で destroy() を呼び出してください。

Revision History