テキストエディタライブラリ、Aceで分割表示をする

　Javascriptで簡単にテキストエディタを作れる **Ace**、これを最近いじり始めたので、その忘備録的な内容です。

　自分は昔、CMeDitorというテキストエディタをWindowsアプリケーション用に公開していました。（今もVector等で公開中）
　それをそろそろ最新の環境とトレンドでリニューアルさせたいと思い、ウェブアプリとして作成中です。その最中で、Aceを本格的に利用しようと考え、同ライブラリをいじりつつのテキストエディタ開発中です。

# やりたいこと

　テキストエディタでよくある分割表示をさせたい

# 実装

## Aceの理解

　公式サイトにもあるように、とりあえずエディタを生成するなら次の通りでよいのは理解。

```javascript
var editor = ace.edit("要素のID");
```

　Aceの基本構造はエディタ＝文章という単純なものではなく、いくらでも応用しやすいような構造になっていた。
　つまり次の通りである。

- Editor
    - Session
        - Document

　最小構成部分はDocumentで、いわゆる文章単体を管理するもの。そこに文章の状態やエディタ部分の仮想的な管理を含めたのがSession、そして画面表示に関わる部分も含めた大枠がEditorという。
　そのため、一つの文章（ファイル）は1つのDocumentに相当する。1つのSessionに相当しないのはなぜか？その構造の仕組みがテキストエディタの分割表示において大事なポイントなのだろう。
　このような理解で利用を始めてみた次第。


## 第一段階

　最初は1つの文章＝1つのSessionという理解で実装を始めたため、次のコーディングをしていた。

```html:test.html
<div id="editor1"></div>
<div id="editor2"></div>
```

```javascript:test.js
var editor1 = ace.edit("editor1");
var editor2 = ace.edit("editor2");

//---このあたりは適当に・・・
editor1.setTheme("ace/theme/tomorrow");
editor1.getSession().setMode("ace/mode/javascript");
editor2.setTheme("ace/theme/tomorrow");
editor2.getSession().setMode("ace/mode/javascript");

//---Sessionを片方にそのままセット、同期させてやればよい？？
editor2.setSession(editor1.getSession());
```

すると、一応の目的たる1つの文章を複数のエディタ部分に表示、内容を同期させることはできたが、 **カーソルの挙動まで** 同期してしまった。

![win8cap0101.png](https://qiita-image-store.s3.amazonaws.com/0/38416/72271fa1-fffe-360d-27af-e75c2b26a9bc.png)


**違う、そうじゃない！**

　テキストエディタの挙動としては使えるテクニックだとは思うが、自作のテキストエディタで実現したい内容とは若干違った。
　そこで、Aceの仕様を公式サイトのリファレンスやブラウザの開発者ツールのコンソールでリアルタイムにAceの各メソッドを弄って勉強し直し。

## 第二段階

　理解したのは、1つの文章　＝　1つのDocument ≠ 1つのSession ということ。
　1つのDocument ＝　n個のSession　にもなりうるということに気づいた。

　そして次のコーディング。

```javascript:test.js
//---ただのDocumentにするとブラウザのDocumentと競合するので aceDocument とした
var aceDocument = ace.require("ace/document").Document;
var doc = new aceDocument("適当な文章");
var ses1 = ace.createEditSession(doc,"ace/mode/javascript");
var ses2 = ace.createEditSession(doc,"ace/mode/javascript");

var editor1 = ace.edit("editor1");
var editor2 = ace.edit("editor2");

//---このあたりは適当に・・・
/* 中略 */

//---各エディタのSessionに新規に作成したEditSessionをセット
editor1.setSession(ses1);
editor2.setSession(ses2);
```

　docを両方のEditSessionにセットするところが重要なポイントである。
　なお、試しにace.editで生成されるEditorに紐付いているSessionに直接Documentをセットしたところ、カーソルがまったく動かないというなにか別の競合？が起きて、挙動がおかしくなってしまった。
　どうやら、正規の実装方法はきちんとそれぞれのオブジェクトを一から生成してやるものなのだろう。
　そしてこのようになった。

![win8cap0100.png](https://qiita-image-store.s3.amazonaws.com/0/38416/4c61257c-2d21-2d03-814f-ef55fd904bc4.png)

　文章の内容だけが同期され、カーソル位置は各エディタで別々に動くようにできた！
　これで目的達成となった。

# 第三段階
 
  <2018/05/21追記>

 　第二段階までは実はace editorとEasyUIのLayoutを駆使してやっていたのだが、aceのパッケージ内容を見てみたら、そのものズバリ、 **ext-split.js** があった。
　こちらのほうが安定して簡単そう。試したので以下追記。

```html:test.html
<!--これを追加-->
<script src="/static/cmeditor/plugin/ace/src/ext-split.js"></script>
```

```javascript:test.js
var aceSplit = ace.require("ace/ext/split");  //---使い回すため

//途中でaceのEditSessionを作成しておく。
/* 諸々設定 */

//Splitオブジェクトを作成
//aceエディタとするHTML要素、テーマ、分割数
var a = new aceSplit.Split(document.getElementById("editor1"),aceThemelist.themesByName["tomorrow"].theme,1);

//---分割の方向を決める　BELOWとBESIDEがある
a.setOrientation(aceSplit.Split.BELOW);
//---分割数を決める
a.setSplits(2);
//---エディタのセッションを分割IDとともにセットして分割完了
a.setSession(session1,0);
a.setSession(session1,1);
```

するとこうなる。2つと言わず、3つ以上も分割できる。
![win8cap0107.png](https://qiita-image-store.s3.amazonaws.com/0/38416/d17ff77f-d5ab-fa72-e8c3-62c9ae07d7a0.png)

　この方法でのポイントは、まず拡張機能の"ace/ext/split"を読み込んでおくことである。定数もあるようなので、グローバル変数で宣言しておくと便利。
　そしてSplitオブジェクトの作成。これに好きなHTML要素を指定する。これでエディタとの連携の準備ができた。
　あとはアプリケーションの任意のタイミングで分割数をsetSplits()で指定し、それぞれに対しsessionを割り当ててあげれば分割完了だ。

　気をつけたい点は、Splitでaceエディタを連携させると、セッションや表示周りもSplitオブジェクトの配下で動作する点。aceエディタ単体の時にsessionに何か文章がすでにあり、後からSplitオブジェクトを作成し、Split配下の各分割にセッションを割り当てようものなら、こうなる。
![win8cap0107a.png](https://qiita-image-store.s3.amazonaws.com/0/38416/0b953d49-23e6-8b82-966f-6b9f8fc447e5.png)

　表示が元のエディタのセッションの内容と、Split配下のセッションの内容でかぶってしまうのだ。なるほどどうやら、Split自体がエディタとセッションを固有に保持するのだ。
　だから分割を使いたいなら単独でエディタを作成してはいけない。分割を使わないなら通常通り

```javascript
var editor1 = ace.edit("editor1");
```

でよい。しかし分割を後々使いたいならば、次のようにする必要があるのだ。

```javascript
var editor1 = new aceSplit.Split(document.getElementById("editor1"),aceThemelist.themesByName["tomorrow"].theme,1);

//エディタ自体はこうして取得する
var a = editor1.getCurrentEditor(); //フォーカスの当たっているアクティブなエディタ
var b = editor1.getEditor(0); //任意の分割配下のエディタ
```

　まとめると、オブジェクトの階層的にはこうなるようだ。

- Split
    - Editor
        - Session
            - Document

　分割をしない場合、Editorが最上位と言ってもよい状態だが、Splitを使うことでSplitが最上位になる。実際にEditorが物理的に複数できて分割される。（見た目的には分割数を2以上にしないかぎりは何も目立たない）
　つまり、オブジェクトの数的にはこうなる。

　Split : Editor : Session : Document　＝　1 : N : N : N

　物理的にも論理的にも管理がSplitにまとまるため、こちらを使うほうが良さそうだ。あと気になるのは分割の境目を動かして幅を調整したい点。境目にカーソルを当てても動かせるわけではない。
　どうやらその機能はないみたいだ。しかし、それ以外は拡張機能の分割はさすがに使いやすい。
　その他詳しい使い方は、公式ソースの src\ext-split.js を見るとよいだろう。


# 終わりに

　このAce、存在自体は昔から知っていたのですが、今の今に至るまでこれを使ってアプリ作ってみようという意欲を沸き立たせる気持ちにまで至らなかったのです。
　この度別の目的があって自作のテキストエディタ、CMeDitorをウェブアプリに移植しようということで、Aceに白羽の矢を立てて本格的に使い始めた次第でした。そこで昔からやりたかった分割表示は Ace ではどうすればいいのだろう・・・という疑問と壁にぶつかり、今回の試行錯誤と発見に至りました。

　余談ですが、昔はDelphiと TEditor(TEditorEx)というライブラリを使って作っていました。今となってはjavascriptとHTMLでかつての環境以上のものが手軽に実装できるという事実に軽くカルチャーショックを受けました。かつては自力で実装していたテキストエディタの機能も、Aceであれば標準のメソッドで備わっていたりと・・・。

　良い時代になったものです。