Monaco Editorで、埋め込みエディタのコード変更イベントをリッスンする方法

VS codeのソースをもとにウェブブラウザー上で動くよう作成されたMonaco Editor。自身のウェブサイトにエディタを埋め込むことで、エンジニア向けサービス等はぐっとUI/UXが改善されます。

今回は、Monaco Editorで埋め込まれたエディタに対して、ユーザーがコードを書き込む等の変更を行ったイベントをリッスンする方法についてまとめます。Monaco Editorに関する基本的な使い方が分かっている前提で説明を進めるので、場合によっては[Monaco Editorの基本的な使い方](https://qiita.com/kyok01_japan/items/41c7ab287b2233bd4313)を事前に読んでいただくとよいかと思います。

https://qiita.com/kyok01_japan/items/41c7ab287b2233bd4313

## 前提
chrome（バージョン: 101.0.4951.67）
Monaco Editor version 0.33.0.

## 説明のために使用するサンプルコード

以下のようなサンプルコードのサイトを使用します。
```サンプルコード.html
<!DOCTYPE html>
<html>
  <head>
    <title>browser-amd-editor</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <h2>Monaco Editor Sample</h2>
    <div
      id="container"
      style="width: 800px; height: 600px; border: 1px solid grey;"
    ></div>
    <script src="./node_modules/monaco-editor/min/vs/loader.js"></script>
    <script>
      require.config({ paths: { vs: "./node_modules/monaco-editor/min/vs" } });

      require(["vs/editor/editor.main"], function () {
        var editor = monaco.editor.create(
          document.getElementById("container"),
          {
            value: [
              "function x() {",
              '\tconsole.log("Hello world!");',
              "}",
            ].join("\n"),
            language: "javascript",
          }
        );
      });
    </script>
  </body>
</html>
```
サイトの見た目は以下のようにシンプルです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/bdf5ed2e-8285-e9d9-78b0-b66e513906e1.png)

今回リッスンしようとしているイベントは、例えば、以下の画像のように空行が追加する等です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/08d21c84-9cd1-cfbc-ef35-53c91ca305bc.png)

テキストエディタの変更をリッスンするために必要なイベントが何なのかは、公式ドキュメントやPlay Groundに明記されておらず、Monaco Editorに関する情報は日本語でほとんど存在しないです。自分なりにいろんなコードを試すか、海外サイト(例えばStack Overflow等)を見た方が近道だったりします。


## テキストエディタの変更をリッスンするために必要なコード

テキストエディタの変更をリッスンするために必要なコードは以下のように、getModelメソッドとonDidChangeContentメソッドを用いたものになります。
```
editor.getModel().onDidChangeContent((event) => {
  console.log(editor.getValue());
  console.log(event);
});
```

HTMLファイル全体はこんな感じです。

```テキストエディタの変更をリッスンできるHTML.html
<!DOCTYPE html>
<html>
  <head>
    <title>browser-amd-editor</title>
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
  </head>
  <body>
    <h2>Monaco Editor Sample</h2>
    <div
      id="container"
      style="width: 800px; height: 600px; border: 1px solid grey;"
    ></div>

    <!-- OR ANY OTHER AMD LOADER HERE INSTEAD OF loader.js -->
    <script src="./node_modules/monaco-editor/min/vs/loader.js"></script>
    <script>
      require.config({ paths: { vs: "./node_modules/monaco-editor/min/vs" } });

      require(["vs/editor/editor.main"], function () {
        var editor = monaco.editor.create(
          document.getElementById("container"),
          {
            value: [
              "function x() {",
              '\tconsole.log("Hello world!");',
              "}",
            ].join("\n"),
            language: "javascript",
          }
        );

        editor.getModel().onDidChangeContent((event) => {
          console.log(editor.getValue());
          console.log(event);
        });
      });
    </script>
  </body>
</html>
```

これで、エディタ内に変更があるたびに、特定の処理を実行できます。
今回は、
```
console.log(editor.getValue());
console.log(event);
```
との処理を書いています。

それぞれどのようにコンソールに出力されているか見てみましょう。

例えば、Enterを押して空行を入れたとします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/08d21c84-9cd1-cfbc-ef35-53c91ca305bc.png)

すると、イベントがリッスンされて、`editor.getValue()`がコンソールに出力されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/9069b0f1-b92e-a0fd-a9a9-0df798f6bfea.png)
画像. `console.log(editor.getValue());`の結果

また、`console.log(event);`の方では、以下のような連想配列(Hash, Object)が出力がされています。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/64142bfa-4547-6519-cc47-30d41c7bcee0.png)
changesというkeyの値である配列の0番目に格納されている連想配列のtextというkeyの場所に、今回の変更`'\n\t'`(改行とタブを行ったことになっている)が書かれています。

`console.log(event);`については、改行だと分かりにくいかと思いますので、`a`と記載した場合のコンソールも合わせて表示します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/395f7ce7-0242-3698-1044-90c31fac63bc.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573922/4488bd4f-6db7-6c4d-2671-d90f665e89d7.png)

## getModelメソッドを詳しく解説
getModelについては[Monaco Editor公式ドキュメントのおそらくここ](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ITextModel.html#uri)が該当します。「おそらく」とつけたのは、パラメーター(引数)がなくて実行された場合の記述がないためです。

[Interface ITextModel](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ITextModel.html#uri)オブジェクトを返します。

## onDidChangeContentメソッドを詳しく解説
onDidChangeContentについては、[Monaco Editor公式ドキュメント](https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.ITextModel.html#onDidChangeContent)にて、以下のように記述されています。

:::note info
**onDidChangeContent**
onDidChangeContent(listener: (e: IModelContentChangedEvent) => void): IDisposable
An event emitted when the contents of the model have changed.
:::

Interface ITextModelオブジェクト(=model)に変化があった場合に、IModelContentChangedEventイベントをリッスンします。


## 参考にした記事
- [Get the value of Monaco Editor(Stack Overflow)](https://stackoverflow.com/questions/38086013/get-the-value-of-monaco-editor)
- [Monaco Editor ‘onChange’ event? – Code Utility](https://codeutility.org/monaco-editor-onchange-event-stack-overflow/)