JavaFXにAceを埋め込んだらMacのJapaneseIMがバグったので解消した

現在、勉強がてらJavaFxでAsciiDocエディタを製作中です。エディタ部分にJavaScript製の[Ace](https://ace.c9.io/)を採用しましたが、タイトル通り挙動がおかしくなってしまいました。

## なにが起こったか
### 環境
* macOS Mojave 10.14.3
* Intellij IDEA 2018.3.4
* JavaFX 11

```
$ java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
```

そして起こったのがこちら
<img src="https://media.giphy.com/media/8mb5ISfjaasP0jygvq/giphy.gif" width=50%>
見ての通り変換して確定すると改行されてしまいます。しかもこの後は日本語入力すらできない...
じゃあ[Google 日本語入力](https://www.google.co.jp/ime/)を使ってみよう！
<img src="https://media.giphy.com/media/2yujeWTjQ20YC6ta4p/giphy.gif" width=50%>
えぇ...
こちらはスペースキーで変換する場合にはいいのですが、方向キーで変換候補を選択してしまうとJapeneseIMと同様日本語が打てなくなります。

コントローラクラスとWebviewがロードするHTMLのソースはこちら

```html:ace.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Ace Editor</title>
    <style type="text/css" media="screen">
        #editor {
            position: absolute;
            top: 0;
            right: 0;
            bottom: 0;
            left: 0;
        }
    </style>
</head>
<body>
<div id="editor"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ace/1.4.2/ace.js" type="text/javascript" charset="utf-8"></script>
<script>
    var editor = ace.edit("editor");
    editor.setTheme("ace/theme/monokai");
</script>
</body>
</html>
```

```java:SampleController.java
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.web.WebView;

import java.net.URL;
import java.util.ResourceBundle;

public class SampleController implements Initializable {

    @FXML
    WebView aceEditor;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        aceEditor.getEngine().setJavaScriptEnabled(true);
        aceEditor.getEngine().load(getClass().getResource("/ace.html").toExternalForm());
    }
}
```
ちなみに、Aceはemacsやvimのキーバインドを用意してくれていて

```javascript
editor.setKeyboardHandler("ace/keyboard/emacs");
```
とすれば変えられるのですが、これを使っても少し挙動が変わるだけで実用は無理でした。
加えて、僕はJavaScriptが書けないのでJavaScript側でどうにかするのは諦めました。:innocent:

上記のHTMLをブラウザで見た場合、chrome、safariでは良かったものの、firefoxではうまく動きませんでした(日本語入力ができなくなることはなかった)。JavaFXだけではなくAceの問題もあるのかもしれません。

# 解決策
じゃあどうしたかというと、IME使用中はJavaFXの`KeyEvent`を無効化しました。

```java
public class SampleController implements Initializable {

    @FXML
    WebView aceEditor;

    private boolean isComposing;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        aceEditor.getEngine().setJavaScriptEnabled(true);
        aceEditor.getEngine().load(getClass().getResource("/ace.html").toExternalForm());

        aceEditor.addEventFilter(InputMethodEvent.ANY, new EventHandler<InputMethodEvent>() {
            @Override
            public void handle(InputMethodEvent event) {
                isComposing = !(event.getComposed().size() == 0);
            }
        });

        aceEditor.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
            @Override
            public void handle(KeyEvent event) {
                if (isComposing) event.consume();
            }
        });
    }
}
```

JavaFXのイベント処理の流れはググればいっぱい分かりやすい記事がでできますので割愛させていただきます。

`KeyEvent`には`KEY_PRESSED`、`KEY_RELEASED`、`KEY_TYPED`があります。これらの発生する順番は

`KEY_PRESSED` -> `KEY_TYPED` -> `KEY_RELEASED`

なのですが`InputMethodEvent`中は基本的に`KEY_RELEASED`しか発火しません。
しかし特定のキー(方向キーやshiftキーなど)に限っては`KEY_PRESSED`や`KEY_TYPED`が発火します。そんなわけで最初のGIFのような動作になっていたのでした。

`InputMethodEvent`の`getComposed`メソッドは`ObservableList`を返してくれるので、`size`メソッドを使えばIME使用中か否かを判断できます。これで動きます:+1:

## 改良後
<img src="https://media.giphy.com/media/RLW9YZFkq1lFWKdvuh/giphy.gif" width=50%>

# まとめ
Aceは日本語対応が微妙と評判？でしたがなんとかJavaFXで実用できるようになりました。
Aceを使っているJavaFX製AsciiDocエディタ[AsciidocFX](https://asciidocfx.com/)も僕の環境では最初のGIFと同じように動いてしまって困っていました。ちなみにParalles DesktopのWindows 10上でAsciidocFXを動かしても不具合はなかったので、Macだけだと思います。修正したコードがWindowsでどうなるのかはわかりません。

もしかしたらAceやJavaFXのAPIを見落としているのかもしれません。「もっと良い実装があるよ！」、「Aceの使い方間違ってるよ！」という場合はぜひコメントで教えていただけると嬉しいです。

(製作中のAsciiDocエディタも形になったらまた投稿したいと思います)